1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 __all__ = [
24 'DigestTooLargeError',
25 'HASH_ALGORITHMS',
26 'parse_pem_private_key',
27 'parse_private_key',
28 'parse_public_key',
29 'RSASSA_PKCS1_v1_5_sign',
30 'RSASSA_PKCS1_v1_5_verify',
31 'UnparsableKeyError',
32 ]
33
34 import base64
35 import hashlib
36 import re
37
38 from dkim.asn1 import (
39 ASN1FormatError,
40 asn1_build,
41 asn1_parse,
42 BIT_STRING,
43 INTEGER,
44 SEQUENCE,
45 OBJECT_IDENTIFIER,
46 OCTET_STRING,
47 NULL,
48 )
49
50
51 ASN1_Object = [
52 (SEQUENCE, [
53 (SEQUENCE, [
54 (OBJECT_IDENTIFIER,),
55 (NULL,),
56 ]),
57 (BIT_STRING,),
58 ])
59 ]
60
61 ASN1_RSAPublicKey = [
62 (SEQUENCE, [
63 (INTEGER,),
64 (INTEGER,),
65 ])
66 ]
67
68 ASN1_RSAPrivateKey = [
69 (SEQUENCE, [
70 (INTEGER,),
71 (INTEGER,),
72 (INTEGER,),
73 (INTEGER,),
74 (INTEGER,),
75 (INTEGER,),
76 (INTEGER,),
77 (INTEGER,),
78 (INTEGER,),
79 ])
80 ]
81
82 HASH_ALGORITHMS = {
83 b'rsa-sha1': hashlib.sha1,
84 b'rsa-sha256': hashlib.sha256,
85 b'ed25519-sha256': hashlib.sha256
86 }
87
88
89 HASH_ID_MAP = {
90 'sha1': b"\x2b\x0e\x03\x02\x1a",
91 'sha256': b"\x60\x86\x48\x01\x65\x03\x04\x02\x01",
92 }
93
94
98
99
101 """The data could not be parsed as a key."""
102 pass
103
104
106 """Parse an RSA public key.
107
108 @param data: DER-encoded X.509 subjectPublicKeyInfo
109 containing an RFC8017 RSAPublicKey.
110 @return: RSA public key
111 """
112 try:
113
114 x = asn1_parse(ASN1_Object, data)
115 pkd = asn1_parse(ASN1_RSAPublicKey, x[0][1][1:])
116 except ASN1FormatError as e:
117 raise UnparsableKeyError('Unparsable public key: ' + str(e))
118 pk = {
119 'modulus': pkd[0][0],
120 'publicExponent': pkd[0][1],
121 }
122 return pk
123
124
126 """Parse an RSA private key.
127
128 @param data: DER-encoded RFC8017 RSAPrivateKey.
129 @return: RSA private key
130 """
131 try:
132 pka = asn1_parse(ASN1_RSAPrivateKey, data)
133 except ASN1FormatError as e:
134 raise UnparsableKeyError('Unparsable private key: ' + str(e))
135 pk = {
136 'version': pka[0][0],
137 'modulus': pka[0][1],
138 'publicExponent': pka[0][2],
139 'privateExponent': pka[0][3],
140 'prime1': pka[0][4],
141 'prime2': pka[0][5],
142 'exponent1': pka[0][6],
143 'exponent2': pka[0][7],
144 'coefficient': pka[0][8],
145 }
146 return pk
147
148
150 """Parse a PEM RSA private key.
151
152 @param data: RFC8017 RSAPrivateKey in PEM format.
153 @return: RSA private key
154 """
155 m = re.search(b"--\n(.*?)\n--", data, re.DOTALL)
156 if m is None:
157 raise UnparsableKeyError("Private key not found")
158 try:
159 pkdata = base64.b64decode(m.group(1))
160 except TypeError as e:
161 raise UnparsableKeyError(str(e))
162 return parse_private_key(pkdata)
163
164
166 """Encode a digest with RFC8017 EMSA-PKCS1-v1_5.
167
168 @param hash: hash object to encode
169 @param mlen: desired message length
170 @return: encoded digest byte string
171 """
172 dinfo = asn1_build(
173 (SEQUENCE, [
174 (SEQUENCE, [
175 (OBJECT_IDENTIFIER, HASH_ID_MAP[hash.name.lower()]),
176 (NULL, None),
177 ]),
178 (OCTET_STRING, hash.digest()),
179 ]))
180 if len(dinfo) + 11 > mlen:
181 raise DigestTooLargeError()
182 return b"\x00\x01"+b"\xff"*(mlen-len(dinfo)-3)+b"\x00"+dinfo
183
184
186 """Convert a byte string to an integer.
187
188 @param s: byte string representing a positive integer to convert
189 @return: converted integer
190 """
191 s = bytearray(s)
192 r = 0
193 for c in s:
194 r = (r << 8) | c
195 return r
196
197
199 """Convert an integer to a byte string.
200
201 @param n: positive integer to convert
202 @param length: minimum length
203 @return: converted bytestring, of at least the minimum length if it was
204 specified
205 """
206 assert n >= 0
207 r = bytearray()
208 while length < 0 or len(r) < length:
209 r.append(n & 0xff)
210 n >>= 8
211 if length < 0 and n == 0:
212 break
213 r.reverse()
214 assert length < 0 or len(r) == length
215 return r
216
217
219 """Perform RSA decryption/signing
220
221 @param message: byte string to operate on
222 @param pk: private key data
223 @param mlen: desired output length
224 @return: byte string result of the operation
225 """
226 c = str2int(message)
227
228 m1 = pow(c, pk['exponent1'], pk['prime1'])
229 m2 = pow(c, pk['exponent2'], pk['prime2'])
230
231 if m1 < m2:
232 h = pk['coefficient'] * (m1 + pk['prime1'] - m2) % pk['prime1']
233 else:
234 h = pk['coefficient'] * (m1 - m2) % pk['prime1']
235
236 return int2str(m2 + h * pk['prime2'], mlen)
237
238
240 """Perform RSA encryption/verification
241
242 @param message: byte string to operate on
243 @param pk: public key data
244 @param mlen: desired output length
245 @return: byte string result of the operation
246 """
247 m = str2int(message)
248 return int2str(pow(m, pk['publicExponent'], pk['modulus']), mlen)
249
250
252 """Sign a digest with RFC8017 RSASSA-PKCS1-v1_5.
253
254 @param hash: hash object to sign
255 @param private_key: private key data
256 @return: signed digest byte string
257 """
258 modlen = len(int2str(private_key['modulus']))
259 encoded_digest = EMSA_PKCS1_v1_5_encode(hash, modlen)
260 return rsa_decrypt(encoded_digest, private_key, modlen)
261
262
264 """Verify a digest signed with RFC8017 RSASSA-PKCS1-v1_5.
265
266 @param hash: hash object to check
267 @param signature: signed digest byte string
268 @param public_key: public key data
269 @return: True if the signature is valid, False otherwise
270 """
271 modlen = len(int2str(public_key['modulus']))
272 encoded_digest = EMSA_PKCS1_v1_5_encode(hash, modlen)
273 signed_digest = rsa_encrypt(signature, public_key, modlen)
274 return encoded_digest == signed_digest
275