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 subprocess import Popen, PIPE, call
17 from tempfile import NamedTemporaryFile, mkdtemp
18 from lxml import etree
20 from PLC.Faults import *
22 def canonicalize(args, methodname = None, methodresponse = False):
24 Returns a canonicalized XML-RPC representation of the specified
25 method call (methodname != None) or response (methodresponse =
29 xml = xmlrpclib.dumps(args, methodname, methodresponse, encoding = 'utf-8', allow_none = 1)
30 dom = etree.fromstring(xml)
31 canonical=etree.tostring(dom)
32 # pre-f20 version was using Canonicalize from PyXML
33 # from xml.dom.ext import Canonicalize
34 # Canonicalize(), though it claims to, does not encode unicode
35 # nodes to UTF-8 properly and throws an exception unless you write
36 # the stream to a file object, so just encode it ourselves.
37 return canonical.encode('utf-8')
39 def gpg_export(keyring, armor = True):
41 Exports the specified public keyring file.
45 args = ["gpg", "--batch", "--no-tty",
47 "--no-default-keyring",
51 args.append("--armor")
53 p = Popen(args, stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
54 export = p.stdout.read()
59 shutil.rmtree(homedir)
62 raise PLCAuthenticationFailure, "GPG export failed with return code %d: %s" % (rc, err)
66 def gpg_sign(args, secret_keyring, keyring, methodname = None, methodresponse = False, detach_sign = True):
68 Signs the specified method call (methodname != None) or response
69 (methodresponse == True) using the specified GPG keyring files. If
70 args is not a tuple representing the arguments to the method call
71 or the method response value, then it should be a string
72 representing a generic message to sign (detach_sign == True) or
73 sign/encrypt (detach_sign == False) specified). Returns the
74 detached signature (detach_sign == True) or signed/encrypted
75 message (detach_sign == False).
78 # Accept either an opaque string blob or a Python tuple
79 if isinstance(args, StringTypes):
81 elif isinstance(args, tuple):
82 message = canonicalize(args, methodname, methodresponse)
84 # Use temporary trustdb
87 cmd = ["gpg", "--batch", "--no-tty",
89 "--no-default-keyring",
90 "--secret-keyring", secret_keyring,
95 cmd.append("--detach-sign")
99 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
100 p.stdin.write(message)
102 signature = p.stdout.read()
103 err = p.stderr.read()
107 shutil.rmtree(homedir)
110 raise PLCAuthenticationFailure, "GPG signing failed with return code %d: %s" % (rc, err)
114 def gpg_verify(args, key, signature = None, methodname = None, methodresponse = False):
116 Verifies the signature of the specified method call (methodname !=
117 None) or response (methodresponse = True) using the specified
118 public key material. If args is not a tuple representing the
119 arguments to the method call or the method response value, then it
120 should be a string representing a generic message to verify (if
121 signature is specified) or verify/decrypt (if signature is not
125 # Accept either an opaque string blob or a Python tuple
126 if isinstance(args, StringTypes):
129 message = canonicalize(args, methodname, methodresponse)
131 # Write public key to temporary file
132 if os.path.exists(key):
136 keyfile = NamedTemporaryFile(suffix = '.pub')
139 keyfilename = keyfile.name
141 # Import public key into temporary keyring
143 call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfilename],
144 stdin = PIPE, stdout = PIPE, stderr = PIPE)
146 cmd = ["gpg", "--batch", "--no-tty",
147 "--homedir", homedir]
149 if signature is not None:
150 # Write detached signature to temporary file
151 sigfile = NamedTemporaryFile()
152 sigfile.write(signature)
154 cmd += ["--verify", sigfile.name, "-"]
158 cmd.append("--decrypt")
160 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
161 p.stdin.write(message)
163 if signature is None:
164 message = p.stdout.read()
165 err = p.stderr.read()
169 shutil.rmtree(homedir)
176 raise PLCAuthenticationFailure, "GPG verification failed with return code %d: %s" % (rc, err)