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.
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)
72 x
= base64
.b64encode(x
)
74 for i
in xrange(0, len(x
), 64):
75 res
.append(x
[i
:i
+64]+"\n")
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()
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
))
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)
101 extensions
= "\x01\x00\x20\x04\x00%s"%(pk_master)
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"
113 "-----END {0}-----").format(identifier
, body
)
115 MAGIC1
= "<<<<<<MAGIC>>>>>>"
116 MAGIC2
= "<<<<<!#!#!#XYZZY#!#!#!>>>>>"
118 class OnDemandKeys(object):
119 def __init__(self
, certDate
=None):
121 certDate
= time
.time() + 86400
122 self
.certDate
= certDate
124 self
.rsa_onion_key
= None
127 self
.ntor_crosscert
= None
128 self
.rsa_crosscert_ed
= None
129 self
.rsa_crosscert_noed
= None
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
139 def RSA_ID_DIGEST(self
):
141 return self
.rsa_id_digest
144 def RSA_FINGERPRINT_NOSPACE(self
):
145 return binascii
.b2a_hex(self
.RSA_ID_DIGEST
).upper()
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
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))
160 def RSA_SIGNATURE(self
):
164 def ED_SIGNATURE(self
):
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())
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
))
186 def NTOR_CROSSCERT(self
):
187 if self
.ntor_crosscert
is None:
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
))
201 def NTOR_CROSSCERT_SIGN(self
):
203 return self
.ntor_crosscert_sign
206 def RSA_CROSSCERT_NOED(self
):
207 if self
.rsa_crosscert_noed
is None:
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
))
214 def RSA_CROSSCERT_ED(self
):
215 if self
.rsa_crosscert_ed
is None:
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 ")
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
)))
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
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#!#!#!>>>>>"
266 "RSA-IDENTITY" : ident_pem
,
267 "ONION-KEY" : onion_pem
,
268 "FINGERPRINT" : fingerprint
,
269 "FINGERPRINT-NOSPACE" : hexdigest
,
270 "RSA-SIGNATURE" : MAGIC
273 args
['ED25519-CERT'] = makeEdSigningKeyCert(
274 sk_master
, pk_master
, pk_signing
)
275 args
['ED25519-SIGNATURE'] = MORE_MAGIC
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)
293 sig
= """-----BEGIN SIGNATURE-----
295 -----END SIGNATURE-----""" % b64(sig
).rstrip()
296 body
= body
.replace(MAGIC
, sig
)
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)
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
)
324 while s
.startswith(":::"):
325 first
,s
=s
.split("\n", 1)
326 m
= re
.match(r
'^:::(\w+)=(.*)',first
)
328 raise ValueError(first
)
334 fields
, s
= analyze(s
)
336 name
= fields
['name']
339 raise ValueError("missing required field")
346 raise ValueError("unrecognized type")
348 if __name__
== '__main__':
350 for fn
in sys
.argv
[1:]:
351 process_file(open(fn
).read())