示例#1
0
    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)
示例#2
0
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")
示例#3
0
 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)
示例#4
0
    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)
示例#5
0
def sig():
    return PGPSignature.from_file('tests/testdata/blocks/rsasignature.asc')
示例#6
0
def sig():
    return PGPSignature.from_file('tests/testdata/blocks/rsasignature.asc')
示例#7
0
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