summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gemato/openpgp.py44
-rw-r--r--tests/test_openpgp.py28
2 files changed, 66 insertions, 6 deletions
diff --git a/gemato/openpgp.py b/gemato/openpgp.py
index 051cfc1..42b2949 100644
--- a/gemato/openpgp.py
+++ b/gemato/openpgp.py
@@ -3,6 +3,7 @@
# (c) 2017-2018 Michał Górny
# Licensed under the terms of 2-clause BSD license
+import datetime
import errno
import os.path
import shutil
@@ -12,6 +13,18 @@ import tempfile
import gemato.exceptions
+class OpenPGPSignatureData(object):
+ __slots__ = ['fingerprint', 'timestamp', 'expire_timestamp',
+ 'primary_key_fingerprint']
+
+ def __init__(self, fingerprint, timestamp, expire_timestamp,
+ primary_key_fingerprint):
+ self.fingerprint = fingerprint
+ self.timestamp = timestamp
+ self.expire_timestamp = expire_timestamp
+ self.primary_key_fingerprint = primary_key_fingerprint
+
+
class OpenPGPSystemEnvironment(object):
"""
OpenPGP environment class that uses the global OpenPGP environment
@@ -50,6 +63,21 @@ class OpenPGPSystemEnvironment(object):
raise NotImplementedError('refresh_keys() is not implemented by this OpenPGP provider')
+ def _parse_gpg_ts(self, ts):
+ """
+ Parse GnuPG status timestamp that can either be time_t value
+ or ISO 8601 timestamp.
+ """
+ # that's how upstream tells us to detect this
+ if 'T' in ts:
+ # TODO: is this correct for all cases? is it localtime?
+ return datetime.datetime.strptime(ts, '%Y%m%dT%H%M%S')
+ elif ts == '0':
+ # no timestamp
+ return None
+ else:
+ return datetime.datetime.fromtimestamp(int(ts))
+
def verify_file(self, f):
"""
Perform an OpenPGP verification of Manifest data in open file @f.
@@ -64,6 +92,7 @@ class OpenPGPSystemEnvironment(object):
raise gemato.exceptions.OpenPGPVerificationFailure(err.decode('utf8'))
is_good = False
+ sig_data = None
# process the output of gpg to find the exact result
for l in out.splitlines():
@@ -73,9 +102,20 @@ class OpenPGPSystemEnvironment(object):
raise gemato.exceptions.OpenPGPExpiredKeyFailure(err.decode('utf8'))
elif l.startswith(b'[GNUPG:] REVKEYSIG'):
raise gemato.exceptions.OpenPGPRevokedKeyFailure(err.decode('utf8'))
-
- if not is_good:
+ elif l.startswith(b'[GNUPG:] VALIDSIG'):
+ spl = l.split(b' ')
+ assert len(spl) >= 12
+ fp = spl[2].decode('utf8')
+ ts = self._parse_gpg_ts(spl[4].decode('utf8'))
+ expts = self._parse_gpg_ts(spl[5].decode('utf8'))
+ pkfp = spl[11].decode('utf8')
+
+ sig_data = OpenPGPSignatureData(fp, ts, expts, pkfp)
+
+ # require both GOODSIG and VALIDSIG
+ if not is_good or sig_data is None:
raise gemato.exceptions.OpenPGPUnknownSigFailure(err.decode('utf8'))
+ return sig_data
def clear_sign_file(self, f, outf, keyid=None):
"""
diff --git a/tests/test_openpgp.py b/tests/test_openpgp.py
index f42ad85..6f09014 100644
--- a/tests/test_openpgp.py
+++ b/tests/test_openpgp.py
@@ -4,6 +4,7 @@
# Licensed under the terms of 2-clause BSD license
import base64
+import datetime
import io
import os.path
import shutil
@@ -210,6 +211,9 @@ mkkhTd2Auao4D2K74BePBuiZ9+eDQA==
-----END PGP SIGNATURE-----
'''
+KEY_FINGERPRINT = '81E12C16BD8DCD60BE180845136880E72A7B1384'
+SIG_TIMESTAMP = datetime.datetime(2017, 11, 8, 10, 1, 26)
+
def strip_openpgp(text):
lines = text.lstrip().splitlines()
@@ -355,11 +359,19 @@ class OpenPGPCorrectKeyTest(unittest.TestCase):
def test_verify_manifest(self):
with io.StringIO(SIGNED_MANIFEST) as f:
- self.env.verify_file(f)
+ sig = self.env.verify_file(f)
+ self.assertEqual(sig.fingerprint, KEY_FINGERPRINT)
+ self.assertEqual(sig.timestamp, SIG_TIMESTAMP)
+ self.assertIsNone(sig.expire_timestamp)
+ self.assertEqual(sig.primary_key_fingerprint, KEY_FINGERPRINT)
def test_verify_dash_escaped_manifest(self):
with io.StringIO(DASH_ESCAPED_SIGNED_MANIFEST) as f:
- self.env.verify_file(f)
+ sig = self.env.verify_file(f)
+ self.assertEqual(sig.fingerprint, KEY_FINGERPRINT)
+ self.assertEqual(sig.timestamp, SIG_TIMESTAMP)
+ self.assertIsNone(sig.expire_timestamp)
+ self.assertEqual(sig.primary_key_fingerprint, KEY_FINGERPRINT)
def test_verify_modified_manifest(self):
with io.StringIO(MODIFIED_SIGNED_MANIFEST) as f:
@@ -628,7 +640,11 @@ class OpenPGPContextManagerTest(unittest.TestCase):
except RuntimeError:
raise unittest.SkipTest('Unable to import OpenPGP key')
- env.verify_file(f)
+ sig = env.verify_file(f)
+ self.assertEqual(sig.fingerprint, KEY_FINGERPRINT)
+ self.assertEqual(sig.timestamp, SIG_TIMESTAMP)
+ self.assertIsNone(sig.expire_timestamp)
+ self.assertEqual(sig.primary_key_fingerprint, KEY_FINGERPRINT)
except gemato.exceptions.OpenPGPNoImplementation as e:
raise unittest.SkipTest(str(e))
@@ -666,7 +682,11 @@ class OpenPGPPrivateKeyTest(unittest.TestCase):
def test_verify_manifest(self):
with io.StringIO(SIGNED_MANIFEST) as f:
- self.env.verify_file(f)
+ sig = self.env.verify_file(f)
+ self.assertEqual(sig.fingerprint, KEY_FINGERPRINT)
+ self.assertEqual(sig.timestamp, SIG_TIMESTAMP)
+ self.assertIsNone(sig.expire_timestamp)
+ self.assertEqual(sig.primary_key_fingerprint, KEY_FINGERPRINT)
def test_sign_data(self):
with io.StringIO(self.TEST_STRING) as f: