- added missing __copy__ implementations

- fixed a weird signature bug and simplified the way signature hashdata is computed for keys
- fixed and simplified backwards math for padding ByteFlag signature subpackets
- added PGPKey.key_size property
[skip ci]
This commit is contained in:
Michael Greene
2017-01-11 14:00:30 -08:00
parent e90ed6a1a5
commit 1773593242
3 changed files with 113 additions and 39 deletions

View File

@@ -788,6 +788,17 @@ class String2Key(Field):
def __nonzero__(self):
return self.__bool__()
def __copy__(self):
s2k = String2Key()
s2k.usage = self.usage
s2k.encalg = self.encalg
s2k.specifier = self.specifier
s2k.iv = self.iv
s2k.halg = self.halg
s2k.salt = copy.copy(self.salt)
s2k.count = self._count
return s2k
def parse(self, packet, iv=True):
self.usage = packet[0]
del packet[0]
@@ -985,6 +996,13 @@ class PrivKey(PubKey):
return l
def __copy__(self):
pk = super(PrivKey, self).__copy__()
pk.s2k = copy.copy(self.s2k)
pk.encbytes = copy.copy(self.encbytes)
pk.chksum = copy.copy(self.chksum)
return pk
@abc.abstractmethod
def __privkey__(self):
"""return the requisite *PrivateKey class from the cryptography library"""

View File

@@ -162,8 +162,8 @@ class ByteFlag(Signature):
_bytes = super(ByteFlag, self).__bytearray__()
_bytes += self.int_to_bytes(sum(self.flags))
# null-pad _bytes if they are not up to the end now
if self.header.length + len(self.header) != len(_bytes):
_bytes += b'\x00' * (len(_bytes) - len(self.header) - 1)
if len(_bytes) < len(self):
_bytes += b'\x00' * (len(self) - len(_bytes))
return _bytes
def parse(self, packet):

View File

@@ -3,6 +3,7 @@
this is where the armorable PGP block objects live
"""
import binascii
import calendar
import collections
import contextlib
import copy
@@ -360,10 +361,9 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
"""
_data += re.subn(br'\r?\n', b'\r\n', subject)[0]
if self.type in [SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
if self.type in {SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
SignatureType.Positive_Cert, SignatureType.CertRevocation, SignatureType.Subkey_Binding,
SignatureType.PrimaryKey_Binding, SignatureType.DirectlyOnKey, SignatureType.KeyRevocation,
SignatureType.SubkeyRevocation]:
SignatureType.PrimaryKey_Binding}:
"""
When a signature is made over a key, the hash data starts with the
octet 0x99, followed by a two-octet length of the key, and then body
@@ -385,7 +385,7 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
if len(_s) > 0:
_data += b'\x99' + self.int_to_bytes(len(_s), 2) + _s
if self.type in [SignatureType.Subkey_Binding, SignatureType.PrimaryKey_Binding, SignatureType.SubkeyRevocation]:
if self.type in {SignatureType.Subkey_Binding, SignatureType.PrimaryKey_Binding}:
"""
A subkey binding signature
(type 0x18) or primary key binding signature (type 0x19) then hashes
@@ -400,8 +400,34 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
_data += b'\x99' + self.int_to_bytes(len(_s), 2) + _s
if self.type in [SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
SignatureType.Positive_Cert, SignatureType.CertRevocation]:
if self.type in {SignatureType.KeyRevocation, SignatureType.SubkeyRevocation, SignatureType.DirectlyOnKey}:
"""
The signature is calculated directly on the key being revoked. A
revoked key is not to be used. Only revocation signatures by the
key being revoked, or by an authorized revocation key, should be
considered valid revocation signatures.
Subkey revocation signature
The signature is calculated directly on the subkey being revoked.
A revoked subkey is not to be used. Only revocation signatures
by the top-level signature key that is bound to this subkey, or
by an authorized revocation key, should be considered valid
revocation signatures.
Signature directly on a key
This signature is calculated directly on a key. It binds the
information in the Signature subpackets to the key, and is
appropriate to be used for subpackets that provide information
about the key, such as the Revocation Key subpacket. It is also
appropriate for statements that non-self certifiers want to make
about the key itself, rather than the binding between a key and a
name.
"""
_s = subject.hashdata
_data += b'\x99' + self.int_to_bytes(len(_s), 2) + _s
if self.type in {SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
SignatureType.Positive_Cert, SignatureType.CertRevocation}:
"""
A certification signature (type 0x10 through 0x13) hashes the User
ID being bound to the key into the hash context after the above
@@ -421,10 +447,12 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
_s = subject.hashdata
if subject.is_uid:
_data += b'\xb4' + self.int_to_bytes(len(_s), 4) + _s
_data += b'\xb4'
if subject.is_ua:
_data += b'\xd1' + self.int_to_bytes(len(_s), 4) + _s
else:
_data += b'\xd1'
_data += self.int_to_bytes(len(_s), 4) + _s
# if this is a new signature, do update_hlen
if 0 in list(self._signature.signature):
@@ -789,12 +817,18 @@ class PGPMessage(Armorable, PGPObject):
def __str__(self):
if self.type == 'cleartext':
return "-----BEGIN PGP SIGNED MESSAGE-----\n" \
"Hash: {hashes:s}\n\n" \
tmpl = "-----BEGIN PGP SIGNED MESSAGE-----\n" \
"{hhdr:s}\n" \
"{cleartext:s}\n" \
"{signature:s}".format(hashes=','.join(set(s.hash_algorithm.name for s in self.signatures)),
cleartext=self.dash_escape(self.bytes_to_text(self._message)),
signature=super(PGPMessage, self).__str__())
"{signature:s}"
# only add a Hash: header if we actually have at least one signature
hashes = set(s.hash_algorithm.name for s in self.signatures)
hhdr = 'Hash: {hashes:s}\n'.format(hashes=','.join(sorted(hashes))) if hashes else ''
return tmpl.format(hhdr=hhdr,
cleartext=self.dash_escape(self.bytes_to_text(self._message)),
signature=super(PGPMessage, self).__str__())
return super(PGPMessage, self).__str__()
@@ -1207,8 +1241,8 @@ class PGPKey(Armorable, ParentRef, PGPObject):
if self.is_public:
return self._key.__bytearray__()[len(self._key.header):]
publen = len(self._key) - len(self._key.keymaterial) + self._key.keymaterial.publen()
return self._key.__bytearray__()[len(self._key.header):publen]
pub = self._key.pubkey()
return pub.__bytearray__()[len(pub.header):]
@property
def is_expired(self):
@@ -1253,6 +1287,15 @@ class PGPKey(Armorable, ParentRef, PGPObject):
"""The :py:obj:`constants.PubKeyAlgorithm` pertaining to this key"""
return self._key.pkalg
@property
def key_size(self):
"""*new in 0.5.0*
The size pertaining to this key. ``int`` for non-EC key algorithms; :py:pbj:`constants.EllipticCurveOID` for EC keys.
"""
if self.key_algorithm in {PubKeyAlgorithm.ECDSA, PubKeyAlgorithm.ECDH}:
return self._key.keymaterial.oid
return next(iter(self._key.keymaterial)).bit_length()
@property
def magic(self):
return '{:s} KEY BLOCK'.format('PUBLIC' if (isinstance(self._key, Public) and not isinstance(self._key, Private)) else
@@ -1425,7 +1468,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
raise TypeError
def __or__(self, other):
def __or__(self, other, from_sib=False):
if isinstance(other, Key) and self._key is None:
self._key = other
@@ -1451,9 +1494,13 @@ class PGPKey(Armorable, ParentRef, PGPObject):
raise TypeError("unsupported operand type(s) for |: '{:s}' and '{:s}'"
"".format(self.__class__.__name__, other.__class__.__name__))
if isinstance(self._sibling, weakref.ref): # pragma: no cover
if isinstance(self._sibling, weakref.ref) and not from_sib:
sib = self._sibling()
sib |= copy.copy(other)
if sib is None:
self._sibling = None
else:
sib.__or__(copy.copy(other), True)
return self
@@ -1461,13 +1508,18 @@ class PGPKey(Armorable, ParentRef, PGPObject):
key = super(PGPKey, self).__copy__()
key._key = copy.copy(self._key)
for id, subkey in self._children.items():
key |= copy.copy(subkey)
for uid in self._uids:
key |= copy.copy(uid)
for id, subkey in self._children.items():
key |= copy.copy(subkey)
for sig in self._signatures:
if sig.embedded:
# embedded signatures don't need to be explicitly copied
continue
print(len(key._signatures))
key |= copy.copy(sig)
return key
@@ -2049,24 +2101,28 @@ class PGPKey(Armorable, ParentRef, PGPObject):
return [ sig for sig in sigs if sig.signer in _ids ]
# collect signature(s)
if isinstance(signature, PGPSignature):
if signature.signer != self.fingerprint.keyid and signature.signer not in self.subkeys:
raise PGPError("Incorrect key. Expected: {:s}".format(signature.signer))
sspairs.append((signature, subject))
if signature is None:
if isinstance(signature, PGPSignature):
if signature.signer != self.fingerprint.keyid and signature.signer not in self.subkeys:
raise PGPError("Incorrect key. Expected: {:s}".format(signature.signer))
sspairs.append((signature, subject))
if isinstance(subject, PGPMessage):
sspairs += [ (sig, subject.message) for sig in _filter_sigs(subject.signatures) ]
if isinstance(subject, PGPMessage):
sspairs += [ (sig, subject.message) for sig in _filter_sigs(subject.signatures) ]
if isinstance(subject, (PGPUID, PGPKey)):
sspairs += [ (sig, subject) for sig in _filter_sigs(subject.__sig__) ]
if isinstance(subject, (PGPUID, PGPKey)):
sspairs += [ (sig, subject) for sig in _filter_sigs(subject.__sig__) ]
if isinstance(subject, PGPKey):
# user ids
sspairs += [ (sig, uid) for uid in subject.userids for sig in _filter_sigs(uid.__sig__) ]
# user attributes
sspairs += [ (sig, ua) for ua in subject.userattributes for sig in _filter_sigs(ua.__sig__) ]
# subkey binding signatures
sspairs += [ (sig, subkey) for subkey in subject.subkeys.values() for sig in _filter_sigs(subkey.__sig__) ]
if isinstance(subject, PGPKey):
# user ids
sspairs += [ (sig, uid) for uid in subject.userids for sig in _filter_sigs(uid.__sig__) ]
# user attributes
sspairs += [ (sig, ua) for ua in subject.userattributes for sig in _filter_sigs(ua.__sig__) ]
# subkey binding signatures
sspairs += [ (sig, subkey) for subkey in subject.subkeys.values() for sig in _filter_sigs(subkey.__sig__) ]
elif signature.signer in {self.fingerprint.keyid} | set(self.subkeys):
sspairs += [(signature, subject)]
if len(sspairs) == 0:
raise PGPError("No signatures to verify")