- use temporary home directory since apache user does not have write
[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: GPG.py,v 1.1 2006/12/15 18:21:57 mlhuang Exp $
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     homedir = mkdtemp()
49     p = Popen(["gpg", "--batch", "--no-tty",
50                "--homedir", homedir,
51                "--no-default-keyring",
52                "--secret-keyring", secret_keyring,
53                "--keyring", keyring,
54                "--detach-sign", "--armor"],
55               stdin = PIPE, stdout = PIPE, stderr = PIPE)
56     p.stdin.write(message)
57     p.stdin.close()
58     signature = p.stdout.read()
59     rc = p.wait()
60
61     # Clean up
62     shutil.rmtree(homedir)
63
64     if rc:
65         raise PLCAuthenticationFailure, "GPG signing failed with return code %d" % rc
66
67     return signature
68
69 def gpg_verify(methodname, args, signature, key):
70     """
71     Verifys the signature of the method call using the specified public
72     key material.
73     """
74
75     message = canonicalize(methodname, args)
76
77     # Write public key to temporary file
78     keyfile = NamedTemporaryFile(suffix = '.pub')
79     keyfile.write(key)
80     keyfile.flush()
81
82     # Import public key into temporary keyring
83     homedir = mkdtemp()
84     call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfile.name],
85          stdin = PIPE, stdout = PIPE, stderr = PIPE)
86
87     # Write detached signature to temporary file
88     sigfile = NamedTemporaryFile()
89     sigfile.write(signature)
90     sigfile.flush()
91
92     # Verify signature
93     p = Popen(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--verify", sigfile.name, "-"],
94               stdin = PIPE, stdout = PIPE, stderr = PIPE)
95     p.stdin.write(message)
96     p.stdin.close()
97     rc = p.wait()
98
99     # Clean up
100     sigfile.close()
101     shutil.rmtree(homedir)
102     keyfile.close()
103
104     if rc:
105         raise PLCAuthenticationFailure, "GPG verification failed with return code %d" % rc