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.
7 # Mark Huang <mlhuang@cs.princeton.edu>
8 # Copyright (C) 2006 The Trustees of Princeton University
17 from types import StringTypes
18 from StringIO import StringIO
19 from xml.dom import minidom
20 from xml.dom.ext import Canonicalize
21 from subprocess import Popen, PIPE, call
22 from tempfile import NamedTemporaryFile, mkdtemp
24 from PLC.Faults import *
26 def canonicalize(args, methodname = None, methodresponse = False):
28 Returns a canonicalized XML-RPC representation of the specified
29 method call (methodname != None) or response (methodresponse =
33 xml = xmlrpclib.dumps(args, methodname, methodresponse, encoding = 'utf-8', allow_none = 1)
34 dom = minidom.parseString(xml)
36 # Canonicalize(), though it claims to, does not encode unicode
37 # nodes to UTF-8 properly and throws an exception unless you write
38 # the stream to a file object, so just encode it ourselves.
40 Canonicalize(dom, output = buf)
41 xml = buf.getvalue().encode('utf-8')
45 def gpg_export(keyring, armor = True):
47 Exports the specified public keyring file.
51 args = ["gpg", "--batch", "--no-tty",
53 "--no-default-keyring",
57 args.append("--armor")
59 p = Popen(args, stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
60 export = p.stdout.read()
65 shutil.rmtree(homedir)
68 raise PLCAuthenticationFailure, "GPG export failed with return code %d: %s" % (rc, err)
72 def gpg_sign(args, secret_keyring, keyring, methodname = None, methodresponse = False, detach_sign = True):
74 Signs the specified method call (methodname != None) or response
75 (methodresponse == True) using the specified GPG keyring files. If
76 args is not a tuple representing the arguments to the method call
77 or the method response value, then it should be a string
78 representing a generic message to sign (detach_sign == True) or
79 sign/encrypt (detach_sign == False) specified). Returns the
80 detached signature (detach_sign == True) or signed/encrypted
81 message (detach_sign == False).
84 # Accept either an opaque string blob or a Python tuple
85 if isinstance(args, StringTypes):
87 elif isinstance(args, tuple):
88 message = canonicalize(args, methodname, methodresponse)
90 # Use temporary trustdb
93 cmd = ["gpg", "--batch", "--no-tty",
95 "--no-default-keyring",
96 "--secret-keyring", secret_keyring,
101 cmd.append("--detach-sign")
105 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
106 p.stdin.write(message)
108 signature = p.stdout.read()
109 err = p.stderr.read()
113 shutil.rmtree(homedir)
116 raise PLCAuthenticationFailure, "GPG signing failed with return code %d: %s" % (rc, err)
120 def gpg_verify(args, key, signature = None, methodname = None, methodresponse = False):
122 Verifies the signature of the specified method call (methodname !=
123 None) or response (methodresponse = True) using the specified
124 public key material. If args is not a tuple representing the
125 arguments to the method call or the method response value, then it
126 should be a string representing a generic message to verify (if
127 signature is specified) or verify/decrypt (if signature is not
131 # Accept either an opaque string blob or a Python tuple
132 if isinstance(args, StringTypes):
135 message = canonicalize(args, methodname, methodresponse)
137 # Write public key to temporary file
138 if os.path.exists(key):
142 keyfile = NamedTemporaryFile(suffix = '.pub')
145 keyfilename = keyfile.name
147 # Import public key into temporary keyring
149 call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfilename],
150 stdin = PIPE, stdout = PIPE, stderr = PIPE)
152 cmd = ["gpg", "--batch", "--no-tty",
153 "--homedir", homedir]
155 if signature is not None:
156 # Write detached signature to temporary file
157 sigfile = NamedTemporaryFile()
158 sigfile.write(signature)
160 cmd += ["--verify", sigfile.name, "-"]
164 cmd.append("--decrypt")
166 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
167 p.stdin.write(message)
169 if signature is None:
170 message = p.stdout.read()
171 err = p.stderr.read()
175 shutil.rmtree(homedir)
182 raise PLCAuthenticationFailure, "GPG verification failed with return code %d: %s" % (rc, err)