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
|
# Distribute and use freely; there are no restrictions on further
# dissemination and usage except those imposed by the laws of your
# country of residence. This software is provided "as is" without
# warranty of fitness for use or suitability for any purpose, express
# or implied. Use at your own risk or not at all.
"""Verify a DSA signature, for use with PyPI mirrors.
Verification should use the following steps:
1. Download the DSA key from http://pypi.python.org/serverkey, as key_string
2. key = load_key(key_string)
3. Download the package page, from <mirror>/simple/<package>/, as data
4. Download the package signature, from <mirror>/serversig/<package>, as sig
5. Check verify(key, data, sig)
"""
try:
from M2Crypto import EVP, DSA, BIO
def load_key(string):
"""load_key(string) -> key
Convert a PEM format public DSA key into
an internal representation."""
return DSA.load_pub_key_bio(BIO.MemoryBuffer(string))
def verify(key, data, signature):
"""verify(key, data, sig) -> bool
Verify autenticity of the signature created by key for
data. data is the bytes that got signed; signature is the
bytes that represent the signature, using the sha1+DSA
algorithm. key is an internal representation of the DSA key
as returned from load_key."""
md = EVP.MessageDigest('sha1')
md.update(data)
digest = md.final()
return key.verify_asn1(digest, sig)
except ImportError:
# DSA signature algorithm, taken from pycrypto 2.0.1
# The license terms are the same as the ones for this module.
def _inverse(u, v):
"""_inverse(u:long, u:long):long
Return the inverse of u mod v.
"""
u3, v3 = long(u), long(v)
u1, v1 = 1L, 0L
while v3 > 0:
q=u3 / v3
u1, v1 = v1, u1 - v1*q
u3, v3 = v3, u3 - v3*q
while u1<0:
u1 = u1 + v
return u1
def _verify(key, M, sig):
p, q, g, y = key
r, s = sig
if r<=0 or r>=q or s<=0 or s>=q:
return False
w=_inverse(s, q)
u1, u2 = (M*w) % q, (r*w) % q
v1 = pow(g, u1, p)
v2 = pow(y, u2, p)
v = ((v1*v2) % p)
v = v % q
return v==r
# END OF pycrypto
def _bytes2int(b):
value = 0
for c in b:
value = value*256 + ord(c)
return value
_SEQUENCE = 0x30 # cons
_INTEGER = 2 # prim
_BITSTRING = 3 # prim
_OID = 6 # prim
def _asn1parse(string):
#import pdb; pdb.set_trace()
tag = ord(string[0])
assert tag & 31 != 31 # only support one-byte tags
length = ord(string[1])
assert length != 128 # indefinite length not supported
pos = 2
if length > 128:
# multi-byte length
val = 0
length -= 128
val = _bytes2int(string[pos:pos+length])
pos += length
length = val
data = string[pos:pos+length]
rest = string[pos+length:]
assert pos+length <= len(string)
if tag == _SEQUENCE:
result = []
while data:
value, data = _asn1parse(data)
result.append(value)
elif tag == _INTEGER:
assert ord(data[0]) < 128 # negative numbers not supported
result = 0
for c in data:
result = result*256 + ord(c)
elif tag == _BITSTRING:
result = data
elif tag == _OID:
result = data
else:
raise ValueError, "Unsupported tag %x" % tag
return (tag, result), rest
def load_key(string):
"""load_key(string) -> key
Convert a PEM format public DSA key into
an internal representation."""
import base64
lines = [line.strip() for line in string.splitlines()]
assert lines[0] == "-----BEGIN PUBLIC KEY-----"
assert lines[-1] == "-----END PUBLIC KEY-----"
data = base64.decodestring(''.join(lines[1:-1]))
spki, rest = _asn1parse(data)
assert not rest
# SubjectPublicKeyInfo ::= SEQUENCE {
# algorithm AlgorithmIdentifier,
# subjectPublicKey BIT STRING }
assert spki[0] == _SEQUENCE
algoid, key = spki[1]
assert key[0] == _BITSTRING
key = key[1]
# AlgorithmIdentifier ::= SEQUENCE {
# algorithm OBJECT IDENTIFIER,
# parameters ANY DEFINED BY algorithm OPTIONAL }
assert algoid[0] == _SEQUENCE
algorithm, parameters = algoid[1]
assert algorithm[0] == _OID and algorithm[1] == '*\x86H\xce8\x04\x01' # dsaEncryption
# Dss-Parms ::= SEQUENCE {
# p INTEGER,
# q INTEGER,
# g INTEGER }
assert parameters[0] == _SEQUENCE
p, q, g = parameters[1]
assert p[0] == q[0] == g[0] == _INTEGER
p, q, g = p[1], q[1], g[1]
# Parse bit string value as integer
assert key[0] == '\0' # number of bits multiple of 8
y, rest = _asn1parse(key[1:])
assert not rest
assert y[0] == _INTEGER
y = y[1]
return p,q,g,y
def verify(key, data, sig):
"""verify(key, data, sig) -> bool
Verify autenticity of the signature created by key for
data. data is the bytes that got signed; signature is the
bytes that represent the signature, using the sha1+DSA
algorithm. key is an internal representation of the DSA key
as returned from load_key."""
import sha
data = sha.new(data).digest()
data = _bytes2int(data)
# Dss-Sig-Value ::= SEQUENCE {
# r INTEGER,
# s INTEGER }
sig, rest = _asn1parse(sig)
assert not rest
assert sig[0] == _SEQUENCE
r, s = sig[1]
assert r[0] == s[0] == _INTEGER
sig = r[1], s[1]
return _verify(key, data, sig)
if __name__=='__main__':
# usage: verify serverkey pypi-url package
import sys, urllib
key = open(sys.argv[1], 'rb').read()
url = sys.argv[2]
package = sys.argv[3]
data = urllib.urlopen(url + "/simple/" + package).read()
sig = urllib.urlopen(url + "/serversig/" + package).read()
k = load_key(key)
if verify(k, data, sig):
print "ok"
else:
print "fail"
|