1425 lines
48 KiB
Python
1425 lines
48 KiB
Python
""" packet.py
|
|
"""
|
|
import abc
|
|
import binascii
|
|
import calendar
|
|
import hashlib
|
|
import os
|
|
import re
|
|
|
|
from datetime import datetime
|
|
|
|
import six
|
|
|
|
from cryptography.hazmat.backends import default_backend
|
|
from cryptography.hazmat.primitives import constant_time
|
|
from cryptography.hazmat.primitives.asymmetric import padding
|
|
|
|
from .fields import DSAPriv
|
|
from .fields import DSAPub
|
|
from .fields import DSASignature
|
|
from .fields import ElGCipherText
|
|
from .fields import ElGPriv
|
|
from .fields import ElGPub
|
|
from .fields import RSACipherText
|
|
from .fields import RSAPriv
|
|
from .fields import RSAPub
|
|
from .fields import 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
|
|
|
|
|
|
class PKESessionKey(VersionedPacket):
|
|
__typeid__ = 0x01
|
|
__ver__ = 0
|
|
|
|
@abc.abstractmethod
|
|
def decrypt_sk(self, pk):
|
|
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_(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_(self, val):
|
|
self._pkalg = PubKeyAlgorithm(val)
|
|
|
|
_c = {PubKeyAlgorithm.RSAEncryptOrSign: RSACipherText,
|
|
PubKeyAlgorithm.RSAEncrypt: RSACipherText,
|
|
PubKeyAlgorithm.ElGamal: ElGCipherText,
|
|
PubKeyAlgorithm.FormerlyElGamalEncryptOrSign: ElGCipherText}
|
|
|
|
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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(PKESessionKeyV3, self).__bytes__()
|
|
_bytes += binascii.unhexlify(self.encrypter.encode())
|
|
_bytes.append(self.pkalg)
|
|
_bytes += self.ct.__bytes__() if self.ct is not None else b'\x00' * (self.header.length - 10)
|
|
return bytes(_bytes)
|
|
|
|
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.key_size // 8) - len(ct)) + ct
|
|
|
|
decargs = (ct, padding.PKCS1v15(), default_backend())
|
|
|
|
else:
|
|
raise NotImplementedError(self.pkalg)
|
|
|
|
m = bytearray(pk.decrypt(*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:
|
|
encargs = (bytes(m), padding.PKCS1v15(), default_backend())
|
|
|
|
else:
|
|
raise NotImplementedError(self.pkalg)
|
|
|
|
self.ct.from_encrypter(pk.encrypt(*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_(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
|
|
|
|
@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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(Signature, self).__bytes__()
|
|
_bytes += self.int_to_bytes(self.sigtype)
|
|
_bytes += self.int_to_bytes(self.pubalg)
|
|
_bytes += self.int_to_bytes(self.halg)
|
|
_bytes += self.subpackets.__bytes__()
|
|
_bytes += self.hash2
|
|
_bytes += self.signature.__bytes__()
|
|
|
|
return bytes(_bytes)
|
|
|
|
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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(SKESessionKeyV4, self).__bytes__()
|
|
_bytes += self.s2k.__bytes__()[1:]
|
|
_bytes += self.ct
|
|
return bytes(_bytes)
|
|
|
|
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 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_(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(bytes)
|
|
@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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(OnePassSignatureV3, self).__bytes__()
|
|
_bytes.append(self.sigtype)
|
|
_bytes.append(self.halg)
|
|
_bytes.append(self.pubalg)
|
|
_bytes += binascii.unhexlify(self.signer.encode('latin-1'))
|
|
_bytes.append(0 if self.nested else 1)
|
|
return bytes(_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] == 0)
|
|
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,
|
|
# 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,
|
|
}
|
|
|
|
k = (self.public, self.pkalg)
|
|
|
|
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.__bytes__()[: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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(PubKeyV4, self).__bytes__()
|
|
_bytes += self.int_to_bytes(calendar.timegm(self.created.timetuple()), 4)
|
|
_bytes += self.int_to_bytes(self.pkalg)
|
|
_bytes += self.keymaterial.__bytes__()
|
|
return bytes(_bytes)
|
|
|
|
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
|
|
|
|
@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 unprotect(self, passphrase):
|
|
self.keymaterial.decrypt_keyblob(passphrase)
|
|
del passphrase
|
|
|
|
|
|
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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(CompressedData, self).__bytes__()
|
|
_bytes.append(self.calg)
|
|
_bytes += self.calg.compress(b''.join(pkt.__bytes__() for pkt in self.packets))
|
|
return bytes(_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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(SKEData, self).__bytes__()
|
|
_bytes += self.ct
|
|
return bytes(_bytes)
|
|
|
|
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__ = 0x10
|
|
pass
|
|
|
|
|
|
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': # pragma: no cover
|
|
return self._contents.decode('latin-1')
|
|
|
|
if self.format == 'u': # pragma: no cover
|
|
return six.u(self._contents.decode('latin-1'))
|
|
|
|
return self._contents
|
|
|
|
def __init__(self):
|
|
super(LiteralData, self).__init__()
|
|
self.format = 'b'
|
|
self.filename = ''
|
|
self.mtime = datetime.utcnow()
|
|
self._contents = bytearray()
|
|
|
|
def __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(LiteralData, self).__bytes__()
|
|
_bytes += self.format.encode('latin-1')
|
|
_bytes.append(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(_bytes)
|
|
|
|
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_(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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(Trust, self).__bytes__()
|
|
_bytes += self.int_to_bytes(self.trustlevel + sum(self.trustflags), 2)
|
|
return bytes(_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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(UserID, self).__bytes__()
|
|
_bytes += "{name:s}{comment:s}{email:s}".format(
|
|
name=self.name,
|
|
comment=" ({comment:s})".format(comment=self.comment) if self.comment not in [None, ""] else "",
|
|
email=" <{email:s}>".format(email=self.email) if self.email not in [None, ""] else "").encode()
|
|
return bytes(_bytes)
|
|
|
|
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']
|
|
self.email = uid['email']
|
|
|
|
|
|
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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(UserAttribute, self).__bytes__()
|
|
_bytes += self.subpackets.__bytes__()
|
|
return bytes(_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 __bytes__(self):
|
|
_bytes = bytearray()
|
|
_bytes += super(IntegrityProtectedSKEDataV1, self).__bytes__()
|
|
_bytes += self.ct
|
|
return bytes(_bytes)
|
|
|
|
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 __bytes__(self):
|
|
return super(MDC, self).__bytes__() + binascii.unhexlify(self.mdc)
|
|
|
|
def parse(self, packet):
|
|
super(MDC, self).parse(packet)
|
|
self.mdc = binascii.hexlify(packet[:20])
|
|
del packet[:20]
|