Python "binding" for GPG. I'll write GPGME bindings eventually. The
[plcapi.git] / PLC / GPG.py
diff --git a/PLC/GPG.py b/PLC/GPG.py
new file mode 100644 (file)
index 0000000..ce86422
--- /dev/null
@@ -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 <mlhuang@cs.princeton.edu>
+# 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