diff options
Diffstat (limited to 'src/leap/mx/vendor/pgpy/packet/packets.py')
-rw-r--r-- | src/leap/mx/vendor/pgpy/packet/packets.py | 1605 |
1 files changed, 1605 insertions, 0 deletions
diff --git a/src/leap/mx/vendor/pgpy/packet/packets.py b/src/leap/mx/vendor/pgpy/packet/packets.py new file mode 100644 index 0000000..38a7200 --- /dev/null +++ b/src/leap/mx/vendor/pgpy/packet/packets.py @@ -0,0 +1,1605 @@ +""" packet.py +""" +import abc +import binascii +import calendar +import copy +import hashlib +import os +import re + +from datetime import datetime + +import six + +from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.asymmetric import padding + +from .fields import DSAPriv, DSAPub, DSASignature +from .fields import ECDSAPub, ECDSAPriv, ECDSASignature +from .fields import ECDHPub, ECDHPriv, ECDHCipherText +from .fields import ElGCipherText, ElGPriv, ElGPub +from .fields import OpaquePubKey +from .fields import OpaquePrivKey +from .fields import RSACipherText, RSAPriv, RSAPub, RSASignature +from .fields import String2Key +from .fields import SubPackets +from .fields import UserAttributeSubPackets + +from .types import Packet +from .types import Primary +from .types import Private +from .types import Public +from .types import Sub +from .types import VersionedPacket + +from ..constants import CompressionAlgorithm +from ..constants import HashAlgorithm +from ..constants import PubKeyAlgorithm +from ..constants import SignatureType +from ..constants import SymmetricKeyAlgorithm +from ..constants import TrustFlags +from ..constants import TrustLevel + +from ..decorators import sdproperty + +from ..errors import PGPDecryptionError + +from ..symenc import _decrypt +from ..symenc import _encrypt + +from ..types import Fingerprint + +__all__ = ['PKESessionKey', + 'PKESessionKeyV3', + 'Signature', + 'SignatureV4', + 'SKESessionKey', + 'SKESessionKeyV4', + 'OnePassSignature', + 'OnePassSignatureV3', + 'PrivKey', + 'PubKey', + 'PubKeyV4', + 'PrivKeyV4', + 'PrivSubKey', + 'PrivSubKeyV4', + 'CompressedData', + 'SKEData', + 'Marker', + 'LiteralData', + 'Trust', + 'UserID', + 'PubSubKey', + 'PubSubKeyV4', + 'UserAttribute', + 'IntegrityProtectedSKEData', + 'IntegrityProtectedSKEDataV1', + 'MDC'] + + +class PKESessionKey(VersionedPacket): + __typeid__ = 0x01 + __ver__ = 0 + + @abc.abstractmethod + def decrypt_sk(self, pk): + raise NotImplementedError() + + @abc.abstractmethod + def encrypt_sk(self, pk, symalg, symkey): + raise NotImplementedError() + + +class PKESessionKeyV3(PKESessionKey): + """ + 5.1. Public-Key Encrypted Session Key Packets (Tag 1) + + A Public-Key Encrypted Session Key packet holds the session key used + to encrypt a message. Zero or more Public-Key Encrypted Session Key + packets and/or Symmetric-Key Encrypted Session Key packets may + precede a Symmetrically Encrypted Data Packet, which holds an + encrypted message. The message is encrypted with the session key, + and the session key is itself encrypted and stored in the Encrypted + Session Key packet(s). The Symmetrically Encrypted Data Packet is + preceded by one Public-Key Encrypted Session Key packet for each + OpenPGP key to which the message is encrypted. The recipient of the + message finds a session key that is encrypted to their public key, + decrypts the session key, and then uses the session key to decrypt + the message. + + The body of this packet consists of: + + - A one-octet number giving the version number of the packet type. + The currently defined value for packet version is 3. + + - An eight-octet number that gives the Key ID of the public key to + which the session key is encrypted. If the session key is + encrypted to a subkey, then the Key ID of this subkey is used + here instead of the Key ID of the primary key. + + - A one-octet number giving the public-key algorithm used. + + - A string of octets that is the encrypted session key. This + string takes up the remainder of the packet, and its contents are + dependent on the public-key algorithm used. + + Algorithm Specific Fields for RSA encryption + + - multiprecision integer (MPI) of RSA encrypted value m**e mod n. + + Algorithm Specific Fields for Elgamal encryption: + + - MPI of Elgamal (Diffie-Hellman) value g**k mod p. + + - MPI of Elgamal (Diffie-Hellman) value m * y**k mod p. + + The value "m" in the above formulas is derived from the session key + as follows. First, the session key is prefixed with a one-octet + algorithm identifier that specifies the symmetric encryption + algorithm used to encrypt the following Symmetrically Encrypted Data + Packet. Then a two-octet checksum is appended, which is equal to the + sum of the preceding session key octets, not including the algorithm + identifier, modulo 65536. This value is then encoded as described in + PKCS#1 block encoding EME-PKCS1-v1_5 in Section 7.2.1 of [RFC3447] to + form the "m" value used in the formulas above. See Section 13.1 of + this document for notes on OpenPGP's use of PKCS#1. + + Note that when an implementation forms several PKESKs with one + session key, forming a message that can be decrypted by several keys, + the implementation MUST make a new PKCS#1 encoding for each key. + + An implementation MAY accept or use a Key ID of zero as a "wild card" + or "speculative" Key ID. In this case, the receiving implementation + would try all available private keys, checking for a valid decrypted + session key. This format helps reduce traffic analysis of messages. + """ + __ver__ = 3 + + @sdproperty + def encrypter(self): + return self._encrypter + + @encrypter.register(bytearray) + def encrypter_bin(self, val): + self._encrypter = binascii.hexlify(val).upper().decode('latin-1') + + @sdproperty + def pkalg(self): + return self._pkalg + + @pkalg.register(int) + @pkalg.register(PubKeyAlgorithm) + def pkalg_int(self, val): + self._pkalg = PubKeyAlgorithm(val) + + _c = {PubKeyAlgorithm.RSAEncryptOrSign: RSACipherText, + PubKeyAlgorithm.RSAEncrypt: RSACipherText, + PubKeyAlgorithm.ElGamal: ElGCipherText, + PubKeyAlgorithm.FormerlyElGamalEncryptOrSign: ElGCipherText, + PubKeyAlgorithm.ECDH: ECDHCipherText} + + ct = _c.get(self._pkalg, None) + self.ct = ct() if ct is not None else ct + + def __init__(self): + super(PKESessionKeyV3, self).__init__() + self.encrypter = bytearray(8) + self.pkalg = 0 + self.ct = None + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(PKESessionKeyV3, self).__bytearray__() + _bytes += binascii.unhexlify(self.encrypter.encode()) + _bytes += bytearray([self.pkalg]) + _bytes += self.ct.__bytearray__() if self.ct is not None else b'\x00' * (self.header.length - 10) + return _bytes + + def __copy__(self): + sk = self.__class__() + sk.header = copy.copy(self.header) + sk._encrypter = self._encrypter + sk.pkalg = self.pkalg + if self.ct is not None: + sk.ct = copy.copy(self.ct) + + return sk + + def decrypt_sk(self, pk): + if self.pkalg == PubKeyAlgorithm.RSAEncryptOrSign: + # pad up ct with null bytes if necessary + ct = self.ct.me_mod_n.to_mpibytes()[2:] + ct = b'\x00' * ((pk.keymaterial.__privkey__().key_size // 8) - len(ct)) + ct + + decrypter = pk.keymaterial.__privkey__().decrypt + decargs = (ct, padding.PKCS1v15(),) + + elif self.pkalg == PubKeyAlgorithm.ECDH: + decrypter = pk + decargs = () + + else: + raise NotImplementedError(self.pkalg) + + m = bytearray(self.ct.decrypt(decrypter, *decargs)) + + """ + The value "m" in the above formulas is derived from the session key + as follows. First, the session key is prefixed with a one-octet + algorithm identifier that specifies the symmetric encryption + algorithm used to encrypt the following Symmetrically Encrypted Data + Packet. Then a two-octet checksum is appended, which is equal to the + sum of the preceding session key octets, not including the algorithm + identifier, modulo 65536. This value is then encoded as described in + PKCS#1 block encoding EME-PKCS1-v1_5 in Section 7.2.1 of [RFC3447] to + form the "m" value used in the formulas above. See Section 13.1 of + this document for notes on OpenPGP's use of PKCS#1. + """ + + symalg = SymmetricKeyAlgorithm(m[0]) + del m[0] + + symkey = m[:symalg.key_size // 8] + del m[:symalg.key_size // 8] + + checksum = self.bytes_to_int(m[:2]) + del m[:2] + + if not sum(symkey) % 65536 == checksum: # pragma: no cover + raise PGPDecryptionError("{:s} decryption failed".format(self.pkalg.name)) + + return (symalg, symkey) + + def encrypt_sk(self, pk, symalg, symkey): + m = bytearray(self.int_to_bytes(symalg) + symkey) + m += self.int_to_bytes(sum(bytearray(symkey)) % 65536, 2) + + if self.pkalg == PubKeyAlgorithm.RSAEncryptOrSign: + encrypter = pk.keymaterial.__pubkey__().encrypt + encargs = (bytes(m), padding.PKCS1v15(),) + + elif self.pkalg == PubKeyAlgorithm.ECDH: + encrypter = pk + encargs = (bytes(m),) + + else: + raise NotImplementedError(self.pkalg) + + self.ct = self.ct.encrypt(encrypter, *encargs) + self.update_hlen() + + def parse(self, packet): + super(PKESessionKeyV3, self).parse(packet) + self.encrypter = packet[:8] + del packet[:8] + + self.pkalg = packet[0] + del packet[0] + + if self.ct is not None: + self.ct.parse(packet) + + else: # pragma: no cover + del packet[:(self.header.length - 18)] + + +class Signature(VersionedPacket): + __typeid__ = 0x02 + __ver__ = 0 + + +class SignatureV4(Signature): + """ + 5.2.3. Version 4 Signature Packet Format + + The body of a version 4 Signature packet contains: + + - One-octet version number (4). + + - One-octet signature type. + + - One-octet public-key algorithm. + + - One-octet hash algorithm. + + - Two-octet scalar octet count for following hashed subpacket data. + Note that this is the length in octets of all of the hashed + subpackets; a pointer incremented by this number will skip over + the hashed subpackets. + + - Hashed subpacket data set (zero or more subpackets). + + - Two-octet scalar octet count for the following unhashed subpacket + data. Note that this is the length in octets of all of the + unhashed subpackets; a pointer incremented by this number will + skip over the unhashed subpackets. + + - Unhashed subpacket data set (zero or more subpackets). + + - Two-octet field holding the left 16 bits of the signed hash + value. + + - One or more multiprecision integers comprising the signature. + This portion is algorithm specific, as described above. + + The concatenation of the data being signed and the signature data + from the version number through the hashed subpacket data (inclusive) + is hashed. The resulting hash value is what is signed. The left 16 + bits of the hash are included in the Signature packet to provide a + quick test to reject some invalid signatures. + + There are two fields consisting of Signature subpackets. The first + field is hashed with the rest of the signature data, while the second + is unhashed. The second set of subpackets is not cryptographically + protected by the signature and should include only advisory + information. + + The algorithms for converting the hash function result to a signature + are described in a section below. + """ + __ver__ = 4 + + @sdproperty + def sigtype(self): + return self._sigtype + + @sigtype.register(int) + @sigtype.register(SignatureType) + def sigtype_int(self, val): + self._sigtype = SignatureType(val) + + @sdproperty + def pubalg(self): + return self._pubalg + + @pubalg.register(int) + @pubalg.register(PubKeyAlgorithm) + def pubalg_int(self, val): + self._pubalg = PubKeyAlgorithm(val) + + sigs = {PubKeyAlgorithm.RSAEncryptOrSign: RSASignature, + PubKeyAlgorithm.RSAEncrypt: RSASignature, + PubKeyAlgorithm.RSASign: RSASignature, + PubKeyAlgorithm.DSA: DSASignature, + PubKeyAlgorithm.ECDSA: ECDSASignature, } + + if self.pubalg in sigs: + self.signature = sigs[self.pubalg]() + + @sdproperty + def halg(self): + return self._halg + + @halg.register(int) + @halg.register(HashAlgorithm) + def halg_int(self, val): + try: + self._halg = HashAlgorithm(val) + + except ValueError: # pragma: no cover + self._halg = val + + @property + def signature(self): + return self._signature + + @signature.setter + def signature(self, val): + self._signature = val + + @property + def signer(self): + return self.subpackets['Issuer'][-1].issuer + + def __init__(self): + super(Signature, self).__init__() + self._sigtype = None + self._pubalg = None + self._halg = None + self.subpackets = SubPackets() + self.hash2 = bytearray(2) + self.signature = None + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(Signature, self).__bytearray__() + _bytes += self.int_to_bytes(self.sigtype) + _bytes += self.int_to_bytes(self.pubalg) + _bytes += self.int_to_bytes(self.halg) + _bytes += self.subpackets.__bytearray__() + _bytes += self.hash2 + _bytes += self.signature.__bytearray__() + + return _bytes + + def __copy__(self): + spkt = SignatureV4() + spkt.header = copy.copy(self.header) + spkt._sigtype = self._sigtype + spkt._pubalg = self._pubalg + spkt._halg = self._halg + + spkt.subpackets = copy.copy(self.subpackets) + spkt.hash2 = copy.copy(self.hash2) + spkt.signature = copy.copy(self.signature) + + return spkt + + def update_hlen(self): + self.subpackets.update_hlen() + super(SignatureV4, self).update_hlen() + + def parse(self, packet): + super(Signature, self).parse(packet) + self.sigtype = packet[0] + del packet[0] + + self.pubalg = packet[0] + del packet[0] + + self.halg = packet[0] + del packet[0] + + self.subpackets.parse(packet) + + self.hash2 = packet[:2] + del packet[:2] + + self.signature.parse(packet) + + +class SKESessionKey(VersionedPacket): + __typeid__ = 0x03 + __ver__ = 0 + + @abc.abstractmethod + def decrypt_sk(self, passphrase): + raise NotImplementedError() + + @abc.abstractmethod + def encrypt_sk(self, passphrase, sk): + raise NotImplementedError() + + +class SKESessionKeyV4(SKESessionKey): + """ + 5.3. Symmetric-Key Encrypted Session Key Packets (Tag 3) + + The Symmetric-Key Encrypted Session Key packet holds the + symmetric-key encryption of a session key used to encrypt a message. + Zero or more Public-Key Encrypted Session Key packets and/or + Symmetric-Key Encrypted Session Key packets may precede a + Symmetrically Encrypted Data packet that holds an encrypted message. + The message is encrypted with a session key, and the session key is + itself encrypted and stored in the Encrypted Session Key packet or + the Symmetric-Key Encrypted Session Key packet. + + If the Symmetrically Encrypted Data packet is preceded by one or + more Symmetric-Key Encrypted Session Key packets, each specifies a + passphrase that may be used to decrypt the message. This allows a + message to be encrypted to a number of public keys, and also to one + or more passphrases. This packet type is new and is not generated + by PGP 2.x or PGP 5.0. + + The body of this packet consists of: + + - A one-octet version number. The only currently defined version + is 4. + + - A one-octet number describing the symmetric algorithm used. + + - A string-to-key (S2K) specifier, length as defined above. + + - Optionally, the encrypted session key itself, which is decrypted + with the string-to-key object. + + If the encrypted session key is not present (which can be detected + on the basis of packet length and S2K specifier size), then the S2K + algorithm applied to the passphrase produces the session key for + decrypting the file, using the symmetric cipher algorithm from the + Symmetric-Key Encrypted Session Key packet. + + If the encrypted session key is present, the result of applying the + S2K algorithm to the passphrase is used to decrypt just that + encrypted session key field, using CFB mode with an IV of all zeros. + The decryption result consists of a one-octet algorithm identifier + that specifies the symmetric-key encryption algorithm used to + encrypt the following Symmetrically Encrypted Data packet, followed + by the session key octets themselves. + + Note: because an all-zero IV is used for this decryption, the S2K + specifier MUST use a salt value, either a Salted S2K or an + Iterated-Salted S2K. The salt value will ensure that the decryption + key is not repeated even if the passphrase is reused. + """ + __ver__ = 4 + + @property + def symalg(self): + return self.s2k.encalg + + def __init__(self): + super(SKESessionKeyV4, self).__init__() + self.s2k = String2Key() + self.ct = bytearray() + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(SKESessionKeyV4, self).__bytearray__() + _bytes += self.s2k.__bytearray__()[1:] + _bytes += self.ct + return _bytes + + def __copy__(self): + sk = self.__class__() + sk.header = copy.copy(self.header) + sk.s2k = copy.copy(self.s2k) + sk.ct = self.ct[:] + + return sk + + def parse(self, packet): + super(SKESessionKeyV4, self).parse(packet) + # prepend a valid usage identifier so this parses correctly + packet.insert(0, 255) + self.s2k.parse(packet, iv=False) + + ctend = self.header.length - len(self.s2k) + self.ct = packet[:ctend] + del packet[:ctend] + + def decrypt_sk(self, passphrase): + # derive the first session key from our passphrase + sk = self.s2k.derive_key(passphrase) + del passphrase + + # if there is no ciphertext, then the first session key is the session key being used + if len(self.ct) == 0: + return self.symalg, sk + + # otherwise, we now need to decrypt the encrypted session key + m = bytearray(_decrypt(bytes(self.ct), sk, self.symalg)) + del sk + + symalg = SymmetricKeyAlgorithm(m[0]) + del m[0] + + return symalg, bytes(m) + + def encrypt_sk(self, passphrase, sk): + # generate the salt and derive the key to encrypt sk with from it + self.s2k.salt = bytearray(os.urandom(8)) + esk = self.s2k.derive_key(passphrase) + del passphrase + + self.ct = _encrypt(self.int_to_bytes(self.symalg) + sk, esk, self.symalg) + + # update header length and return sk + self.update_hlen() + + +class OnePassSignature(VersionedPacket): + __typeid__ = 0x04 + __ver__ = 0 + + +class OnePassSignatureV3(OnePassSignature): + """ + 5.4. One-Pass Signature Packets (Tag 4) + + The One-Pass Signature packet precedes the signed data and contains + enough information to allow the receiver to begin calculating any + hashes needed to verify the signature. It allows the Signature + packet to be placed at the end of the message, so that the signer + can compute the entire signed message in one pass. + + A One-Pass Signature does not interoperate with PGP 2.6.x or + earlier. + + The body of this packet consists of: + + - A one-octet version number. The current version is 3. + + - A one-octet signature type. Signature types are described in + Section 5.2.1. + + - A one-octet number describing the hash algorithm used. + + - A one-octet number describing the public-key algorithm used. + + - An eight-octet number holding the Key ID of the signing key. + + - A one-octet number holding a flag showing whether the signature + is nested. A zero value indicates that the next packet is + another One-Pass Signature packet that describes another + signature to be applied to the same message data. + + Note that if a message contains more than one one-pass signature, + then the Signature packets bracket the message; that is, the first + Signature packet after the message corresponds to the last one-pass + packet and the final Signature packet corresponds to the first + one-pass packet. + """ + __ver__ = 3 + + @sdproperty + def sigtype(self): + return self._sigtype + + @sigtype.register(int) + @sigtype.register(SignatureType) + def sigtype_int(self, val): + self._sigtype = SignatureType(val) + + @sdproperty + def pubalg(self): + return self._pubalg + + @pubalg.register(int) + @pubalg.register(PubKeyAlgorithm) + def pubalg_int(self, val): + self._pubalg = PubKeyAlgorithm(val) + if self._pubalg in [PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.RSAEncrypt, PubKeyAlgorithm.RSASign]: + self.signature = RSASignature() + + elif self._pubalg == PubKeyAlgorithm.DSA: + self.signature = DSASignature() + + @sdproperty + def halg(self): + return self._halg + + @halg.register(int) + @halg.register(HashAlgorithm) + def halg_int(self, val): + try: + self._halg = HashAlgorithm(val) + + except ValueError: # pragma: no cover + self._halg = val + + @sdproperty + def signer(self): + return self._signer + + @signer.register(str) + @signer.register(six.text_type) + def signer_str(self, val): + self._signer = val + + @signer.register(bytearray) + def signer_bin(self, val): + self._signer = binascii.hexlify(val).upper().decode('latin-1') + + def __init__(self): + super(OnePassSignatureV3, self).__init__() + self._sigtype = None + self._halg = None + self._pubalg = None + self._signer = b'\x00' * 8 + self.nested = False + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(OnePassSignatureV3, self).__bytearray__() + _bytes += bytearray([self.sigtype]) + _bytes += bytearray([self.halg]) + _bytes += bytearray([self.pubalg]) + _bytes += binascii.unhexlify(six.b(self.signer)) + _bytes += bytearray([int(self.nested)]) + return _bytes + + def parse(self, packet): + super(OnePassSignatureV3, self).parse(packet) + self.sigtype = packet[0] + del packet[0] + + self.halg = packet[0] + del packet[0] + + self.pubalg = packet[0] + del packet[0] + + self.signer = packet[:8] + del packet[:8] + + self.nested = (packet[0] == 1) + del packet[0] + + +class PrivKey(VersionedPacket, Primary, Private): + __typeid__ = 0x05 + __ver__ = 0 + + +class PubKey(VersionedPacket, Primary, Public): + __typeid__ = 0x06 + __ver__ = 0 + + @abc.abstractproperty + def fingerprint(self): + """compute and return the fingerprint of the key""" + + +class PubKeyV4(PubKey): + __ver__ = 4 + + @sdproperty + def created(self): + return self._created + + @created.register(datetime) + def created_datetime(self, val): + self._created = val + + @created.register(int) + def created_int(self, val): + self.created = datetime.utcfromtimestamp(val) + + @created.register(bytes) + @created.register(bytearray) + def created_bin(self, val): + self.created = self.bytes_to_int(val) + + @sdproperty + def pkalg(self): + return self._pkalg + + @pkalg.register(int) + @pkalg.register(PubKeyAlgorithm) + def pkalg_int(self, val): + self._pkalg = PubKeyAlgorithm(val) + + _c = { + # True means public + (True, PubKeyAlgorithm.RSAEncryptOrSign): RSAPub, + (True, PubKeyAlgorithm.RSAEncrypt): RSAPub, + (True, PubKeyAlgorithm.RSASign): RSAPub, + (True, PubKeyAlgorithm.DSA): DSAPub, + (True, PubKeyAlgorithm.ElGamal): ElGPub, + (True, PubKeyAlgorithm.FormerlyElGamalEncryptOrSign): ElGPub, + (True, PubKeyAlgorithm.ECDSA): ECDSAPub, + (True, PubKeyAlgorithm.ECDH): ECDHPub, + # False means private + (False, PubKeyAlgorithm.RSAEncryptOrSign): RSAPriv, + (False, PubKeyAlgorithm.RSAEncrypt): RSAPriv, + (False, PubKeyAlgorithm.RSASign): RSAPriv, + (False, PubKeyAlgorithm.DSA): DSAPriv, + (False, PubKeyAlgorithm.ElGamal): ElGPriv, + (False, PubKeyAlgorithm.FormerlyElGamalEncryptOrSign): ElGPriv, + (False, PubKeyAlgorithm.ECDSA): ECDSAPriv, + (False, PubKeyAlgorithm.ECDH): ECDHPriv, + } + + k = (self.public, self.pkalg) + km = _c.get(k, None) + + self.keymaterial = (km or (OpaquePubKey if self.public else OpaquePrivKey))() + + # km = _c.get(k, None) + # self.keymaterial = km() if km is not None else km + + @property + def public(self): + return isinstance(self, PubKey) and not isinstance(self, PrivKey) + + @property + def fingerprint(self): + # A V4 fingerprint is the 160-bit SHA-1 hash of the octet 0x99, followed by the two-octet packet length, + # followed by the entire Public-Key packet starting with the version field. The Key ID is the + # low-order 64 bits of the fingerprint. + fp = hashlib.new('sha1') + + plen = self.keymaterial.publen() + bcde_len = self.int_to_bytes(6 + plen, 2) + + # a.1) 0x99 (1 octet) + # a.2) high-order length octet + # a.3) low-order length octet + fp.update(b'\x99' + bcde_len[:1] + bcde_len[-1:]) + # b) version number = 4 (1 octet); + fp.update(b'\x04') + # c) timestamp of key creation (4 octets); + fp.update(self.int_to_bytes(calendar.timegm(self.created.timetuple()), 4)) + # d) algorithm (1 octet): 17 = DSA (example); + fp.update(self.int_to_bytes(self.pkalg)) + # e) Algorithm-specific fields. + fp.update(self.keymaterial.__bytearray__()[:plen]) + + # and return the digest + return Fingerprint(fp.hexdigest().upper()) + + def __init__(self): + super(PubKeyV4, self).__init__() + self.created = datetime.utcnow() + self.pkalg = 0 + self.keymaterial = None + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(PubKeyV4, self).__bytearray__() + _bytes += self.int_to_bytes(calendar.timegm(self.created.timetuple()), 4) + _bytes += self.int_to_bytes(self.pkalg) + _bytes += self.keymaterial.__bytearray__() + return _bytes + + def __copy__(self): + pk = self.__class__() + pk.header = copy.copy(self.header) + pk.created = self.created + pk.pkalg = self.pkalg + pk.keymaterial = copy.copy(self.keymaterial) + + return pk + + def verify(self, subj, sigbytes, hash_alg): + return self.keymaterial.verify(subj, sigbytes, hash_alg) + + def parse(self, packet): + super(PubKeyV4, self).parse(packet) + + self.created = packet[:4] + del packet[:4] + + self.pkalg = packet[0] + del packet[0] + + # bound keymaterial to the remaining length of the packet + pend = self.header.length - 6 + self.keymaterial.parse(packet[:pend]) + del packet[:pend] + + +class PrivKeyV4(PrivKey, PubKeyV4): + __ver__ = 4 + + @classmethod + def new(cls, key_algorithm, key_size): + # build a key packet + pk = PrivKeyV4() + pk.pkalg = key_algorithm + if pk.keymaterial is None: + raise NotImplementedError(key_algorithm) + pk.keymaterial._generate(key_size) + pk.update_hlen() + return pk + + def pubkey(self): + # return a copy of ourselves, but just the public half + pk = PubKeyV4() if not isinstance(self, PrivSubKeyV4) else PubSubKeyV4() + pk.created = self.created + pk.pkalg = self.pkalg + + # copy over MPIs + for pm in self.keymaterial.__pubfields__: + setattr(pk.keymaterial, pm, copy.copy(getattr(self.keymaterial, pm))) + + if self.pkalg == PubKeyAlgorithm.ECDSA: + pk.keymaterial.oid = self.keymaterial.oid + + if self.pkalg == PubKeyAlgorithm.ECDH: + pk.keymaterial.oid = self.keymaterial.oid + pk.keymaterial.kdf = copy.copy(self.keymaterial.kdf) + + pk.update_hlen() + return pk + + @property + def protected(self): + return bool(self.keymaterial.s2k) + + @property + def unlocked(self): + if self.protected: + return 0 not in list(self.keymaterial) + return True # pragma: no cover + + def protect(self, passphrase, enc_alg, hash_alg): + self.keymaterial.encrypt_keyblob(passphrase, enc_alg, hash_alg) + del passphrase + self.update_hlen() + + def unprotect(self, passphrase): + self.keymaterial.decrypt_keyblob(passphrase) + del passphrase + + def sign(self, sigdata, hash_alg): + return self.keymaterial.sign(sigdata, hash_alg) + + +class PrivSubKey(VersionedPacket, Sub, Private): + __typeid__ = 0x07 + __ver__ = 0 + + +class PrivSubKeyV4(PrivSubKey, PrivKeyV4): + __ver__ = 4 + + +class CompressedData(Packet): + """ + 5.6. Compressed Data Packet (Tag 8) + + The Compressed Data packet contains compressed data. Typically, this + packet is found as the contents of an encrypted packet, or following + a Signature or One-Pass Signature packet, and contains a literal data + packet. + + The body of this packet consists of: + + - One octet that gives the algorithm used to compress the packet. + + - Compressed data, which makes up the remainder of the packet. + + A Compressed Data Packet's body contains an block that compresses + some set of packets. See section "Packet Composition" for details on + how messages are formed. + + ZIP-compressed packets are compressed with raw RFC 1951 [RFC1951] + DEFLATE blocks. Note that PGP V2.6 uses 13 bits of compression. If + an implementation uses more bits of compression, PGP V2.6 cannot + decompress it. + + ZLIB-compressed packets are compressed with RFC 1950 [RFC1950] ZLIB- + style blocks. + + BZip2-compressed packets are compressed using the BZip2 [BZ2] + algorithm. + """ + __typeid__ = 0x08 + + @sdproperty + def calg(self): + return self._calg + + @calg.register(int) + @calg.register(CompressionAlgorithm) + def calg_int(self, val): + self._calg = CompressionAlgorithm(val) + + def __init__(self): + super(CompressedData, self).__init__() + self._calg = None + self.packets = [] + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(CompressedData, self).__bytearray__() + _bytes += bytearray([self.calg]) + + _pb = bytearray() + for pkt in self.packets: + _pb += pkt.__bytearray__() + _bytes += self.calg.compress(bytes(_pb)) + + return _bytes + + def parse(self, packet): + super(CompressedData, self).parse(packet) + self.calg = packet[0] + del packet[0] + + cdata = bytearray(self.calg.decompress(packet[:self.header.length - 1])) + del packet[:self.header.length - 1] + + while len(cdata) > 0: + self.packets.append(Packet(cdata)) + + +class SKEData(Packet): + """ + 5.7. Symmetrically Encrypted Data Packet (Tag 9) + + The Symmetrically Encrypted Data packet contains data encrypted with + a symmetric-key algorithm. When it has been decrypted, it contains + other packets (usually a literal data packet or compressed data + packet, but in theory other Symmetrically Encrypted Data packets or + sequences of packets that form whole OpenPGP messages). + + The body of this packet consists of: + + - Encrypted data, the output of the selected symmetric-key cipher + operating in OpenPGP's variant of Cipher Feedback (CFB) mode. + + The symmetric cipher used may be specified in a Public-Key or + Symmetric-Key Encrypted Session Key packet that precedes the + Symmetrically Encrypted Data packet. In that case, the cipher + algorithm octet is prefixed to the session key before it is + encrypted. If no packets of these types precede the encrypted data, + the IDEA algorithm is used with the session key calculated as the MD5 + hash of the passphrase, though this use is deprecated. + + The data is encrypted in CFB mode, with a CFB shift size equal to the + cipher's block size. The Initial Vector (IV) is specified as all + zeros. Instead of using an IV, OpenPGP prefixes a string of length + equal to the block size of the cipher plus two to the data before it + is encrypted. The first block-size octets (for example, 8 octets for + a 64-bit block length) are random, and the following two octets are + copies of the last two octets of the IV. For example, in an 8-octet + block, octet 9 is a repeat of octet 7, and octet 10 is a repeat of + octet 8. In a cipher of length 16, octet 17 is a repeat of octet 15 + and octet 18 is a repeat of octet 16. As a pedantic clarification, + in both these examples, we consider the first octet to be numbered 1. + + After encrypting the first block-size-plus-two octets, the CFB state + is resynchronized. The last block-size octets of ciphertext are + passed through the cipher and the block boundary is reset. + + The repetition of 16 bits in the random data prefixed to the message + allows the receiver to immediately check whether the session key is + incorrect. See the "Security Considerations" section for hints on + the proper use of this "quick check". + """ + __typeid__ = 0x09 + + def __init__(self): + super(SKEData, self).__init__() + self.ct = bytearray() + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(SKEData, self).__bytearray__() + _bytes += self.ct + return _bytes + + def __copy__(self): + skd = self.__class__() + skd.ct = self.ct[:] + return skd + + def parse(self, packet): + super(SKEData, self).parse(packet) + self.ct = packet[:self.header.length] + del packet[:self.header.length] + + def decrypt(self, key, alg): # pragma: no cover + pt = _decrypt(bytes(self.ct), bytes(key), alg) + + iv = bytes(pt[:alg.block_size // 8]) + del pt[:alg.block_size // 8] + + ivl2 = bytes(pt[:2]) + del pt[:2] + + if not constant_time.bytes_eq(iv[-2:], ivl2): + raise PGPDecryptionError("Decryption failed") + + return pt + + +class Marker(Packet): + __typeid__ = 0x0a + + def __init__(self): + super(Marker, self).__init__() + self.data = b'PGP' + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(Marker, self).__bytearray__() + _bytes += self.data + return _bytes + + def parse(self, packet): + super(Marker, self).parse(packet) + self.data = packet[:self.header.length] + del packet[:self.header.length] + + +class LiteralData(Packet): + """ + 5.9. Literal Data Packet (Tag 11) + + A Literal Data packet contains the body of a message; data that is + not to be further interpreted. + + The body of this packet consists of: + + - A one-octet field that describes how the data is formatted. + + If it is a 'b' (0x62), then the Literal packet contains binary data. + If it is a 't' (0x74), then it contains text data, and thus may need + line ends converted to local form, or other text-mode changes. The + tag 'u' (0x75) means the same as 't', but also indicates that + implementation believes that the literal data contains UTF-8 text. + + Early versions of PGP also defined a value of 'l' as a 'local' mode + for machine-local conversions. RFC 1991 [RFC1991] incorrectly stated + this local mode flag as '1' (ASCII numeral one). Both of these local + modes are deprecated. + + - File name as a string (one-octet length, followed by a file + name). This may be a zero-length string. Commonly, if the + source of the encrypted data is a file, this will be the name of + the encrypted file. An implementation MAY consider the file name + in the Literal packet to be a more authoritative name than the + actual file name. + + If the special name "_CONSOLE" is used, the message is considered to + be "for your eyes only". This advises that the message data is + unusually sensitive, and the receiving program should process it more + carefully, perhaps avoiding storing the received data to disk, for + example. + + - A four-octet number that indicates a date associated with the + literal data. Commonly, the date might be the modification date + of a file, or the time the packet was created, or a zero that + indicates no specific time. + + - The remainder of the packet is literal data. + + Text data is stored with <CR><LF> text endings (i.e., network- + normal line endings). These should be converted to native line + endings by the receiving software. + """ + __typeid__ = 0x0B + + @sdproperty + def mtime(self): + return self._mtime + + @mtime.register(datetime) + def mtime_datetime(self, val): + self._mtime = val + + @mtime.register(int) + def mtime_int(self, val): + self.mtime = datetime.utcfromtimestamp(val) + + @mtime.register(bytes) + @mtime.register(bytearray) + def mtime_bin(self, val): + self.mtime = self.bytes_to_int(val) + + @property + def contents(self): + if self.format == 't': + return self._contents.decode('latin-1') + + if self.format == 'u': + return self._contents.decode('utf-8') + + return self._contents + + def __init__(self): + super(LiteralData, self).__init__() + self.format = 'b' + self.filename = '' + self.mtime = datetime.utcnow() + self._contents = bytearray() + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(LiteralData, self).__bytearray__() + _bytes += self.format.encode('latin-1') + _bytes += bytearray([len(self.filename)]) + _bytes += self.filename.encode('latin-1') + _bytes += self.int_to_bytes(calendar.timegm(self.mtime.timetuple()), 4) + _bytes += self._contents + return _bytes + + def __copy__(self): + pkt = LiteralData() + pkt.header = copy.copy(self.header) + pkt.format = self.format + pkt.filename = self.filename + pkt.mtime = self.mtime + pkt._contents = self._contents[:] + + return pkt + + def parse(self, packet): + super(LiteralData, self).parse(packet) + self.format = chr(packet[0]) + del packet[0] + + fnl = packet[0] + del packet[0] + + self.filename = packet[:fnl].decode() + del packet[:fnl] + + self.mtime = packet[:4] + del packet[:4] + + self._contents = packet[:self.header.length - (6 + fnl)] + del packet[:self.header.length - (6 + fnl)] + + +class Trust(Packet): + """ + 5.10. Trust Packet (Tag 12) + + The Trust packet is used only within keyrings and is not normally + exported. Trust packets contain data that record the user's + specifications of which key holders are trustworthy introducers, + along with other information that implementing software uses for + trust information. The format of Trust packets is defined by a given + implementation. + + Trust packets SHOULD NOT be emitted to output streams that are + transferred to other users, and they SHOULD be ignored on any input + other than local keyring files. + """ + __typeid__ = 0x0C + + @sdproperty + def trustlevel(self): + return self._trustlevel + + @trustlevel.register(int) + @trustlevel.register(TrustLevel) + def trustlevel_int(self, val): + self._trustlevel = TrustLevel(val & 0x0F) + + @sdproperty + def trustflags(self): + return self._trustflags + + @trustflags.register(list) + def trustflags_list(self, val): + self._trustflags = val + + @trustflags.register(int) + def trustflags_int(self, val): + self._trustflags = TrustFlags & val + + def __init__(self): + super(Trust, self).__init__() + self.trustlevel = TrustLevel.Unknown + self.trustflags = [] + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(Trust, self).__bytearray__() + _bytes += self.int_to_bytes(self.trustlevel + sum(self.trustflags), 2) + return _bytes + + def parse(self, packet): + super(Trust, self).parse(packet) + # self.trustlevel = packet[0] & 0x1f + t = self.bytes_to_int(packet[:2]) + del packet[:2] + + self.trustlevel = t + self.trustflags = t + + +class UserID(Packet): + """ + 5.11. User ID Packet (Tag 13) + + A User ID packet consists of UTF-8 text that is intended to represent + the name and email address of the key holder. By convention, it + includes an RFC 2822 [RFC2822] mail name-addr, but there are no + restrictions on its content. The packet length in the header + specifies the length of the User ID. + """ + __typeid__ = 0x0D + + def __init__(self): + super(UserID, self).__init__() + self.name = "" + self.comment = "" + self.email = "" + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(UserID, self).__bytearray__() + _bytes += self.text_to_bytes(self.name) + if self.comment: + _bytes += b' (' + self.text_to_bytes(self.comment) + b')' + + if self.email: + _bytes += b' <' + self.text_to_bytes(self.email) + b'>' + + return _bytes + + def __copy__(self): + uid = UserID() + uid.header = copy.copy(self.header) + uid.name = self.name + uid.comment = self.comment + uid.email = self.email + return uid + + def parse(self, packet): + super(UserID, self).parse(packet) + + uid_text = packet[:self.header.length].decode('latin-1') + del packet[:self.header.length] + + # came across a UID packet with no payload. If that happens, don't bother trying to parse anything! + if self.header.length > 0: + uid = re.match(r"""^ + # name should always match something + (?P<name>.+?) + # comment *optionally* matches text in parens following name + # this should never come after email and must be followed immediately by + # either the email field, or the end of the packet. + (\ \((?P<comment>.+?)\)(?=(\ <|$)))? + # email *optionally* matches text in angle brackets following name or comment + # this should never come before a comment, if comment exists, + # but can immediately follow name if comment does not exist + (\ <(?P<email>.+)>)? + $ + """, uid_text, flags=re.VERBOSE).groupdict() + + self.name = uid['name'] + self.comment = uid['comment'] or "" + self.email = uid['email'] or "" + + +class PubSubKey(VersionedPacket, Sub, Public): + __typeid__ = 0x0E + __ver__ = 0 + + +class PubSubKeyV4(PubSubKey, PubKeyV4): + __ver__ = 4 + + +class UserAttribute(Packet): + """ + 5.12. User Attribute Packet (Tag 17) + + The User Attribute packet is a variation of the User ID packet. It + is capable of storing more types of data than the User ID packet, + which is limited to text. Like the User ID packet, a User Attribute + packet may be certified by the key owner ("self-signed") or any other + key owner who cares to certify it. Except as noted, a User Attribute + packet may be used anywhere that a User ID packet may be used. + + While User Attribute packets are not a required part of the OpenPGP + standard, implementations SHOULD provide at least enough + compatibility to properly handle a certification signature on the + User Attribute packet. A simple way to do this is by treating the + User Attribute packet as a User ID packet with opaque contents, but + an implementation may use any method desired. + + The User Attribute packet is made up of one or more attribute + subpackets. Each subpacket consists of a subpacket header and a + body. The header consists of: + + - the subpacket length (1, 2, or 5 octets) + + - the subpacket type (1 octet) + + and is followed by the subpacket specific data. + + The only currently defined subpacket type is 1, signifying an image. + An implementation SHOULD ignore any subpacket of a type that it does + not recognize. Subpacket types 100 through 110 are reserved for + private or experimental use. + """ + __typeid__ = 0x11 + + @property + def image(self): + if 'Image' not in self.subpackets: + self.subpackets.addnew('Image') + return next(iter(self.subpackets['Image'])) + + def __init__(self): + super(UserAttribute, self).__init__() + self.subpackets = UserAttributeSubPackets() + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(UserAttribute, self).__bytearray__() + _bytes += self.subpackets.__bytearray__() + return _bytes + + def parse(self, packet): + super(UserAttribute, self).parse(packet) + + plen = len(packet) + while self.header.length > (plen - len(packet)): + self.subpackets.parse(packet) + + def update_hlen(self): + self.subpackets.update_hlen() + super(UserAttribute, self).update_hlen() + + +class IntegrityProtectedSKEData(VersionedPacket): + __typeid__ = 0x12 + __ver__ = 0 + + +class IntegrityProtectedSKEDataV1(IntegrityProtectedSKEData): + """ + 5.13. Sym. Encrypted Integrity Protected Data Packet (Tag 18) + + The Symmetrically Encrypted Integrity Protected Data packet is a + variant of the Symmetrically Encrypted Data packet. It is a new + feature created for OpenPGP that addresses the problem of detecting a + modification to encrypted data. It is used in combination with a + Modification Detection Code packet. + + There is a corresponding feature in the features Signature subpacket + that denotes that an implementation can properly use this packet + type. An implementation MUST support decrypting these packets and + SHOULD prefer generating them to the older Symmetrically Encrypted + Data packet when possible. Since this data packet protects against + modification attacks, this standard encourages its proliferation. + While blanket adoption of this data packet would create + interoperability problems, rapid adoption is nevertheless important. + An implementation SHOULD specifically denote support for this packet, + but it MAY infer it from other mechanisms. + + For example, an implementation might infer from the use of a cipher + such as Advanced Encryption Standard (AES) or Twofish that a user + supports this feature. It might place in the unhashed portion of + another user's key signature a Features subpacket. It might also + present a user with an opportunity to regenerate their own self- + signature with a Features subpacket. + + This packet contains data encrypted with a symmetric-key algorithm + and protected against modification by the SHA-1 hash algorithm. When + it has been decrypted, it will typically contain other packets (often + a Literal Data packet or Compressed Data packet). The last decrypted + packet in this packet's payload MUST be a Modification Detection Code + packet. + + The body of this packet consists of: + + - A one-octet version number. The only currently defined value is + 1. + + - Encrypted data, the output of the selected symmetric-key cipher + operating in Cipher Feedback mode with shift amount equal to the + block size of the cipher (CFB-n where n is the block size). + + The symmetric cipher used MUST be specified in a Public-Key or + Symmetric-Key Encrypted Session Key packet that precedes the + Symmetrically Encrypted Data packet. In either case, the cipher + algorithm octet is prefixed to the session key before it is + encrypted. + + The data is encrypted in CFB mode, with a CFB shift size equal to the + cipher's block size. The Initial Vector (IV) is specified as all + zeros. Instead of using an IV, OpenPGP prefixes an octet string to + the data before it is encrypted. The length of the octet string + equals the block size of the cipher in octets, plus two. The first + octets in the group, of length equal to the block size of the cipher, + are random; the last two octets are each copies of their 2nd + preceding octet. For example, with a cipher whose block size is 128 + bits or 16 octets, the prefix data will contain 16 random octets, + then two more octets, which are copies of the 15th and 16th octets, + respectively. Unlike the Symmetrically Encrypted Data Packet, no + special CFB resynchronization is done after encrypting this prefix + data. See "OpenPGP CFB Mode" below for more details. + + The repetition of 16 bits in the random data prefixed to the message + allows the receiver to immediately check whether the session key is + incorrect. + + The plaintext of the data to be encrypted is passed through the SHA-1 + hash function, and the result of the hash is appended to the + plaintext in a Modification Detection Code packet. The input to the + hash function includes the prefix data described above; it includes + all of the plaintext, and then also includes two octets of values + 0xD3, 0x14. These represent the encoding of a Modification Detection + Code packet tag and length field of 20 octets. + + The resulting hash value is stored in a Modification Detection Code + (MDC) packet, which MUST use the two octet encoding just given to + represent its tag and length field. The body of the MDC packet is + the 20-octet output of the SHA-1 hash. + + The Modification Detection Code packet is appended to the plaintext + and encrypted along with the plaintext using the same CFB context. + + During decryption, the plaintext data should be hashed with SHA-1, + including the prefix data as well as the packet tag and length field + of the Modification Detection Code packet. The body of the MDC + packet, upon decryption, is compared with the result of the SHA-1 + hash. + + Any failure of the MDC indicates that the message has been modified + and MUST be treated as a security problem. Failures include a + difference in the hash values, but also the absence of an MDC packet, + or an MDC packet in any position other than the end of the plaintext. + Any failure SHOULD be reported to the user. + + Note: future designs of new versions of this packet should consider + rollback attacks since it will be possible for an attacker to change + the version back to 1. + """ + __ver__ = 1 + + def __init__(self): + super(IntegrityProtectedSKEDataV1, self).__init__() + self.ct = bytearray() + + def __bytearray__(self): + _bytes = bytearray() + _bytes += super(IntegrityProtectedSKEDataV1, self).__bytearray__() + _bytes += self.ct + return _bytes + + def __copy__(self): + skd = self.__class__() + skd.ct = self.ct[:] + return skd + + def parse(self, packet): + super(IntegrityProtectedSKEDataV1, self).parse(packet) + self.ct = packet[:self.header.length - 1] + del packet[:self.header.length - 1] + + def encrypt(self, key, alg, data): + iv = alg.gen_iv() + data = iv + iv[-2:] + data + + mdc = MDC() + mdc.mdc = binascii.hexlify(hashlib.new('SHA1', data + b'\xd3\x14').digest()) + mdc.update_hlen() + + data += mdc.__bytes__() + self.ct = _encrypt(data, key, alg) + self.update_hlen() + + def decrypt(self, key, alg): + # iv, ivl2, pt = super(IntegrityProtectedSKEDataV1, self).decrypt(key, alg) + pt = _decrypt(bytes(self.ct), bytes(key), alg) + + # do the MDC checks + _expected_mdcbytes = b'\xd3\x14' + hashlib.new('SHA1', pt[:-20]).digest() + if not constant_time.bytes_eq(bytes(pt[-22:]), _expected_mdcbytes): + raise PGPDecryptionError("Decryption failed") # pragma: no cover + + iv = bytes(pt[:alg.block_size // 8]) + del pt[:alg.block_size // 8] + + ivl2 = bytes(pt[:2]) + del pt[:2] + + if not constant_time.bytes_eq(iv[-2:], ivl2): + raise PGPDecryptionError("Decryption failed") # pragma: no cover + + return pt + + +class MDC(Packet): + """ + 5.14. Modification Detection Code Packet (Tag 19) + + The Modification Detection Code packet contains a SHA-1 hash of + plaintext data, which is used to detect message modification. It is + only used with a Symmetrically Encrypted Integrity Protected Data + packet. The Modification Detection Code packet MUST be the last + packet in the plaintext data that is encrypted in the Symmetrically + Encrypted Integrity Protected Data packet, and MUST appear in no + other place. + + A Modification Detection Code packet MUST have a length of 20 octets. + The body of this packet consists of: + + - A 20-octet SHA-1 hash of the preceding plaintext data of the + Symmetrically Encrypted Integrity Protected Data packet, + including prefix data, the tag octet, and length octet of the + Modification Detection Code packet. + + Note that the Modification Detection Code packet MUST always use a + new format encoding of the packet tag, and a one-octet encoding of + the packet length. The reason for this is that the hashing rules for + modification detection include a one-octet tag and one-octet length + in the data hash. While this is a bit restrictive, it reduces + complexity. + """ + __typeid__ = 0x13 + + def __init__(self): + super(MDC, self).__init__() + self.mdc = '' + + def __bytearray__(self): + return super(MDC, self).__bytearray__() + binascii.unhexlify(self.mdc) + + def parse(self, packet): + super(MDC, self).parse(packet) + self.mdc = binascii.hexlify(packet[:20]) + del packet[:20] |