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
16 from types import StringTypes
17 from StringIO import StringIO
18 from xml.dom import minidom
19 from xml.dom.ext import Canonicalize
20 from subprocess import Popen, PIPE, call
21 from tempfile import NamedTemporaryFile, mkdtemp
23 from PLC.Faults import *
25 def canonicalize(args, methodname = None, methodresponse = False):
27 Returns a canonicalized XML-RPC representation of the specified
28 method call (methodname != None) or response (methodresponse =
32 xml = xmlrpclib.dumps(args, methodname, methodresponse, encoding = 'utf-8', allow_none = 1)
33 dom = minidom.parseString(xml)
35 # Canonicalize(), though it claims to, does not encode unicode
36 # nodes to UTF-8 properly and throws an exception unless you write
37 # the stream to a file object, so just encode it ourselves.
39 Canonicalize(dom, output = buf)
40 xml = buf.getvalue().encode('utf-8')
44 def gpg_export(keyring, armor = True):
46 Exports the specified public keyring file.
50 args = ["gpg", "--batch", "--no-tty",
52 "--no-default-keyring",
56 args.append("--armor")
58 p = Popen(args, stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
59 export = p.stdout.read()
64 shutil.rmtree(homedir)
67 raise PLCAuthenticationFailure, "GPG export failed with return code %d: %s" % (rc, err)
71 def gpg_sign(args, secret_keyring, keyring, methodname = None, methodresponse = False, detach_sign = True):
73 Signs the specified method call (methodname != None) or response
74 (methodresponse == True) using the specified GPG keyring files. If
75 args is not a tuple representing the arguments to the method call
76 or the method response value, then it should be a string
77 representing a generic message to sign (detach_sign == True) or
78 sign/encrypt (detach_sign == False) specified). Returns the
79 detached signature (detach_sign == True) or signed/encrypted
80 message (detach_sign == False).
83 # Accept either an opaque string blob or a Python tuple
84 if isinstance(args, StringTypes):
86 elif isinstance(args, tuple):
87 message = canonicalize(args, methodname, methodresponse)
89 # Use temporary trustdb
92 cmd = ["gpg", "--batch", "--no-tty",
94 "--no-default-keyring",
95 "--secret-keyring", secret_keyring,
100 cmd.append("--detach-sign")
104 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
105 p.stdin.write(message)
107 signature = p.stdout.read()
108 err = p.stderr.read()
112 shutil.rmtree(homedir)
115 raise PLCAuthenticationFailure, "GPG signing failed with return code %d: %s" % (rc, err)
119 def gpg_verify(args, key, signature = None, methodname = None, methodresponse = False):
121 Verifies the signature of the specified method call (methodname !=
122 None) or response (methodresponse = True) using the specified
123 public key material. If args is not a tuple representing the
124 arguments to the method call or the method response value, then it
125 should be a string representing a generic message to verify (if
126 signature is specified) or verify/decrypt (if signature is not
130 # Accept either an opaque string blob or a Python tuple
131 if isinstance(args, StringTypes):
134 message = canonicalize(args, methodname, methodresponse)
136 # Write public key to temporary file
137 if os.path.exists(key):
141 keyfile = NamedTemporaryFile(suffix = '.pub')
144 keyfilename = keyfile.name
146 # Import public key into temporary keyring
148 call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfilename],
149 stdin = PIPE, stdout = PIPE, stderr = PIPE)
151 cmd = ["gpg", "--batch", "--no-tty",
152 "--homedir", homedir]
154 if signature is not None:
155 # Write detached signature to temporary file
156 sigfile = NamedTemporaryFile()
157 sigfile.write(signature)
159 cmd += ["--verify", sigfile.name, "-"]
163 cmd.append("--decrypt")
165 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
166 p.stdin.write(message)
168 if signature is None:
169 message = p.stdout.read()
170 err = p.stderr.read()
174 shutil.rmtree(homedir)
181 raise PLCAuthenticationFailure, "GPG verification failed with return code %d: %s" % (rc, err)