print GPG errors to log
[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.2 2007/01/05 18:50:40 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     err = p.stderr.read()
60     rc = p.wait()
61
62     # Clean up
63     shutil.rmtree(homedir)
64
65     if rc:
66         raise PLCAuthenticationFailure, "GPG signing failed with return code %d: %s" % (rc, err)
67
68     return signature
69
70 def gpg_verify(methodname, args, signature, key):
71     """
72     Verifys the signature of the method call using the specified public
73     key material.
74     """
75
76     message = canonicalize(methodname, args)
77
78     # Write public key to temporary file
79     keyfile = NamedTemporaryFile(suffix = '.pub')
80     keyfile.write(key)
81     keyfile.flush()
82
83     # Import public key into temporary keyring
84     homedir = mkdtemp()
85     call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfile.name],
86          stdin = PIPE, stdout = PIPE, stderr = PIPE)
87
88     # Write detached signature to temporary file
89     sigfile = NamedTemporaryFile()
90     sigfile.write(signature)
91     sigfile.flush()
92
93     # Verify signature
94     p = Popen(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--verify", sigfile.name, "-"],
95               stdin = PIPE, stdout = PIPE, stderr = PIPE)
96     p.stdin.write(message)
97     p.stdin.close()
98     rc = p.wait()
99
100     # Clean up
101     sigfile.close()
102     shutil.rmtree(homedir)
103     keyfile.close()
104
105     if rc:
106         raise PLCAuthenticationFailure, "GPG verification failed with return code %d" % rc