From 140c23a81dfeb23d4643d17678eefd01e9f22d55 Mon Sep 17 00:00:00 2001 From: Michał Górny Date: Tue, 16 Jan 2018 15:46:08 +0100 Subject: openpgp: Reject signatures made with expired & revoked keys --- gemato/exceptions.py | 28 ++++++++++++++ gemato/openpgp.py | 14 ++++++- tests/test_openpgp.py | 103 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 139 insertions(+), 6 deletions(-) diff --git a/gemato/exceptions.py b/gemato/exceptions.py index 843da02..a85ffc2 100644 --- a/gemato/exceptions.py +++ b/gemato/exceptions.py @@ -107,6 +107,34 @@ class OpenPGPVerificationFailure(Exception): return "OpenPGP verification failed:\n{}".format(self.output) +class OpenPGPExpiredKeyFailure(OpenPGPVerificationFailure): + """ + OpenPGP verification rejected because of expired key. + """ + + def __str__(self): + return "OpenPGP signature rejected because of expired key:\n{}".format(self.output) + + +class OpenPGPRevokedKeyFailure(OpenPGPVerificationFailure): + """ + OpenPGP verification rejected because of revoked key. + """ + + def __str__(self): + return "OpenPGP signature rejected because of revoked key:\n{}".format(self.output) + + +class OpenPGPUnknownSigFailure(OpenPGPVerificationFailure): + """ + OpenPGP verification rejected for unknown reason (i.e. unrecognized + GPG status). + """ + + def __str__(self): + return "OpenPGP signature rejected for unknown reason:\n{}".format(self.output) + + class OpenPGPSigningFailure(Exception): """ An exception raised when OpenPGP signing fails. diff --git a/gemato/openpgp.py b/gemato/openpgp.py index e2d97d1..0394cee 100644 --- a/gemato/openpgp.py +++ b/gemato/openpgp.py @@ -156,10 +156,22 @@ disable-scdaemon raise RuntimeError('Unable to import key: {}'.format(err.decode('utf8'))) def verify_file(self, f): - exitst, out, err = self._spawn_gpg(['--verify'], f.read().encode('utf8')) + exitst, out, err = self._spawn_gpg(['--status-fd', '1', '--verify'], + f.read().encode('utf8')) if exitst != 0: raise gemato.exceptions.OpenPGPVerificationFailure(err.decode('utf8')) + # process the output of gpg to find the exact result + for l in out.splitlines(): + if l.startswith(b'[GNUPG:] GOODSIG'): + break + elif l.startswith(b'[GNUPG:] EXPKEYSIG'): + raise gemato.exceptions.OpenPGPExpiredKeyFailure(err.decode('utf8')) + elif l.startswith(b'[GNUPG:] REVKEYSIG'): + raise gemato.exceptions.OpenPGPRevokedKeyFailure(err.decode('utf8')) + else: + raise gemato.exceptions.OpenPGPUnknownSigFailure(err.decode('utf8')) + def clear_sign_file(self, f, outf, keyid=None): args = [] if keyid is not None: diff --git a/tests/test_openpgp.py b/tests/test_openpgp.py index 0e765af..2241931 100644 --- a/tests/test_openpgp.py +++ b/tests/test_openpgp.py @@ -37,6 +37,53 @@ jCvJNJ7pU8YnJSRTQDH0PZEupAdzDU/AhGSrBz5+Jr7N0pQIxq4duE/Q -----END PGP PUBLIC KEY BLOCK----- ''' +EXPIRED_PUBLIC_KEY = b''' +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFnwXJMBCACgaTVz+d10TGL9zR920sb0GBFsitAJ5ZFzO4E0cg3SHhwI+reM +JQ6LLKmHowY/E1dl5FBbnJoRMxXP7/eScQ7HlhYj1gMPN5XiS2pkPwVkmJKBDV42 +DLwoytC+ot0frRTJvSdEPCX81BNMgFiBSpkeZfXqb9XmU03bh6mFnrdd4CsHpTQG +csVXHK8QKhaxuqmHTALdpSzKCb/r0N/Z3sQExZhfLcBf/9UUVXj44Nwc6ooqZLRi +zHydxwQdxNu0aOFGEBn9WTi8Slf7MfR/pF0dI8rs9w6zMzVEq0lhDPpKFGDveoGf +g/+TpvBNXZ7DWH23GM4kID3pk4LLMc24U1PhABEBAAG0D2dlbWF0byB0ZXN0IGtl +eYkBTAQTAQoANgIbAwULCQoNBAMVCggCHgECF4AWIQSB4SwWvY3NYL4YCEUTaIDn +KnsThAUCWfEJZAUJAAH+UQAKCRATaIDnKnsThJTJB/9nXG1vgEBXHp8JsgkbmsAA +WzcSsdmuRFcr2FI3KDYJ0G7rmBpirJuAaGbWS/2+3BmQGVlOf77RjeC6CtI/DH4U +Tw3hcI7FYJrRdILV+p3HTkLhPs5fNjxH8bTyKthEE8pM0gQ3fuZxsaNnv1XbSpf0 +P+d/y06ehvGCVYyEe4MHPV6f6YgCrP+ePqQvMqEpvlSizZE/HoFoKy7Ik4u2fDOH +5RRmIoNLv8j1gOKwp5+SncsuXVdQucY7jdFWSgECOAGIRvzBbGDq9+6ccCQHiOOz +ncaJWqeCHuTvNj9WfoyvKXM+hpQUdSaTURgz4a92htIGpON5wN7o32VuJz2nWXS4 +=RwD3 +-----END PGP PUBLIC KEY BLOCK----- +''' + +REVOKED_PUBLIC_KEY = b''' +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFnwXJMBCACgaTVz+d10TGL9zR920sb0GBFsitAJ5ZFzO4E0cg3SHhwI+reM +JQ6LLKmHowY/E1dl5FBbnJoRMxXP7/eScQ7HlhYj1gMPN5XiS2pkPwVkmJKBDV42 +DLwoytC+ot0frRTJvSdEPCX81BNMgFiBSpkeZfXqb9XmU03bh6mFnrdd4CsHpTQG +csVXHK8QKhaxuqmHTALdpSzKCb/r0N/Z3sQExZhfLcBf/9UUVXj44Nwc6ooqZLRi +zHydxwQdxNu0aOFGEBn9WTi8Slf7MfR/pF0dI8rs9w6zMzVEq0lhDPpKFGDveoGf +g/+TpvBNXZ7DWH23GM4kID3pk4LLMc24U1PhABEBAAGJATYEIAEIACAWIQSB4SwW +vY3NYL4YCEUTaIDnKnsThAUCWl4LpQIdAAAKCRATaIDnKnsThCvQB/9gzrXiRv7g +7UIzwRvTBMVXbKlbwjJpI0XnjdZmlYVis5y4ZWxh65z8j94lV4NmTtDtAdlMN8Xd +OyTVWPGKN5cJMRLMzMRWb+aQV1fCEfwJgngE0hQe0w2dPwuVQQVP3Kv8CC+4f2lQ +ummcgPW2LVEN3HihAwx9VWA91JSlrsX3luNSvTi2c63BM9YqGb64nJc1sAWqxzDy +x157gzt0AHKAAQ+Hmwhqt0vnR8MyKJYo60PwNUkzWlUhOLaBpb7WvHAAmva14Rw+ +fCaldU4iFlC5oJrj0jE/yKvGG6SuSYZaS9O0H/UNI5vF8Y/HgGM0i8+NJxXu0hud +NRH/MmEilKxUtA9nZW1hdG8gdGVzdCBrZXmJAUYEEwEKADAWIQSB4SwWvY3NYL4Y +CEUTaIDnKnsThAUCWfBckwIbAwULCQoNBAMVCggCHgECF4AACgkQE2iA5yp7E4Qp +5Af9H4Ux9t9InYZX2YRW1YEy8a4+K6bBoNtCvB5DGlswobDjXSpKmGmLkXJtlEGd +CoOa5vY0LBmLgJ8x/+18JjpXmPecnFXwZI3vWlxegRKBonJOgvwCucO+73dZHAbS +Q60+CO78A+MEZMlHQpKVTFU+M+Gme5RyeBAq66yP5oNG/rBkch31z7yD6exdvefS +/3aW4QWQ3zDhQtAPSwUASZXShW0C4N8c1+LZ4s/wQa6eyo0zjClNLLn7HnSOdMXO +nLwmAsCRJaqPOr7+vZJWR2oRP5PZyWlADIwryTSe6VPGJyUkU0Ax9D2RLqQHcw1P +wIRkqwc+fia+zdKUCMauHbhP0A== +=Zvmi +-----END PGP PUBLIC KEY BLOCK----- +''' + PRIVATE_KEY = b''' -----BEGIN PGP PRIVATE KEY BLOCK----- @@ -400,6 +447,8 @@ class OpenPGPNoKeyTest(unittest.TestCase): Tests performed without correct OpenPGP key set. """ + expected_exception = gemato.exceptions.OpenPGPVerificationFailure + def setUp(self): self.env = gemato.openpgp.OpenPGPEnvironment() @@ -409,7 +458,7 @@ class OpenPGPNoKeyTest(unittest.TestCase): def test_verify_manifest(self): with io.StringIO(SIGNED_MANIFEST) as f: try: - self.assertRaises(gemato.exceptions.OpenPGPVerificationFailure, + self.assertRaises(self.expected_exception, self.env.verify_file, f) except gemato.exceptions.OpenPGPNoImplementation as e: raise unittest.SkipTest(str(e)) @@ -418,7 +467,7 @@ class OpenPGPNoKeyTest(unittest.TestCase): m = gemato.manifest.ManifestFile() with io.StringIO(SIGNED_MANIFEST) as f: try: - self.assertRaises(gemato.exceptions.OpenPGPVerificationFailure, + self.assertRaises(self.expected_exception, m.load, f, openpgp_env=self.env) except gemato.exceptions.OpenPGPNoImplementation as e: raise unittest.SkipTest(str(e)) @@ -431,7 +480,7 @@ class OpenPGPNoKeyTest(unittest.TestCase): with io.StringIO(SIGNED_MANIFEST) as f: try: m.load(f, openpgp_env=self.env) - except gemato.exceptions.OpenPGPVerificationFailure: + except self.expected_exception: pass except gemato.exceptions.OpenPGPNoImplementation: pass @@ -446,7 +495,7 @@ class OpenPGPNoKeyTest(unittest.TestCase): f.write(SIGNED_MANIFEST) try: - self.assertRaises(gemato.exceptions.OpenPGPVerificationFailure, + self.assertRaises(self.expected_exception, gemato.recursiveloader.ManifestRecursiveLoader, os.path.join(d, 'Manifest'), verify_openpgp=True, @@ -464,7 +513,7 @@ class OpenPGPNoKeyTest(unittest.TestCase): cf.write(SIGNED_MANIFEST) try: - self.assertRaises(gemato.exceptions.OpenPGPVerificationFailure, + self.assertRaises(self.expected_exception, gemato.recursiveloader.ManifestRecursiveLoader, os.path.join(d, 'Manifest.gz'), verify_openpgp=True, @@ -487,6 +536,50 @@ class OpenPGPNoKeyTest(unittest.TestCase): shutil.rmtree(d) +class OpenPGPExpiredKeyTest(OpenPGPNoKeyTest): + """ + Tests performed with an expired OpenPGP key. + """ + + expected_exception = gemato.exceptions.OpenPGPExpiredKeyFailure + + def setUp(self): + self.env = gemato.openpgp.OpenPGPEnvironment() + try: + self.env.import_key(io.BytesIO(EXPIRED_PUBLIC_KEY)) + except gemato.exceptions.OpenPGPNoImplementation as e: + self.env.close() + raise unittest.SkipTest(str(e)) + except RuntimeError: + self.env.close() + raise unittest.SkipTest('Unable to import OpenPGP key') + + def tearDown(self): + self.env.close() + + +class OpenPGPRevokedKeyTest(OpenPGPNoKeyTest): + """ + Tests performed with a revoked OpenPGP key. + """ + + expected_exception = gemato.exceptions.OpenPGPRevokedKeyFailure + + def setUp(self): + self.env = gemato.openpgp.OpenPGPEnvironment() + try: + self.env.import_key(io.BytesIO(REVOKED_PUBLIC_KEY)) + except gemato.exceptions.OpenPGPNoImplementation as e: + self.env.close() + raise unittest.SkipTest(str(e)) + except RuntimeError: + self.env.close() + raise unittest.SkipTest('Unable to import OpenPGP key') + + def tearDown(self): + self.env.close() + + class OpenPGPContextManagerTest(unittest.TestCase): """ Test the context manager API for OpenPGPEnvironment. -- cgit v1.2.3