From 0b0dcd7fd9c08eb15cb39700b895a2d39593a8b6 Mon Sep 17 00:00:00 2001 From: Mark Huang Date: Fri, 15 Dec 2006 18:21:57 +0000 Subject: [PATCH] Python "binding" for GPG. I'll write GPGME bindings eventually. The intent is to use GPG to sign method calls, as a way of identifying and authenticating peers. Calls should still go over an encrypted transport such as HTTPS, with certificate checking. --- PLC/GPG.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 PLC/GPG.py diff --git a/PLC/GPG.py b/PLC/GPG.py new file mode 100644 index 0000000..ce86422 --- /dev/null +++ b/PLC/GPG.py @@ -0,0 +1,99 @@ +# +# Python "binding" for GPG. I'll write GPGME bindings eventually. The +# intent is to use GPG to sign method calls, as a way of identifying +# and authenticating peers. Calls should still go over an encrypted +# transport such as HTTPS, with certificate checking. +# +# Mark Huang +# Copyright (C) 2006 The Trustees of Princeton University +# +# $Id$ +# + +import xmlrpclib +import shutil +from StringIO import StringIO +from xml.dom import minidom +from xml.dom.ext import Canonicalize +from subprocess import Popen, PIPE, call +from tempfile import NamedTemporaryFile, mkdtemp + +from PLC.Faults import * + +def canonicalize(methodname, args): + """ + Returns a canonicalized XML-RPC representation of the + specified method call. + """ + + xml = xmlrpclib.dumps(args, methodname, encoding = 'utf-8', allow_none = 1) + dom = minidom.parseString(xml) + + # Canonicalize(), though it claims to, does not encode unicode + # nodes to UTF-8 properly and throws an exception unless you write + # the stream to a file object, so just encode it ourselves. + buf = StringIO() + Canonicalize(dom, output = buf) + xml = buf.getvalue().encode('utf-8') + + return xml + +def gpg_sign(methodname, args, secret_keyring, keyring): + """ + Signs the specified method call using the specified keyring files. + """ + + message = canonicalize(methodname, args) + + p = Popen(["gpg", "--batch", "--no-tty", + "--no-default-keyring", + "--secret-keyring", secret_keyring, + "--keyring", keyring, + "--detach-sign", "--armor"], + stdin = PIPE, stdout = PIPE, stderr = PIPE) + p.stdin.write(message) + p.stdin.close() + signature = p.stdout.read() + rc = p.wait() + if rc: + raise PLCAuthenticationFailure, "GPG signing failed with return code %d" % rc + + return signature + +def gpg_verify(methodname, args, signature, key): + """ + Verifys the signature of the method call using the specified public + key material. + """ + + message = canonicalize(methodname, args) + + # Write public key to temporary file + keyfile = NamedTemporaryFile(suffix = '.pub') + keyfile.write(key) + keyfile.flush() + + # Import public key into temporary keyring + homedir = mkdtemp() + call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfile.name], + stdin = PIPE, stdout = PIPE, stderr = PIPE) + + # Write detached signature to temporary file + sigfile = NamedTemporaryFile() + sigfile.write(signature) + sigfile.flush() + + # Verify signature + p = Popen(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--verify", sigfile.name, "-"], + stdin = PIPE, stdout = PIPE, stderr = PIPE) + p.stdin.write(message) + p.stdin.close() + rc = p.wait() + + # Clean up + sigfile.close() + shutil.rmtree(homedir) + keyfile.close() + + if rc: + raise PLCAuthenticationFailure, "GPG verification failed with return code %d" % rc -- 2.43.0