From 6e49862c0a3539da2826063c434d523a82fbc461 Mon Sep 17 00:00:00 2001 From: Michał Górny Date: Fri, 27 Oct 2017 22:07:27 +0200 Subject: openpgp: Support signing keys --- gemato/exceptions.py | 10 +++++++ gemato/openpgp.py | 32 ++++++++++++++++++++++ tests/test_openpgp.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/gemato/exceptions.py b/gemato/exceptions.py index 7c75f46..901d0b1 100644 --- a/gemato/exceptions.py +++ b/gemato/exceptions.py @@ -79,6 +79,16 @@ class OpenPGPVerificationFailure(Exception): "OpenPGP verification failed:\n{}".format(output)) +class OpenPGPSigningFailure(Exception): + """ + An exception raised when OpenPGP signing fails. + """ + + def __init__(self, output): + super(OpenPGPSigningFailure, self).__init__( + "OpenPGP signing failed:\n{}".format(output)) + + class OpenPGPNoImplementation(Exception): """ An exception raised when no supported OpenPGP implementation diff --git a/gemato/openpgp.py b/gemato/openpgp.py index 607c46f..b316f78 100644 --- a/gemato/openpgp.py +++ b/gemato/openpgp.py @@ -74,6 +74,14 @@ class OpenPGPEnvironment(object): verify_file(f, env=self) + def clear_sign_file(self, f, outf, keyid=None): + """ + A convenience wrapper for clear_sign_file(), using this + environment. + """ + + clear_sign_file(f, outf, keyid=keyid, env=self) + @property def home(self): if self._home is None: @@ -99,3 +107,27 @@ def verify_file(f, env=None): f) if exitst != 0: raise gemato.exceptions.OpenPGPVerificationFailure(err.decode('utf8')) + + +def clear_sign_file(f, outf, keyid=None, env=None): + """ + Create an OpenPGP cleartext signed message containing the data + from open file @f, and writing it into open file @outf. + Both files should be open in binary mode and set at the appropriate + position. Raises an exception if signing fails. + + Pass @keyid to specify the key to use. If not specified, + the implementation will use the default key. Pass @env to use + a dedicated OpenPGPEnvironment. + """ + + args = [] + if keyid is not None: + args += ['--local-user', keyid] + exitst, out, err = _spawn_gpg(['--clearsign'] + args, + env.home if env is not None else None, + f) + if exitst != 0: + raise gemato.exceptions.OpenPGPSigningFailure(err.decode('utf8')) + + outf.write(out) diff --git a/tests/test_openpgp.py b/tests/test_openpgp.py index f9071c9..0ee295d 100644 --- a/tests/test_openpgp.py +++ b/tests/test_openpgp.py @@ -36,6 +36,43 @@ jCvJNJ7pU8YnJSRTQDH0PZEupAdzDU/AhGSrBz5+Jr7N0pQIxq4duE/Q -----END PGP PUBLIC KEY BLOCK----- ''' +PRIVATE_KEY = u''' +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQOYBFnwXJMBCACgaTVz+d10TGL9zR920sb0GBFsitAJ5ZFzO4E0cg3SHhwI+reM +JQ6LLKmHowY/E1dl5FBbnJoRMxXP7/eScQ7HlhYj1gMPN5XiS2pkPwVkmJKBDV42 +DLwoytC+ot0frRTJvSdEPCX81BNMgFiBSpkeZfXqb9XmU03bh6mFnrdd4CsHpTQG +csVXHK8QKhaxuqmHTALdpSzKCb/r0N/Z3sQExZhfLcBf/9UUVXj44Nwc6ooqZLRi +zHydxwQdxNu0aOFGEBn9WTi8Slf7MfR/pF0dI8rs9w6zMzVEq0lhDPpKFGDveoGf +g/+TpvBNXZ7DWH23GM4kID3pk4LLMc24U1PhABEBAAEAB/sEgeBMIXW9ClZvvj9H +lfWcLz7yF1ZwKMC1BbOENz43LLxp7i2RJQtrErayxnxq8k6u4ML3SAe2OwK+ZIZG +2aFqL0fw+tb8KvotsSPMrE6o/HaFZMxEZYg19zj1WlsvRCxE3OlJDA2fNJBUQnj6 +LQ/vYDsQOtM+VRHnfMDhLcwGObZnNPMwtmwkHLKWTgyTwAGnLObSheVutVbdyU6+ +wI3UXwAoilW2e+9pKtwaODjqT7pQ2maVSCY4MPGdLQpbPy61COstdpK/hRdI3liL +uwszdlnT1QhiLsOTHPt4JjYdv2jgDjQobbe/ziKNzFp1eoMHDkbjzAh7oD2FxJcZ +EYLnBADE5oryW+9GlyYQe3x74QD5BGTZfvJctvEOgUg8BsoIfXJgBzwnEwOD0XBg +Jcl5qgt3IBH9Fn3JnYMpw12SEG2W4N8VCIBxIkDEBABVJfp1Q7HAJ8GSmzENnvt1 +iaAZPUscaFVpMyuajsCDmyK92NMymGiNAb1H5MU4gaFGaEaajwQA0I7gglsehQA2 +MSyJD0Uj+0b6n9KtiUzjyWEOcITXn4buf4O8Llor8gU0BWuv3hmIcvNsuJfmgXav +Vxq2UHtiGaO7T9Vk4Sr8MKS9EYrLNbK41Lyb+tjxk3jYjEyFqCDNEtWKIZR4ENdR +jo5gYKBtuqv1AYYSkflOTeaRlv/kIo8D/jVcyjmO19tNJM8lQE1xCvhp5maXOoSk +1UoUmDprsKA2Em47J83sVivrIwBySB2n9srQynnV+8I47mX7YzYtNQ6uXdL3p/5e +FRW+yfqVCShhSfyQdOmJ978UyQEwY0+0hhK372KatmaL9KEkKSuXgsqshv3XiB9y +u3Su1jw5y2IQNP20D2dlbWF0byB0ZXN0IGtleYkBRgQTAQoAMBYhBIHhLBa9jc1g +vhgIRRNogOcqexOEBQJZ8FyTAhsDBQsJCg0EAxUKCAIeAQIXgAAKCRATaIDnKnsT +hCnkB/0fhTH230idhlfZhFbVgTLxrj4rpsGg20K8HkMaWzChsONdKkqYaYuRcm2U +QZ0Kg5rm9jQsGYuAnzH/7XwmOleY95ycVfBkje9aXF6BEoGick6C/AK5w77vd1kc +BtJDrT4I7vwD4wRkyUdCkpVMVT4z4aZ7lHJ4ECrrrI/mg0b+sGRyHfXPvIPp7F29 +59L/dpbhBZDfMOFC0A9LBQBJldKFbQLg3xzX4tniz/BBrp7KjTOMKU0sufsedI50 +xc6cvCYCwJElqo86vv69klZHahE/k9nJaUAMjCvJNJ7pU8YnJSRTQDH0PZEupAdz +DU/AhGSrBz5+Jr7N0pQIxq4duE/Q +=wOFB +-----END PGP PRIVATE KEY BLOCK----- + +''' + +PRIVATE_KEY_ID = b'0x136880E72A7B1384' + MALFORMED_PUBLIC_KEY = u''' -----BEGIN PGP PUBLIC KEY BLOCK----- @@ -486,3 +523,42 @@ class OpenPGPContextManagerTest(unittest.TestCase): env.close() with self.assertRaises(RuntimeError): env.home + + +class OpenPGPPrivateKeyTest(unittest.TestCase): + """ + Tests performed with the private key available. + """ + + TEST_STRING = b'The quick brown fox jumps over the lazy dog' + + def setUp(self): + self.env = gemato.openpgp.OpenPGPEnvironment() + try: + self.env.import_key( + io.BytesIO(PRIVATE_KEY.encode('utf8'))) + except gemato.exceptions.OpenPGPNoImplementation as e: + raise unittest.SkipTest(str(e)) + except RuntimeError: + raise unittest.SkipTest('Unable to import OpenPGP key') + + def tearDown(self): + self.env.close() + + def test_verify_manifest(self): + with io.BytesIO(SIGNED_MANIFEST.encode('utf8')) as f: + self.env.verify_file(f) + + def test_sign_data(self): + with io.BytesIO(self.TEST_STRING) as f: + with io.BytesIO() as wf: + self.env.clear_sign_file(f, wf) + wf.seek(0) + self.env.verify_file(wf) + + def test_sign_data_keyid(self): + with io.BytesIO(self.TEST_STRING) as f: + with io.BytesIO() as wf: + self.env.clear_sign_file(f, wf, keyid=PRIVATE_KEY_ID) + wf.seek(0) + self.env.verify_file(wf) -- cgit v1.2.3