summaryrefslogtreecommitdiff
path: root/tools/verify.py
blob: 3355354550e9c9379fb076d6a832282fc835aaf7 (plain)
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"