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 io import StringIO
15 from subprocess import Popen, PIPE, call
16 from tempfile import NamedTemporaryFile, mkdtemp
17 from lxml import etree
19 from PLC.Faults import *
21 def canonicalize(args, methodname = None, methodresponse = False):
23 Returns a canonicalized XML-RPC representation of the specified
24 method call (methodname != None) or response (methodresponse =
28 xml = xmlrpc.client.dumps(args, methodname, methodresponse, encoding = 'utf-8', allow_none = 1)
29 dom = etree.fromstring(xml)
30 canonical = etree.tostring(dom)
31 # pre-f20 version was using Canonicalize from PyXML
32 # from xml.dom.ext import Canonicalize
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.
36 return canonical.encode('utf-8')
38 def gpg_export(keyring, armor = True):
40 Exports the specified public keyring file.
44 args = ["gpg", "--batch", "--no-tty",
46 "--no-default-keyring",
50 args.append("--armor")
52 p = Popen(args, stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
53 export = p.stdout.read()
58 shutil.rmtree(homedir)
61 raise PLCAuthenticationFailure("GPG export failed with return code %d: %s" % (rc, err))
65 def gpg_sign(args, secret_keyring, keyring, methodname = None, methodresponse = False, detach_sign = True):
67 Signs the specified method call (methodname != None) or response
68 (methodresponse == True) using the specified GPG keyring files. If
69 args is not a tuple representing the arguments to the method call
70 or the method response value, then it should be a string
71 representing a generic message to sign (detach_sign == True) or
72 sign/encrypt (detach_sign == False) specified). Returns the
73 detached signature (detach_sign == True) or signed/encrypted
74 message (detach_sign == False).
77 # Accept either an opaque string blob or a Python tuple
78 if isinstance(args, str):
80 elif isinstance(args, tuple):
81 message = canonicalize(args, methodname, methodresponse)
83 # Use temporary trustdb
86 cmd = ["gpg", "--batch", "--no-tty",
88 "--no-default-keyring",
89 "--secret-keyring", secret_keyring,
94 cmd.append("--detach-sign")
98 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
99 p.stdin.write(message)
101 signature = p.stdout.read()
102 err = p.stderr.read()
106 shutil.rmtree(homedir)
109 raise PLCAuthenticationFailure("GPG signing failed with return code %d: %s" % (rc, err))
113 def gpg_verify(args, key, signature = None, methodname = None, methodresponse = False):
115 Verifies the signature of the specified method call (methodname !=
116 None) or response (methodresponse = True) using the specified
117 public key material. If args is not a tuple representing the
118 arguments to the method call or the method response value, then it
119 should be a string representing a generic message to verify (if
120 signature is specified) or verify/decrypt (if signature is not
124 # Accept either an opaque string blob or a Python tuple
125 if isinstance(args, str):
128 message = canonicalize(args, methodname, methodresponse)
130 # Write public key to temporary file
131 if os.path.exists(key):
135 keyfile = NamedTemporaryFile(suffix = '.pub')
138 keyfilename = keyfile.name
140 # Import public key into temporary keyring
142 call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfilename],
143 stdin = PIPE, stdout = PIPE, stderr = PIPE)
145 cmd = ["gpg", "--batch", "--no-tty",
146 "--homedir", homedir]
148 if signature is not None:
149 # Write detached signature to temporary file
150 sigfile = NamedTemporaryFile()
151 sigfile.write(signature)
153 cmd += ["--verify", sigfile.name, "-"]
157 cmd.append("--decrypt")
159 p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
160 p.stdin.write(message)
162 if signature is None:
163 message = p.stdout.read()
164 err = p.stderr.read()
168 shutil.rmtree(homedir)
175 raise PLCAuthenticationFailure("GPG verification failed with return code %d: %s" % (rc, err))