Python "binding" for GPG. I'll write GPGME bindings eventually. The
[plcapi.git] / PLC / GPG.py
1 #
2 # Python "binding" for GPG. I'll write GPGME bindings eventually. The
3 # intent is to use GPG to sign method calls, as a way of identifying
4 # and authenticating peers. Calls should still go over an encrypted
5 # transport such as HTTPS, with certificate checking.
6 #
7 # Mark Huang <mlhuang@cs.princeton.edu>
8 # Copyright (C) 2006 The Trustees of Princeton University
9 #
10 # $Id$
11 #
12
13 import xmlrpclib
14 import shutil
15 from StringIO import StringIO
16 from xml.dom import minidom
17 from xml.dom.ext import Canonicalize
18 from subprocess import Popen, PIPE, call
19 from tempfile import NamedTemporaryFile, mkdtemp
20
21 from PLC.Faults import *
22
23 def canonicalize(methodname, args):
24     """
25     Returns a canonicalized XML-RPC representation of the
26     specified method call.
27     """
28
29     xml = xmlrpclib.dumps(args, methodname, encoding = 'utf-8', allow_none = 1)
30     dom = minidom.parseString(xml)
31
32     # Canonicalize(), though it claims to, does not encode unicode
33     # nodes to UTF-8 properly and throws an exception unless you write
34     # the stream to a file object, so just encode it ourselves.
35     buf = StringIO()
36     Canonicalize(dom, output = buf)
37     xml = buf.getvalue().encode('utf-8')
38
39     return xml
40
41 def gpg_sign(methodname, args, secret_keyring, keyring):
42     """
43     Signs the specified method call using the specified keyring files.
44     """
45
46     message = canonicalize(methodname, args)
47
48     p = Popen(["gpg", "--batch", "--no-tty",
49                "--no-default-keyring",
50                "--secret-keyring", secret_keyring,
51                "--keyring", keyring,
52                "--detach-sign", "--armor"],
53               stdin = PIPE, stdout = PIPE, stderr = PIPE)
54     p.stdin.write(message)
55     p.stdin.close()
56     signature = p.stdout.read()
57     rc = p.wait()
58     if rc:
59         raise PLCAuthenticationFailure, "GPG signing failed with return code %d" % rc
60
61     return signature
62
63 def gpg_verify(methodname, args, signature, key):
64     """
65     Verifys the signature of the method call using the specified public
66     key material.
67     """
68
69     message = canonicalize(methodname, args)
70
71     # Write public key to temporary file
72     keyfile = NamedTemporaryFile(suffix = '.pub')
73     keyfile.write(key)
74     keyfile.flush()
75
76     # Import public key into temporary keyring
77     homedir = mkdtemp()
78     call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfile.name],
79          stdin = PIPE, stdout = PIPE, stderr = PIPE)
80
81     # Write detached signature to temporary file
82     sigfile = NamedTemporaryFile()
83     sigfile.write(signature)
84     sigfile.flush()
85
86     # Verify signature
87     p = Popen(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--verify", sigfile.name, "-"],
88               stdin = PIPE, stdout = PIPE, stderr = PIPE)
89     p.stdin.write(message)
90     p.stdin.close()
91     rc = p.wait()
92
93     # Clean up
94     sigfile.close()
95     shutil.rmtree(homedir)
96     keyfile.close()
97
98     if rc:
99         raise PLCAuthenticationFailure, "GPG verification failed with return code %d" % rc