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
14 from types import StringTypes
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
21 from PLC.Faults import *
23 def canonicalize(args, methodname = None, methodresponse = False):
25 Returns a canonicalized XML-RPC representation of the specified
26 method call (methodname != None) or response (methodresponse =
30 xml = xmlrpclib.dumps(args, methodname, methodresponse, encoding = 'utf-8', allow_none = 1)
31 dom = minidom.parseString(xml)
33 # Canonicalize(), though it claims to, does not encode unicode
34 # nodes to UTF-8 properly and throws an exception unless you write
35 # the stream to a file object, so just encode it ourselves.
37 Canonicalize(dom, output = buf)
38 xml = buf.getvalue().encode('utf-8')
42 def gpg_export(keyring, armor = True):
44 Exports the specified public keyring file.
48 args = ["gpg", "--batch", "--no-tty",
50 "--no-default-keyring",
54 args.append("--armor")
56 p = Popen(args, stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
57 export = p.stdout.read()
62 shutil.rmtree(homedir)
65 raise PLCAuthenticationFailure, "GPG export failed with return code %d: %s" % (rc, err)
69 def gpg_sign(args, secret_keyring, keyring, methodname = None, methodresponse = False, detach_sign = True):
71 Signs the specified method call (methodname != None) or response
72 (methodresponse == True) using the specified GPG keyring files. If
73 args is not a tuple representing the arguments to the method call
74 or the method response value, then it should be a string
75 representing a generic message to sign (detach_sign == True) or
76 sign/encrypt (detach_sign == False) specified). Returns the
77 detached signature (detach_sign == True) or signed/encrypted
78 message (detach_sign == False).
81 # Accept either an opaque string blob or a Python tuple
82 if isinstance(args, StringTypes):
84 elif isinstance(args, tuple):
85 message = canonicalize(args, methodname, methodresponse)
87 # Use temporary trustdb
90 cmd = ["gpg", "--batch", "--no-tty",
92 "--no-default-keyring",
93 "--secret-keyring", secret_keyring,
98 cmd.append("--detach-sign")
102 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
103 p.stdin.write(message)
105 signature = p.stdout.read()
106 err = p.stderr.read()
110 shutil.rmtree(homedir)
113 raise PLCAuthenticationFailure, "GPG signing failed with return code %d: %s" % (rc, err)
117 def gpg_verify(args, key, signature = None, methodname = None, methodresponse = False):
119 Verifies the signature of the specified method call (methodname !=
120 None) or response (methodresponse = True) using the specified
121 public key material. If args is not a tuple representing the
122 arguments to the method call or the method response value, then it
123 should be a string representing a generic message to verify (if
124 signature is specified) or verify/decrypt (if signature is not
128 # Accept either an opaque string blob or a Python tuple
129 if isinstance(args, StringTypes):
132 message = canonicalize(args, methodname, methodresponse)
134 # Write public key to temporary file
135 if os.path.exists(key):
139 keyfile = NamedTemporaryFile(suffix = '.pub')
142 keyfilename = keyfile.name
144 # Import public key into temporary keyring
146 call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfilename],
147 stdin = PIPE, stdout = PIPE, stderr = PIPE)
149 cmd = ["gpg", "--batch", "--no-tty",
150 "--homedir", homedir]
152 if signature is not None:
153 # Write detached signature to temporary file
154 sigfile = NamedTemporaryFile()
155 sigfile.write(signature)
157 cmd += ["--verify", sigfile.name, "-"]
161 cmd.append("--decrypt")
163 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
164 p.stdin.write(message)
166 if signature is None:
167 message = p.stdout.read()
168 err = p.stderr.read()
172 shutil.rmtree(homedir)
179 raise PLCAuthenticationFailure, "GPG verification failed with return code %d: %s" % (rc, err)