/
signing.py
103 lines (95 loc) · 3.09 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
from zeroinstall import SafeException, support
from zeroinstall.injector import gpg
import tempfile, os, base64, sys, shutil
import subprocess
from logging import warn
def check_signature(path):
with open(path, 'rb') as stream:
data = stream.read()
xml_comment = data.rfind(b'\n<!-- Base64 Signature')
if xml_comment >= 0:
with open(path, 'rb') as stream:
data_stream, sigs = gpg.check_stream(stream)
sign_fn = sign_xml
data = data[:xml_comment + 1]
data_stream.close()
elif data.startswith(b'-----BEGIN'):
warn("Plain GPG signatures are no longer supported - not checking signature!")
warn("Will save in XML format.")
child = subprocess.Popen(['gpg', '--decrypt', path], stdout = subprocess.PIPE)
data, unused = child.communicate()
import __main__
__main__.force_save = True
return data, sign_xml, None
else:
return data, sign_unsigned, None
for sig in sigs:
if isinstance(sig, gpg.ValidSig):
return data, sign_fn, sig.fingerprint
print("ERROR: No valid signatures found!")
for sig in sigs:
print("Got:", sig)
ok = input('Ignore and load anyway? (y/N) ').lower()
if ok and 'yes'.startswith(ok):
import __main__
__main__.force_save = True
return data, sign_unsigned, None
sys.exit(1)
def write_tmp(path, data):
"""Create a temporary file in the same directory as 'path' and write data to it."""
tmpdir = os.path.dirname(path)
if tmpdir:
assert os.path.isdir(tmpdir), "Not a directory: " + tmpdir
fd, tmp = tempfile.mkstemp(prefix = 'tmp-', dir = tmpdir)
stream = os.fdopen(fd, 'wb')
stream.write(data)
stream.close()
umask = os.umask(0)
os.umask(umask)
os.chmod(tmp, 0o644 & ~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, '--use-agent')
arguments.insert(0, 'gpg')
import subprocess
if subprocess.call(arguments):
raise SafeException("Command '%s' failed" % arguments)
def sign_unsigned(path, data, key):
support.portable_rename(write_tmp(path, data), path)
def sign_xml(path, data, key):
tmp = write_tmp(path, data)
sigtmp = tmp + '.sig'
try:
run_gpg(key, '--detach-sign', '--output', sigtmp, tmp)
finally:
os.unlink(tmp)
with open(sigtmp, 'rb') as stream:
encoded = base64.encodestring(stream.read())
os.unlink(sigtmp)
sig = b"<!-- Base64 Signature\n" + encoded + b"\n-->\n"
support.portable_rename(write_tmp(path, data + sig), path)
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
with open(key_file, 'wb') as key_stream:
stream = os.popen("gpg -a --export '%s'" % fingerprint, mode = 'rb')
shutil.copyfileobj(stream, key_stream)
stream.close()
print("Exported public key as '%s'" % key_file)