- change variable name
[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.3 2007/01/08 18:11:54 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_export(keyring, armor = True):
42     """
43     Exports the specified public keyring file.
44     """
45
46     homedir = mkdtemp()
47     args = ["gpg", "--batch", "--no-tty",
48             "--homedir", homedir,
49             "--no-default-keyring",
50             "--keyring", keyring,
51             "--export"]
52     if armor:
53         args.append("--armor")
54
55     p = Popen(args, stdin = PIPE, stdout = PIPE, stderr = PIPE)
56     export = p.stdout.read()
57     err = p.stderr.read()
58     rc = p.wait()
59
60     # Clean up
61     shutil.rmtree(homedir)
62
63     if rc:
64         raise PLCAuthenticationFailure, "GPG export failed with return code %d: %s" % (rc, err)
65
66     return export
67
68 def gpg_sign(methodname, args, secret_keyring, keyring):
69     """
70     Signs the specified method call using the specified keyring files.
71     """
72
73     message = canonicalize(methodname, args)
74
75     homedir = mkdtemp()
76     p = Popen(["gpg", "--batch", "--no-tty",
77                "--homedir", homedir,
78                "--no-default-keyring",
79                "--secret-keyring", secret_keyring,
80                "--keyring", keyring,
81                "--detach-sign", "--armor"],
82               stdin = PIPE, stdout = PIPE, stderr = PIPE)
83     p.stdin.write(message)
84     p.stdin.close()
85     signature = p.stdout.read()
86     err = p.stderr.read()
87     rc = p.wait()
88
89     # Clean up
90     shutil.rmtree(homedir)
91
92     if rc:
93         raise PLCAuthenticationFailure, "GPG signing failed with return code %d: %s" % (rc, err)
94
95     return signature
96
97 def gpg_verify(methodname, args, signature, key):
98     """
99     Verifys the signature of the method call using the specified public
100     key material.
101     """
102
103     message = canonicalize(methodname, args)
104
105     # Write public key to temporary file
106     keyfile = NamedTemporaryFile(suffix = '.pub')
107     keyfile.write(key)
108     keyfile.flush()
109
110     # Import public key into temporary keyring
111     homedir = mkdtemp()
112     call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfile.name],
113          stdin = PIPE, stdout = PIPE, stderr = PIPE)
114
115     # Write detached signature to temporary file
116     sigfile = NamedTemporaryFile()
117     sigfile.write(signature)
118     sigfile.flush()
119
120     # Verify signature
121     p = Popen(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--verify", sigfile.name, "-"],
122               stdin = PIPE, stdout = PIPE, stderr = PIPE)
123     p.stdin.write(message)
124     p.stdin.close()
125     rc = p.wait()
126
127     # Clean up
128     sigfile.close()
129     shutil.rmtree(homedir)
130     keyfile.close()
131
132     if rc:
133         raise PLCAuthenticationFailure, "GPG verification failed with return code %d" % rc