#!/usr/bin/python # Copyright 2014-2015, The Tor Project, Inc. # See LICENSE for license information # This is a kludgey python script that uses ctypes and openssl to sign # router descriptors and extrainfo documents and put all the keys in # the right places. There are examples at the end of the file. # I've used this to make inputs for unit tests. I wouldn't suggest # using it for anything else. import base64 import binascii import ctypes import ctypes.util import hashlib crypt = ctypes.CDLL(ctypes.util.find_library('crypto')) BIO_s_mem = crypt.BIO_s_mem BIO_s_mem.argtypes = [] BIO_s_mem.restype = ctypes.c_void_p BIO_new = crypt.BIO_new BIO_new.argtypes = [ctypes.c_void_p] BIO_new.restype = ctypes.c_void_p RSA_generate_key = crypt.RSA_generate_key RSA_generate_key.argtypes = [ctypes.c_int, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_void_p] RSA_generate_key.restype = ctypes.c_void_p RSA_private_encrypt = crypt.RSA_private_encrypt RSA_private_encrypt.argtypes = [ ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int ] RSA_private_encrypt.restype = ctypes.c_int i2d_RSAPublicKey = crypt.i2d_RSAPublicKey i2d_RSAPublicKey.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p) ] i2d_RSAPublicKey.restype = ctypes.c_int def b64(x): x = base64.b64encode(x) res = [] for i in xrange(0, len(x), 64): res.append(x[i:i+64]+"\n") return "".join(res) def bio_extract(bio): buf = ctypes.c_char_p() length = crypt.BIO_ctrl(bio, 3, 0, ctypes.byref(buf)) return ctypes.string_at(buf, length) def make_key(e=65537): rsa = crypt.RSA_generate_key(1024, e, None, None) bio = BIO_new(BIO_s_mem()) crypt.PEM_write_bio_RSAPublicKey(bio, rsa) pem = bio_extract(bio).rstrip() crypt.BIO_free(bio) buf = ctypes.create_string_buffer(1024) pBuf = ctypes.c_char_p(ctypes.addressof(buf)) n = crypt.i2d_RSAPublicKey(rsa, ctypes.byref(pBuf)) s = buf.raw[:n] digest = hashlib.sha1(s).digest() return (rsa,pem,digest) def signdesc(body, args_out=None): rsa, ident_pem, id_digest = make_key() _, onion_pem, _ = make_key() hexdigest = binascii.b2a_hex(id_digest).upper() fingerprint = " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4)) MAGIC = "<<<<<>>>>>" args = { "RSA-IDENTITY" : ident_pem, "ONION-KEY" : onion_pem, "FINGERPRINT" : fingerprint, "FINGERPRINT-NOSPACE" : hexdigest, "RSA-SIGNATURE" : MAGIC } if args_out: args_out.update(args) body = body.format(**args) idx = body.rindex("\nrouter-signature") end_of_sig = body.index("\n", idx+1) signed_part = body[:end_of_sig+1] digest = hashlib.sha1(signed_part).digest() assert len(digest) == 20 buf = ctypes.create_string_buffer(1024) n = RSA_private_encrypt(20, digest, buf, rsa, 1) sig = buf.raw[:n] sig = """-----BEGIN SIGNATURE----- %s -----END SIGNATURE-----""" % b64(sig).rstrip() body = body.replace(MAGIC, sig) return body.rstrip() def emit_ri(name, body, args_out=None): print "const char %s[] ="%name body = "\n".join(line.rstrip() for line in body.split("\n"))+"\n" b = signdesc(body, args_out) for line in b.split("\n"): print ' "%s\\n"'%line print " ;" def emit_ei(name, body): args = { 'NAME' : name } emit_ri(name, body, args) args['key'] = "\n".join( ' "%s\\n"'%line for line in args['RSA-IDENTITY'].split("\n")) print """ const char {NAME}_fp[] = "{FINGERPRINT-NOSPACE}"; const char {NAME}_key[] = {key};""".format(**args) if 0: emit_ri("minimal", """\ router fred 127.0.0.1 9001 0 9002 signing-key {RSA-IDENTITY} onion-key {ONION-KEY} published 2014-10-05 12:00:00 bandwidth 1000 1000 1000 reject *:* router-signature {RSA-SIGNATURE} """) if 0: emit_ri("maximal", """\ router fred 127.0.0.1 9001 0 9002 signing-key {RSA-IDENTITY} onion-key {ONION-KEY} published 2014-10-05 12:00:00 bandwidth 1000 1000 1000 reject 127.0.0.1:* accept *:80 reject *:* ipv6-policy accept 80,100,101 ntor-onion-key s7rSohmz9SXn8WWh1EefTHIsWePthsEntQi0WL+ScVw uptime 1000 hibernating 0 unrecognized-keywords are just dandy in this format platform Tor 0.2.4.23 on a Banana PC Jr 6000 Series contact O.W.Jones fingerprint {FINGERPRINT} read-history 900 1,2,3,4 write-history 900 1,2,3,4 extra-info-digest AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA hidden-service-dir allow-single-hop-exits family $AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA $BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB caches-extra-info or-address [::1:2:3:4]:9999 or-address 127.0.0.99:10000 opt fred is a fine router router-signature {RSA-SIGNATURE} """) if 0: emit_ei("maximal", """\ extra-info bob {FINGERPRINT-NOSPACE} published 2014-10-05 20:07:00 opt foobarbaz read-history 900 1,2,3 write-history 900 1,2,3 dirreq-v2-ips 1 dirreq-v3-ips 100 dirreq-v3-reqs blahblah dirreq-v2-share blahblah dirreq-v3-share blahblah dirreq-v2-resp djfkdj dirreq-v3-resp djfkdj dirreq-v2-direct-dl djfkdj dirreq-v3-direct-dl djfkdj dirreq-v2-tunneled-dl djfkdj dirreq-v3-tunneled-dl djfkdj dirreq-stats-end foobar entry-ips jfsdfds entry-stats-end ksdflkjfdkf cell-stats-end FOO cell-processed-cells FOO cell-queued-cells FOO cell-time-in-queue FOO cell-circuits-per-decile FOO exit-stats-end FOO exit-kibibytes-written FOO exit-kibibytes-read FOO exit-streams-opened FOO router-signature {RSA-SIGNATURE} """) if 0: emit_ei("minimal", """\ extra-info bob {FINGERPRINT-NOSPACE} published 2014-10-05 20:07:00 router-signature {RSA-SIGNATURE} """)