commit b600b68b209984fa55f1dc0c311f77e2942a8a17 Author: Nick Mathewson nickm@torproject.org Date: Wed Oct 22 14:19:18 2014 -0400
Revise makedesc.py: teach it how to emit ed signatures and crosscerts
Also, add a trivial ed25519-signed routerinfo to the tests. --- scripts/codegen/makedesc.py | 349 +++++++++++++++++++++++++++----------- src/test/failing_routerdescs.inc | 46 +++++ src/test/test_dir.c | 6 +- 3 files changed, 299 insertions(+), 102 deletions(-)
diff --git a/scripts/codegen/makedesc.py b/scripts/codegen/makedesc.py index 8339519..0ed1f7e 100644 --- a/scripts/codegen/makedesc.py +++ b/scripts/codegen/makedesc.py @@ -14,6 +14,17 @@ import binascii import ctypes import ctypes.util import hashlib +import optparse +import os +import struct +import time +import UserDict + +import slow_ed25519 +import slownacl_curve25519 +import ed25519_exts_ref + +# Pull in the openssl stuff we need.
crypt = ctypes.CDLL(ctypes.util.find_library('crypto')) BIO_s_mem = crypt.BIO_s_mem @@ -24,6 +35,15 @@ BIO_new = crypt.BIO_new BIO_new.argtypes = [ctypes.c_void_p] BIO_new.restype = ctypes.c_void_p
+crypt.BIO_free.argtypes = [ctypes.c_void_p] +crypt.BIO_free.restype = ctypes.c_int + +crypt.BIO_ctrl.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_long, ctypes.c_void_p ] +crypt.BIO_ctrl.restype = ctypes.c_long + +crypt.PEM_write_bio_RSAPublicKey.argtypes = [ ctypes.c_void_p, ctypes.c_void_p ] +crypt.PEM_write_bio_RSAPublicKey.restype = ctypes.c_int + 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 @@ -39,6 +59,14 @@ i2d_RSAPublicKey.argtypes = [ ] i2d_RSAPublicKey.restype = ctypes.c_int
+ +def rsa_sign(msg, rsa): + buf = ctypes.create_string_buffer(1024) + n = RSA_private_encrypt(len(msg), msg, buf, rsa, 1) + if n <= 0: + raise Exception() + return buf.raw[:n] + def b64(x): x = base64.b64encode(x) res = [] @@ -51,29 +79,188 @@ def bio_extract(bio): length = crypt.BIO_ctrl(bio, 3, 0, ctypes.byref(buf)) return ctypes.string_at(buf, length)
-def make_key(e=65537): +def make_rsa_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 makeEdSigningKeyCert(sk_master, pk_master, pk_signing, date, + includeSigning=False, certType=1): + assert len(pk_signing) == len(pk_master) == 32 + expiration = struct.pack("!L", date//3600) + if includeSigning: + extensions = "\x01\x00\x20\x04\x00%s"%(pk_master) + else: + extensions = "\x00" + signed = "\x01%s%s\x01%s%s" % ( + chr(certType), expiration, pk_signing, extensions) + signature = ed25519_exts_ref.signatureWithESK(signed, sk_master, pk_master) + assert len(signature) == 64 + return signed+signature + +def objwrap(identifier, body): + return ("-----BEGIN {0}-----\n" + "{1}" + "-----END {0}-----").format(identifier, body) + +MAGIC1 = "<<<<<<MAGIC>>>>>>" +MAGIC2 = "<<<<<!#!#!#XYZZY#!#!#!>>>>>" + +class OnDemandKeys(object): + def __init__(self, certDate=None): + if certDate is None: + certDate = time.time() + 86400 + self.certDate = certDate + self.rsa_id = None + self.rsa_onion_key = None + self.ed_id_sk = None + self.ntor_sk = None + self.ntor_crosscert = None + self.rsa_crosscert_ed = None + self.rsa_crosscert_noed = None + + @property + def RSA_IDENTITY(self): + if self.rsa_id is None: + self.rsa_id, self.rsa_ident_pem, self.rsa_id_digest = make_rsa_key() + + return self.rsa_ident_pem + + @property + def RSA_ID_DIGEST(self): + self.RSA_IDENTITY + return self.rsa_id_digest + + @property + def RSA_FINGERPRINT_NOSPACE(self): + return binascii.b2a_hex(self.RSA_ID_DIGEST).upper() + + @property + def RSA_ONION_KEY(self): + if self.rsa_onion_key is None: + self.rsa_onion_key, self.rsa_onion_pem, _ = make_rsa_key() + + return self.rsa_onion_pem + + @property + def RSA_FINGERPRINT(self): + hexdigest = self.RSA_FINGERPRINT_NOSPACEK + return " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4)) + + @property + def RSA_SIGNATURE(self): + return MAGIC1 + + @property + def ED_SIGNATURE(self): + return MAGIC2 + + @property + def NTOR_ONION_KEY(self): + if self.ntor_sk is None: + self.ntor_sk = slownacl_curve25519.Private() + self.ntor_pk = self.ntor_sk.get_public() + return base64.b64encode(self.ntor_pk.serialize()) + + @property + def ED_CERT(self): + if self.ed_id_sk is None: + self.ed_id_sk = ed25519_exts_ref.expandSK(os.urandom(32)) + self.ed_signing_sk = ed25519_exts_ref.expandSK(os.urandom(32)) + self.ed_id_pk = ed25519_exts_ref.publickeyFromESK(self.ed_id_sk) + self.ed_signing_pk = ed25519_exts_ref.publickeyFromESK(self.ed_signing_sk) + self.ed_cert = makeEdSigningKeyCert(self.ed_id_sk, self.ed_id_pk, self.ed_signing_pk, self.certDate, includeSigning=True, certType=4) + + return objwrap('ED25519 CERT', b64(self.ed_cert)) + + @property + def NTOR_CROSSCERT(self): + if self.ntor_crosscert is None: + self.ED_CERT + self.NTOR_ONION_KEY + + ed_privkey = self.ntor_sk.serialize() + os.urandom(32) + ed_pub0 = ed25519_exts_ref.publickeyFromESK(ed_privkey) + sign = (ord(ed_pub0[31]) & 255) >> 7 + + self.ntor_crosscert = makeEdSigningKeyCert(self.ntor_sk.serialize() + os.urandom(32), ed_pub0, self.ed_id_pk, self.certDate, certType=10) + self.ntor_crosscert_sign = sign + + return objwrap('ED25519 CERT', b64(self.ntor_crosscert)) + + @property + def NTOR_CROSSCERT_SIGN(self): + self.NTOR_CROSSCERT + return self.ntor_crosscert_sign + + @property + def RSA_CROSSCERT_NOED(self): + if self.rsa_crosscert_noed is None: + self.RSA_ONION_KEY + signed = self.RSA_ID_DIGEST + self.rsa_crosscert_noed = rsa_sign(signed, self.rsa_onion_key) + return objwrap("CROSSCERT",b64(self.rsa_crosscert_noed)) + + @property + def RSA_CROSSCERT_ED(self): + if self.rsa_crosscert_ed is None: + self.RSA_ONION_KEY + self.ED_CERT + signed = self.RSA_ID_DIGEST + self.ed_id_pk + self.rsa_crosscert_ed = rsa_sign(signed, self.rsa_onion_key) + return objwrap("CROSSCERT",b64(self.rsa_crosscert_ed)) + + def sign_desc(self, body): + idx = body.rfind("\nrouter-sig-ed25519 ") + if idx >= 0: + self.ED_CERT + signed_part = body[:idx+len("\nrouter-sig-ed25519 ")] + signed_part = "Tor router descriptor signature v1" + signed_part + digest = hashlib.sha256(signed_part).digest() + ed_sig = ed25519_exts_ref.signatureWithESK(digest, + self.ed_signing_sk, self.ed_signing_pk) + + body = body.replace(MAGIC2, base64.b64encode(ed_sig).replace("=","")) + + 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 + + rsasig = rsa_sign(digest, self.rsa_id) + + body = body.replace(MAGIC1, objwrap("SIGNATURE", b64(rsasig))) + + return body + + def signdesc(body, args_out=None): rsa, ident_pem, id_digest = make_key() _, onion_pem, _ = make_key()
+ need_ed = '{ED25519-CERT}' in body or '{ED25519-SIGNATURE}' in body + if need_ed: + sk_master = os.urandom(32) + sk_signing = os.urandom(32) + pk_master = slow_ed25519.pubkey(sk_master) + pk_signing = slow_ed25519.pubkey(sk_signing) + hexdigest = binascii.b2a_hex(id_digest).upper() fingerprint = " ".join(hexdigest[i:i+4] for i in range(0,len(hexdigest),4))
MAGIC = "<<<<<<MAGIC>>>>>>" + MORE_MAGIC = "<<<<<!#!#!#XYZZY#!#!#!>>>>>" args = { "RSA-IDENTITY" : ident_pem, "ONION-KEY" : onion_pem, @@ -81,6 +268,11 @@ def signdesc(body, args_out=None): "FINGERPRINT-NOSPACE" : hexdigest, "RSA-SIGNATURE" : MAGIC } + if need_ed: + args['ED25519-CERT'] = makeEdSigningKeyCert( + sk_master, pk_master, pk_signing) + args['ED25519-SIGNATURE'] = MORE_MAGIC + if args_out: args_out.update(args) body = body.format(**args) @@ -104,115 +296,72 @@ def signdesc(body, args_out=None):
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 +def print_c_string(ident, body): + print "static const char EX %s[] =" % ident + for line in body.split("\n"): + print ' "%s\n"' %(line) print " ;"
+def emit_ri(name, body): + info = OnDemandKeys() + body = body.format(d=info) + body = info.sign_desc(body) + print_c_string("EX_RI_%s"%name.upper(), body) + 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) + info = OnDemandKeys() + body = body.format(d=info) + body = info.sign_desc(body) + print_c_string("EX_EI_%s"%name.upper(), body)
-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} -""") + print 'const char {NAME}_FP[] = "{d.RSA_FINGERPRINT_NOSPACE}";'.format( + d=info, NAME=name.upper()) + prnit_c_string("%s_KEY"%name.upper(), d.RSA_IDENTITY) + +def analyze(s): + fields = {} + while s.startswith(":::"): + first,s=s.split("\n", 1) + m = re.match(r'^:::(\w+)=(.*)',first) + if not m: + raise ValueError(first) + k,v = m.groups() + fields[k] = v + return fields, s + +def process_file(s): + try: + name = fields['name'] + tp = fields['type'] + except KeyError: + raise ValueError("missing required field") + + if tp == 'ei': + emit_ei(name, s) + elif tp == 'ri': + emit_ri(name, s) + else: + raise ValueError("unrecognized type")
if 0: - emit_ri("maximal", + emit_ri("minimal_ed", """\ router fred 127.0.0.1 9001 0 9002 +identity-ed25519 +{d.ED_CERT} signing-key -{RSA-IDENTITY} +{d.RSA_IDENTITY} onion-key -{ONION-KEY} +{d.RSA_ONION_KEY} +ntor-onion-key {d.NTOR_ONION_KEY} +ntor-onion-key-crosscert {d.NTOR_CROSSCERT_SIGN} +{d.NTOR_CROSSCERT} +onion-key-crosscert +{d.RSA_CROSSCERT_ED} 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-sig-ed25519 {d.ED_SIGNATURE} router-signature -{RSA-SIGNATURE} +{d.RSA_SIGNATURE} """) - diff --git a/src/test/failing_routerdescs.inc b/src/test/failing_routerdescs.inc index b49d59f..db3fd70 100644 --- a/src/test/failing_routerdescs.inc +++ b/src/test/failing_routerdescs.inc @@ -666,3 +666,49 @@ static const char EX_RI_ZERO_ORPORT[] = "wgFKhHI/49NHyWHX5IMQpeicg0T7Qa6qwnUvspH62p8=\n" "-----END SIGNATURE-----\n" ; + +static const char EX_RI_MINIMAL_ED[] = + "router fred 127.0.0.1 9001 0 9002\n" + "identity-ed25519\n" + "-----BEGIN ED25519 CERT-----\n" + "AQQABf5iAa+2yD5ryD5kXaWbpmzaTyuTjRfjMTFleDuFGkHe26wrAQAgBABFTAHm\n" + "hdZriC+6BRCCMYu48cYc9tUN1adfEROqSHZN3HHP4k/fYgncoxrS3OYDX1x8Ysm/\n" + "sqxAXBY4NhCMswWvuDYgtQpro9YaFohiorJkHjyLQXjUeZikCfDrlxyR8AM=\n" + "-----END ED25519 CERT-----\n" + "signing-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAOsjlHgM/lPQgjJyfrq0y+cR+iipcAeS2HAU8CK9SATETOTZYrxoL5vH\n" + "1BNteT+JxAxpjva+j7r7XZV41xPDx7alVr8G3zQsjqkAt5NnleTfUREUbg0+OSMV\n" + "10gU+DgcZJTMehfGYJnuJsF4eQHio/ZTdJLaZML7qwq0iWg3sZfBAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n" + "onion-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAK9NjRY7GtAZnlxrAZlImChXmGzml0uk2KlCugvju+eIsjSA/zW3LuqW\n" + "wqp7Kh488Ak5nUFSlCaV9GjAexT134pynst8P0m/ofrejwlzl5DHd6sFbR33Fkzl\n" + "H48zic0QDY+8tKXI732dA4GveEwZDlxxy8sPcvUDaVyTsuZLHR4zAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n" + "ntor-onion-key 71DgscFrk4i58O5GuTerI9g3JL0kz+6QaCstAllz9xw=\n" + "ntor-onion-key-crosscert 1\n" + "-----BEGIN ED25519 CERT-----\n" + "AQoABf5iAUVMAeaF1muIL7oFEIIxi7jxxhz21Q3Vp18RE6pIdk3cAH5ijeKqa+LM\n" + "T5Nb0I42Io4Z7BVjXG7sYVSxrospCOI4dqkl2ln3BKNuEFFT42xJwt+XGz3aMyK2\n" + "Cpp8w8I8nwU=\n" + "-----END ED25519 CERT-----\n" + "onion-key-crosscert\n" + "-----BEGIN CROSSCERT-----\n" + "lAZwD6YVic61NvJ0Iy62cSPuzJl5hJOFYNh9iSG/vn4/lVfnnCik+Gqi2v9pwItC\n" + "acwmutCSrMprmmFAW1dgzoU7GzUtdbxaGaOJdg8WwtO4JjFSzScTDB8R6sp0SCAI\n" + "PdbzAzJyiMqYcynyyCTiL77iwhUOBPzs2fXlivMtW2E=\n" + "-----END CROSSCERT-----\n" + "published 2014-10-05 12:00:00\n" + "bandwidth 1000 1000 1000\n" + "reject *:*\n" + "router-sig-ed25519 Oyo/eES+/wsgse1f+YSiJDGatBDaiB4fASf7vJ7GxFeD4OfLbB7OYa4hYNEo5NBssNt/PA55AQVSL8hvzBE3Cg\n" + "router-signature\n" + "-----BEGIN SIGNATURE-----\n" + "wdk26ZtS1H81IxcUThyirANLoszrnYYhOMP57YRAUDEzUr88X6yNDZ5S0tLl+FoT\n" + "9XlEVrpN7Z3k4N9WloWb0o/zVVidPMRVwt8YQakSgR8axzMQg6QhQ6zXTiYhiXa4\n" + "mawlwYFXsaVDSIIqYA2CudIyF3UBRZuTbw0CFZElMWc=\n" + "-----END SIGNATURE-----\n" + "\n" + ; diff --git a/src/test/test_dir.c b/src/test/test_dir.c index d7b5e1b..20863bf 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -352,7 +352,7 @@ test_dir_formats(void *arg) #include "failing_routerdescs.inc"
static void -test_dir_routerparse_bad(void *arg) +test_dir_routerinfo_parsing(void *arg) { (void) arg;
@@ -377,6 +377,8 @@ test_dir_routerparse_bad(void *arg) CHECK_OK(EX_RI_MINIMAL); CHECK_OK(EX_RI_MAXIMAL);
+ CHECK_OK(EX_RI_MINIMAL_ED); + /* good annotations prepended */ routerinfo_free(ri); ri = router_parse_entry_from_string(EX_RI_MINIMAL, NULL, 0, 0, @@ -3168,7 +3170,7 @@ test_dir_packages(void *arg) struct testcase_t dir_tests[] = { DIR_LEGACY(nicknames), DIR_LEGACY(formats), - DIR(routerparse_bad, 0), + DIR(routerinfo_parsing, 0), DIR(extrainfo_parsing, 0), DIR(parse_router_list, TT_FORK), DIR(load_routers, TT_FORK),