Remove check-tor script
[tor.git] / scripts / codegen / makedesc.py
blobefca4dda9acafe0403d5793e3f793a2d7ad0e429
1 #!/usr/bin/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 import base64
13 import binascii
14 import ctypes
15 import ctypes.util
16 import hashlib
17 import optparse
18 import os
19 import re
20 import struct
21 import time
22 import UserDict
24 import slow_ed25519
25 import slownacl_curve25519
26 import ed25519_exts_ref
28 # Pull in the openssl stuff we need.
30 crypt = ctypes.CDLL(ctypes.util.find_library('crypto'))
31 BIO_s_mem = crypt.BIO_s_mem
32 BIO_s_mem.argtypes = []
33 BIO_s_mem.restype = ctypes.c_void_p
35 BIO_new = crypt.BIO_new
36 BIO_new.argtypes = [ctypes.c_void_p]
37 BIO_new.restype = ctypes.c_void_p
39 crypt.BIO_free.argtypes = [ctypes.c_void_p]
40 crypt.BIO_free.restype = ctypes.c_int
42 crypt.BIO_ctrl.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_long, ctypes.c_void_p ]
43 crypt.BIO_ctrl.restype = ctypes.c_long
45 crypt.PEM_write_bio_RSAPublicKey.argtypes = [ ctypes.c_void_p, ctypes.c_void_p ]
46 crypt.PEM_write_bio_RSAPublicKey.restype = ctypes.c_int
48 RSA_generate_key = crypt.RSA_generate_key
49 RSA_generate_key.argtypes = [ctypes.c_int, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_void_p]
50 RSA_generate_key.restype = ctypes.c_void_p
52 RSA_private_encrypt = crypt.RSA_private_encrypt
53 RSA_private_encrypt.argtypes = [
54 ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int ]
55 RSA_private_encrypt.restype = ctypes.c_int
57 i2d_RSAPublicKey = crypt.i2d_RSAPublicKey
58 i2d_RSAPublicKey.argtypes = [
59 ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p)
61 i2d_RSAPublicKey.restype = ctypes.c_int
64 def rsa_sign(msg, rsa):
65 buf = ctypes.create_string_buffer(1024)
66 n = RSA_private_encrypt(len(msg), msg, buf, rsa, 1)
67 if n <= 0:
68 raise Exception()
69 return buf.raw[:n]
71 def b64(x):
72 x = base64.b64encode(x)
73 res = []
74 for i in xrange(0, len(x), 64):
75 res.append(x[i:i+64]+"\n")
76 return "".join(res)
78 def bio_extract(bio):
79 buf = ctypes.c_char_p()
80 length = crypt.BIO_ctrl(bio, 3, 0, ctypes.byref(buf))
81 return ctypes.string_at(buf, length)
83 def make_rsa_key(e=65537):
84 rsa = crypt.RSA_generate_key(1024, e, None, None)
85 bio = BIO_new(BIO_s_mem())
86 crypt.PEM_write_bio_RSAPublicKey(bio, rsa)
87 pem = bio_extract(bio).rstrip()
88 crypt.BIO_free(bio)
89 buf = ctypes.create_string_buffer(1024)
90 pBuf = ctypes.c_char_p(ctypes.addressof(buf))
91 n = crypt.i2d_RSAPublicKey(rsa, ctypes.byref(pBuf))
92 s = buf.raw[:n]
93 digest = hashlib.sha1(s).digest()
94 return (rsa,pem,digest)
96 def makeEdSigningKeyCert(sk_master, pk_master, pk_signing, date,
97 includeSigning=False, certType=1):
98 assert len(pk_signing) == len(pk_master) == 32
99 expiration = struct.pack("!L", date//3600)
100 if includeSigning:
101 extensions = "\x01\x00\x20\x04\x00%s"%(pk_master)
102 else:
103 extensions = "\x00"
104 signed = "\x01%s%s\x01%s%s" % (
105 chr(certType), expiration, pk_signing, extensions)
106 signature = ed25519_exts_ref.signatureWithESK(signed, sk_master, pk_master)
107 assert len(signature) == 64
108 return signed+signature
110 def objwrap(identifier, body):
111 return ("-----BEGIN {0}-----\n"
112 "{1}"
113 "-----END {0}-----").format(identifier, body)
115 MAGIC1 = "<<<<<<MAGIC>>>>>>"
116 MAGIC2 = "<<<<<!#!#!#XYZZY#!#!#!>>>>>"
118 class OnDemandKeys(object):
119 def __init__(self, certDate=None):
120 if certDate is None:
121 certDate = time.time() + 86400
122 self.certDate = certDate
123 self.rsa_id = None
124 self.rsa_onion_key = None
125 self.ed_id_sk = None
126 self.ntor_sk = None
127 self.ntor_crosscert = None
128 self.rsa_crosscert_ed = None
129 self.rsa_crosscert_noed = None
131 @property
132 def RSA_IDENTITY(self):
133 if self.rsa_id is None:
134 self.rsa_id, self.rsa_ident_pem, self.rsa_id_digest = make_rsa_key()
136 return self.rsa_ident_pem
138 @property
139 def RSA_ID_DIGEST(self):
140 self.RSA_IDENTITY
141 return self.rsa_id_digest
143 @property
144 def RSA_FINGERPRINT_NOSPACE(self):
145 return binascii.b2a_hex(self.RSA_ID_DIGEST).upper()
147 @property
148 def RSA_ONION_KEY(self):
149 if self.rsa_onion_key is None:
150 self.rsa_onion_key, self.rsa_onion_pem, _ = make_rsa_key()
152 return self.rsa_onion_pem
154 @property
155 def RSA_FINGERPRINT(self):
156 hexdigest = self.RSA_FINGERPRINT_NOSPACEK
157 return " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4))
159 @property
160 def RSA_SIGNATURE(self):
161 return MAGIC1
163 @property
164 def ED_SIGNATURE(self):
165 return MAGIC2
167 @property
168 def NTOR_ONION_KEY(self):
169 if self.ntor_sk is None:
170 self.ntor_sk = slownacl_curve25519.Private()
171 self.ntor_pk = self.ntor_sk.get_public()
172 return base64.b64encode(self.ntor_pk.serialize())
174 @property
175 def ED_CERT(self):
176 if self.ed_id_sk is None:
177 self.ed_id_sk = ed25519_exts_ref.expandSK(os.urandom(32))
178 self.ed_signing_sk = ed25519_exts_ref.expandSK(os.urandom(32))
179 self.ed_id_pk = ed25519_exts_ref.publickeyFromESK(self.ed_id_sk)
180 self.ed_signing_pk = ed25519_exts_ref.publickeyFromESK(self.ed_signing_sk)
181 self.ed_cert = makeEdSigningKeyCert(self.ed_id_sk, self.ed_id_pk, self.ed_signing_pk, self.certDate, includeSigning=True, certType=4)
183 return objwrap('ED25519 CERT', b64(self.ed_cert))
185 @property
186 def NTOR_CROSSCERT(self):
187 if self.ntor_crosscert is None:
188 self.ED_CERT
189 self.NTOR_ONION_KEY
191 ed_privkey = self.ntor_sk.serialize() + os.urandom(32)
192 ed_pub0 = ed25519_exts_ref.publickeyFromESK(ed_privkey)
193 sign = (ord(ed_pub0[31]) & 255) >> 7
195 self.ntor_crosscert = makeEdSigningKeyCert(self.ntor_sk.serialize() + os.urandom(32), ed_pub0, self.ed_id_pk, self.certDate, certType=10)
196 self.ntor_crosscert_sign = sign
198 return objwrap('ED25519 CERT', b64(self.ntor_crosscert))
200 @property
201 def NTOR_CROSSCERT_SIGN(self):
202 self.NTOR_CROSSCERT
203 return self.ntor_crosscert_sign
205 @property
206 def RSA_CROSSCERT_NOED(self):
207 if self.rsa_crosscert_noed is None:
208 self.RSA_ONION_KEY
209 signed = self.RSA_ID_DIGEST
210 self.rsa_crosscert_noed = rsa_sign(signed, self.rsa_onion_key)
211 return objwrap("CROSSCERT",b64(self.rsa_crosscert_noed))
213 @property
214 def RSA_CROSSCERT_ED(self):
215 if self.rsa_crosscert_ed is None:
216 self.RSA_ONION_KEY
217 self.ED_CERT
218 signed = self.RSA_ID_DIGEST + self.ed_id_pk
219 self.rsa_crosscert_ed = rsa_sign(signed, self.rsa_onion_key)
220 return objwrap("CROSSCERT",b64(self.rsa_crosscert_ed))
222 def sign_desc(self, body):
223 idx = body.rfind("\nrouter-sig-ed25519 ")
224 if idx >= 0:
225 self.ED_CERT
226 signed_part = body[:idx+len("\nrouter-sig-ed25519 ")]
227 signed_part = "Tor router descriptor signature v1" + signed_part
228 digest = hashlib.sha256(signed_part).digest()
229 ed_sig = ed25519_exts_ref.signatureWithESK(digest,
230 self.ed_signing_sk, self.ed_signing_pk)
232 body = body.replace(MAGIC2, base64.b64encode(ed_sig).replace("=",""))
234 idx = body.rindex("\nrouter-signature")
235 end_of_sig = body.index("\n", idx+1)
237 signed_part = body[:end_of_sig+1]
239 digest = hashlib.sha1(signed_part).digest()
240 assert len(digest) == 20
242 rsasig = rsa_sign(digest, self.rsa_id)
244 body = body.replace(MAGIC1, objwrap("SIGNATURE", b64(rsasig)))
246 return body
249 def signdesc(body, args_out=None):
250 rsa, ident_pem, id_digest = make_key()
251 _, onion_pem, _ = make_key()
253 need_ed = '{ED25519-CERT}' in body or '{ED25519-SIGNATURE}' in body
254 if need_ed:
255 sk_master = os.urandom(32)
256 sk_signing = os.urandom(32)
257 pk_master = slow_ed25519.pubkey(sk_master)
258 pk_signing = slow_ed25519.pubkey(sk_signing)
260 hexdigest = binascii.b2a_hex(id_digest).upper()
261 fingerprint = " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4))
263 MAGIC = "<<<<<<MAGIC>>>>>>"
264 MORE_MAGIC = "<<<<<!#!#!#XYZZY#!#!#!>>>>>"
265 args = {
266 "RSA-IDENTITY" : ident_pem,
267 "ONION-KEY" : onion_pem,
268 "FINGERPRINT" : fingerprint,
269 "FINGERPRINT-NOSPACE" : hexdigest,
270 "RSA-SIGNATURE" : MAGIC
272 if need_ed:
273 args['ED25519-CERT'] = makeEdSigningKeyCert(
274 sk_master, pk_master, pk_signing)
275 args['ED25519-SIGNATURE'] = MORE_MAGIC
277 if args_out:
278 args_out.update(args)
279 body = body.format(**args)
281 idx = body.rindex("\nrouter-signature")
282 end_of_sig = body.index("\n", idx+1)
284 signed_part = body[:end_of_sig+1]
286 digest = hashlib.sha1(signed_part).digest()
287 assert len(digest) == 20
289 buf = ctypes.create_string_buffer(1024)
290 n = RSA_private_encrypt(20, digest, buf, rsa, 1)
291 sig = buf.raw[:n]
293 sig = """-----BEGIN SIGNATURE-----
295 -----END SIGNATURE-----""" % b64(sig).rstrip()
296 body = body.replace(MAGIC, sig)
298 return body.rstrip()
300 def print_c_string(ident, body):
301 print "static const char %s[] =" % ident
302 for line in body.split("\n"):
303 print ' "%s\\n"' %(line)
304 print " ;"
306 def emit_ri(name, body):
307 info = OnDemandKeys()
308 body = body.format(d=info)
309 body = info.sign_desc(body)
310 print_c_string("EX_RI_%s"%name.upper(), body)
312 def emit_ei(name, body):
313 info = OnDemandKeys()
314 body = body.format(d=info)
315 body = info.sign_desc(body)
316 print_c_string("EX_EI_%s"%name.upper(), body)
318 print 'const char EX_EI_{NAME}_FP[] = "{d.RSA_FINGERPRINT_NOSPACE}";'.format(
319 d=info, NAME=name.upper())
320 print_c_string("EX_EI_%s_KEY"%name.upper(), info.RSA_IDENTITY)
322 def analyze(s):
323 fields = {}
324 while s.startswith(":::"):
325 first,s=s.split("\n", 1)
326 m = re.match(r'^:::(\w+)=(.*)',first)
327 if not m:
328 raise ValueError(first)
329 k,v = m.groups()
330 fields[k] = v
331 return fields, s
333 def process_file(s):
334 fields, s = analyze(s)
335 try:
336 name = fields['name']
337 tp = fields['type']
338 except KeyError:
339 raise ValueError("missing required field")
341 if tp == 'ei':
342 emit_ei(name, s)
343 elif tp == 'ri':
344 emit_ri(name, s)
345 else:
346 raise ValueError("unrecognized type")
348 if __name__ == '__main__':
349 import sys
350 for fn in sys.argv[1:]:
351 process_file(open(fn).read())