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
18 from lxml import etree
19 from subprocess import Popen, PIPE, call
20 from tempfile import NamedTemporaryFile, mkdtemp
22 from PLC.Faults import *
24 def canonicalize(args, methodname = None, methodresponse = False):
26 Returns a canonicalized XML-RPC representation of the specified
27 method call (methodname != None) or response (methodresponse =
31 xml = xmlrpclib.dumps(args, methodname, methodresponse, encoding = 'utf-8', allow_none = 1)
32 parser = etree.XMLParser(remove_blank_text=True)
33 tree = etree.parse(StringIO(xml), parser)
34 return etree.tostring(tree.getroot(), method='c14n')
36 def gpg_export(keyring, armor = True):
38 Exports the specified public keyring file.
42 args = ["gpg", "--batch", "--no-tty",
44 "--no-default-keyring",
48 args.append("--armor")
50 p = Popen(args, stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
51 export = p.stdout.read()
56 shutil.rmtree(homedir)
59 raise PLCAuthenticationFailure, "GPG export failed with return code %d: %s" % (rc, err)
63 def gpg_sign(args, secret_keyring, keyring, methodname = None, methodresponse = False, detach_sign = True):
65 Signs the specified method call (methodname != None) or response
66 (methodresponse == True) using the specified GPG keyring files. If
67 args is not a tuple representing the arguments to the method call
68 or the method response value, then it should be a string
69 representing a generic message to sign (detach_sign == True) or
70 sign/encrypt (detach_sign == False) specified). Returns the
71 detached signature (detach_sign == True) or signed/encrypted
72 message (detach_sign == False).
75 # Accept either an opaque string blob or a Python tuple
76 if isinstance(args, StringTypes):
78 elif isinstance(args, tuple):
79 message = canonicalize(args, methodname, methodresponse)
81 # Use temporary trustdb
84 cmd = ["gpg", "--batch", "--no-tty",
86 "--no-default-keyring",
87 "--secret-keyring", secret_keyring,
92 cmd.append("--detach-sign")
96 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
97 p.stdin.write(message)
99 signature = p.stdout.read()
100 err = p.stderr.read()
104 shutil.rmtree(homedir)
107 raise PLCAuthenticationFailure, "GPG signing failed with return code %d: %s" % (rc, err)
111 def gpg_verify(args, key, signature = None, methodname = None, methodresponse = False):
113 Verifies the signature of the specified method call (methodname !=
114 None) or response (methodresponse = True) using the specified
115 public key material. If args is not a tuple representing the
116 arguments to the method call or the method response value, then it
117 should be a string representing a generic message to verify (if
118 signature is specified) or verify/decrypt (if signature is not
122 # Accept either an opaque string blob or a Python tuple
123 if isinstance(args, StringTypes):
126 message = canonicalize(args, methodname, methodresponse)
128 # Write public key to temporary file
129 if os.path.exists(key):
133 keyfile = NamedTemporaryFile(suffix = '.pub')
136 keyfilename = keyfile.name
138 # Import public key into temporary keyring
140 call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfilename],
141 stdin = PIPE, stdout = PIPE, stderr = PIPE)
143 cmd = ["gpg", "--batch", "--no-tty",
144 "--homedir", homedir]
146 if signature is not None:
147 # Write detached signature to temporary file
148 sigfile = NamedTemporaryFile()
149 sigfile.write(signature)
151 cmd += ["--verify", sigfile.name, "-"]
155 cmd.append("--decrypt")
157 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
158 p.stdin.write(message)
160 if signature is None:
161 message = p.stdout.read()
162 err = p.stderr.read()
166 shutil.rmtree(homedir)
173 raise PLCAuthenticationFailure, "GPG verification failed with return code %d: %s" % (rc, err)