Merge branch 'maint-0.4.6'
[tor.git] / scripts / codegen / makedesc.py
blob5c59a52af1fe43450da60cfed41435e0ce6aea72
1 #!/usr/bin/env python
2 # Copyright 2014-2019, The Tor Project, Inc.
3 # See LICENSE for license information
5 # This is a kludgey python script that uses ctypes and openssl to sign
6 # router descriptors and extrainfo documents and put all the keys in
7 # the right places. There are examples at the end of the file.
9 # I've used this to make inputs for unit tests. I wouldn't suggest
10 # using it for anything else.
12 # Future imports for Python 2.7, mandatory in 3.0
13 from __future__ import division
14 from __future__ import print_function
15 from __future__ import unicode_literals
17 import base64
18 import binascii
19 import ctypes
20 import ctypes.util
21 import hashlib
22 import optparse
23 import os
24 import re
25 import struct
26 import time
28 import slow_ed25519
29 import slownacl_curve25519
30 import ed25519_exts_ref
32 try:
33 xrange # Python 2
34 except NameError:
35 xrange = range # Python 3
37 # Pull in the openssl stuff we need.
39 crypt = ctypes.CDLL(ctypes.util.find_library('crypto'))
40 BIO_s_mem = crypt.BIO_s_mem
41 BIO_s_mem.argtypes = []
42 BIO_s_mem.restype = ctypes.c_void_p
44 BIO_new = crypt.BIO_new
45 BIO_new.argtypes = [ctypes.c_void_p]
46 BIO_new.restype = ctypes.c_void_p
48 crypt.BIO_free.argtypes = [ctypes.c_void_p]
49 crypt.BIO_free.restype = ctypes.c_int
51 crypt.BIO_ctrl.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_long, ctypes.c_void_p ]
52 crypt.BIO_ctrl.restype = ctypes.c_long
54 crypt.PEM_write_bio_RSAPublicKey.argtypes = [ ctypes.c_void_p, ctypes.c_void_p ]
55 crypt.PEM_write_bio_RSAPublicKey.restype = ctypes.c_int
57 RSA_generate_key = crypt.RSA_generate_key
58 RSA_generate_key.argtypes = [ctypes.c_int, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_void_p]
59 RSA_generate_key.restype = ctypes.c_void_p
61 RSA_private_encrypt = crypt.RSA_private_encrypt
62 RSA_private_encrypt.argtypes = [
63 ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int ]
64 RSA_private_encrypt.restype = ctypes.c_int
66 i2d_RSAPublicKey = crypt.i2d_RSAPublicKey
67 i2d_RSAPublicKey.argtypes = [
68 ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p)
70 i2d_RSAPublicKey.restype = ctypes.c_int
73 HEADER = """\
74 router fred 127.0.0.1 9001 0 9002
75 identity-ed25519
76 {d.ED_CERT}
77 signing-key
78 {d.RSA_IDENTITY}
79 master-key-ed25519 {d.ED_IDENTITY}
80 onion-key
81 {d.RSA_ONION_KEY}
82 ntor-onion-key {d.NTOR_ONION_KEY}
83 ntor-onion-key-crosscert {d.NTOR_CROSSCERT_SIGN}
84 {d.NTOR_CROSSCERT}
85 onion-key-crosscert
86 {d.RSA_CROSSCERT_ED}
87 """
89 FOOTER="""
91 """
93 def rsa_sign(msg, rsa):
94 buf = ctypes.create_string_buffer(2048)
95 n = RSA_private_encrypt(len(msg), msg, buf, rsa, 1)
96 if n <= 0:
97 raise Exception()
98 return buf.raw[:n]
100 def b64(x1):
101 x = binascii.b2a_base64(x1)
102 res = []
103 for i in xrange(0, len(x), 64):
104 res.append((x[i:i+64]).decode("ascii"))
105 return "\n".join(res)
107 def bio_extract(bio):
108 buf = ctypes.c_char_p()
109 length = crypt.BIO_ctrl(bio, 3, 0, ctypes.byref(buf))
110 return ctypes.string_at(buf, length)
112 def make_rsa_key(e=65537):
113 rsa = crypt.RSA_generate_key(1024, e, None, None)
114 bio = BIO_new(BIO_s_mem())
115 crypt.PEM_write_bio_RSAPublicKey(bio, rsa)
116 pem = bio_extract(bio).rstrip()
117 crypt.BIO_free(bio)
118 buf = ctypes.create_string_buffer(1024)
119 pBuf = ctypes.c_char_p(ctypes.addressof(buf))
120 n = crypt.i2d_RSAPublicKey(rsa, ctypes.byref(pBuf))
121 s = buf.raw[:n]
122 digest = hashlib.sha1(s).digest()
123 pem = pem.decode("ascii")
124 return (rsa,pem,digest)
126 def makeEdSigningKeyCert(sk_master, pk_master, pk_signing, date,
127 includeSigning=False, certType=1):
128 assert len(pk_signing) == len(pk_master) == 32
129 expiration = struct.pack(b"!L", date//3600)
130 if includeSigning:
131 extensions = b"\x01\x00\x20\x04\x00%s"%(pk_master)
132 else:
133 extensions = b"\x00"
134 signed = b"\x01%s%s\x01%s%s" % (
135 bytes([certType]), expiration, pk_signing, extensions)
136 signature = ed25519_exts_ref.signatureWithESK(signed, sk_master, pk_master)
137 assert len(signature) == 64
138 return signed+signature
140 def objwrap(identifier, body):
141 return ("-----BEGIN {0}-----\n"
142 "{1}"
143 "-----END {0}-----").format(identifier, body)
145 MAGIC1 = "<<<<<<MAGIC>>>>>>"
146 MAGIC2 = "<<<<<!#!#!#XYZZY#!#!#!>>>>>"
148 class OnDemandKeys(object):
149 def __init__(self, certDate=None):
150 if certDate is None:
151 certDate = int(time.time()) + 86400
152 self.certDate = certDate
153 self.rsa_id = None
154 self.rsa_onion_key = None
155 self.ed_id_sk = None
156 self.ntor_sk = None
157 self.ntor_crosscert = None
158 self.rsa_crosscert_ed = None
159 self.rsa_crosscert_noed = None
161 @property
162 def RSA_IDENTITY(self):
163 if self.rsa_id is None:
164 self.rsa_id, self.rsa_ident_pem, self.rsa_id_digest = make_rsa_key()
166 return self.rsa_ident_pem
168 @property
169 def RSA_ID_DIGEST(self):
170 self.RSA_IDENTITY
171 return self.rsa_id_digest
173 @property
174 def RSA_FINGERPRINT_NOSPACE(self):
175 return binascii.b2a_hex(self.RSA_ID_DIGEST).upper().decode("ascii")
177 @property
178 def RSA_ONION_KEY(self):
179 if self.rsa_onion_key is None:
180 self.rsa_onion_key, self.rsa_onion_pem, _ = make_rsa_key()
182 return self.rsa_onion_pem
184 @property
185 def RSA_FINGERPRINT(self):
186 hexdigest = self.RSA_FINGERPRINT_NOSPACE
187 return " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4))
189 @property
190 def RSA_SIGNATURE(self):
191 return MAGIC1
193 @property
194 def ED_SIGNATURE(self):
195 return MAGIC2
197 @property
198 def NTOR_ONION_KEY(self):
199 if self.ntor_sk is None:
200 self.ntor_sk = slownacl_curve25519.Private()
201 self.ntor_pk = self.ntor_sk.get_public()
202 return base64.b64encode(self.ntor_pk.serialize()).decode("ascii")
204 @property
205 def ED_CERT(self):
206 if self.ed_id_sk is None:
207 self.ed_id_sk = ed25519_exts_ref.expandSK(os.urandom(32))
208 self.ed_signing_sk = ed25519_exts_ref.expandSK(os.urandom(32))
209 self.ed_id_pk = ed25519_exts_ref.publickeyFromESK(self.ed_id_sk)
210 self.ed_signing_pk = ed25519_exts_ref.publickeyFromESK(self.ed_signing_sk)
211 self.ed_cert = makeEdSigningKeyCert(self.ed_id_sk, self.ed_id_pk, self.ed_signing_pk, self.certDate, includeSigning=True, certType=4)
213 return objwrap('ED25519 CERT', b64(self.ed_cert))
215 @property
216 def ED_IDENTITY(self):
217 self.ED_CERT
218 return binascii.b2a_base64(self.ed_id_pk).strip().decode("ascii")
220 @property
221 def NTOR_CROSSCERT(self):
222 if self.ntor_crosscert is None:
223 self.ED_CERT
224 self.NTOR_ONION_KEY
226 ed_privkey = self.ntor_sk.serialize() + os.urandom(32)
227 ed_pub0 = ed25519_exts_ref.publickeyFromESK(ed_privkey)
228 sign = ((ed_pub0[31]) & 255) >> 7
230 self.ntor_crosscert = makeEdSigningKeyCert(self.ntor_sk.serialize() + os.urandom(32), ed_pub0, self.ed_id_pk, self.certDate, certType=10)
231 self.ntor_crosscert_sign = sign
233 return objwrap('ED25519 CERT', b64(self.ntor_crosscert))
235 @property
236 def NTOR_CROSSCERT_SIGN(self):
237 self.NTOR_CROSSCERT
238 return self.ntor_crosscert_sign
240 @property
241 def RSA_CROSSCERT_NOED(self):
242 if self.rsa_crosscert_noed is None:
243 self.RSA_ONION_KEY
244 signed = self.RSA_ID_DIGEST
245 self.rsa_crosscert_noed = rsa_sign(signed, self.rsa_onion_key)
246 return objwrap("CROSSCERT",b64(self.rsa_crosscert_noed))
248 @property
249 def RSA_CROSSCERT_ED(self):
250 if self.rsa_crosscert_ed is None:
251 self.RSA_ONION_KEY
252 self.ED_CERT
253 signed = self.RSA_ID_DIGEST + self.ed_id_pk
254 self.rsa_crosscert_ed = rsa_sign(signed, self.rsa_onion_key)
255 return objwrap("CROSSCERT",b64(self.rsa_crosscert_ed))
257 def sign_desc(self, body):
258 idx = body.rfind("\nrouter-sig-ed25519 ")
259 if idx >= 0:
260 self.ED_CERT
261 signed_part = body[:idx+len("\nrouter-sig-ed25519 ")]
262 signed_part = "Tor router descriptor signature v1" + signed_part
263 digest = hashlib.sha256(signed_part.encode("utf-8")).digest()
264 ed_sig = ed25519_exts_ref.signatureWithESK(digest,
265 self.ed_signing_sk, self.ed_signing_pk)
267 body = body.replace(MAGIC2, base64.b64encode(ed_sig).decode("ascii").replace("=",""))
269 self.RSA_IDENTITY
270 idx = body.rindex("\nrouter-signature")
271 end_of_sig = body.index("\n", idx+1)
273 signed_part = body[:end_of_sig+1]
275 digest = hashlib.sha1(signed_part.encode("utf-8")).digest()
276 assert len(digest) == 20
278 rsasig = rsa_sign(digest, self.rsa_id)
280 body = body.replace(MAGIC1, objwrap("SIGNATURE", b64(rsasig)))
282 return body
285 def signdesc(body, args_out=None):
286 rsa, ident_pem, id_digest = make_rsa_key()
287 _, onion_pem, _ = make_rsa_key()
289 need_ed = '{ED25519-CERT}' in body or '{ED25519-SIGNATURE}' in body
290 if need_ed:
291 sk_master = os.urandom(32)
292 sk_signing = os.urandom(32)
293 pk_master = slow_ed25519.pubkey(sk_master)
294 pk_signing = slow_ed25519.pubkey(sk_signing)
296 hexdigest = binascii.b2a_hex(id_digest).upper()
297 fingerprint = " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4))
299 MAGIC = "<<<<<<MAGIC>>>>>>"
300 MORE_MAGIC = "<<<<<!#!#!#XYZZY#!#!#!>>>>>"
301 args = {
302 "RSA-IDENTITY" : ident_pem,
303 "ONION-KEY" : onion_pem,
304 "FINGERPRINT" : fingerprint,
305 "FINGERPRINT-NOSPACE" : hexdigest,
306 "RSA-SIGNATURE" : MAGIC
308 if need_ed:
309 args['ED25519-CERT'] = makeEdSigningKeyCert(
310 sk_master, pk_master, pk_signing)
311 args['ED25519-SIGNATURE'] = MORE_MAGIC
313 if args_out:
314 args_out.update(args)
315 body = body.format(**args)
317 idx = body.rindex("\nrouter-signature")
318 end_of_sig = body.index("\n", idx+1)
320 signed_part = body[:end_of_sig+1]
322 digest = hashlib.sha1(signed_part).digest()
323 assert len(digest) == 20
325 buf = ctypes.create_string_buffer(1024)
326 n = RSA_private_encrypt(20, digest, buf, rsa, 1)
327 sig = buf.raw[:n]
329 sig = """-----BEGIN SIGNATURE-----
331 -----END SIGNATURE-----""" % b64(sig).rstrip()
332 body = body.replace(MAGIC, sig)
334 return body.rstrip()
336 def print_c_string(ident, body):
337 print("static const char %s[] =" % ident)
338 for line in body.split("\n"):
339 print(' "%s\\n"' %(line))
340 print(" ;")
342 def emit_ri(name, body):
343 info = OnDemandKeys()
344 body = body.format(d=info)
345 body = info.sign_desc(body)
346 print_c_string("EX_RI_%s"%name.upper(), body)
348 def emit_ei(name, body, fields):
349 info = OnDemandKeys()
350 body = body.format(d=info)
351 body = info.sign_desc(body)
352 print_c_string("EX_EI_%s"%name.upper(), body)
354 print('ATTR_UNUSED static const char EX_EI_{NAME}_FP[] = "{d.RSA_FINGERPRINT_NOSPACE}";'.format(
355 d=info, NAME=name.upper()))
356 print("ATTR_UNUSED")
357 print_c_string("EX_EI_%s_KEY"%name.upper(), info.RSA_IDENTITY)
359 def analyze(s):
360 while s:
361 fields = {}
362 s_pre = s
363 while s.startswith(":::"):
364 first,s=s.split("\n", 1)
365 m = re.match(r'^:::(\w+)=(.*)',first)
366 if not m:
367 raise ValueError(first)
368 k,v = m.groups()
369 fields[k] = v
370 if "name" not in fields:
371 print(repr(s_pre))
373 idx = s.find(":::")
374 if idx != -1:
375 body = s[:idx].rstrip()
376 s = s[idx:]
377 else:
378 body = s.rstrip()
379 s = ""
381 yield (fields, body)
383 def emit_entry(fields, s):
384 try:
385 name = fields['name']
386 tp = fields['type']
387 except KeyError:
388 raise ValueError("missing required field")
390 if tp == 'ei':
391 emit_ei(name, s, fields)
392 elif tp == 'ri':
393 emit_ri(name, s)
394 else:
395 raise ValueError("unrecognized type")
397 def process_file(s):
398 print("""\
399 /* These entries are automatically generated by makedesc.py to make sure
400 * that their keys and signatures are right except when otherwise
401 * specified. */
402 """)
403 for (fields, s) in analyze(s):
404 emit_entry(fields, s)
406 if __name__ == '__main__':
407 import sys
408 for fn in sys.argv[1:]:
409 process_file(open(fn).read())