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
29 import slownacl_curve25519
30 import ed25519_exts_ref
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
74 router fred 127.0.0.1 9001 0 9002
79 master-key-ed25519 {d.ED_IDENTITY}
82 ntor-onion-key {d.NTOR_ONION_KEY}
83 ntor-onion-key-crosscert {d.NTOR_CROSSCERT_SIGN}
93 def rsa_sign(msg
, rsa
):
94 buf
= ctypes
.create_string_buffer(2048)
95 n
= RSA_private_encrypt(len(msg
), msg
, buf
, rsa
, 1)
101 x
= binascii
.b2a_base64(x1
)
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()
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
))
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)
131 extensions
= b
"\x01\x00\x20\x04\x00%s"%(pk_master)
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"
143 "-----END {0}-----").format(identifier
, body
)
145 MAGIC1
= "<<<<<<MAGIC>>>>>>"
146 MAGIC2
= "<<<<<!#!#!#XYZZY#!#!#!>>>>>"
148 class OnDemandKeys(object):
149 def __init__(self
, certDate
=None):
151 certDate
= int(time
.time()) + 86400
152 self
.certDate
= certDate
154 self
.rsa_onion_key
= None
157 self
.ntor_crosscert
= None
158 self
.rsa_crosscert_ed
= None
159 self
.rsa_crosscert_noed
= None
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
169 def RSA_ID_DIGEST(self
):
171 return self
.rsa_id_digest
174 def RSA_FINGERPRINT_NOSPACE(self
):
175 return binascii
.b2a_hex(self
.RSA_ID_DIGEST
).upper().decode("ascii")
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
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))
190 def RSA_SIGNATURE(self
):
194 def ED_SIGNATURE(self
):
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")
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
))
216 def ED_IDENTITY(self
):
218 return binascii
.b2a_base64(self
.ed_id_pk
).strip().decode("ascii")
221 def NTOR_CROSSCERT(self
):
222 if self
.ntor_crosscert
is None:
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
))
236 def NTOR_CROSSCERT_SIGN(self
):
238 return self
.ntor_crosscert_sign
241 def RSA_CROSSCERT_NOED(self
):
242 if self
.rsa_crosscert_noed
is None:
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
))
249 def RSA_CROSSCERT_ED(self
):
250 if self
.rsa_crosscert_ed
is None:
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 ")
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("=",""))
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
)))
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
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#!#!#!>>>>>"
302 "RSA-IDENTITY" : ident_pem
,
303 "ONION-KEY" : onion_pem
,
304 "FINGERPRINT" : fingerprint
,
305 "FINGERPRINT-NOSPACE" : hexdigest
,
306 "RSA-SIGNATURE" : MAGIC
309 args
['ED25519-CERT'] = makeEdSigningKeyCert(
310 sk_master
, pk_master
, pk_signing
)
311 args
['ED25519-SIGNATURE'] = MORE_MAGIC
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)
329 sig
= """-----BEGIN SIGNATURE-----
331 -----END SIGNATURE-----""" % b64(sig
).rstrip()
332 body
= body
.replace(MAGIC
, sig
)
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))
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()))
357 print_c_string("EX_EI_%s_KEY"%name
.upper(), info
.RSA_IDENTITY
)
363 while s
.startswith(":::"):
364 first
,s
=s
.split("\n", 1)
365 m
= re
.match(r
'^:::(\w+)=(.*)',first
)
367 raise ValueError(first
)
370 if "name" not in fields
:
375 body
= s
[:idx
].rstrip()
383 def emit_entry(fields
, s
):
385 name
= fields
['name']
388 raise ValueError("missing required field")
391 emit_ei(name
, s
, fields
)
395 raise ValueError("unrecognized type")
399 /* These entries are automatically generated by makedesc.py to make sure
400 * that their keys and signatures are right except when otherwise
403 for (fields
, s
) in analyze(s
):
404 emit_entry(fields
, s
)
406 if __name__
== '__main__':
408 for fn
in sys
.argv
[1:]:
409 process_file(open(fn
).read())