100% test coverage - closes #115

This commit is contained in:
Michael Greene
2014-09-30 18:38:10 -07:00
parent 70038ef002
commit 851f645cdd
14 changed files with 291 additions and 245 deletions

View File

@@ -1,6 +1,5 @@
[run] [run]
branch = True branch = False
[report] [report]
exclude_lines = exclude_lines =

View File

@@ -128,7 +128,7 @@ class PubKeyAlgorithm(IntEnum):
return self in [PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.DSA] return self in [PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.DSA]
@property @property
def can_encrypt(self): def can_encrypt(self): # pragma: no cover
return self in [PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.ElGamal] return self in [PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.ElGamal]
@@ -239,7 +239,7 @@ class ImageEncoding(IntEnum):
type = imghdr.what(None, h=imagebytes) type = imghdr.what(None, h=imagebytes)
if type == 'jpeg': if type == 'jpeg':
return ImageEncoding.JPEG return ImageEncoding.JPEG
return ImageEncoding.Unknown return ImageEncoding.Unknown # pragma: no cover
class SignatureType(IntEnum): class SignatureType(IntEnum):
@@ -264,10 +264,6 @@ class SignatureType(IntEnum):
return {SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert, return {SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
SignatureType.Positive_Cert, SignatureType.CertRevocation} SignatureType.Positive_Cert, SignatureType.CertRevocation}
@property
def is_certification(self):
return self in SignatureType.certifications
class KeyServerPreferences(IntEnum): class KeyServerPreferences(IntEnum):
Unknown = 0x00 Unknown = 0x00

View File

@@ -77,7 +77,7 @@ class KeyAction(object):
if self.flags & _key.usageflags: if self.flags & _key.usageflags:
break break
else: else: # pragma: no cover
raise PGPError("Key {keyid:s} does not have the required usage flag {flags:s}".format(**em)) raise PGPError("Key {keyid:s} does not have the required usage flag {flags:s}".format(**em))
if _key is not key: if _key is not key:
@@ -93,7 +93,7 @@ class KeyAction(object):
ignore_usage = kwargs.pop('ignore_usage', False) ignore_usage = kwargs.pop('ignore_usage', False)
if ignore_usage: if ignore_usage:
for prop, expected in self.conditions.items(): for prop, expected in self.conditions.items():
if getattr(key, prop) != expected: if getattr(key, prop) != expected: # pragma: no cover
raise PGPError("Expected: {prop:s} == {eval:s}. Got: {got:s}" raise PGPError("Expected: {prop:s} == {eval:s}. Got: {got:s}"
"".format(prop=prop, eval=str(expected), got=str(getattr(key, prop)))) "".format(prop=prop, eval=str(expected), got=str(getattr(key, prop))))

View File

@@ -58,7 +58,7 @@ class SubPackets(collections.MutableMapping, Field):
_bytes += b''.join(uhsp.__bytes__() for uhsp in self._unhashed_sp.values()) _bytes += b''.join(uhsp.__bytes__() for uhsp in self._unhashed_sp.values())
return bytes(_bytes) return bytes(_bytes)
def __len__(self): def __len__(self): # pragma: no cover
return sum(sp.header.length for sp in itertools.chain(self._hashed_sp.values(), self._unhashed_sp.values())) + 4 return sum(sp.header.length for sp in itertools.chain(self._hashed_sp.values(), self._unhashed_sp.values())) + 4
def __iter__(self): def __iter__(self):
@@ -72,7 +72,7 @@ class SubPackets(collections.MutableMapping, Field):
# where: # where:
# - <key> is the classname of val # - <key> is the classname of val
# - <seqid> is a sequence id, starting at 0, for a given classname # - <seqid> is a sequence id, starting at 0, for a given classname
if not isinstance(key, tuple): if not isinstance(key, tuple): # pragma: no cover
i = 0 i = 0
while (key, i) in self: while (key, i) in self:
i += 1 i += 1
@@ -85,7 +85,7 @@ class SubPackets(collections.MutableMapping, Field):
self._unhashed_sp[key] = val self._unhashed_sp[key] = val
def __getitem__(self, key): def __getitem__(self, key):
if isinstance(key, tuple): if isinstance(key, tuple): # pragma: no cover
return self._hashed_sp[key] return self._hashed_sp[key]
if key.startswith('h_'): if key.startswith('h_'):
@@ -96,7 +96,7 @@ class SubPackets(collections.MutableMapping, Field):
def __delitem__(self, key): def __delitem__(self, key):
##TODO: this ##TODO: this
pass raise NotImplementedError
def __contains__(self, key): def __contains__(self, key):
return key in set(k for k, _ in itertools.chain(self._hashed_sp, self._unhashed_sp)) return key in set(k for k, _ in itertools.chain(self._hashed_sp, self._unhashed_sp))
@@ -149,7 +149,7 @@ class UserAttributeSubPackets(SubPackets):
def __bytes__(self): def __bytes__(self):
return b''.join(uhsp.__bytes__() for uhsp in self._unhashed_sp.values()) return b''.join(uhsp.__bytes__() for uhsp in self._unhashed_sp.values())
def __len__(self): def __len__(self): # pragma: no cover
return sum(len(sp) for sp in self._unhashed_sp.values()) return sum(len(sp) for sp in self._unhashed_sp.values())
def parse(self, packet): def parse(self, packet):
@@ -212,7 +212,7 @@ class DSASignature(Signature):
bilen = self.int_to_bytes(ilen) bilen = self.int_to_bytes(ilen)
# long-form must be used ilen > 127 # long-form must be used ilen > 127
if len(bilen) > 127: if len(bilen) > 127: # pragma: no cover
_b += 0x80 ^ len(bilen) _b += 0x80 ^ len(bilen)
return _b + bilen return _b + bilen
@@ -228,11 +228,11 @@ class DSASignature(Signature):
def from_signer(self, sig): def from_signer(self, sig):
def _der_intf(_asn): def _der_intf(_asn):
if _asn[0] != 0x02: if _asn[0] != 0x02: # pragma: no cover
raise ValueError("Expected: Integer (0x02). Got: 0x{:02X}".format(_asn[0])) raise ValueError("Expected: Integer (0x02). Got: 0x{:02X}".format(_asn[0]))
del _asn[0] del _asn[0]
if _asn[0] & 0x80: if _asn[0] & 0x80: # pragma: no cover
llen = _asn[0] & 0x7F llen = _asn[0] & 0x7F
del _asn[0] del _asn[0]
@@ -256,7 +256,7 @@ class DSASignature(Signature):
del sig[0] del sig[0]
# skip the sequence length field # skip the sequence length field
if sig[0] & 0x80: if sig[0] & 0x80: # pragma: no cover
llen = sig[0] & 0x7F llen = sig[0] & 0x7F
del sig[:llen + 1] del sig[:llen + 1]
@@ -656,12 +656,12 @@ class PrivKey(PubKey):
pt = _decrypt(bytes(self.encbytes), bytes(sessionkey), self.s2k.encalg, bytes(self.s2k.iv)) pt = _decrypt(bytes(self.encbytes), bytes(sessionkey), self.s2k.encalg, bytes(self.s2k.iv))
# check the hash to see if we decrypted successfully or not # check the hash to see if we decrypted successfully or not
if self.s2k.usage == 254 and not pt[-20:] == hashlib.new('sha1', pt[:-20]).digest(): if self.s2k.usage == 254 and not pt[-20:] == hashlib.new('sha1', pt[:-20]).digest(): # pragma: no cover
# if the usage byte is 254, key material is followed by a 20-octet sha-1 hash of the rest # if the usage byte is 254, key material is followed by a 20-octet sha-1 hash of the rest
# of the key material block # of the key material block
raise PGPDecryptionError("Passphrase was incorrect!") raise PGPDecryptionError("Passphrase was incorrect!")
if self.s2k.usage == 255 and not self.bytes_to_int(pt[-2:]) == (sum(bytearray(pt[:-2])) % 65536): if self.s2k.usage == 255 and not self.bytes_to_int(pt[-2:]) == (sum(bytearray(pt[:-2])) % 65536): # pragma: no cover
# if the usage byte is 255, key material is followed by a 2-octet checksum of the rest # if the usage byte is 255, key material is followed by a 2-octet checksum of the rest
# of the key material block # of the key material block
raise PGPDecryptionError("Passphrase was incorrect!") raise PGPDecryptionError("Passphrase was incorrect!")
@@ -861,7 +861,7 @@ class ElGCipherText(CipherText):
self.gk_mod_p = MPI(0) self.gk_mod_p = MPI(0)
self.myk_mod_p = MPI(0) self.myk_mod_p = MPI(0)
def __iter__(self): def __iter__(self): # pragma: no cover
yield self.gk_mod_p yield self.gk_mod_p
yield self.myk_mod_p yield self.myk_mod_p

View File

@@ -202,7 +202,7 @@ class PKESessionKeyV3(PKESessionKey):
checksum = self.bytes_to_int(m[:2]) checksum = self.bytes_to_int(m[:2])
del m[:2] del m[:2]
if not sum(symkey) % 65536 == checksum: if not sum(symkey) % 65536 == checksum: # pragma: no cover
raise PGPDecryptionError("{:s} decryption failed".format(self.pkalg.name)) raise PGPDecryptionError("{:s} decryption failed".format(self.pkalg.name))
return (symalg, symkey) return (symalg, symkey)
@@ -912,7 +912,7 @@ class SKEData(Packet):
self.ct = packet[:self.header.length] self.ct = packet[:self.header.length]
del packet[:self.header.length] del packet[:self.header.length]
def decrypt(self, key, alg): def decrypt(self, key, alg): # pragma: no cover
pt = _decrypt(bytes(self.ct), bytes(key), alg) pt = _decrypt(bytes(self.ct), bytes(key), alg)
iv = bytes(pt[:alg.block_size // 8]) iv = bytes(pt[:alg.block_size // 8])
@@ -999,10 +999,10 @@ class LiteralData(Packet):
@property @property
def contents(self): def contents(self):
if self.format == 't': if self.format == 't': # pragma: no cover
return self._contents.decode('latin-1') return self._contents.decode('latin-1')
if self.format == 'u': if self.format == 'u': # pragma: no cover
return six.u(self._contents.decode('latin-1')) return six.u(self._contents.decode('latin-1'))
return self._contents return self._contents
@@ -1098,7 +1098,7 @@ class Trust(Packet):
del packet[:2] del packet[:2]
self.trustlevel = t self.trustlevel = t
self.flags = t self.trustflags = t
class UserID(Packet): class UserID(Packet):

View File

@@ -163,7 +163,7 @@ class Boolean(Signature):
return _bytes return _bytes
def __bool__(self): def __bool__(self):
return self.bool return self.bflag
def __nonzero__(self): def __nonzero__(self):
return self.__bool__() return self.__bool__()
@@ -828,7 +828,7 @@ class EmbeddedSignature(Signature):
return self._sig.subpackets return self._sig.subpackets
@property @property
def hash2(self): def hash2(self): # pragma: no cover
return self._sig.hash2 return self._sig.hash2
@property @property

View File

@@ -72,14 +72,14 @@ class Image(UserAttribute):
@image.register(bytes) @image.register(bytes)
@image.register(bytearray) @image.register(bytearray)
def image(self, val): def image_bin(self, val):
self._image = bytearray(val) self._image = bytearray(val)
def __init__(self): def __init__(self):
super(Image, self).__init__() super(Image, self).__init__()
self.version = 1 self.version = 1
self.iencoding = 1 self.iencoding = 1
self.image = bytearray(b'') self.image = bytearray()
def __bytes__(self): def __bytes__(self):
_bytes = super(Image, self).__bytes__() _bytes = super(Image, self).__bytes__()

View File

@@ -111,7 +111,7 @@ class VersionedHeader(Header):
_bytes.append(self.version) _bytes.append(self.version)
return bytes(_bytes) return bytes(_bytes)
def parse(self, packet): def parse(self, packet): # pragma: no cover
if self.tag == 0: if self.tag == 0:
super(VersionedHeader, self).parse(packet) super(VersionedHeader, self).parse(packet)
@@ -171,7 +171,7 @@ class Opaque(Packet):
@payload.register(bytearray) @payload.register(bytearray)
@payload.register(bytes) @payload.register(bytes)
def payload(self, val): def payload_bin(self, val):
self._payload = val self._payload = val
def __init__(self): def __init__(self):
@@ -183,7 +183,7 @@ class Opaque(Packet):
_bytes += self.payload _bytes += self.payload
return _bytes return _bytes
def parse(self, packet): def parse(self, packet): # pragma: no cover
super(Opaque, self).parse(packet) super(Opaque, self).parse(packet)
pend = self.header.length pend = self.header.length
if hasattr(self.header, 'version'): if hasattr(self.header, 'version'):
@@ -224,7 +224,7 @@ class MPI(long):
mpi = num mpi = num
if isinstance(num, (bytes, bytearray)): if isinstance(num, (bytes, bytearray)):
if isinstance(num, bytes): if isinstance(num, bytes): # pragma: no cover
num = bytearray(num) num = bytearray(num)
fl = ((MPIs.bytes_to_int(num[:2]) + 7) // 8) fl = ((MPIs.bytes_to_int(num[:2]) + 7) // 8)

View File

@@ -72,37 +72,21 @@ from .types import SignatureVerification
def _deque_insort(seq, item): def _deque_insort(seq, item):
i = bisect.bisect_left(seq, item) i = bisect.bisect_left(seq, item)
seqlen = len(seq)
# go left if i is in the first half of the list
if i < (seqlen // 2):
seq.rotate(- i)
seq.appendleft(item)
seq.rotate(i)
# go right if i is in the second half
else:
i = (seqlen - i)
seq.rotate(i)
seq.append(item)
seq.rotate(- i)
def _deque_popat(seq, i):
seq.rotate(- i) seq.rotate(- i)
item = seq.popleft() seq.appendleft(item)
seq.rotate(i) seq.rotate(i)
return item
def _deque_resort(seq, item): def _deque_resort(seq, item):
# find where item is
i = bisect.bisect_left(seq, item) i = bisect.bisect_left(seq, item)
if i != len(seq) and seq[i] == item: if i != len(seq):
_deque_insort(seq, _deque_popat(seq, i)) if seq[i] != item: # pragma: no cover
seq.remove(item)
_deque_insort(item)
return return
raise ValueError
raise ValueError # pragma: no cover
class PGPSignature(PGPObject, Armorable): class PGPSignature(PGPObject, Armorable):
@@ -136,7 +120,7 @@ class PGPSignature(PGPObject, Armorable):
return False return False
expd = next(iter(self._signature.subpackets['SignatureExpirationTime'])).expires expd = next(iter(self._signature.subpackets['SignatureExpirationTime'])).expires
if expd.total_seconds() == 0: if expd.total_seconds() == 0: # pragma: no cover
return False return False
exp = self.created + expd exp = self.created + expd
@@ -144,15 +128,15 @@ class PGPSignature(PGPObject, Armorable):
@property @property
def exportable(self): def exportable(self):
if 'ExportableCertification' not in self._signature.subpackets: if 'ExportableCertification' in self._signature.subpackets:
return True return bool(next(iter(self._signature.subpackets['ExportableCertification'])))
return bool(self._signature.subpackets['ExportableCertification']) return True
@property @property
def features(self): def features(self):
if 'Features' in self._signature.subpackets: if 'Features' in self._signature.subpackets:
return self._signature.subpackets['Features'].flags return next(iter(self._signature.subpackets['Features'])).flags
return [] return []
@property @property
@@ -184,23 +168,23 @@ class PGPSignature(PGPObject, Armorable):
return [] return []
@property @property
def keyserver(self): def keyserver(self): # pragma: no cover
if 'PreferredKeyServer' not in self._signature.subpackets: if 'PreferredKeyServer' in self._signature.subpackets:
return '' return next(iter(self._signature.subpackets['h_PreferredKeyServer'])).uri
return self._signature.subpackets['h_PreferredKeyServer'].uri return ''
@property @property
def keyserverprefs(self): def keyserverprefs(self): # pragma: no cover
if 'KeyServerPreferences' not in self._signature.subpackets: if 'KeyServerPreferences' in self._signature.subpackets:
return [] return next(iter(self._signature.subpackets['h_KeyServerPreferences'])).flags
return self._signature.subpackets['h_KeyServerPreferences'].flags return []
@property @property
def magic(self): def magic(self):
return "SIGNATURE" return "SIGNATURE"
@property @property
def notation(self): def notation(self): # pragma: no cover
if 'NotationData' in self._signature.subpackets: if 'NotationData' in self._signature.subpackets:
nd = self._signature.subpackets['NotationData'] nd = self._signature.subpackets['NotationData']
return {'flags': nd.flags, 'name': nd.name, 'value': nd.value} return {'flags': nd.flags, 'name': nd.name, 'value': nd.value}
@@ -208,15 +192,15 @@ class PGPSignature(PGPObject, Armorable):
@property @property
def revocable(self): def revocable(self):
if 'Revocable' not in self._signature.subpackets: if 'Revocable' in self._signature.subpackets:
return True return bool(next(iter(self._signature.subpackets['Revocable'])))
return bool(self._signature.subpackets['Revocable']) return True
@property @property
def revocation_key(self): def revocation_key(self):
if 'RevocationKey' not in self._signature.subpackets: if 'RevocationKey' in self._signature.subpackets:
return None raise NotImplementedError()
raise NotImplementedError() return None
@property @property
def signer(self): def signer(self):
@@ -253,9 +237,7 @@ class PGPSignature(PGPObject, Armorable):
self.parent = None self.parent = None
def __bytes__(self): def __bytes__(self):
if self._signature is None: return b''.join(s.__bytes__() for s in [self._signature] if s is not None)
return b''
return self._signature.__bytes__()
def __repr__(self): def __repr__(self):
return "<PGPSignature [{:s}] object at 0x{:02x}>".format(self.type.name, id(self)) return "<PGPSignature [{:s}] object at 0x{:02x}>".format(self.type.name, id(self))
@@ -503,7 +485,7 @@ class PGPUID(object):
def __repr__(self): def __repr__(self):
return "<PGPUID [{:s}][{}] at 0x{:02X}>".format(self._uid.__class__.__name__, self.selfsig.created, id(self)) return "<PGPUID [{:s}][{}] at 0x{:02X}>".format(self._uid.__class__.__name__, self.selfsig.created, id(self))
def __lt__(self, other): def __lt__(self, other): # pragma: no cover
if self.is_uid == other.is_uid: if self.is_uid == other.is_uid:
if self.is_primary == other.is_primary: if self.is_primary == other.is_primary:
return self.selfsig > other.selfsig return self.selfsig > other.selfsig
@@ -522,9 +504,7 @@ class PGPUID(object):
def __add__(self, other): def __add__(self, other):
if isinstance(other, PGPSignature): if isinstance(other, PGPSignature):
_deque_insort(self._signatures, other) _deque_insort(self._signatures, other)
if self._parent is not None and self in self._parent._uids:
# is this a new self-signature?
if self._parent is not None and self in self._parent and other is self.selfsig and len(self._signatures) > 1:
_deque_resort(self._parent._uids, self) _deque_resort(self._parent._uids, self)
return self return self
@@ -654,7 +634,7 @@ class PGPMessage(PGPObject, Armorable):
yield ops yield ops
yield self._message yield self._message
if self._mdc is not None: if self._mdc is not None: # pragma: no cover
yield self._mdc yield self._mdc
for sig in self._signatures: for sig in self._signatures:
@@ -704,42 +684,44 @@ class PGPMessage(PGPObject, Armorable):
@classmethod @classmethod
def new(cls, message, **kwargs): def new(cls, message, **kwargs):
prefs = {'cleartext': False, cleartext = kwargs.pop('cleartext', False)
'sensitive': False, sensitive = kwargs.pop('sensitive', False)
'compression': CompressionAlgorithm.ZIP, compression = kwargs.pop('compression', CompressionAlgorithm.ZIP)
'format': 'b'} format = kwargs.pop('format', 'b')
prefs.update(kwargs)
if prefs['cleartext']: if cleartext:
_m = message _m = message
else: else:
# load literal data # load literal data
lit = LiteralData() lit = LiteralData()
lit._contents = bytearray(six.b(message))
lit.format = prefs['format']
if os.path.isfile(message): if os.path.isfile(message):
lit.filename = os.path.basename(message) lit.filename = os.path.basename(message)
lit.mtime = datetime.utcfromtimestamp(os.stat(message).st_mtime) lit.mtime = datetime.utcfromtimestamp(os.stat(message).st_mtime)
with open(message, 'rb') as mf:
lit._contents = bytearray(os.path.getsize(message))
mf.readinto(lit._contents)
else: else:
lit._contents = bytearray(six.b(message))
lit.mtime = datetime.utcnow() lit.mtime = datetime.utcnow()
if prefs['sensitive']: lit.format = format
if sensitive:
lit.filename = '_CONSOLE' lit.filename = '_CONSOLE'
lit.update_hlen() lit.update_hlen()
_m = lit _m = lit
if prefs['compression'] != CompressionAlgorithm.Uncompressed: if compression != CompressionAlgorithm.Uncompressed:
_m = CompressedData() _m = CompressedData()
_m.calg = prefs['compression'] _m.calg = compression
_m.packets.append(lit) _m.packets.append(lit)
_m.update_hlen() _m.update_hlen()
msg = PGPMessage() + _m msg = PGPMessage() + _m
msg._compression = prefs['compression'] msg._compression = compression
return msg return msg
@@ -1011,7 +993,7 @@ class PGPKey(PGPObject, Armorable):
@property @property
def parent(self): def parent(self):
if isinstance(self, Primary): if self.is_primary:
return None return None
return self._parent return self._parent
@@ -1025,7 +1007,7 @@ class PGPKey(PGPObject, Armorable):
for sig in iter(u.selfsig for u in self.userids): for sig in iter(u.selfsig for u in self.userids):
yield sig yield sig
else: else: # pragma: no cover
for sig in self.parent.self_signatures: for sig in self.parent.self_signatures:
yield sig yield sig
@@ -1091,7 +1073,7 @@ class PGPKey(PGPObject, Armorable):
"".format(self._key.__class__.__name__, self.fingerprint.keyid, id(self)) "".format(self._key.__class__.__name__, self.fingerprint.keyid, id(self))
def __contains__(self, item): def __contains__(self, item):
if isinstance(item, PGPKey): if isinstance(item, PGPKey): # pragma: no cover
return item.fingerprint.keyid in self.subkeys return item.fingerprint.keyid in self.subkeys
if isinstance(item, PGPUID): if isinstance(item, PGPUID):
@@ -1129,11 +1111,11 @@ class PGPKey(PGPObject, Armorable):
@contextlib.contextmanager @contextlib.contextmanager
def unlock(self, passphrase): def unlock(self, passphrase):
if self.is_public: if self.is_public: # pragma: no cover
##TODO: we can't unprotect public keys because only private key material is ever protected ##TODO: we can't unprotect public keys because only private key material is ever protected
return return
if not self.is_protected: if not self.is_protected: # pragma: no cover
##TODO: we can't unprotect private keys that are not protected, because there is no ciphertext to decrypt ##TODO: we can't unprotect private keys that are not protected, because there is no ciphertext to decrypt
return return
@@ -1164,14 +1146,13 @@ class PGPKey(PGPObject, Armorable):
self += uid self += uid
def del_uid(self, search): def del_uid(self, search):
i = next( (i for i, u in enumerate(self._uids) u = next((u for u in self._uids if search in filter(lambda a: a is not None, (u.name, u.comment, u.email))),
if search in filter(lambda a: a is not None, (u.name, u.comment, u.email))), None)
None)
if i is None: if u is None:
raise PGPError("uid '{:s}' not found".format(search)) raise PGPError("uid '{:s}' not found".format(search))
_deque_popat(self._uids, i) self._uids.remove(u)
@KeyAction(KeyFlags.Sign, KeyFlags.Certify, is_unlocked=True, is_public=False) @KeyAction(KeyFlags.Sign, KeyFlags.Certify, is_unlocked=True, is_public=False)
def sign(self, subject, **prefs): def sign(self, subject, **prefs):
@@ -1249,7 +1230,7 @@ class PGPKey(PGPObject, Armorable):
exportable = prefs.pop('exportable', None) exportable = prefs.pop('exportable', None)
if exportable is not None: if exportable is not None:
sig._signature.subpackets.addnew('Exportable', hashed=True, bflag=exportable) sig._signature.subpackets.addnew('ExportableCertification', hashed=True, bflag=exportable)
if combo.id == 'selfcertify' and isinstance(subject, PGPUID): if combo.id == 'selfcertify' and isinstance(subject, PGPUID):
sig._signature.subpackets.addnew('Features', hashed=True, flags=[Features.ModificationDetection]) sig._signature.subpackets.addnew('Features', hashed=True, flags=[Features.ModificationDetection])
@@ -1341,13 +1322,6 @@ class PGPKey(PGPObject, Armorable):
raise NotImplementedError(sig.key_algorithm) raise NotImplementedError(sig.key_algorithm)
sigdata = sig.hashdata(subj) sigdata = sig.hashdata(subj)
# temporary testing
def _hash2(sd):
_h = sig.hash_algorithm.hasher
_h.update(sd)
return _h.digest()[:2]
verifier = self.__key__.__pubkey__().verifier(*vargs) verifier = self.__key__.__pubkey__().verifier(*vargs)
verifier.update(sigdata) verifier.update(sigdata)
verified = False verified = False
@@ -1404,12 +1378,6 @@ class PGPKey(PGPObject, Armorable):
return _m return _m
def decrypt(self, message): def decrypt(self, message):
if not isinstance(message, PGPMessage):
_message = PGPMessage()
_message.parse(message)
message = _message
del _message
if not message.is_encrypted: if not message.is_encrypted:
warnings.warn("This message is not encrypted", stacklevel=2) warnings.warn("This message is not encrypted", stacklevel=2)
return message return message
@@ -1475,7 +1443,7 @@ class PGPKey(PGPObject, Armorable):
elif isinstance(pkt, (UserID, UserAttribute)): elif isinstance(pkt, (UserID, UserAttribute)):
pgpobj = PGPUID() + pkt pgpobj = PGPUID() + pkt
else: else: # pragma: no cover
break break
# add signatures to whatever we got # add signatures to whatever we got
@@ -1500,16 +1468,16 @@ class PGPKey(PGPObject, Armorable):
# parent is likely the most recently parsed primary key # parent is likely the most recently parsed primary key
keys[next(reversed(keys))] += pgpobj keys[next(reversed(keys))] += pgpobj
else: else: # pragma: no cover
break break
else: else:
# finished normally # finished normally
break break
# this will only be reached called if the inner loop hit a break # this will only be reached called if the inner loop hit a break
warnings.warn("Warning: Orphaned packet detected! {:s}".format(repr(pkt)), stacklevel=2) warnings.warn("Warning: Orphaned packet detected! {:s}".format(repr(pkt)), stacklevel=2) # pragma: no cover
orphaned[(pkt.header.tag, len([k for k, v in orphaned.keys() if k == pkt.header.tag]))] = pkt orphaned[(pkt.header.tag, len([k for k, v in orphaned.keys() if k == pkt.header.tag]))] = pkt # pragma: no cover
for pkt in group: for pkt in group: # pragma: no cover
orphaned[(pkt.header.tag, len([k for k, v in orphaned.keys() if k == pkt.header.tag]))] = pkt orphaned[(pkt.header.tag, len([k for k, v in orphaned.keys() if k == pkt.header.tag]))] = pkt
# remove the reference to self from keys # remove the reference to self from keys
@@ -1532,12 +1500,12 @@ class PGPKeyring(collections.Container, collections.Iterable, collections.Sized)
if isinstance(alias, six.string_types): if isinstance(alias, six.string_types):
return alias in aliases or alias.replace(' ', '') in aliases return alias in aliases or alias.replace(' ', '') in aliases
return alias in aliases return alias in aliases # pragma: no cover
def __len__(self): def __len__(self):
return len(self._keys) return len(self._keys)
def __iter__(self): def __iter__(self): # pragma: no cover
for pgpkey in itertools.chain(self._pubkeys, self._privkeys): for pgpkey in itertools.chain(self._pubkeys, self._privkeys):
yield pgpkey yield pgpkey
@@ -1570,7 +1538,7 @@ class PGPKeyring(collections.Container, collections.Iterable, collections.Sized)
self._aliases[depth][alias] = pkid self._aliases[depth][alias] = pkid
# finally, remove any empty dicts left over # finally, remove any empty dicts left over
while {} in self._aliases: while {} in self._aliases: # pragma: no cover
self._aliases.remove({}) self._aliases.remove({})
def _add_alias(self, alias, pkid): def _add_alias(self, alias, pkid):
@@ -1580,7 +1548,7 @@ class PGPKeyring(collections.Container, collections.Iterable, collections.Sized)
# this is a duplicate alias->key link; ignore it # this is a duplicate alias->key link; ignore it
elif alias in self and pkid in set(m[alias] for m in self._aliases if alias in m): elif alias in self and pkid in set(m[alias] for m in self._aliases if alias in m):
pass pass # pragma: no cover
# this is an alias that already exists, but points to a key that is not already referenced by it # this is an alias that already exists, but points to a key that is not already referenced by it
else: else:

View File

@@ -7,6 +7,7 @@ import base64
import binascii import binascii
import collections import collections
import re import re
import warnings
from enum import EnumMeta from enum import EnumMeta
from enum import IntEnum from enum import IntEnum
@@ -34,7 +35,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
@staticmethod @staticmethod
def is_ascii(text): def is_ascii(text):
if not isinstance(text, (str, bytes, bytearray)): if not isinstance(text, (str, bytes, bytearray)): # pragma: no cover
raise ValueError("Expected: ASCII input of type str, bytes, or bytearray") raise ValueError("Expected: ASCII input of type str, bytes, or bytearray")
if isinstance(text, str): if isinstance(text, str):
@@ -81,7 +82,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
m['body'] = bytearray(text) m['body'] = bytearray(text)
return m return m
if isinstance(text, (bytes, bytearray)): if isinstance(text, (bytes, bytearray)): # pragma: no cover
text = text.decode('latin-1') text = text.decode('latin-1')
# the re.VERBOSE flag allows for: # the re.VERBOSE flag allows for:
@@ -107,7 +108,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
""", """,
text, flags=re.MULTILINE | re.VERBOSE) text, flags=re.MULTILINE | re.VERBOSE)
if m is None: if m is None: # pragma: no cover
raise ValueError("Expected: ASCII-armored PGP data") raise ValueError("Expected: ASCII-armored PGP data")
m = m.groupdict() m = m.groupdict()
@@ -123,6 +124,9 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
if m['crc'] is not None: if m['crc'] is not None:
m['crc'] = Header.bytes_to_int(base64.b64decode(m['crc'].encode())) m['crc'] = Header.bytes_to_int(base64.b64decode(m['crc'].encode()))
if Armorable.crc24(None, m['body']) != m['crc']:
warnings.warn('Incorrect crc24', stacklevel=3)
return m return m
@@ -139,7 +143,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
if data is None: if data is None:
data = self.__bytes__() data = self.__bytes__()
crc = self.__crc24_init__ crc = Armorable.__crc24_init__
for b in six.iterbytes(data): for b in six.iterbytes(data):
crc ^= b << 16 crc ^= b << 16
@@ -147,7 +151,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
for i in range(8): for i in range(8):
crc <<= 1 crc <<= 1
if crc & 0x1000000: if crc & 0x1000000:
crc ^= self.__crc24_poly__ crc ^= Armorable.__crc24_poly__
return crc & 0xFFFFFF return crc & 0xFFFFFF
@@ -156,7 +160,7 @@ class PGPObject(six.with_metaclass(abc.ABCMeta, object)):
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
@staticmethod @staticmethod
def bytes_to_int(b, order='big'): def bytes_to_int(b, order='big'): # pragma: no cover
"""convert bytes to integer""" """convert bytes to integer"""
if hasattr(int, 'from_bytes'): if hasattr(int, 'from_bytes'):
return int.from_bytes(b, order) return int.from_bytes(b, order)
@@ -165,7 +169,7 @@ class PGPObject(six.with_metaclass(abc.ABCMeta, object)):
return int(binascii.hexlify(b), 16) return int(binascii.hexlify(b), 16)
@staticmethod @staticmethod
def int_to_bytes(i, minlen=1, order='big'): def int_to_bytes(i, minlen=1, order='big'): # pragma: no cover
"""convert integer to bytes""" """convert integer to bytes"""
if hasattr(int, 'to_bytes'): if hasattr(int, 'to_bytes'):
@@ -237,7 +241,7 @@ class Header(Field):
self._len = self.bytes_to_int(b[1:5]) self._len = self.bytes_to_int(b[1:5])
del b[:5] del b[:5]
else: else: # pragma: no cover
raise ValueError("Malformed length!") raise ValueError("Malformed length!")
def _old_len(b): def _old_len(b):
@@ -245,7 +249,7 @@ class Header(Field):
self._len = self.bytes_to_int(b[:self.llen]) self._len = self.bytes_to_int(b[:self.llen])
del b[:self.llen] del b[:self.llen]
else: else: # pragma: no cover
self._len = 0 self._len = 0
_new_len(val) if self._lenfmt == 1 else _old_len(val) _new_len(val) if self._lenfmt == 1 else _old_len(val)
@@ -358,7 +362,7 @@ class MetaDispatchable(abc.ABCMeta):
if cls in MetaDispatchable._roots: if cls in MetaDispatchable._roots:
rcls = cls rcls = cls
elif issubclass(cls, tuple(MetaDispatchable._roots)): elif issubclass(cls, tuple(MetaDispatchable._roots)): # pragma: no cover
rcls = next(root for root in MetaDispatchable._roots if issubclass(cls, root)) rcls = next(root for root in MetaDispatchable._roots if issubclass(cls, root))
##TODO: else raise an exception of some kind, but this should never happen ##TODO: else raise an exception of some kind, but this should never happen
@@ -380,7 +384,7 @@ class MetaDispatchable(abc.ABCMeta):
if (rcls, header.typeid, header.version) in MetaDispatchable._registry: if (rcls, header.typeid, header.version) in MetaDispatchable._registry:
ncls = MetaDispatchable._registry[(rcls, header.typeid, header.version)] ncls = MetaDispatchable._registry[(rcls, header.typeid, header.version)]
else: else: # pragma: no cover
ncls = None ncls = None
if ncls is None: if ncls is None:
@@ -424,7 +428,7 @@ class SignatureVerification(object):
yield s yield s
@property @property
def bad_signatures(self): def bad_signatures(self): # pragma: no cover
for s in [ i for i in self._subjects if not i.verified ]: for s in [ i for i in self._subjects if not i.verified ]:
yield s yield s
@@ -443,7 +447,7 @@ class SignatureVerification(object):
def __and__(self, other): def __and__(self, other):
if not isinstance(other, SignatureVerification): if not isinstance(other, SignatureVerification):
raise ValueError(type(other)) raise TypeError(type(other))
self._subjects += other._subjects self._subjects += other._subjects
return self return self
@@ -456,8 +460,8 @@ class FlagEnumMeta(EnumMeta):
def __and__(self, other): def __and__(self, other):
return { f for f in self._member_map_.values() if f.value & other } return { f for f in self._member_map_.values() if f.value & other }
def __rand__(self, other): def __rand__(self, other): # pragma: no cover
return { f for f in self._member_map_.values() if f.value & other } return FlagEnumMeta & other
class FlagEnum(six.with_metaclass(FlagEnumMeta, IntEnum)): class FlagEnum(six.with_metaclass(FlagEnumMeta, IntEnum)):
@@ -474,12 +478,12 @@ class Fingerprint(str):
return str(self).replace(' ', '')[-8:] return str(self).replace(' ', '')[-8:]
def __new__(cls, content): def __new__(cls, content):
if isinstance(content, Fingerprint): if isinstance(content, Fingerprint): # pragma: no cover
return content return content
# validate input before continuing: this should be a string of 40 hex digits # validate input before continuing: this should be a string of 40 hex digits
content = content.upper().replace(' ', '') content = content.upper().replace(' ', '')
if not bool(re.match(r'^[A-Z0-9]{40}$', content)): if not bool(re.match(r'^[A-F0-9]{40}$', content)):
raise ValueError("Expected: String of 40 hex digits") raise ValueError("Expected: String of 40 hex digits")
# store in the format: "AAAA BBBB CCCC DDDD EEEE FFFF 0000 1111 2222 3333" # store in the format: "AAAA BBBB CCCC DDDD EEEE FFFF 0000 1111 2222 3333"

View File

@@ -1,7 +1,6 @@
""" test parsing packets """ test parsing packets
""" """
import glob import glob
import os
from pgpy.packet import Packet from pgpy.packet import Packet
from pgpy.packet import Opaque from pgpy.packet import Opaque
@@ -35,16 +34,17 @@ def binload(f):
class TestPacket(object): class TestPacket(object):
params = { params = {
'packet': sorted([ binload(os.path.abspath(f)) + b'\xca\xfe\xba\xbe' # 'packet': sorted([ binload(os.path.abspath(f)) + b'\xca\xfe\xba\xbe'
for f in glob.glob('tests/testdata/packets/[0-9]*') ]) # for f in glob.glob('tests/testdata/packets/[0-9]*') ])
'packet': sorted(glob.glob('tests/testdata/packets/[0-9]*'))
} }
def test_load(self, packet): def test_load(self, packet):
b = packet[:] b = binload(packet) + b'\xca\xfe\xba\xbe'
p = Packet(packet) _b = b[:]
p = Packet(_b)
# parsed all bytes # parsed all bytes
# assert len(packet) == 0 assert _b == b'\xca\xfe\xba\xbe'
assert packet == b'\xca\xfe\xba\xbe'
# length is computed correctly # length is computed correctly
assert p.header.length + len(p.header) == len(p) assert p.header.length + len(p.header) == len(p)

View File

@@ -1,7 +1,5 @@
""" test the functionality of PGPKeyring """ test the functionality of PGPKeyring
""" """
import pytest
import glob import glob
from pgpy import PGPKeyring from pgpy import PGPKeyring
@@ -11,154 +9,111 @@ from pgpy.types import Fingerprint
class TestPGPKeyring(object): class TestPGPKeyring(object):
kr = PGPKeyring()
def test_load(self): def test_load(self):
kr = PGPKeyring()
kc = [] kc = []
for kf in glob.glob('tests/testdata/*test.asc'): for kf in glob.glob('tests/testdata/*test.asc') + glob.glob('tests/testdata/signatures/*.key.asc'):
with open(kf, 'r') as kff: with open(kf, 'r') as kff:
kc.append(kff.read()) kc.append(kff.read())
keys = kr.load(kc) keys = self.kr.load(kc)
# keys # keys
assert all(isinstance(k, Fingerprint) for k in keys) assert all(isinstance(k, Fingerprint) for k in keys)
# __len__ # __len__
assert len(keys) == 6 assert len(keys) == 10
assert len(kr) == 12 assert len(self.kr) == 16
# __contains__ # __contains__
# RSA von TestKey # RSA von TestKey
selectors = ["F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36", "37473B3758C44F36", "58C44F36", selectors = ["F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36", "37473B3758C44F36", "58C44F36",
"RSA von TestKey", "rsa@test.key"] "RSA von TestKey", "rsa@test.key"]
for selector in selectors: for selector in selectors:
assert selector in kr assert selector in self.kr
# DSA von TestKey # DSA von TestKey
selectors = ["EBC8 8A94 ACB1 10F1 BE3F E3C1 2B47 4BB0 2084 C712", "2B474BB02084C712", "2084C712", selectors = ["EBC8 8A94 ACB1 10F1 BE3F E3C1 2B47 4BB0 2084 C712", "2B474BB02084C712", "2084C712",
"DSA von TestKey", "dsa@test.key"] "DSA von TestKey", "dsa@test.key"]
for selector in selectors: for selector in selectors:
assert selector in kr assert selector in self.kr
# fingerprints filtering # fingerprints filtering
# we have 6 complete keys # we have 10 keys
assert len(kr.fingerprints()) == 6 assert len(self.kr.fingerprints()) == 10
# 6 public halves, 6 private halves # 10 public halves, 6 private halves
assert len(kr.fingerprints(keyhalf='public')) == 6 assert len(self.kr.fingerprints(keyhalf='public')) == 10
assert len(kr.fingerprints(keyhalf='private')) == 6 assert len(self.kr.fingerprints(keyhalf='private')) == 6
# we have 2 primary keys; 2 public and 2 private # we have 5 primary keys; 5 public and 2 private
assert len(kr.fingerprints(keytype='primary')) == 2 assert len(self.kr.fingerprints(keytype='primary')) == 5
assert len(kr.fingerprints(keytype='primary', keyhalf='public')) == 2 assert len(self.kr.fingerprints(keytype='primary', keyhalf='public')) == 5
assert len(kr.fingerprints(keytype='primary', keyhalf='private')) == 2 assert len(self.kr.fingerprints(keytype='primary', keyhalf='private')) == 2
# and the other 4; 4 public and 4 private # and the other 5; 5 public and 4 private
assert len(kr.fingerprints(keytype='sub')) == 4 assert len(self.kr.fingerprints(keytype='sub')) == 5
assert len(kr.fingerprints(keytype='sub', keyhalf='public')) == 4 assert len(self.kr.fingerprints(keytype='sub', keyhalf='public')) == 5
assert len(kr.fingerprints(keytype='sub', keyhalf='private')) == 4 assert len(self.kr.fingerprints(keytype='sub', keyhalf='private')) == 4
# now test sorting: # now test sorting:
rvt = kr._get_keys("RSA von TestKey") rvt = self.kr._get_keys("RSA von TestKey")
assert len(rvt) == 2 assert len(rvt) == 2
assert not rvt[0].is_public assert not rvt[0].is_public
assert rvt[1].is_public assert rvt[1].is_public
def test_select_fingerprint(self): def test_select_fingerprint(self):
kc = [] with self.kr.key("F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36") as rsa:
for kf in glob.glob('tests/testdata/*test.asc'):
with open(kf, 'r') as kff:
kc.append(kff.read())
kr = PGPKeyring(kc)
with kr.key("F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36") as rsa:
assert rsa.userids[0].name == "RSA von TestKey" assert rsa.userids[0].name == "RSA von TestKey"
with kr.key("EBC8 8A94 ACB1 10F1 BE3F E3C1 2B47 4BB0 2084 C712") as dsa: with self.kr.key("EBC8 8A94 ACB1 10F1 BE3F E3C1 2B47 4BB0 2084 C712") as dsa:
assert dsa.userids[0].name == "DSA von TestKey" assert dsa.userids[0].name == "DSA von TestKey"
def test_select_keyid(self): def test_select_keyid(self):
kc = [] with self.kr.key("37473B3758C44F36") as rsa:
for kf in glob.glob('tests/testdata/*test.asc'):
with open(kf, 'r') as kff:
kc.append(kff.read())
kr = PGPKeyring(kc)
with kr.key("37473B3758C44F36") as rsa:
assert rsa.userids[0].name == "RSA von TestKey" assert rsa.userids[0].name == "RSA von TestKey"
with kr.key("2B474BB02084C712") as dsa: with self.kr.key("2B474BB02084C712") as dsa:
assert dsa.userids[0].name == "DSA von TestKey" assert dsa.userids[0].name == "DSA von TestKey"
def test_select_shortid(self): def test_select_shortid(self):
kc = [] with self.kr.key("58C44F36") as rsa:
for kf in glob.glob('tests/testdata/*test.asc'):
with open(kf, 'r') as kff:
kc.append(kff.read())
kr = PGPKeyring(kc)
with kr.key("58C44F36") as rsa:
assert rsa.userids[0].name == "RSA von TestKey" assert rsa.userids[0].name == "RSA von TestKey"
with kr.key("2084C712") as dsa: with self.kr.key("2084C712") as dsa:
assert dsa.userids[0].name == "DSA von TestKey" assert dsa.userids[0].name == "DSA von TestKey"
def test_select_name(self): def test_select_name(self):
kc = [] with self.kr.key("RSA von TestKey") as rsa:
for kf in glob.glob('tests/testdata/*test.asc'):
with open(kf, 'r') as kff:
kc.append(kff.read())
kr = PGPKeyring(kc)
with kr.key("RSA von TestKey") as rsa:
assert rsa.userids[0].name == "RSA von TestKey" assert rsa.userids[0].name == "RSA von TestKey"
with kr.key("DSA von TestKey") as dsa: with self.kr.key("DSA von TestKey") as dsa:
assert dsa.userids[0].name == "DSA von TestKey" assert dsa.userids[0].name == "DSA von TestKey"
def test_select_comment(self): def test_select_comment(self):
kc = [] with self.kr.key("2048-bit RSA") as rsa:
for kf in glob.glob('tests/testdata/*test.asc'):
with open(kf, 'r') as kff:
kc.append(kff.read())
kr = PGPKeyring(kc)
with kr.key("2048-bit RSA") as rsa:
assert rsa.userids[0].name == "RSA von TestKey" assert rsa.userids[0].name == "RSA von TestKey"
with kr.key("2048-bit DSA") as dsa: with self.kr.key("2048-bit DSA") as dsa:
assert dsa.userids[0].name == "DSA von TestKey" assert dsa.userids[0].name == "DSA von TestKey"
def test_select_email(self): def test_select_email(self):
kc = [] with self.kr.key("rsa@test.key") as rsa:
for kf in glob.glob('tests/testdata/*test.asc'):
with open(kf, 'r') as kff:
kc.append(kff.read())
kr = PGPKeyring(kc)
with kr.key("rsa@test.key") as rsa:
assert rsa.userids[0].name == "RSA von TestKey" assert rsa.userids[0].name == "RSA von TestKey"
with kr.key("dsa@test.key") as dsa: with self.kr.key("dsa@test.key") as dsa:
assert dsa.userids[0].name == "DSA von TestKey" assert dsa.userids[0].name == "DSA von TestKey"
def test_select_pgpsignature(self): def test_select_pgpsignature(self):
with open('tests/testdata/signatures/debian-sid.key.asc', 'r') as dskf:
kr = PGPKeyring(dskf.read())
sig = PGPSignature() sig = PGPSignature()
with open('tests/testdata/signatures/debian-sid.sig.asc', 'r') as sigf: with open('tests/testdata/signatures/debian-sid.sig.asc', 'r') as sigf:
sig.parse(sigf.read()) sig.parse(sigf.read())
with kr.key(sig) as sigkey: with self.kr.key(sig) as sigkey:
assert sigkey.fingerprint.keyid == sig.signer assert sigkey.fingerprint.keyid == sig.signer
def test_select_pgpmessage(self): def test_select_pgpmessage(self):
kc = []
for kf in glob.glob('tests/testdata/*test.asc'):
with open(kf, 'r') as kff:
kc.append(kff.read())
kr = PGPKeyring(kc)
m1 = PGPMessage() m1 = PGPMessage()
with open('tests/testdata/messages/message.rsa.cast5.asc', 'r') as m1f: with open('tests/testdata/messages/message.rsa.cast5.asc', 'r') as m1f:
m1.parse(m1f.read()) m1.parse(m1f.read())
with kr.key(m1) as rsakey: with self.kr.key(m1) as rsakey:
assert rsakey.fingerprint == "00EC FAF5 48AE B655 F861 8193 EEE0 97A0 17B9 79CA" assert rsakey.fingerprint == "00EC FAF5 48AE B655 F861 8193 EEE0 97A0 17B9 79CA"
assert rsakey.parent.fingerprint == "F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36" assert rsakey.parent.fingerprint == "F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36"

View File

@@ -14,6 +14,7 @@ from pgpy import PGPSignature
from pgpy import PGPUID from pgpy import PGPUID
from pgpy.constants import CompressionAlgorithm from pgpy.constants import CompressionAlgorithm
from pgpy.constants import Features
from pgpy.constants import ImageEncoding from pgpy.constants import ImageEncoding
from pgpy.constants import PubKeyAlgorithm from pgpy.constants import PubKeyAlgorithm
from pgpy.constants import RevocationReason from pgpy.constants import RevocationReason
@@ -55,9 +56,8 @@ class TestPGPMessage(object):
'enc_msg': [ _pgpmessage(f) for f in glob.glob('tests/testdata/messages/message*.pass*.asc') ], 'enc_msg': [ _pgpmessage(f) for f in glob.glob('tests/testdata/messages/message*.pass*.asc') ],
'lit': [ PGPMessage.new(_read('tests/testdata/lit')) ], 'lit': [ PGPMessage.new(_read('tests/testdata/lit')) ],
} }
def test_new_message(self, comp_alg, write_clean, gpg_import, gpg_print): def test_new(self, comp_alg, write_clean, gpg_import, gpg_print):
with open('tests/testdata/lit', 'r') as litf: msg = PGPMessage.new(_read('tests/testdata/lit'))
msg = PGPMessage.new(litf.read(), compression=comp_alg)
assert msg.type == 'literal' assert msg.type == 'literal'
assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n' assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'
@@ -65,13 +65,22 @@ class TestPGPMessage(object):
with write_clean('tests/testdata/cmsg.asc', 'w', str(msg)): with write_clean('tests/testdata/cmsg.asc', 'w', str(msg)):
assert gpg_print('cmsg.asc') == msg.message.decode('latin-1') assert gpg_print('cmsg.asc') == msg.message.decode('latin-1')
def test_new_sensitive(self, write_clean, gpg_import, gpg_print):
msg = PGPMessage.new('tests/testdata/lit', sensitive=True)
assert msg.type == 'literal'
assert msg.message.decode('latin-1') == 'This is stored, literally\!\n\n'
with write_clean('tests/testdata/csmsg.asc', 'w', str(msg)):
assert gpg_print('csmsg.asc') == msg.message.decode('latin-1')
def test_decrypt_passphrase_message(self, enc_msg): def test_decrypt_passphrase_message(self, enc_msg):
decmsg = enc_msg.decrypt("QwertyUiop") decmsg = enc_msg.decrypt("QwertyUiop")
assert isinstance(decmsg, PGPMessage) assert isinstance(decmsg, PGPMessage)
assert decmsg.message == b"This is stored, literally\\!\n\n" assert decmsg.message == b"This is stored, literally\\!\n\n"
def test_encrypt_passphrase_message(self, lit, write_clean, gpg_decrypt): def test_encrypt_passphrase(self, lit, write_clean, gpg_decrypt):
encmsg = lit.encrypt("QwertyUiop") encmsg = lit.encrypt("QwertyUiop")
# make sure lit was untouched # make sure lit was untouched
@@ -92,7 +101,7 @@ class TestPGPMessage(object):
with write_clean('tests/testdata/semsg.asc', 'w', str(lit)): with write_clean('tests/testdata/semsg.asc', 'w', str(lit)):
assert gpg_decrypt('./semsg.asc', "QwertyUiop") == "This is stored, literally\!\n\n" assert gpg_decrypt('./semsg.asc', "QwertyUiop") == "This is stored, literally\!\n\n"
def test_encrypt_passphrase_message_2(self, lit, write_clean, gpg_decrypt): def test_encrypt_passphrase_2(self, lit, write_clean, gpg_decrypt):
sk = SymmetricKeyAlgorithm.AES256.gen_key() sk = SymmetricKeyAlgorithm.AES256.gen_key()
encmsg = lit.encrypt("QwertyUiop", sessionkey=sk).encrypt("AsdfGhjkl", sessionkey=sk) encmsg = lit.encrypt("QwertyUiop", sessionkey=sk).encrypt("AsdfGhjkl", sessionkey=sk)
@@ -272,6 +281,48 @@ class TestPGPKey(object):
with write_clean(os.path.join('tests', 'testdata', tkfp), 'w', str(tk)), gpg_import(*ikeys): with write_clean(os.path.join('tests', 'testdata', tkfp), 'w', str(tk)), gpg_import(*ikeys):
assert gpg_check_sigs(tk.fingerprint.keyid) assert gpg_check_sigs(tk.fingerprint.keyid)
def test_sign_userid_unrevocable(self, sec, pub, write_clean, gpg_import, gpg_check_sigs):
for tk in self.targettes:
with self.assert_warnings():
# sign tk's last uid generically
rsig = sec.sign(tk.userids[-1], revocable=False)
assert not rsig.revocable
tk.userids[-1] += rsig
# verify with PGPy
assert pub.verify(tk.userids[-1])
# verify with GnuPG
tkfp = '{:s}.asc'.format(tk.fingerprint.shortid)
ikeys = self.ikeys
ikeys.append(os.path.join('.', tkfp))
with write_clean(os.path.join('tests', 'testdata', tkfp), 'w', str(tk)), gpg_import(*ikeys):
assert gpg_check_sigs(tk.fingerprint.keyid)
def test_sign_userid_unexportable(self, sec, pub, write_clean, gpg_import, gpg_check_sigs):
for tk in self.targettes:
# sign tk's first uid generically
rsig = sec.sign(tk.userids[0], exportable=False)
assert not rsig.exportable
tk.userids[0] += rsig
# check that it is properly skipped over when the packet order is composed
tk2 = PGPKey()
tk2.parse(tk.__bytes__())
assert len([sig for uid in tk2.userids for sig in uid._signatures
if sig.created == rsig.created
and sig._signature.signature.__sig__ == rsig._signature.signature.__sig__]) == 0
# verify with PGPy
assert pub.verify(tk.userids[-1])
# verify with GnuPG
tkfp = '{:s}.asc'.format(tk.fingerprint.shortid)
ikeys = self.ikeys
ikeys.append(os.path.join('.', tkfp))
with write_clean(os.path.join('tests', 'testdata', tkfp), 'w', str(tk)), gpg_import(*ikeys):
assert gpg_check_sigs(tk.fingerprint.keyid)
def test_revoke_certification(self, sec, pub, write_clean, gpg_import, gpg_check_sigs): def test_revoke_certification(self, sec, pub, write_clean, gpg_import, gpg_check_sigs):
for tk in self.targettes: for tk in self.targettes:
# we should have already signed the key in test_sign_userid above # we should have already signed the key in test_sign_userid above
@@ -499,6 +550,7 @@ class TestPGPKey(object):
assert u._signatures[0].hashprefs == [HashAlgorithm.SHA256, HashAlgorithm.SHA1] assert u._signatures[0].hashprefs == [HashAlgorithm.SHA256, HashAlgorithm.SHA1]
assert u._signatures[0].cipherprefs == [SymmetricKeyAlgorithm.AES128, SymmetricKeyAlgorithm.CAST5] assert u._signatures[0].cipherprefs == [SymmetricKeyAlgorithm.AES128, SymmetricKeyAlgorithm.CAST5]
assert u._signatures[0].compprefs == [CompressionAlgorithm.ZIP, CompressionAlgorithm.Uncompressed] assert u._signatures[0].compprefs == [CompressionAlgorithm.ZIP, CompressionAlgorithm.Uncompressed]
assert u._signatures[0].features == [Features.ModificationDetection]
# verify with PGPy # verify with PGPy
with self.assert_warnings(): with self.assert_warnings():

View File

@@ -10,6 +10,11 @@ from pgpy import PGPMessage
from pgpy import PGPSignature from pgpy import PGPSignature
from pgpy import PGPUID from pgpy import PGPUID
from pgpy.types import Fingerprint
from pgpy.types import SignatureVerification
from pgpy.constants import HashAlgorithm
from pgpy.constants import SignatureType
from pgpy.constants import SymmetricKeyAlgorithm from pgpy.constants import SymmetricKeyAlgorithm
from pgpy.errors import PGPDecryptionError from pgpy.errors import PGPDecryptionError
@@ -42,7 +47,9 @@ def _read(f, mode='r'):
class TestPGPKey(object): class TestPGPKey(object):
rsa_1_sec = _pgpkey('tests/testdata/keys/rsa.1.sec.asc') rsa_1_sec = _pgpkey('tests/testdata/keys/rsa.1.sec.asc')
rsa_1_pub = _pgpkey('tests/testdata/keys/rsa.1.pub.asc')
rsa_2_sec = _pgpkey('tests/testdata/keys/rsa.2.sec.asc') rsa_2_sec = _pgpkey('tests/testdata/keys/rsa.2.sec.asc')
rsa_2_pub = _pgpkey('tests/testdata/keys/rsa.2.pub.asc')
def test_verify_wrongkey(self): def test_verify_wrongkey(self):
wrongkey = _pgpkey('tests/testdata/signatures/aptapproval-test.key.asc') wrongkey = _pgpkey('tests/testdata/signatures/aptapproval-test.key.asc')
@@ -76,6 +83,46 @@ class TestPGPKey(object):
with pytest.raises(PGPError): with pytest.raises(PGPError):
self.rsa_1_sec.del_uid("ASDFDSGSAJGKSAJG") self.rsa_1_sec.del_uid("ASDFDSGSAJGKSAJG")
def test_sign_wrong_type(self):
msg = _pgpmessage('tests/testdata/messages/message.rsa.cast5.asc')
ctmsg = _pgpmessage('tests/testdata/messages/cleartext.signed.asc')
uid = PGPUID.new(name="asdf")
sigtypes = {SignatureType.BinaryDocument, SignatureType.CanonicalDocument, SignatureType.Standalone,
SignatureType.Subkey_Binding, SignatureType.PrimaryKey_Binding, SignatureType.DirectlyOnKey,
SignatureType.KeyRevocation, SignatureType.SubkeyRevocation, SignatureType.Timestamp,
SignatureType.ThirdParty_Confirmation} | SignatureType.certifications
# invalid subject/sigtype combinations
invalid_combos = []
invalid_combos += [('asdf', st) for st in sigtypes ^ {SignatureType.BinaryDocument}]
invalid_combos += [(msg, st) for st in sigtypes ^ {SignatureType.BinaryDocument}]
invalid_combos += [(ctmsg, st) for st in sigtypes ^ {SignatureType.CanonicalDocument}]
invalid_combos += [(uid, st) for st in sigtypes ^ SignatureType.certifications]
invalid_combos += [(self.rsa_2_sec, st) for st in sigtypes ^ {SignatureType.DirectlyOnKey,
SignatureType.PrimaryKey_Binding,
SignatureType.KeyRevocation}]
for subj, type in invalid_combos:
with pytest.raises(PGPError):
self.rsa_1_sec.sign(subj, sigtype=type)
def test_sign_bad_prefs(self, recwarn):
self.rsa_1_pub.subkeys['EEE097A017B979CA'].encrypt(PGPMessage.new('asdf'),
cipher=SymmetricKeyAlgorithm.CAST5,
hash=HashAlgorithm.SHA1)
w = recwarn.pop(UserWarning)
assert str(w.message) == "Selected symmetric algorithm not in key preferences"
assert w.filename == __file__
w = recwarn.pop(UserWarning)
assert str(w.message) == "Selected hash algorithm not in key preferences"
assert w.filename == __file__
w = recwarn.pop(UserWarning)
assert str(w.message) == "Selected compression algorithm not in key preferences"
assert w.filename == __file__
def test_verify_typeerror(self): def test_verify_typeerror(self):
with pytest.raises(TypeError): with pytest.raises(TypeError):
self.rsa_1_sec.verify(12) self.rsa_1_sec.verify(12)
@@ -94,6 +141,17 @@ class TestPGPKey(object):
with pytest.raises(ValueError): with pytest.raises(ValueError):
key.parse(keytext) key.parse(keytext)
def test_parse_wrong_crc24(self, recwarn):
keytext = _read('tests/testdata/keys/rsa.1.sec.asc').splitlines()
keytext[-2] = "=abcd"
keytext = '\n'.join(keytext)
key = PGPKey()
key.parse(keytext)
w = recwarn.pop(UserWarning)
assert str(w.message) == "Incorrect crc24"
assert w.filename == __file__
class TestPGPKeyring(object): class TestPGPKeyring(object):
kr = PGPKeyring(_read('tests/testdata/pubtest.asc')) kr = PGPKeyring(_read('tests/testdata/pubtest.asc'))
@@ -159,3 +217,17 @@ class TestPGPUID(object):
u = PGPUID.new(name="Asdf Qwert") u = PGPUID.new(name="Asdf Qwert")
with pytest.raises(TypeError): with pytest.raises(TypeError):
u += 12 u += 12
class TestSignatureVerification(object):
def test_and_typeerror(self):
with pytest.raises(TypeError):
sv = SignatureVerification() & 12
class TestFingerprint(object):
def test_bad_input(self):
with pytest.raises(ValueError):
Fingerprint("ABCDEFG")
with pytest.raises(ValueError):
Fingerprint("ABCD EFGH IJKL MNOP QRST UVWX YZ01 2345 6789 AABB")