def test_verify_wrongkey(self, rsa_pub): wrongkey, _ = PGPKey.from_file( 'tests/testdata/signatures/aptapproval-test.key.asc') sig = PGPSignature.from_file( 'tests/testdata/signatures/debian-sid.sig.asc') with pytest.raises(PGPError): wrongkey.verify(_read('tests/testdata/signatures/debian-sid.subj'), sig)
def verify() -> None: # signature verification requested commit_data = sys.stdin.read() if len(args) != 5: raise Exception( "Unexpected number of parameters encountered: {}".format( len(args))) if not args[0] == "--keyid-format=long": raise Exception( "Unexpected parameter encountered in args[0]: {}".format(args[0])) if not args[1] == "--status-fd=1": raise Exception( "Unexpected parameter encountered in args[1]: {}".format(args[1])) if not args[2] == "--verify": raise Exception( "Unexpected parameter encountered in args[2]: {}".format(args[2])) filename = args[3] if not args[4] == "-": raise Exception( "Unexpected parameter encountered in args[4]: {}".format(args[2])) signature = PGPSignature.from_file(filename) key = API.combined_keystore.get_key(signature.signer) if key is not None: if key.verify(commit_data, signature): trusted_uids, untrusted_uids = deduplicate_uids( verify_uid_certifications(key)) trusted_uids = strip_pseudo_uid(trusted_uids) untrusted_uids = strip_pseudo_uid(untrusted_uids) status_to_user(signature, key, trusted_uids, untrusted_uids) status_to_caller(signature, key, trusted_uids) else: raise Exception("Invalid signature") else: raise NotImplementedError("Key not found")
def test_gpg_ed25519_verify(self, abe): # test verification of Ed25519 signature generated by GnuPG pubkey, _ = PGPKey.from_file('tests/testdata/keys/ecc.2.pub.asc') sig = PGPSignature.from_file('tests/testdata/signatures/ecc.2.sig.asc') assert pubkey.verify("This is a test signature message", sig)
def test_verify_wrongkey(self, rsa_pub): wrongkey, _ = PGPKey.from_file('tests/testdata/signatures/aptapproval-test.key.asc') sig = PGPSignature.from_file('tests/testdata/signatures/debian-sid.sig.asc') with pytest.raises(PGPError): wrongkey.verify(_read('tests/testdata/signatures/debian-sid.subj'), sig)
def sig(): return PGPSignature.from_file('tests/testdata/blocks/rsasignature.asc')
class TestPGPKey(object): params = { 'pub': [ PGPKey.from_file(f)[0] for f in sorted(glob.glob('tests/testdata/keys/*.pub.asc')) ], 'sec': [ PGPKey.from_file(f)[0] for f in sorted(glob.glob('tests/testdata/keys/*.sec.asc')) ], 'enc': [ PGPKey.from_file(f)[0] for f in sorted(glob.glob('tests/testdata/keys/*.enc.asc')) ], 'sigkey': [ PGPKey.from_file(f)[0] for f in sorted(glob.glob('tests/testdata/signatures/*.key.asc')) ], 'sigsig': [ PGPSignature.from_file(f) for f in sorted(glob.glob('tests/testdata/signatures/*.sig.asc')) ], 'sigsubj': sorted(glob.glob('tests/testdata/signatures/*.subj')), 'key_alg': key_algs, } ids = { 'test_protect': [ '-'.join(os.path.basename(f).split('.')[:-2]) for f in sorted(glob.glob('tests/testdata/keys/*.sec.asc')) ], 'test_encrypt_message': [ '-'.join(os.path.basename(f).split('.')[:-2]) for f in sorted(glob.glob('tests/testdata/keys/*.pub.asc')) ], 'test_decrypt_encmessage': [ '-'.join(os.path.basename(f).split('.')[:-2]) for f in sorted(glob.glob('tests/testdata/keys/*.sec.asc')) ], 'test_verify_detached': [ os.path.basename(f).replace('.', '_') for f in sorted(glob.glob('tests/testdata/signatures/*.key.asc')) ], 'test_new_key': [str(ka).split('.')[-1] for ka in key_algs], 'test_new_subkey': [str(ka).split('.')[-1] for ka in key_algs], 'test_pub_from_sec': [str(ka).split('.')[-1] for ka in key_algs], 'test_gpg_verify_new_key': [str(ka).split('.')[-1] for ka in key_algs], 'test_verify_invalid_sig': [str(ka).split('.')[-1] for ka in key_algs], } string_sigs = dict() timestamp_sigs = dict() standalone_sigs = dict() gen_keys = dict() encmessage = [] @contextmanager def assert_warnings(self): with catch_warnings(record=True) as w: try: yield finally: for warning in w: try: assert warning.filename == __file__ except AssertionError as e: e.args += (warning.message, ) raise def test_protect(self, sec): # if sec.key_algorithm == PubKeyAlgorithm.ECDSA: # pytest.skip("Cannot properly encrypt ECDSA keys yet") assert sec.is_protected is False # copy sec so we have a comparison point sec2 = copy.deepcopy(sec) # ensure that the key material values are the same assert _compare_keys(sec, sec2) sec2.protect('There Are Many Like It, But This Key Is Mine', SymmetricKeyAlgorithm.AES256, HashAlgorithm.SHA256) assert sec2.is_protected assert sec2.is_unlocked is False # ensure that sec2 is now assert _compare_keys(sec, sec2) is False assert sec2._key.keymaterial.__bytes__()[sec2._key.keymaterial.publen( ):] not in sec._key.keymaterial.__bytes__() # unlock with the correct passphrase and compare the keys with sec2.unlock( 'There Are Many Like It, But This Key Is Mine') as _unlocked: assert _unlocked.is_unlocked assert _compare_keys(sec, sec2) def test_unlock(self, enc, sec): assert enc.is_protected assert enc.is_unlocked is False assert sec.is_protected is False # unlock with the correct passphrase with enc.unlock('QwertyUiop') as _unlocked, self.assert_warnings(): assert _unlocked is enc assert enc.is_unlocked def test_change_passphrase(self, enc): enc2 = copy.deepcopy(enc) assert enc.is_protected assert enc2.is_protected assert enc.is_unlocked is False assert enc2.is_unlocked is False assert enc._key.keymaterial.encbytes == enc2._key.keymaterial.encbytes # change the passphrase on enc2 with enc.unlock('QwertyUiop') as e1u, enc2.unlock( 'QwertyUiop') as e2u, self.assert_warnings(): assert _compare_keys(e1u, e2u) enc2.protect('AsdfGhjkl', enc2._key.keymaterial.s2k.encalg, enc2._key.keymaterial.s2k.halg) assert enc._key.keymaterial.encbytes != enc2._key.keymaterial.encbytes # unlock again and verify that we still have the same key hiding in there with enc.unlock('QwertyUiop') as e1u, enc2.unlock( 'AsdfGhjkl') as e2u, self.assert_warnings(): assert _compare_keys(e1u, e2u) def test_verify_detached(self, sigkey, sigsig, sigsubj): assert sigkey.verify(_read(sigsubj), sigsig) def test_sign_string(self, sec, string, write_clean, gpg_import, gpg_verify): with self.assert_warnings(): # add all of the subpackets we should be allowed to sig = sec.sign( string, user=sec.userids[0].name, expires=timedelta(seconds=1), revocable=False, notation={ 'Testing': 'This signature was generated during unit testing' }, policy_uri='about:blank') # wait a bit if sig is not yet expired assert sig.type == SignatureType.BinaryDocument assert sig.notation == { 'Testing': 'This signature was generated during unit testing' } assert sig.revocable is False assert sig.policy_uri == 'about:blank' # assert sig.sig.signer_uid == "{:s}".format(sec.userids[0]) assert next(iter(sig._signature.subpackets['SignersUserID']) ).userid == "{:s}".format(sec.userids[0]) if not sig.is_expired: time.sleep((sig.expires_at - datetime.utcnow()).total_seconds()) assert sig.is_expired # verify with GnuPG if sig.key_algorithm not in {PubKeyAlgorithm.ECDSA}: # TODO: cannot test ECDSA against GnuPG as there isn't an easy way of installing v2.1 yet on CI with write_clean('tests/testdata/string', 'w', string), \ write_clean('tests/testdata/string.asc', 'w', str(sig)), \ gpg_import('./pubtest.asc'): assert gpg_verify('./string', './string.asc', keyid=sig.signer) self.string_sigs[sec.fingerprint.keyid] = sig def test_verify_string(self, pub, string): sig = self.string_sigs.pop(pub.fingerprint.keyid) with self.assert_warnings(): sv = pub.verify(string, signature=sig) assert sv assert len(sv) == 1 def test_sign_ctmessage(self, sec, ctmessage, write_clean, gpg_import, gpg_verify): expire_at = datetime.utcnow() + timedelta(days=1) assert isinstance(expire_at, datetime) with self.assert_warnings(): sig = sec.sign(ctmessage, expires=expire_at) assert sig.type == SignatureType.CanonicalDocument assert sig.revocable assert sig.is_expired is False ctmessage |= sig # verify with GnuPG if sig.key_algorithm not in {PubKeyAlgorithm.ECDSA}: # TODO: cannot test ECDSA against GnuPG as there isn't an easy way of installing v2.1 yet on CI with write_clean('tests/testdata/ctmessage.asc', 'w', str(ctmessage)), gpg_import('./pubtest.asc'): assert gpg_verify('./ctmessage.asc', keyid=sig.signer) def test_verify_ctmessage(self, pub, ctmessage): with self.assert_warnings(): sv = pub.verify(ctmessage) assert sv assert len(sv) > 0 def test_sign_message(self, sec, message): with self.assert_warnings(): sig = sec.sign(message) assert sig.type == SignatureType.BinaryDocument assert sig.revocable assert sig.is_expired is False message |= sig def test_verify_message(self, pub, message): with self.assert_warnings(): sv = pub.verify(message) assert sv assert len(sv) > 0 def test_gpg_verify_message(self, message, write_clean, gpg_import, gpg_verify): # verify with GnuPG with write_clean('tests/testdata/message.asc', 'w', str(message)), gpg_import('./pubtest.asc'): assert gpg_verify('./message.asc') def test_verify_invalid_sig(self, string, key_alg): # generate a keypair u = PGPUID.new('asdf') k = PGPKey.new(key_alg, key_alg_size[key_alg]) k.add_uid(u, usage={KeyFlags.Certify, KeyFlags.Sign}, hashes=[HashAlgorithm.SHA1]) # sign string with extra characters (this means k.pubkey.verify(string) will return false sig = k.sign(string + 'asdf') assert not k.pubkey.verify(string, sig) def test_encrypt_message(self, pub, message, sessionkey): if pub.key_algorithm not in { PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.ECDSA }: pytest.skip( 'Asymmetric encryption only implemented for RSA/ECDSA currently' ) return if len(self.encmessage) == 1: message = self.encmessage.pop(0) with self.assert_warnings(): enc = pub.encrypt(message, sessionkey=sessionkey, cipher=SymmetricKeyAlgorithm.AES128) self.encmessage.append(enc) def test_decrypt_encmessage(self, sec, message): if sec.key_algorithm not in { PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.ECDSA }: pytest.skip( 'Asymmetric encryption only implemented for RSA and ECDH currently' ) return encmessage = self.encmessage[0] with self.assert_warnings(): decmsg = sec.decrypt(encmessage) assert decmsg.message == message.message def test_gpg_decrypt_encmessage(self, write_clean, gpg_import, gpg_decrypt): emsg = self.encmessage.pop(0) with write_clean('tests/testdata/aemsg.asc', 'w', str(emsg)): # decrypt using RSA with gpg_import('./sectest.asc'): assert gpg_decrypt('./aemsg.asc', keyid='EEE097A017B979CA') # decrypt using ECDH if gpg_ver >= '2.1': with gpg_import('./keys/ecc.1.sec.asc'): assert gpg_decrypt('./aemsg.asc', keyid='D01055FBCADD268E') def test_encrypt_message_select_uid(self): # generate a temporary key with two UIDs, then encrypt a message u1 = PGPUID.new('UID One') u2 = PGPUID.new('UID Two') k = PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 512) flags = { KeyFlags.Certify, KeyFlags.Sign, KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage } k.add_uid(u1, usage=flags, hashes=[HashAlgorithm.SHA1], ciphers=[SymmetricKeyAlgorithm.AES128]) k.add_uid(u2, usage=flags, hashes=[HashAlgorithm.SHA1], ciphers=[SymmetricKeyAlgorithm.Camellia128]) emsg = k.pubkey.encrypt( PGPMessage.new('This message is about to be encrypted'), user='******') # assert that it was encrypted with Camellia128 and that we can decrypt it normally assert emsg._sessionkeys[0].decrypt_sk( k._key)[0] == SymmetricKeyAlgorithm.Camellia128 assert k.decrypt( emsg).message == 'This message is about to be encrypted' def test_sign_timestamp(self, sec): with self.assert_warnings(): sig = sec.sign(None) assert sig.type == SignatureType.Timestamp self.timestamp_sigs[sec.fingerprint.keyid] = sig def test_verify_timestamp(self, pub): sig = self.timestamp_sigs.pop(pub.fingerprint.keyid) with self.assert_warnings(): sv = pub.verify(None, sig) assert sv assert len(sv) > 0 def test_sign_standalone(self, sec): with self.assert_warnings(): sig = sec.sign(None, notation={"cheese status": "standing alone"}) assert sig.type == SignatureType.Standalone assert sig.notation == {"cheese status": "standing alone"} self.standalone_sigs[sec.fingerprint.keyid] = sig def test_verify_standalone(self, pub): sig = self.standalone_sigs.pop(pub.fingerprint.keyid) with self.assert_warnings(): sv = pub.verify(None, sig) assert sv assert len(sv) > 0 def test_add_userid(self, userid, targette_sec): # add userid to targette_sec expire_in = datetime.utcnow() + timedelta(days=2) with self.assert_warnings(): # add all of the subpackets that only work on self-certifications targette_sec.add_uid(userid, usage=[KeyFlags.Certify, KeyFlags.Sign], ciphers=[ SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.Camellia256 ], hashes=[HashAlgorithm.SHA384], compression=[CompressionAlgorithm.ZLIB], key_expiration=expire_in, keyserver_flags=0x80, keyserver='about:none', primary=False) sig = userid.selfsig assert sig.type == SignatureType.Positive_Cert assert sig.cipherprefs == [ SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.Camellia256 ] assert sig.hashprefs == [HashAlgorithm.SHA384] assert sig.compprefs == [CompressionAlgorithm.ZLIB] assert sig.features == {Features.ModificationDetection} assert sig.key_expiration == expire_in - targette_sec.created assert sig.keyserver == 'about:none' assert sig.keyserverprefs == [KeyServerPreferences.NoModify] assert userid.is_primary is False def test_remove_userid(self, targette_sec): # create a temporary userid, add it, and then remove it tempuid = PGPUID.new('Temporary Youx\'seur') targette_sec.add_uid(tempuid) assert tempuid in targette_sec targette_sec.del_uid('Temporary Youx\'seur') assert tempuid not in targette_sec def test_certify_userid(self, sec, userid): with self.assert_warnings(): # add all of the subpackets that only work on (non-self) certifications sig = sec.certify(userid, SignatureType.Casual_Cert, usage=KeyFlags.Authentication, exportable=True, trust=(1, 60), regex=r'.*') assert sig.type == SignatureType.Casual_Cert assert sig.key_flags == {KeyFlags.Authentication} assert sig.exportable # assert sig.trust_level == 1 # assert sig.trust_amount == 60 # assert sig.regex == r'.*' assert {sec.fingerprint.keyid} | set(sec.subkeys) & userid.signers userid |= sig def test_verify_userid(self, pub, userid): # with PGPy with self.assert_warnings(): sv = pub.verify(userid) assert sv assert len(sv) > 0 def test_add_photo(self, userphoto, targette_sec): with self.assert_warnings(): targette_sec.add_uid(userphoto) def test_certify_photo(self, sec, userphoto): with self.assert_warnings(): userphoto |= sec.certify(userphoto) def test_revoke_certification(self, sec, userphoto): # revoke the certifications of userphoto with self.assert_warnings(): revsig = sec.revoke(userphoto) assert revsig.type == SignatureType.CertRevocation userphoto |= revsig def test_certify_key(self, sec, targette_sec): # let's add an 0x1f signature with notation # GnuPG does not like these, so we'll mark it as non-exportable with self.assert_warnings(): sig = sec.certify(targette_sec, exportable=False, notation={ 'Notice': 'This key has been frobbed!', 'Binary': bytearray(b'\xc0\x01\xd0\x0d') }) assert sig.type == SignatureType.DirectlyOnKey assert sig.exportable is False assert sig.notation == { 'Notice': 'This key has been frobbed!', 'Binary': bytearray(b'\xc0\x01\xd0\x0d') } targette_sec |= sig def test_self_certify_key(self, targette_sec): # let's add an 0x1f signature with notation with self.assert_warnings(): sig = targette_sec.certify( targette_sec, notation={'Notice': 'This key has been self-frobbed!'}) assert sig.type == SignatureType.DirectlyOnKey assert sig.notation == {'Notice': 'This key has been self-frobbed!'} targette_sec |= sig def test_add_revocation_key(self, sec, targette_sec): targette_sec |= targette_sec.revoker(sec) def test_verify_key(self, pub, targette_sec): with self.assert_warnings(): sv = pub.verify(targette_sec) assert len(list(sv.good_signatures)) > 0 assert sv def test_new_key(self, key_alg): # create a key and a user id and add the UID to the key uid = PGPUID.new('Hugo Gernsback', 'Science Fiction Plus', '*****@*****.**') key = PGPKey.new(key_alg, key_alg_size[key_alg]) key.add_uid(uid, hashes=[HashAlgorithm.SHA224]) # self-verify the key assert key.verify(key) self.gen_keys[key_alg] = key def test_new_subkey(self, key_alg): key = self.gen_keys[key_alg] subkey = PGPKey.new(subkey_alg[key_alg], key_alg_size[subkey_alg[key_alg]]) assert subkey._key assert not isinstance(subkey._key, PrivSubKeyV4) # now add the subkey to key and then verify it key.add_subkey(subkey, usage={KeyFlags.EncryptCommunications}) # subkey should be a PrivSubKeyV4 now, not a PrivKeyV4 assert isinstance(subkey._key, PrivSubKeyV4) # self-verify sv = self.gen_keys[key_alg].verify(self.gen_keys[key_alg]) assert sv assert subkey in sv def test_pub_from_sec(self, key_alg): priv = self.gen_keys[key_alg] pub = priv.pubkey assert pub.fingerprint == priv.fingerprint assert len(pub._key) == len(pub._key.__bytes__()) for skid, subkey in priv.subkeys.items(): assert skid in pub.subkeys assert pub.subkeys[skid].is_public assert len(subkey._key) == len(subkey._key.__bytes__()) def test_gpg_verify_new_key(self, key_alg, write_clean, gpg_import, gpg_check_sigs): if gpg_ver < '2.1' and key_alg in { PubKeyAlgorithm.ECDSA, PubKeyAlgorithm.ECDH }: pytest.skip("GnuPG version in use cannot import/verify ") # with GnuPG key = self.gen_keys[key_alg] with write_clean('tests/testdata/genkey.asc', 'w', str(key)), \ gpg_import('./genkey.asc') as kio: assert 'invalid self-signature' not in kio assert gpg_check_sigs(key.fingerprint.keyid, *[skid for skid in key._children.keys()]) def test_gpg_verify_key(self, targette_sec, write_clean, gpg_import, gpg_check_sigs): # with GnuPG with write_clean('tests/testdata/targette.sec.asc', 'w', str(targette_sec)), \ gpg_import('./pubtest.asc', './targette.sec.asc') as kio: assert 'invalid self-signature' not in kio assert gpg_check_sigs(targette_sec.fingerprint.keyid) def test_revoke_key(self, sec, pub, write_clean, gpg_import, gpg_check_sigs): with self.assert_warnings(): rsig = sec.revoke(pub, sigtype=SignatureType.KeyRevocation, reason=RevocationReason.Retired, comment="But you're so oooold") assert 'ReasonForRevocation' in rsig._signature.subpackets pub |= rsig # verify with PGPy # assert pub.verify(pub) # verify with GPG kfp = '{:s}.asc'.format(pub.fingerprint.shortid) with write_clean(os.path.join('tests', 'testdata', kfp), 'w', str(kfp)), \ gpg_import(os.path.join('.', kfp)) as kio: assert 'invalid self-signature' not in kio # and remove it, for good measure pub._signatures.remove(rsig) assert rsig not in pub def test_revoke_key_with_revoker(self): pytest.skip("not implemented yet") def test_revoke_subkey(self, sec, pub, write_clean, gpg_import, gpg_check_sigs): if sec.key_algorithm == PubKeyAlgorithm.ECDSA: pytest.skip( "ECDH not implemented yet which causes this test to fail") subkey = next(iter(pub.subkeys.values())) with self.assert_warnings(): # revoke the first subkey rsig = sec.revoke(subkey, sigtype=SignatureType.SubkeyRevocation) assert 'ReasonForRevocation' in rsig._signature.subpackets subkey |= rsig # verify with PGPy assert pub.verify(subkey) sv = pub.verify(pub) assert sv assert rsig in iter(s.signature for s in sv.good_signatures) # verify with GnuPG kfp = '{:s}.asc'.format(pub.fingerprint.shortid) with write_clean(os.path.join('tests', 'testdata', kfp), 'w', str(kfp)), \ gpg_import(os.path.join('.', kfp)) as kio: assert 'invalid self-signature' not in kio # and remove it, for good measure subkey._signatures.remove(rsig) assert rsig not in subkey