From 4aacad5dfba20af1ea28f5b0fa50f49adcd1165e Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Aug 11 2016 21:38:37 +0000 Subject: Add the initial gpg signature check code This does implement verification of signatures on commits and tags. It does not implement getting the keys from a keyserver, verifying the key is owned by the user, or any UI to indicate signature state. Signed-off-by: Patrick Uiterwijk --- diff --git a/pagure/default_config.py b/pagure/default_config.py index d9a497c..87ac125 100644 --- a/pagure/default_config.py +++ b/pagure/default_config.py @@ -206,6 +206,10 @@ ALLOWED_PREFIX = [] # List of blacklisted group names BLACKLISTED_GROUPS = ['forks'] +# GnuPG configuration +GPG_BINARY = 'gpg2' +GPG_HOMEDIR = 'gpghome' + ACLS = { 'create_project': 'Create a new project', diff --git a/pagure/lib/git.py b/pagure/lib/git.py index 524b1c3..770ef07 100644 --- a/pagure/lib/git.py +++ b/pagure/lib/git.py @@ -11,6 +11,7 @@ import datetime import hashlib +import gnupg import json import os import shutil @@ -1243,6 +1244,56 @@ def get_git_tags(project): return tags +def _check_signature(message, signature): + """ This function performs the actual signature checking. """ + gpg = gnupg.GPG(gnupghome=pagure.APP.config['GPG_HOMEDIR'], + gpgbinary=pagure.APP.config['GPG_BINARY']) + with tempfile.NamedTemporaryFile() as sigfile: + sigfile.write(signature) + sigfile.flush() + verified = gpg.verify_data(sigfile.name, message) + return {'valid': verified.valid, + 'signer': verified.username, + 'keyid': verified.key_id} + return None + + +def check_signature(raw, is_simple): + """ Returns signature state. + + is_simple is a flag whether this is a simple signature (case with tags) + where the signature is just at the end of the raw string, or if it's a + complicated (case with commit) format where we need to parse it.""" + if not 'BEGIN PGP SIGNATURE' in raw: + return None + + if is_simple: + message, line, sig = raw.partition('-----BEGIN PGP SIGNATURE-----') + sig = line + sig + else: + message = '' + sig = '' + in_sig = False + for line in raw.split('\n'): + if line.startswith('gpgsig -----BEGIN PGP SIGNATURE-----'): + in_sig = True + if in_sig: + # The lines are all indented with a single space, remove those + # We also remove the remaining part of the "gpgsig" indicator + sig += line[1:].replace('pgsig ', '') + sig += '\n' + else: + message += line + message += '\n' + + if in_sig and line.startswith(' -----END PGP SIGNATURE-----'): + in_sig = False + # Remove the final newline, which is duplicate and breaks signature + message = message[:-1] + + return _check_signature(message, sig) + + def get_git_tags_objects(project): """ Returns the list of references of the tags created in the git repositorie the specified project. @@ -1278,6 +1329,8 @@ def get_git_tags_objects(project): '-----BEGIN PGP SIGNATURE-----', 1)[0].strip() tags[commit_time]["head_msg"] = head_msg tags[commit_time]["body_msg"] = body_msg + tags[commit_time]["signed"] = check_signature( + tags[commit_time]["object"].read_raw(), True) sorted_tags = [] for tag in sorted(tags, reverse=True): diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py index 6b85ce6..608b3c7 100644 --- a/pagure/ui/repo.py +++ b/pagure/ui/repo.py @@ -723,6 +723,9 @@ def view_commit(repo, commitid, username=None): # First commit in the repo diff = commit.tree.diff_to_tree(swap=True) + # Get signature info + signature = pagure.lib.git.check_signature(commit.read_raw(), False) + return flask.render_template( 'commit.html', select='commits', @@ -734,6 +737,7 @@ def view_commit(repo, commitid, username=None): commit=commit, diff=diff, form=pagure.forms.ConfirmationForm(), + signature=signature, ) diff --git a/requirements.txt b/requirements.txt index 9e6ad4c..02bb949 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ enum34 flask flask-wtf flask-multistatic +python-gnupg kitchen markdown munch