Add PGPPacket._symmetric_key_algorithms
[gpg-migrate.git] / gpg-migrate.py
index 699b560ad4f2ace744173d6c92788c2220d138b2..6f49ad75d7a012d31181dca071e7979b04706ff6 100755 (executable)
@@ -1,5 +1,6 @@
 #!/usr/bin/python
 
+import re as _re
 import subprocess as _subprocess
 import struct as _struct
 
@@ -25,6 +26,84 @@ class PGPPacket (dict):
         3: (None, None),
         }
 
+    _packet_types = {
+        0: 'reserved',
+        1: 'public-key encrypted session key packet',
+        2: 'signature packet',
+        3: 'symmetric-key encrypted session key packet',
+        4: 'one-pass signature packet',
+        5: 'secret-key packet',
+        6: 'public-key packet',
+        7: 'secret-subkey packet',
+        8: 'compressed data packet',
+        9: 'symmetrically encrypted data packet',
+        10: 'marker packet',
+        11: 'literal data packet',
+        12: 'trust packet',
+        13: 'user id packet',
+        14: 'public-subkey packet',
+        17: 'user attribute packet',
+        18: 'sym. encrypted and integrity protected data packet',
+        19: 'modification detection code packet',
+        60: 'private',
+        61: 'private',
+        62: 'private',
+        63: 'private',
+        }
+
+    _public_key_algorithms = {
+        1: 'rsa (encrypt or sign)',
+        2: 'rsa encrypt-only',
+        3: 'rsa sign-only',
+        16: 'elgamal (encrypt-only)',
+        17: 'dsa (digital signature algorithm)',
+        18: 'reserved for elliptic curve',
+        19: 'reserved for ecdsa',
+        20: 'reserved (formerly elgamal encrypt or sign)',
+        21: 'reserved for diffie-hellman',
+        100: 'private',
+        101: 'private',
+        102: 'private',
+        103: 'private',
+        104: 'private',
+        105: 'private',
+        106: 'private',
+        107: 'private',
+        108: 'private',
+        109: 'private',
+        110: 'private',
+        }
+
+    _symmetric_key_algorithms = {
+        0: 'plaintext or unencrypted data',
+        1: 'idea',
+        2: 'tripledes',
+        3: 'cast5',
+        4: 'blowfish',
+        5: 'reserved',
+        6: 'reserved',
+        7: 'aes with 128-bit key',
+        8: 'aes with 192-bit key',
+        9: 'aes with 256-bit key',
+        10: 'twofish',
+        100: 'private',
+        101: 'private',
+        102: 'private',
+        103: 'private',
+        104: 'private',
+        105: 'private',
+        106: 'private',
+        107: 'private',
+        108: 'private',
+        109: 'private',
+        110: 'private',
+        }
+
+    _clean_type_regex = _re.compile('\W+')
+
+    def _clean_type(self):
+        return self._clean_type_regex.sub('_', self['type'])
+
     def from_bytes(self, data):
         offset = self._parse_header(data=data)
         packet = data[offset:offset + self['length']]
@@ -32,6 +111,12 @@ class PGPPacket (dict):
             raise ValueError('packet too short ({} < {})'.format(
                 len(packet), self['length']))
         offset += self['length']
+        method_name = '_parse_{}'.format(self._clean_type())
+        method = getattr(self, method_name, None)
+        if not method:
+            raise NotImplementedError(
+                'cannot parse packet type {!r}'.format(self['type']))
+        method(data=packet)
         return offset
 
     def _parse_header(self, data):
@@ -42,10 +127,10 @@ class PGPPacket (dict):
             raise ValueError('most significant packet tag bit not set')
         self['new-format'] = packet_tag & 1 << 6
         if self['new-format']:
-            self['packet-tag'] = packet_tag & 0b111111
+            type_code = packet_tag & 0b111111
             raise NotImplementedError('new-format packet length')
         else:
-            self['packet-tag'] = packet_tag >> 2 & 0b1111
+            type_code = packet_tag >> 2 & 0b1111
             self['length-type'] = packet_tag & 0b11
             length_bytes, length_type = self._old_format_packet_length_type[
                 self['length-type']]
@@ -56,8 +141,28 @@ class PGPPacket (dict):
             length_data = data[offset: offset + length_bytes]
             offset += length_bytes
             self['length'] = _struct.unpack(length_format, length_data)[0]
+        self['type'] = self._packet_types[type_code]
         return offset
 
+    def _parse_public_key_packet(self, data):
+        self._parse_generic_public_key_packet(data=data)
+
+    def _parse_public_subkey_packet(self, data):
+        self._parse_generic_public_key_packet(data=data)
+
+    def _parse_generic_public_key_packet(self, data):
+        self['key-version'] = data[0]
+        offset = 1
+        if self['key-version'] != 4:
+            raise NotImplementedError(
+                'public (sub)key packet version {}'.format(
+                    self['key-version']))
+        length = 5
+        self['creation_time'], self['public-key-algorithm'] = _struct.unpack(
+            '>IB', data[offset: offset + length])
+        offset += length
+        self['key'] = data[offset:]
+
     def to_bytes(self):
         pass
 
@@ -81,6 +186,10 @@ def migrate(old_key, new_key):
         ['gpg', '--export', old_key])
     old_key_packets = list(
         packets_from_bytes(data=old_key_export))
+    if old_key_packets[0]['type'] != 'public-key packet':
+        raise ValueError(
+            '{} does not start with a public-key packet'.format(
+                old_key))
     old_key_secret_export = _get_stdout(
         ['gpg', '--export-secret-keys', old_key])
     old_key_secret_packets = list(