/
signing.py
210 lines (185 loc) · 5.68 KB
/
signing.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
from zeroinstall import SafeException
from zeroinstall.injector import gpg
import tempfile, os, base64, sys, shutil, signal
import subprocess
from rox import tasks, g
import gobject
umask = os.umask(0)
os.umask(umask)
class LineBuffer:
def __init__(self):
self.data = ''
def add(self, new):
assert new
self.data += new
def __iter__(self):
while '\n' in self.data:
command, self.data = self.data.split('\n', 1)
yield command
def _io_callback(src, cond, blocker):
blocker.trigger()
return False
# (version in ROX-Lib < 2.0.4 is buggy; missing IO_HUP)
class InputBlocker(tasks.Blocker):
"""Triggers when os.read(stream) would not block."""
_tag = None
_stream = None
def __init__(self, stream):
tasks.Blocker.__init__(self)
self._stream = stream
def add_task(self, task):
tasks.Blocker.add_task(self, task)
if self._tag is None:
self._tag = gobject.io_add_watch(self._stream, gobject.IO_IN | gobject.IO_HUP,
_io_callback, self)
def remove_task(self, task):
tasks.Blocker.remove_task(self, task)
if not self._rox_lib_tasks:
gobject.source_remove(self._tag)
self._tag = None
def get_secret_keys():
child = subprocess.Popen(('gpg', '--list-secret-keys', '--with-colons', '--with-fingerprint'),
stdout = subprocess.PIPE)
stdout, _ = child.communicate()
status = child.wait()
if status:
raise Exception("GPG failed with exit code %d" % status)
# First, collect fingerprints for available secret keys
keys = []
for line in stdout.split('\n'):
line = line.split(':')
if line[0] == 'fpr':
keys.append([line[9], None])
# When listing secret keys, the identity show may not be the primary identity as selected by
# the user or shown when verifying a signature. However, the primary identity can be obtained
# by listing the accompanying public key.
loaded_keys = gpg.load_keys([k[0] for k in keys])
for key in keys:
key[1] = loaded_keys[key[0]].name
return keys
def check_signature(path):
data = file(path).read()
xml_comment = data.rfind('\n<!-- Base64 Signature')
if xml_comment >= 0:
data_stream, sigs = gpg.check_stream(file(path))
sign_fn = sign_xml
data = data[:xml_comment + 1]
data_stream.close()
elif data.startswith('-----BEGIN'):
data_stream, sigs = gpg.check_stream(file(path))
sign_fn = sign_xml # Don't support saving as plain
data = data_stream.read()
else:
return data, sign_unsigned, None
for sig in sigs:
if isinstance(sig, gpg.ValidSig):
return data, sign_fn, sig.fingerprint
error = "ERROR: No valid signatures found!\n"
for sig in sigs:
error += "\nGot: %s" % sig
error += '\n\nTo edit it anyway, remove the signature using a text editor.'
raise Exception(error)
def write_tmp(path, data):
"""Create a temporary file in the same directory as 'path' and write data to it."""
fd, tmp = tempfile.mkstemp(prefix = 'tmp-', dir = os.path.dirname(path))
stream = os.fdopen(fd, 'w')
stream.write(data)
stream.close()
os.chmod(tmp, 0644 &~umask)
return tmp
def run_gpg(default_key, *arguments):
arguments = list(arguments)
if default_key is not None:
arguments = ['--default-key', default_key] + arguments
arguments.insert(0, 'gpg')
if os.spawnvp(os.P_WAIT, 'gpg', arguments):
raise SafeException("Command '%s' failed" % arguments)
def sign_unsigned(path, data, key, callback):
os.rename(write_tmp(path, data), path)
if callback: callback()
def sign_xml(path, data, key, callback):
import main
wTree = g.glade.XML(main.gladefile, 'get_passphrase')
box = wTree.get_widget('get_passphrase')
box.set_default_response(g.RESPONSE_OK)
entry = wTree.get_widget('passphrase')
buffer = LineBuffer()
killed = False
error = False
tmp = None
r, w = os.pipe()
try:
def setup_child():
os.close(r)
tmp = write_tmp(path, data)
sigtmp = tmp + '.sig'
agent_info = os.environ.get("GPG_AGENT_INFO", None)
child = subprocess.Popen(('gpg', '--default-key', key,
'--detach-sign', '--status-fd', str(w),
'--command-fd', '0',
'--no-tty',
'--output', sigtmp,
'--use-agent',
'-q',
tmp),
preexec_fn = setup_child,
stdin = subprocess.PIPE)
os.close(w)
w = None
while True:
input = InputBlocker(r)
yield input
msg = os.read(r, 100)
if not msg: break
buffer.add(msg)
for command in buffer:
if command.startswith('[GNUPG:] NEED_PASSPHRASE ') and not agent_info:
entry.set_text('')
box.present()
resp = box.run()
box.hide()
if resp == g.RESPONSE_OK:
child.stdin.write(entry.get_text() + '\n')
child.stdin.flush()
else:
os.kill(child.pid, signal.SIGTERM)
killed = True
status = child.wait()
if status:
raise Exception("GPG failed with exit code %d" % status)
except:
# No generator finally blocks in Python 2.4...
error = True
if r is not None: os.close(r)
if w is not None: os.close(w)
if tmp is not None: os.unlink(tmp)
if killed: return
if error: raise
encoded = base64.encodestring(file(sigtmp).read())
os.unlink(sigtmp)
sig = "<!-- Base64 Signature\n" + encoded + "\n-->\n"
os.rename(write_tmp(path, data + sig), path)
if callback: callback()
def export_key(dir, fingerprint):
assert fingerprint is not None
# Convert fingerprint to key ID
stream = os.popen('gpg --with-colons --list-keys %s' % fingerprint)
try:
keyID = None
for line in stream:
parts = line.split(':')
if parts[0] == 'pub':
if keyID:
raise Exception('Two key IDs returned from GPG!')
keyID = parts[4]
finally:
stream.close()
key_file = os.path.join(dir, keyID + '.gpg')
if os.path.isfile(key_file):
return None
key_stream = file(key_file, 'w')
stream = os.popen("gpg -a --export '%s'" % fingerprint)
shutil.copyfileobj(stream, key_stream)
stream.close()
key_stream.close()
return key_file