Bladeren bron

Merge branch '12498_ed25519_keys_v6'

Fixed numerous conflicts, and ported code to use new base64 api.
Nick Mathewson 10 jaren geleden
bovenliggende
commit
1b52e95028
59 gewijzigde bestanden met toevoegingen van 10365 en 458 verwijderingen
  1. 24 0
      doc/tor.1.txt
  2. 244 111
      scripts/codegen/makedesc.py
  3. 2 2
      scripts/codegen/run_trunnel.sh
  4. 13 0
      src/common/container.c
  5. 1 0
      src/common/container.h
  6. 92 10
      src/common/crypto.c
  7. 15 5
      src/common/crypto.h
  8. 21 0
      src/common/crypto_ed25519.c
  9. 17 2
      src/common/crypto_ed25519.h
  10. 39 0
      src/common/crypto_format.c
  11. 38 38
      src/common/tortls.c
  12. 15 14
      src/common/tortls.h
  13. 4 4
      src/or/channel.c
  14. 3 2
      src/or/channel.h
  15. 69 92
      src/or/channeltls.c
  16. 10 0
      src/or/channeltls.h
  17. 20 0
      src/or/config.c
  18. 4 0
      src/or/config.h
  19. 108 72
      src/or/connection_or.c
  20. 7 5
      src/or/connection_or.h
  21. 257 0
      src/or/dircollate.c
  22. 49 0
      src/or/dircollate.h
  23. 79 0
      src/or/dirserv.c
  24. 48 39
      src/or/dirvote.c
  25. 8 1
      src/or/dirvote.h
  26. 12 8
      src/or/include.am
  27. 419 0
      src/or/keypin.c
  28. 46 0
      src/or/keypin.h
  29. 37 1
      src/or/main.c
  30. 1 0
      src/or/microdesc.c
  31. 39 2
      src/or/or.h
  32. 222 20
      src/or/router.c
  33. 6 2
      src/or/router.h
  34. 648 0
      src/or/routerkeys.c
  35. 67 0
      src/or/routerkeys.h
  36. 26 1
      src/or/routerlist.c
  37. 5 2
      src/or/routerlist.h
  38. 307 3
      src/or/routerparse.c
  39. 3 1
      src/or/routerparse.h
  40. 280 0
      src/or/torcert.c
  41. 76 0
      src/or/torcert.h
  42. 233 0
      src/test/example_extrainfo.inc
  43. 901 0
      src/test/failing_routerdescs.inc
  44. 5 1
      src/test/include.am
  45. 4 0
      src/test/test.c
  46. 38 0
      src/test/test_containers.c
  47. 19 1
      src/test/test_crypto.c
  48. 121 12
      src/test/test_dir.c
  49. 255 0
      src/test/test_keypin.c
  50. 914 0
      src/test/test_link_handshake.c
  51. 93 0
      src/test/test_microdesc.c
  52. 539 1
      src/test/test_routerkeys.c
  53. 887 0
      src/trunnel/ed25519_cert.c
  54. 288 0
      src/trunnel/ed25519_cert.h
  55. 76 0
      src/trunnel/ed25519_cert.trunnel
  56. 15 6
      src/trunnel/include.am
  57. 1885 0
      src/trunnel/link_handshake.c
  58. 654 0
      src/trunnel/link_handshake.h
  59. 57 0
      src/trunnel/link_handshake.trunnel

+ 24 - 0
doc/tor.1.txt

@@ -1857,6 +1857,13 @@ is non-zero):
     this.  If this option is set to 0, Tor will try to pick a reasonable
     default based on your system's physical memory.  (Default: 0)
 
+[[SigningKeyLifetime]] **SigningKeyLifetime** __N__ **days**|**weeks**|**months**::
+    For how long should each Ed25519 signing key be valid?  Tor uses a
+    permanent master identity key that can be kept offline, and periodically
+    generates new "signing" keys that it uses online.  This option
+    configures their lifetime.
+    (Default: 30 days)
+
 DIRECTORY SERVER OPTIONS
 ------------------------
 
@@ -2349,6 +2356,23 @@ The following options are used for running a testing Tor network.
     authority on a testing network. Overrides the usual default lower bound
     of 4 KB. (Default: 0)
 
+[[TestingLinkCertLifetime]] **TestingLinkCertifetime** __N__ **seconds**|**minutes**|**hours**|**days**|**weeks**|**months**::
+    Overrides the default lifetime for the certificates used to authenticate
+    our X509 link cert with our ed25519 signing key.
+    (Default: 2 days)
+
+[[TestingAuthKeyLifetime]] **TestingAuthKeyLifetime** __N__ **seconds**|**minutes**|**hours**|**days**|**weeks**|**months**::
+    Overrides the default lifetime for a signing Ed25519 TLS Link authentication
+    key.
+    (Default: 2 days)
+
+[[TestingLinkKeySlop]] **TestingLinkKeySlop** __N__ **seconds**|**minutes**|**hours**::
+[[TestingAuthKeySlop]] **TestingAuthKeySlop** __N__ **seconds**|**minutes**|**hours**::
+[[TestingSigningKeySlop]] **TestingSigningKeySlop** __N__ **seconds**|**minutes**|**hours**::
+    How early before the official expiration of a an Ed25519 signing key do
+    we replace it and issue a new key?
+    (Default: 3 hours for link and auth; 1 day for signing.)
+
 SIGNALS
 -------
 

+ 244 - 111
scripts/codegen/makedesc.py

@@ -14,6 +14,18 @@ import binascii
 import ctypes
 import ctypes.util
 import hashlib
+import optparse
+import os
+import re
+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 +36,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 +60,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 +80,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 +269,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 +297,55 @@ 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 %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)
-
-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}
-""")
+    info = OnDemandKeys()
+    body = body.format(d=info)
+    body = info.sign_desc(body)
+    print_c_string("EX_EI_%s"%name.upper(), body)
+
+    print 'const char EX_EI_{NAME}_FP[] = "{d.RSA_FINGERPRINT_NOSPACE}";'.format(
+        d=info, NAME=name.upper())
+    print_c_string("EX_EI_%s_KEY"%name.upper(), info.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):
+    fields, s = analyze(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 __name__ == '__main__':
+    import sys
+    for fn in sys.argv[1:]:
+        process_file(open(fn).read())

+ 2 - 2
scripts/codegen/run_trunnel.sh

@@ -5,7 +5,7 @@ if test "x$TRUNNEL_PATH" != "x"; then
   export PYTHONPATH
 fi
 
-python -m trunnel --require-version=1.2 ./src/trunnel/*.trunnel
+python -m trunnel --require-version=1.4 ./src/trunnel/*.trunnel
 
-python -m trunnel --require-version=1.2 --write-c-files --target-dir=./src/ext/trunnel/
+python -m trunnel --require-version=1.4 --write-c-files --target-dir=./src/ext/trunnel/
 

+ 13 - 0
src/common/container.c

@@ -208,6 +208,19 @@ smartlist_string_pos(const smartlist_t *sl, const char *element)
   return -1;
 }
 
+/** If <b>element</b> is the same pointer as an element of <b>sl</b>, return
+ * that element's index.  Otherwise, return -1. */
+int
+smartlist_pos(const smartlist_t *sl, const void *element)
+{
+  int i;
+  if (!sl) return -1;
+  for (i=0; i < sl->num_used; i++)
+    if (element == sl->list[i])
+      return i;
+  return -1;
+}
+
 /** Return true iff <b>sl</b> has some element E such that
  * !strcasecmp(E,<b>element</b>)
  */

+ 1 - 0
src/common/container.h

@@ -38,6 +38,7 @@ void smartlist_reverse(smartlist_t *sl);
 void smartlist_string_remove(smartlist_t *sl, const char *element);
 int smartlist_contains(const smartlist_t *sl, const void *element);
 int smartlist_contains_string(const smartlist_t *sl, const char *element);
+int smartlist_pos(const smartlist_t *sl, const void *element);
 int smartlist_string_pos(const smartlist_t *, const char *elt);
 int smartlist_contains_string_case(const smartlist_t *sl, const char *element);
 int smartlist_contains_int_as_string(const smartlist_t *sl, int num);

+ 92 - 10
src/common/crypto.c

@@ -829,7 +829,7 @@ crypto_pk_public_exponent_ok(crypto_pk_t *env)
  * Note that this may leak information about the keys through timing.
  */
 int
-crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b)
+crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b)
 {
   int result;
   char a_is_non_null = (a != NULL) && (a->key != NULL);
@@ -855,19 +855,19 @@ crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b)
  *  Note that this may leak information about the keys through timing.
  */
 int
-crypto_pk_eq_keys(crypto_pk_t *a, crypto_pk_t *b)
+crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b)
 {
   return (crypto_pk_cmp_keys(a, b) == 0);
 }
 
 /** Return the size of the public key modulus in <b>env</b>, in bytes. */
 size_t
-crypto_pk_keysize(crypto_pk_t *env)
+crypto_pk_keysize(const crypto_pk_t *env)
 {
   tor_assert(env);
   tor_assert(env->key);
 
-  return (size_t) RSA_size(env->key);
+  return (size_t) RSA_size((RSA*)env->key);
 }
 
 /** Return the size of the public key modulus of <b>env</b>, in bits. */
@@ -996,7 +996,7 @@ crypto_pk_private_decrypt(crypto_pk_t *env, char *to,
  * at least the length of the modulus of <b>env</b>.
  */
 int
-crypto_pk_public_checksig(crypto_pk_t *env, char *to,
+crypto_pk_public_checksig(const crypto_pk_t *env, char *to,
                           size_t tolen,
                           const char *from, size_t fromlen)
 {
@@ -1068,7 +1068,7 @@ crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data,
  * at least the length of the modulus of <b>env</b>.
  */
 int
-crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen,
+crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen,
                        const char *from, size_t fromlen)
 {
   int r;
@@ -1083,7 +1083,7 @@ crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen,
 
   r = RSA_private_encrypt((int)fromlen,
                           (unsigned char*)from, (unsigned char*)to,
-                          env->key, RSA_PKCS1_PADDING);
+                          (RSA*)env->key, RSA_PKCS1_PADDING);
   if (r<0) {
     crypto_log_errors(LOG_WARN, "generating RSA signature");
     return -1;
@@ -1297,7 +1297,7 @@ crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out)
   unsigned char *buf = NULL;
   int len;
 
-  len = i2d_RSAPublicKey(pk->key, &buf);
+  len = i2d_RSAPublicKey((RSA*)pk->key, &buf);
   if (len < 0 || buf == NULL)
     return -1;
   if (crypto_digest(digest_out, (char*)buf, len) < 0) {
@@ -1795,7 +1795,24 @@ crypto_digest_assign(crypto_digest_t *into,
  * <b>out_len</b> must be \<= DIGEST256_LEN. */
 void
 crypto_digest_smartlist(char *digest_out, size_t len_out,
-                        const smartlist_t *lst, const char *append,
+                        const smartlist_t *lst,
+                        const char *append,
+                        digest_algorithm_t alg)
+{
+  crypto_digest_smartlist_prefix(digest_out, len_out, NULL, lst, append, alg);
+}
+
+/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest
+ * at <b>digest_out</b> to the hash of the concatenation of: the
+ * optional string <b>prepend</b>, those strings,
+ * and the optional string <b>append</b>, computed with the algorithm
+ * <b>alg</b>.
+ * <b>out_len</b> must be \<= DIGEST256_LEN. */
+void
+crypto_digest_smartlist_prefix(char *digest_out, size_t len_out,
+                        const char *prepend,
+                        const smartlist_t *lst,
+                        const char *append,
                         digest_algorithm_t alg)
 {
   crypto_digest_t *d;
@@ -1803,6 +1820,8 @@ crypto_digest_smartlist(char *digest_out, size_t len_out,
     d = crypto_digest_new();
   else
     d = crypto_digest256_new(alg);
+  if (prepend)
+    crypto_digest_add_bytes(d, prepend, strlen(prepend));
   SMARTLIST_FOREACH(lst, const char *, cp,
                     crypto_digest_add_bytes(d, cp, strlen(cp)));
   if (append)
@@ -2673,6 +2692,65 @@ base64_encode(char *dest, size_t destlen, const char *src, size_t srclen,
   return (int) enclen;
 }
 
+/** As base64_encode, but do not add any internal spaces or external padding
+ * to the output stream. */
+int
+base64_encode_nopad(char *dest, size_t destlen,
+                    const uint8_t *src, size_t srclen)
+{
+  int n = base64_encode(dest, destlen, (const char*) src, srclen, 0);
+  if (n <= 0)
+    return n;
+  tor_assert((size_t)n < destlen && dest[n] == 0);
+  char *in, *out;
+  in = out = dest;
+  while (*in) {
+    if (*in == '=' || *in == '\n') {
+      ++in;
+    } else {
+      *out++ = *in++;
+    }
+  }
+  *out = 0;
+
+  tor_assert(out - dest <= INT_MAX);
+
+  return (int)(out - dest);
+}
+
+/** As base64_decode, but do not require any padding on the input */
+int
+base64_decode_nopad(uint8_t *dest, size_t destlen,
+                    const char *src, size_t srclen)
+{
+  if (srclen > SIZE_T_CEILING - 4)
+    return -1;
+  char *buf = tor_malloc(srclen + 4);
+  memcpy(buf, src, srclen+1);
+  size_t buflen;
+  switch (srclen % 4)
+    {
+    case 0:
+    default:
+      buflen = srclen;
+      break;
+    case 1:
+      tor_free(buf);
+      return -1;
+    case 2:
+      memcpy(buf+srclen, "==", 3);
+      buflen = srclen + 2;
+      break;
+    case 3:
+      memcpy(buf+srclen, "=", 2);
+      buflen = srclen + 1;
+      break;
+  }
+  int n = base64_decode((char*)dest, destlen, buf, buflen);
+  tor_free(buf);
+  return n;
+}
+
 #undef BASE64_OPENSSL_LINELEN
 
 /** @{ */
@@ -2797,6 +2875,7 @@ base64_decode(char *dest, size_t destlen, const char *src, size_t srclen)
 /** Base64 encode DIGEST_LINE bytes from <b>digest</b>, remove the trailing =
  * characters, and store the nul-terminated result in the first
  * BASE64_DIGEST_LEN+1 bytes of <b>d64</b>.  */
+/* XXXX unify with crypto_format.c code */
 int
 digest_to_base64(char *d64, const char *digest)
 {
@@ -2810,6 +2889,7 @@ digest_to_base64(char *d64, const char *digest)
 /** Given a base64 encoded, nul-terminated digest in <b>d64</b> (without
  * trailing newline or = characters), decode it and store the result in the
  * first DIGEST_LEN bytes at <b>digest</b>. */
+/* XXXX unify with crypto_format.c code */
 int
 digest_from_base64(char *digest, const char *d64)
 {
@@ -2821,7 +2901,8 @@ digest_from_base64(char *digest, const char *d64)
 
 /** Base64 encode DIGEST256_LINE bytes from <b>digest</b>, remove the
  * trailing = characters, and store the nul-terminated result in the first
- * BASE64_DIGEST256_LEN+1 bytes of <b>d64</b>.  */
+ * BASE64_DIGEST256_LEN+1 bytes of <b>d64</b>. */
+ /* XXXX unify with crypto_format.c code */
 int
 digest256_to_base64(char *d64, const char *digest)
 {
@@ -2835,6 +2916,7 @@ digest256_to_base64(char *d64, const char *digest)
 /** Given a base64 encoded, nul-terminated digest in <b>d64</b> (without
  * trailing newline or = characters), decode it and store the result in the
  * first DIGEST256_LEN bytes at <b>digest</b>. */
+/* XXXX unify with crypto_format.c code */
 int
 digest256_from_base64(char *digest, const char *d64)
 {

+ 15 - 5
src/common/crypto.h

@@ -146,9 +146,9 @@ int crypto_pk_write_private_key_to_filename(crypto_pk_t *env,
                                             const char *fname);
 
 int crypto_pk_check_key(crypto_pk_t *env);
-int crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b);
-int crypto_pk_eq_keys(crypto_pk_t *a, crypto_pk_t *b);
-size_t crypto_pk_keysize(crypto_pk_t *env);
+int crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b);
+int crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b);
+size_t crypto_pk_keysize(const crypto_pk_t *env);
 int crypto_pk_num_bits(crypto_pk_t *env);
 crypto_pk_t *crypto_pk_dup_key(crypto_pk_t *orig);
 crypto_pk_t *crypto_pk_copy_full(crypto_pk_t *orig);
@@ -160,11 +160,11 @@ int crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen,
 int crypto_pk_private_decrypt(crypto_pk_t *env, char *to, size_t tolen,
                               const char *from, size_t fromlen,
                               int padding, int warnOnFailure);
-int crypto_pk_public_checksig(crypto_pk_t *env, char *to, size_t tolen,
+int crypto_pk_public_checksig(const crypto_pk_t *env, char *to, size_t tolen,
                               const char *from, size_t fromlen);
 int crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data,
                                size_t datalen, const char *sig, size_t siglen);
-int crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen,
+int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen,
                            const char *from, size_t fromlen);
 int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen,
                                   const char *from, size_t fromlen);
@@ -209,6 +209,11 @@ int crypto_digest256(char *digest, const char *m, size_t len,
                      digest_algorithm_t algorithm);
 int crypto_digest_all(digests_t *ds_out, const char *m, size_t len);
 struct smartlist_t;
+void crypto_digest_smartlist_prefix(char *digest_out, size_t len_out,
+                                    const char *prepend,
+                                    const struct smartlist_t *lst,
+                                    const char *append,
+                                    digest_algorithm_t alg);
 void crypto_digest_smartlist(char *digest_out, size_t len_out,
                              const struct smartlist_t *lst, const char *append,
                              digest_algorithm_t alg);
@@ -278,6 +283,11 @@ size_t base64_encode_size(size_t srclen, int flags);
 int base64_encode(char *dest, size_t destlen, const char *src, size_t srclen,
                   int flags);
 int base64_decode(char *dest, size_t destlen, const char *src, size_t srclen);
+int base64_encode_nopad(char *dest, size_t destlen,
+                        const uint8_t *src, size_t srclen);
+int base64_decode_nopad(uint8_t *dest, size_t destlen,
+                        const char *src, size_t srclen);
+
 /** Characters that can appear (case-insensitively) in a base32 encoding. */
 #define BASE32_CHARS "abcdefghijklmnopqrstuvwxyz234567"
 void base32_encode(char *dest, size_t destlen, const char *src, size_t srclen);

+ 21 - 0
src/common/crypto_ed25519.c

@@ -351,3 +351,24 @@ ed25519_pubkey_read_from_file(ed25519_public_key_t *pubkey_out,
   return 0;
 }
 
+/** Release all storage held for <b>kp</b>. */
+void
+ed25519_keypair_free(ed25519_keypair_t *kp)
+{
+  if (! kp)
+    return;
+
+  memwipe(kp, 0, sizeof(*kp));
+  tor_free(kp);
+}
+
+/** Return true iff <b>key1</b> and <b>key2</b> are the same public key. */
+int
+ed25519_pubkey_eq(const ed25519_public_key_t *key1,
+                  const ed25519_public_key_t *key2)
+{
+  tor_assert(key1);
+  tor_assert(key2);
+  return tor_memeq(key1->pubkey, key2->pubkey, ED25519_PUBKEY_LEN);
+}
+

+ 17 - 2
src/common/crypto_ed25519.h

@@ -6,6 +6,7 @@
 
 #include "testsupport.h"
 #include "torint.h"
+#include "crypto_curve25519.h"
 
 #define ED25519_PUBKEY_LEN 32
 #define ED25519_SECKEY_LEN 64
@@ -60,7 +61,7 @@ int ed25519_checksig(const ed25519_signature_t *signature,
  */
 typedef struct {
   /** The public key that supposedly generated the signature. */
-  ed25519_public_key_t *pubkey;
+  const ed25519_public_key_t *pubkey;
   /** The signature to check. */
   ed25519_signature_t signature;
   /** The message that the signature is supposed to have been applied to. */
@@ -87,13 +88,22 @@ int ed25519_public_blind(ed25519_public_key_t *out,
                          const ed25519_public_key_t *inp,
                          const uint8_t *param);
 
-#define ED25519_BASE64_LEN 43
 
+/* XXXX move these to crypto_format.h */
+#define ED25519_BASE64_LEN 43
 int ed25519_public_from_base64(ed25519_public_key_t *pkey,
                                const char *input);
 int ed25519_public_to_base64(char *output,
                              const ed25519_public_key_t *pkey);
 
+/* XXXX move these to crypto_format.h */
+#define ED25519_SIG_BASE64_LEN 86
+
+int ed25519_signature_from_base64(ed25519_signature_t *sig,
+                                  const char *input);
+int ed25519_signature_to_base64(char *output,
+                                const ed25519_signature_t *sig);
+
 /* XXXX read encrypted, write encrypted. */
 
 int ed25519_seckey_write_to_file(const ed25519_secret_key_t *seckey,
@@ -109,5 +119,10 @@ int ed25519_pubkey_read_from_file(ed25519_public_key_t *pubkey_out,
                                   char **tag_out,
                                   const char *filename);
 
+void ed25519_keypair_free(ed25519_keypair_t *kp);
+
+int ed25519_pubkey_eq(const ed25519_public_key_t *key1,
+                      const ed25519_public_key_t *key2);
+
 #endif
 

+ 39 - 0
src/common/crypto_format.c

@@ -65,3 +65,42 @@ ed25519_public_to_base64(char *output,
   return digest256_to_base64(output, (const char *)pkey->pubkey);
 }
 
+/** Encode the signature <b>sig</b> into the buffer at <b>output</b>,
+ * which must have space for ED25519_SIG_BASE64_LEN bytes of encoded signature,
+ * plus one byte for a terminating NUL.  Return 0 on success, -1 on failure.
+ */
+int
+ed25519_signature_to_base64(char *output,
+                            const ed25519_signature_t *sig)
+{
+  char buf[256];
+  int n = base64_encode_nopad(buf, sizeof(buf), sig->sig, ED25519_SIG_LEN);
+  tor_assert(n == ED25519_SIG_BASE64_LEN);
+  memcpy(output, buf, ED25519_SIG_BASE64_LEN+1);
+  return 0;
+}
+
+/** Try to decode the string <b>input</b> into an ed25519 signature. On
+ * success, store the value in <b>sig</b> and return 0. Otherwise return
+ * -1. */
+int
+ed25519_signature_from_base64(ed25519_signature_t *sig,
+                              const char *input)
+{
+
+  if (strlen(input) != ED25519_SIG_BASE64_LEN)
+    return -1;
+  char buf[ED25519_SIG_BASE64_LEN+3];
+  memcpy(buf, input, ED25519_SIG_BASE64_LEN);
+  buf[ED25519_SIG_BASE64_LEN+0] = '=';
+  buf[ED25519_SIG_BASE64_LEN+1] = '=';
+  buf[ED25519_SIG_BASE64_LEN+2] = 0;
+  char decoded[128];
+  int n = base64_decode(decoded, sizeof(decoded), buf, strlen(buf));
+  if (n < 0 || n != ED25519_SIG_LEN)
+    return -1;
+  memcpy(sig->sig, decoded, ED25519_SIG_LEN);
+
+  return 0;
+}
+

+ 38 - 38
src/common/tortls.c

@@ -117,7 +117,7 @@
 #endif
 
 /** Structure that we use for a single certificate. */
-struct tor_cert_t {
+struct tor_x509_cert_t {
   X509 *cert;
   uint8_t *encoded;
   size_t encoded_len;
@@ -132,9 +132,9 @@ struct tor_cert_t {
 typedef struct tor_tls_context_t {
   int refcnt;
   SSL_CTX *ctx;
-  tor_cert_t *my_link_cert;
-  tor_cert_t *my_id_cert;
-  tor_cert_t *my_auth_cert;
+  tor_x509_cert_t *my_link_cert;
+  tor_x509_cert_t *my_id_cert;
+  tor_x509_cert_t *my_auth_cert;
   crypto_pk_t *link_key;
   crypto_pk_t *auth_key;
 } tor_tls_context_t;
@@ -765,7 +765,7 @@ static const int N_CLIENT_CIPHERS = ARRAY_LENGTH(CLIENT_CIPHER_INFO_LIST);
 
 /** Free all storage held in <b>cert</b> */
 void
-tor_cert_free(tor_cert_t *cert)
+tor_x509_cert_free(tor_x509_cert_t *cert)
 {
   if (! cert)
     return;
@@ -777,14 +777,14 @@ tor_cert_free(tor_cert_t *cert)
 }
 
 /**
- * Allocate a new tor_cert_t to hold the certificate "x509_cert".
+ * Allocate a new tor_x509_cert_t to hold the certificate "x509_cert".
  *
  * Steals a reference to x509_cert.
  */
-static tor_cert_t *
-tor_cert_new(X509 *x509_cert)
+static tor_x509_cert_t *
+tor_x509_cert_new(X509 *x509_cert)
 {
-  tor_cert_t *cert;
+  tor_x509_cert_t *cert;
   EVP_PKEY *pkey;
   RSA *rsa;
   int length;
@@ -794,7 +794,7 @@ tor_cert_new(X509 *x509_cert)
     return NULL;
 
   length = i2d_X509(x509_cert, &buf);
-  cert = tor_malloc_zero(sizeof(tor_cert_t));
+  cert = tor_malloc_zero(sizeof(tor_x509_cert_t));
   if (length <= 0 || buf == NULL) {
     tor_free(cert);
     log_err(LD_CRYPTO, "Couldn't get length of encoded x509 certificate");
@@ -824,14 +824,14 @@ tor_cert_new(X509 *x509_cert)
 }
 
 /** Read a DER-encoded X509 cert, of length exactly <b>certificate_len</b>,
- * from a <b>certificate</b>.  Return a newly allocated tor_cert_t on success
- * and NULL on failure. */
-tor_cert_t *
-tor_cert_decode(const uint8_t *certificate, size_t certificate_len)
+ * from a <b>certificate</b>.  Return a newly allocated tor_x509_cert_t on
+ * success and NULL on failure. */
+tor_x509_cert_t *
+tor_x509_cert_decode(const uint8_t *certificate, size_t certificate_len)
 {
   X509 *x509;
   const unsigned char *cp = (const unsigned char *)certificate;
-  tor_cert_t *newcert;
+  tor_x509_cert_t *newcert;
   tor_assert(certificate);
   check_no_tls_errors();
 
@@ -846,14 +846,14 @@ tor_cert_decode(const uint8_t *certificate, size_t certificate_len)
     X509_free(x509);
     goto err; /* Didn't use all the bytes */
   }
-  newcert = tor_cert_new(x509);
+  newcert = tor_x509_cert_new(x509);
   if (!newcert) {
     goto err;
   }
   if (newcert->encoded_len != certificate_len ||
       fast_memneq(newcert->encoded, certificate, certificate_len)) {
     /* Cert wasn't in DER */
-    tor_cert_free(newcert);
+    tor_x509_cert_free(newcert);
     goto err;
   }
   return newcert;
@@ -865,7 +865,7 @@ tor_cert_decode(const uint8_t *certificate, size_t certificate_len)
 /** Set *<b>encoded_out</b> and *<b>size_out</b> to <b>cert</b>'s encoded DER
  * representation and length, respectively. */
 void
-tor_cert_get_der(const tor_cert_t *cert,
+tor_x509_cert_get_der(const tor_x509_cert_t *cert,
                  const uint8_t **encoded_out, size_t *size_out)
 {
   tor_assert(cert);
@@ -878,7 +878,7 @@ tor_cert_get_der(const tor_cert_t *cert,
 /** Return a set of digests for the public key in <b>cert</b>, or NULL if this
  * cert's public key is not one we know how to take the digest of. */
 const digests_t *
-tor_cert_get_id_digests(const tor_cert_t *cert)
+tor_x509_cert_get_id_digests(const tor_x509_cert_t *cert)
 {
   if (cert->pkey_digests_set)
     return &cert->pkey_digests;
@@ -888,7 +888,7 @@ tor_cert_get_id_digests(const tor_cert_t *cert)
 
 /** Return a set of digests for the public key in <b>cert</b>. */
 const digests_t *
-tor_cert_get_cert_digests(const tor_cert_t *cert)
+tor_x509_cert_get_cert_digests(const tor_x509_cert_t *cert)
 {
   return &cert->cert_digests;
 }
@@ -901,9 +901,9 @@ tor_tls_context_decref(tor_tls_context_t *ctx)
   tor_assert(ctx);
   if (--ctx->refcnt == 0) {
     SSL_CTX_free(ctx->ctx);
-    tor_cert_free(ctx->my_link_cert);
-    tor_cert_free(ctx->my_id_cert);
-    tor_cert_free(ctx->my_auth_cert);
+    tor_x509_cert_free(ctx->my_link_cert);
+    tor_x509_cert_free(ctx->my_id_cert);
+    tor_x509_cert_free(ctx->my_auth_cert);
     crypto_pk_free(ctx->link_key);
     crypto_pk_free(ctx->auth_key);
     tor_free(ctx);
@@ -917,8 +917,8 @@ tor_tls_context_decref(tor_tls_context_t *ctx)
  * client mode. */
 int
 tor_tls_get_my_certs(int server,
-                     const tor_cert_t **link_cert_out,
-                     const tor_cert_t **id_cert_out)
+                     const tor_x509_cert_t **link_cert_out,
+                     const tor_x509_cert_t **id_cert_out)
 {
   tor_tls_context_t *ctx = server ? server_tls_context : client_tls_context;
   if (! ctx)
@@ -947,7 +947,7 @@ tor_tls_get_my_client_auth_key(void)
  * certifies.  Return NULL if the cert's key is not RSA.
  */
 crypto_pk_t *
-tor_tls_cert_get_key(tor_cert_t *cert)
+tor_tls_cert_get_key(tor_x509_cert_t *cert)
 {
   crypto_pk_t *result = NULL;
   EVP_PKEY *pkey = X509_get_pubkey(cert->cert);
@@ -967,8 +967,8 @@ tor_tls_cert_get_key(tor_cert_t *cert)
 /** Return true iff the other side of <b>tls</b> has authenticated to us, and
  * the key certified in <b>cert</b> is the same as the key they used to do it.
  */
-int
-tor_tls_cert_matches_key(const tor_tls_t *tls, const tor_cert_t *cert)
+MOCK_IMPL(int,
+tor_tls_cert_matches_key,(const tor_tls_t *tls, const tor_x509_cert_t *cert))
 {
   X509 *peercert = SSL_get_peer_certificate(tls->ssl);
   EVP_PKEY *link_key = NULL, *cert_key = NULL;
@@ -997,8 +997,8 @@ tor_tls_cert_matches_key(const tor_tls_t *tls, const tor_cert_t *cert)
  * we couldn't check it. */
 int
 tor_tls_cert_is_valid(int severity,
-                      const tor_cert_t *cert,
-                      const tor_cert_t *signing_cert,
+                      const tor_x509_cert_t *cert,
+                      const tor_x509_cert_t *signing_cert,
                       int check_rsa_1024)
 {
   check_no_tls_errors();
@@ -1209,9 +1209,9 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime,
   result = tor_malloc_zero(sizeof(tor_tls_context_t));
   result->refcnt = 1;
   if (!is_client) {
-    result->my_link_cert = tor_cert_new(X509_dup(cert));
-    result->my_id_cert = tor_cert_new(X509_dup(idcert));
-    result->my_auth_cert = tor_cert_new(X509_dup(authcert));
+    result->my_link_cert = tor_x509_cert_new(X509_dup(cert));
+    result->my_id_cert = tor_x509_cert_new(X509_dup(idcert));
+    result->my_auth_cert = tor_x509_cert_new(X509_dup(authcert));
     if (!result->my_link_cert || !result->my_id_cert || !result->my_auth_cert)
       goto error;
     result->link_key = crypto_pk_dup_key(rsa);
@@ -2346,15 +2346,15 @@ tor_tls_peer_has_cert(tor_tls_t *tls)
 }
 
 /** Return the peer certificate, or NULL if there isn't one. */
-tor_cert_t *
-tor_tls_get_peer_cert(tor_tls_t *tls)
+MOCK_IMPL(tor_x509_cert_t *,
+tor_tls_get_peer_cert,(tor_tls_t *tls))
 {
   X509 *cert;
   cert = SSL_get_peer_certificate(tls->ssl);
   tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "getting peer certificate");
   if (!cert)
     return NULL;
-  return tor_cert_new(cert);
+  return tor_x509_cert_new(cert);
 }
 
 /** Warn that a certificate lifetime extends through a certain range. */
@@ -2772,8 +2772,8 @@ tor_tls_server_got_renegotiate(tor_tls_t *tls)
  * the v3 handshake to prove that the client knows the TLS secrets for the
  * connection <b>tls</b>.  Return 0 on success, -1 on failure.
  */
-int
-tor_tls_get_tlssecrets(tor_tls_t *tls, uint8_t *secrets_out)
+MOCK_IMPL(int,
+tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out))
 {
 #define TLSSECRET_MAGIC "Tor V3 handshake TLS cross-certification"
   char buf[128];

+ 15 - 14
src/common/tortls.h

@@ -19,7 +19,7 @@
 typedef struct tor_tls_t tor_tls_t;
 
 /* Opaque structure to hold an X509 certificate. */
-typedef struct tor_cert_t tor_cert_t;
+typedef struct tor_x509_cert_t tor_x509_cert_t;
 
 /* Possible return values for most tor_tls_* functions. */
 #define MIN_TOR_TLS_ERROR_VAL_     -9
@@ -72,7 +72,7 @@ void tor_tls_set_renegotiate_callback(tor_tls_t *tls,
 int tor_tls_is_server(tor_tls_t *tls);
 void tor_tls_free(tor_tls_t *tls);
 int tor_tls_peer_has_cert(tor_tls_t *tls);
-tor_cert_t *tor_tls_get_peer_cert(tor_tls_t *tls);
+MOCK_DECL(tor_x509_cert_t *,tor_tls_get_peer_cert,(tor_tls_t *tls));
 int tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity);
 int tor_tls_check_lifetime(int severity,
                            tor_tls_t *tls, int past_tolerance,
@@ -102,7 +102,7 @@ int tor_tls_used_v1_handshake(tor_tls_t *tls);
 int tor_tls_received_v3_certificate(tor_tls_t *tls);
 int tor_tls_get_num_server_handshakes(tor_tls_t *tls);
 int tor_tls_server_got_renegotiate(tor_tls_t *tls);
-int tor_tls_get_tlssecrets(tor_tls_t *tls, uint8_t *secrets_out);
+MOCK_DECL(int,tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out));
 
 /* Log and abort if there are unhandled TLS errors in OpenSSL's error stack.
  */
@@ -120,22 +120,23 @@ struct bufferevent *tor_tls_init_bufferevent(tor_tls_t *tls,
                                      int filter);
 #endif
 
-void tor_cert_free(tor_cert_t *cert);
-tor_cert_t *tor_cert_decode(const uint8_t *certificate,
+void tor_x509_cert_free(tor_x509_cert_t *cert);
+tor_x509_cert_t *tor_x509_cert_decode(const uint8_t *certificate,
                             size_t certificate_len);
-void tor_cert_get_der(const tor_cert_t *cert,
+void tor_x509_cert_get_der(const tor_x509_cert_t *cert,
                       const uint8_t **encoded_out, size_t *size_out);
-const digests_t *tor_cert_get_id_digests(const tor_cert_t *cert);
-const digests_t *tor_cert_get_cert_digests(const tor_cert_t *cert);
+const digests_t *tor_x509_cert_get_id_digests(const tor_x509_cert_t *cert);
+const digests_t *tor_x509_cert_get_cert_digests(const tor_x509_cert_t *cert);
 int tor_tls_get_my_certs(int server,
-                         const tor_cert_t **link_cert_out,
-                         const tor_cert_t **id_cert_out);
+                         const tor_x509_cert_t **link_cert_out,
+                         const tor_x509_cert_t **id_cert_out);
 crypto_pk_t *tor_tls_get_my_client_auth_key(void);
-crypto_pk_t *tor_tls_cert_get_key(tor_cert_t *cert);
-int tor_tls_cert_matches_key(const tor_tls_t *tls, const tor_cert_t *cert);
+crypto_pk_t *tor_tls_cert_get_key(tor_x509_cert_t *cert);
+MOCK_DECL(int,tor_tls_cert_matches_key,(const tor_tls_t *tls,
+                                        const tor_x509_cert_t *cert));
 int tor_tls_cert_is_valid(int severity,
-                          const tor_cert_t *cert,
-                          const tor_cert_t *signing_cert,
+                          const tor_x509_cert_t *cert,
+                          const tor_x509_cert_t *signing_cert,
                           int check_rsa_1024);
 const char *tor_tls_get_ciphersuite_name(tor_tls_t *tls);
 

+ 4 - 4
src/or/channel.c

@@ -4431,10 +4431,10 @@ channel_num_circuits(channel_t *chan)
  * This is called when setting up a channel and replaces the old
  * connection_or_set_circid_type()
  */
-void
-channel_set_circid_type(channel_t *chan,
-                        crypto_pk_t *identity_rcvd,
-                        int consider_identity)
+MOCK_IMPL(void,
+channel_set_circid_type,(channel_t *chan,
+                         crypto_pk_t *identity_rcvd,
+                         int consider_identity))
 {
   int started_here;
   crypto_pk_t *our_identity;

+ 3 - 2
src/or/channel.h

@@ -562,8 +562,9 @@ int channel_matches_extend_info(channel_t *chan, extend_info_t *extend_info);
 int channel_matches_target_addr_for_extend(channel_t *chan,
                                            const tor_addr_t *target);
 unsigned int channel_num_circuits(channel_t *chan);
-void channel_set_circid_type(channel_t *chan, crypto_pk_t *identity_rcvd,
-                             int consider_identity);
+MOCK_DECL(void,channel_set_circid_type,(channel_t *chan,
+                                        crypto_pk_t *identity_rcvd,
+                                        int consider_identity));
 void channel_timestamp_client(channel_t *chan);
 void channel_update_xmit_queue_size(channel_t *chan);
 

+ 69 - 92
src/or/channeltls.c

@@ -13,6 +13,8 @@
 
 #define TOR_CHANNEL_INTERNAL_
 
+#define CHANNELTLS_PRIVATE
+
 #include "or.h"
 #include "channel.h"
 #include "channeltls.h"
@@ -22,6 +24,7 @@
 #include "connection.h"
 #include "connection_or.h"
 #include "control.h"
+#include "link_handshake.h"
 #include "relay.h"
 #include "rephist.h"
 #include "router.h"
@@ -48,9 +51,6 @@ uint64_t stats_n_authorize_cells_processed = 0;
 /** Active listener, if any */
 channel_listener_t *channel_tls_listener = NULL;
 
-/* Utility function declarations */
-static void channel_tls_common_init(channel_tls_t *tlschan);
-
 /* channel_tls_t method declarations */
 
 static void channel_tls_close_method(channel_t *chan);
@@ -92,12 +92,6 @@ static void channel_tls_process_versions_cell(var_cell_t *cell,
                                               channel_tls_t *tlschan);
 static void channel_tls_process_netinfo_cell(cell_t *cell,
                                              channel_tls_t *tlschan);
-static void channel_tls_process_certs_cell(var_cell_t *cell,
-                                           channel_tls_t *tlschan);
-static void channel_tls_process_auth_challenge_cell(var_cell_t *cell,
-                                                    channel_tls_t *tlschan);
-static void channel_tls_process_authenticate_cell(var_cell_t *cell,
-                                                  channel_tls_t *tlschan);
 static int command_allowed_before_handshake(uint8_t command);
 static int enter_v3_handshake_with_cell(var_cell_t *cell,
                                         channel_tls_t *tlschan);
@@ -107,7 +101,7 @@ static int enter_v3_handshake_with_cell(var_cell_t *cell,
  * and channel_tls_handle_incoming().
  */
 
-static void
+STATIC void
 channel_tls_common_init(channel_tls_t *tlschan)
 {
   channel_t *chan;
@@ -1747,16 +1741,17 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
  * If it's the server side, wait for an AUTHENTICATE cell.
  */
 
-static void
+STATIC void
 channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
 {
-  tor_cert_t *link_cert = NULL;
-  tor_cert_t *id_cert = NULL;
-  tor_cert_t *auth_cert = NULL;
-  uint8_t *ptr;
+#define MAX_CERT_TYPE_WANTED OR_CERT_TYPE_AUTH_1024
+  tor_x509_cert_t *certs[MAX_CERT_TYPE_WANTED + 1];
   int n_certs, i;
+  certs_cell_t *cc = NULL;
+
   int send_netinfo = 0;
 
+  memset(certs, 0, sizeof(certs));
   tor_assert(cell);
   tor_assert(chan);
   tor_assert(chan->conn);
@@ -1786,63 +1781,41 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
   if (cell->circ_id)
     ERR("It had a nonzero circuit ID");
 
-  n_certs = cell->payload[0];
-  ptr = cell->payload + 1;
+  if (certs_cell_parse(&cc, cell->payload, cell->payload_len) < 0)
+    ERR("It couldn't be parsed.");
+
+  n_certs = cc->n_certs;
+
   for (i = 0; i < n_certs; ++i) {
-    uint8_t cert_type;
-    uint16_t cert_len;
-    if (cell->payload_len < 3)
-      goto truncated;
-    if (ptr > cell->payload + cell->payload_len - 3) {
-      goto truncated;
-    }
-    cert_type = *ptr;
-    cert_len = ntohs(get_uint16(ptr+1));
-    if (cell->payload_len < 3 + cert_len)
-      goto truncated;
-    if (ptr > cell->payload + cell->payload_len - cert_len - 3) {
-      goto truncated;
-    }
-    if (cert_type == OR_CERT_TYPE_TLS_LINK ||
-        cert_type == OR_CERT_TYPE_ID_1024 ||
-        cert_type == OR_CERT_TYPE_AUTH_1024) {
-      tor_cert_t *cert = tor_cert_decode(ptr + 3, cert_len);
-      if (!cert) {
-        log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
-               "Received undecodable certificate in CERTS cell from %s:%d",
-               safe_str(chan->conn->base_.address),
-               chan->conn->base_.port);
+    certs_cell_cert_t *c = certs_cell_get_certs(cc, i);
+
+    uint16_t cert_type = c->cert_type;
+    uint16_t cert_len = c->cert_len;
+    uint8_t *cert_body = certs_cell_cert_getarray_body(c);
+
+    if (cert_type > MAX_CERT_TYPE_WANTED)
+      continue;
+
+    tor_x509_cert_t *cert = tor_x509_cert_decode(cert_body, cert_len);
+    if (!cert) {
+      log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+             "Received undecodable certificate in CERTS cell from %s:%d",
+             safe_str(chan->conn->base_.address),
+             chan->conn->base_.port);
+    } else {
+      if (certs[cert_type]) {
+        tor_x509_cert_free(cert);
+        ERR("Duplicate x509 certificate");
       } else {
-        if (cert_type == OR_CERT_TYPE_TLS_LINK) {
-          if (link_cert) {
-            tor_cert_free(cert);
-            ERR("Too many TLS_LINK certificates");
-          }
-          link_cert = cert;
-        } else if (cert_type == OR_CERT_TYPE_ID_1024) {
-          if (id_cert) {
-            tor_cert_free(cert);
-            ERR("Too many ID_1024 certificates");
-          }
-          id_cert = cert;
-        } else if (cert_type == OR_CERT_TYPE_AUTH_1024) {
-          if (auth_cert) {
-            tor_cert_free(cert);
-            ERR("Too many AUTH_1024 certificates");
-          }
-          auth_cert = cert;
-        } else {
-          tor_cert_free(cert);
-        }
+        certs[cert_type] = cert;
       }
     }
-    ptr += 3 + cert_len;
-    continue;
-
-  truncated:
-    ERR("It ends in the middle of a certificate");
   }
 
+  tor_x509_cert_t *id_cert = certs[OR_CERT_TYPE_ID_1024];
+  tor_x509_cert_t *auth_cert = certs[OR_CERT_TYPE_AUTH_1024];
+  tor_x509_cert_t *link_cert = certs[OR_CERT_TYPE_TLS_LINK];
+
   if (chan->conn->handshake_state->started_here) {
     int severity;
     if (! (id_cert && link_cert))
@@ -1867,7 +1840,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
 
     chan->conn->handshake_state->authenticated = 1;
     {
-      const digests_t *id_digests = tor_cert_get_id_digests(id_cert);
+      const digests_t *id_digests = tor_x509_cert_get_id_digests(id_cert);
       crypto_pk_t *identity_rcvd;
       if (!id_digests)
         ERR("Couldn't compute digests for key in ID cert");
@@ -1891,7 +1864,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
              safe_str(chan->conn->base_.address), chan->conn->base_.port);
 
     chan->conn->handshake_state->id_cert = id_cert;
-    id_cert = NULL;
+    certs[OR_CERT_TYPE_ID_1024] = NULL;
 
     if (!public_server_mode(get_options())) {
       /* If we initiated the connection and we are not a public server, we
@@ -1918,7 +1891,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
 
     chan->conn->handshake_state->id_cert = id_cert;
     chan->conn->handshake_state->auth_cert = auth_cert;
-    id_cert = auth_cert = NULL;
+    certs[OR_CERT_TYPE_ID_1024] = certs[OR_CERT_TYPE_AUTH_1024] = NULL;
   }
 
   chan->conn->handshake_state->received_certs_cell = 1;
@@ -1932,9 +1905,10 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
   }
 
  err:
-  tor_cert_free(id_cert);
-  tor_cert_free(link_cert);
-  tor_cert_free(auth_cert);
+  for (unsigned i = 0; i < ARRAY_LENGTH(certs); ++i) {
+    tor_x509_cert_free(certs[i]);
+  }
+  certs_cell_free(cc);
 #undef ERR
 }
 
@@ -1949,11 +1923,11 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
  * want to authenticate, send an AUTHENTICATE cell and then a NETINFO cell.
  */
 
-static void
+STATIC void
 channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
 {
   int n_types, i, use_type = -1;
-  uint8_t *cp;
+  auth_challenge_cell_t *ac = NULL;
 
   tor_assert(cell);
   tor_assert(chan);
@@ -1966,7 +1940,7 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
            safe_str(chan->conn->base_.address),                 \
            chan->conn->base_.port, (s));                        \
     connection_or_close_for_error(chan->conn, 0);               \
-    return;                                                     \
+    goto done;                                                  \
   } while (0)
 
   if (chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V3)
@@ -1979,19 +1953,17 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
     ERR("We already received one");
   if (!(chan->conn->handshake_state->received_certs_cell))
     ERR("We haven't gotten a CERTS cell yet");
-  if (cell->payload_len < OR_AUTH_CHALLENGE_LEN + 2)
-    ERR("It was too short");
   if (cell->circ_id)
     ERR("It had a nonzero circuit ID");
 
-  n_types = ntohs(get_uint16(cell->payload + OR_AUTH_CHALLENGE_LEN));
-  if (cell->payload_len < OR_AUTH_CHALLENGE_LEN + 2 + 2*n_types)
-    ERR("It looks truncated");
+  if (auth_challenge_cell_parse(&ac, cell->payload, cell->payload_len) < 0)
+    ERR("It was not well-formed.");
+
+  n_types = ac->n_methods;
 
   /* Now see if there is an authentication type we can use */
-  cp = cell->payload+OR_AUTH_CHALLENGE_LEN + 2;
-  for (i = 0; i < n_types; ++i, cp += 2) {
-    uint16_t authtype = ntohs(get_uint16(cp));
+  for (i = 0; i < n_types; ++i) {
+    uint16_t authtype = auth_challenge_cell_get_methods(ac, i);
     if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET)
       use_type = authtype;
   }
@@ -2002,7 +1974,7 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
     /* If we're not a public server then we don't want to authenticate on a
        connection we originated, and we already sent a NETINFO cell when we
        got the CERTS cell. We have nothing more to do. */
-    return;
+    goto done;
   }
 
   if (use_type >= 0) {
@@ -2016,7 +1988,7 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
       log_warn(LD_OR,
                "Couldn't send authenticate cell");
       connection_or_close_for_error(chan->conn, 0);
-      return;
+      goto done;
     }
   } else {
     log_info(LD_OR,
@@ -2029,9 +2001,12 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
   if (connection_or_send_netinfo(chan->conn) < 0) {
     log_warn(LD_OR, "Couldn't send netinfo cell");
     connection_or_close_for_error(chan->conn, 0);
-    return;
+    goto done;
   }
 
+done:
+  auth_challenge_cell_free(ac);
+
 #undef ERR
 }
 
@@ -2045,10 +2020,10 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
  * the identity of the router on the other side of the connection.
  */
 
-static void
+STATIC void
 channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
 {
-  uint8_t expected[V3_AUTH_FIXED_PART_LEN];
+  uint8_t expected[V3_AUTH_FIXED_PART_LEN+256];
   const uint8_t *auth;
   int authlen;
 
@@ -2104,11 +2079,13 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
   if (authlen < V3_AUTH_BODY_LEN + 1)
     ERR("Authenticator was too short");
 
-  if (connection_or_compute_authenticate_cell_body(
-                        chan->conn, expected, sizeof(expected), NULL, 1) < 0)
+  ssize_t bodylen =
+    connection_or_compute_authenticate_cell_body(
+                        chan->conn, expected, sizeof(expected), NULL, 1);
+  if (bodylen < 0 || bodylen != V3_AUTH_FIXED_PART_LEN)
     ERR("Couldn't compute expected AUTHENTICATE cell body");
 
-  if (tor_memneq(expected, auth, sizeof(expected)))
+  if (tor_memneq(expected, auth, bodylen))
     ERR("Some field in the AUTHENTICATE cell body was not as expected");
 
   {
@@ -2154,7 +2131,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
     crypto_pk_t *identity_rcvd =
       tor_tls_cert_get_key(chan->conn->handshake_state->id_cert);
     const digests_t *id_digests =
-      tor_cert_get_id_digests(chan->conn->handshake_state->id_cert);
+      tor_x509_cert_get_id_digests(chan->conn->handshake_state->id_cert);
 
     /* This must exist; we checked key type when reading the cert. */
     tor_assert(id_digests);

+ 10 - 0
src/or/channeltls.h

@@ -52,5 +52,15 @@ void channel_tls_update_marks(or_connection_t *conn);
 /* Cleanup at shutdown */
 void channel_tls_free_all(void);
 
+#ifdef CHANNELTLS_PRIVATE
+STATIC void channel_tls_process_certs_cell(var_cell_t *cell,
+                                           channel_tls_t *tlschan);
+STATIC void channel_tls_process_auth_challenge_cell(var_cell_t *cell,
+                                                    channel_tls_t *tlschan);
+STATIC void channel_tls_common_init(channel_tls_t *tlschan);
+STATIC void channel_tls_process_authenticate_cell(var_cell_t *cell,
+                                                  channel_tls_t *tlschan);
+#endif
+
 #endif
 

+ 20 - 0
src/or/config.c

@@ -300,6 +300,7 @@ static config_var_t option_vars_[] = {
   VAR("ServerTransportPlugin",   LINELIST, ServerTransportPlugin,  NULL),
   V(ServerTransportListenAddr,   LINELIST, NULL),
   V(ServerTransportOptions,      LINELIST, NULL),
+  V(SigningKeyLifetime,          INTERVAL, "30 days"),
   V(Socks4Proxy,                 STRING,   NULL),
   V(Socks5Proxy,                 STRING,   NULL),
   V(Socks5ProxyUsername,         STRING,   NULL),
@@ -358,6 +359,13 @@ static config_var_t option_vars_[] = {
   V(TestingTorNetwork,           BOOL,     "0"),
   V(TestingMinExitFlagThreshold, MEMUNIT,  "0"),
   V(TestingMinFastFlagThreshold, MEMUNIT,  "0"),
+
+  V(TestingLinkCertLifetime,          INTERVAL, "2 days"),
+  V(TestingAuthKeyLifetime,          INTERVAL, "2 days"),
+  V(TestingLinkKeySlop,              INTERVAL, "3 hours"),
+  V(TestingAuthKeySlop,              INTERVAL, "3 hours"),
+  V(TestingSigningKeySlop,           INTERVAL, "1 day"),
+
   V(OptimisticData,              AUTOBOOL, "auto"),
   V(PortForwarding,              BOOL,     "0"),
   V(PortForwardingHelper,        FILENAME, "tor-fw-helper"),
@@ -3688,8 +3696,20 @@ options_validate(or_options_t *old_options, or_options_t *options,
   CHECK_DEFAULT(TestingDescriptorMaxDownloadTries);
   CHECK_DEFAULT(TestingMicrodescMaxDownloadTries);
   CHECK_DEFAULT(TestingCertMaxDownloadTries);
+  CHECK_DEFAULT(TestingAuthKeyLifetime);
+  CHECK_DEFAULT(TestingLinkCertLifetime);
+  CHECK_DEFAULT(TestingSigningKeySlop);
+  CHECK_DEFAULT(TestingAuthKeySlop);
+  CHECK_DEFAULT(TestingLinkKeySlop);
 #undef CHECK_DEFAULT
 
+  if (options->SigningKeyLifetime < options->TestingSigningKeySlop*2)
+    REJECT("SigningKeyLifetime is too short.");
+  if (options->TestingLinkCertLifetime < options->TestingAuthKeySlop*2)
+    REJECT("LinkCertLifetime is too short.");
+  if (options->TestingAuthKeyLifetime < options->TestingLinkKeySlop*2)
+    REJECT("TestingAuthKeyLifetime is too short.");
+
   if (options->TestingV3AuthInitialVotingInterval
       < MIN_VOTE_INTERVAL_TESTING_INITIAL) {
     REJECT("TestingV3AuthInitialVotingInterval is insanely low.");

+ 4 - 0
src/or/config.h

@@ -61,6 +61,10 @@ char *options_get_datadir_fname2_suffix(const or_options_t *options,
  * get_datadir_fname2_suffix.  */
 #define get_datadir_fname2(sub1,sub2) \
   get_datadir_fname2_suffix((sub1), (sub2), NULL)
+/** Return a newly allocated string containing datadir/sub1/sub2 relative to
+ * opts.  See get_datadir_fname2_suffix.  */
+#define options_get_datadir_fname2(opts,sub1,sub2)                      \
+  options_get_datadir_fname2_suffix((opts),(sub1), (sub2), NULL)
 /** Return a newly allocated string containing datadir/sub1suffix.  See
  * get_datadir_fname2_suffix. */
 #define get_datadir_fname_suffix(sub1, suffix) \

+ 108 - 72
src/or/connection_or.c

@@ -30,6 +30,7 @@
 #include "entrynodes.h"
 #include "geoip.h"
 #include "main.h"
+#include "link_handshake.h"
 #include "networkstatus.h"
 #include "nodelist.h"
 #include "reasons.h"
@@ -1318,8 +1319,8 @@ connection_or_close_normally(or_connection_t *orconn, int flush)
  * the error state.
  */
 
-void
-connection_or_close_for_error(or_connection_t *orconn, int flush)
+MOCK_IMPL(void,
+connection_or_close_for_error,(or_connection_t *orconn, int flush))
 {
   channel_t *chan = NULL;
 
@@ -1879,8 +1880,8 @@ or_handshake_state_free(or_handshake_state_t *state)
     return;
   crypto_digest_free(state->digest_sent);
   crypto_digest_free(state->digest_received);
-  tor_cert_free(state->auth_cert);
-  tor_cert_free(state->id_cert);
+  tor_x509_cert_free(state->auth_cert);
+  tor_x509_cert_free(state->id_cert);
   memwipe(state, 0xBE, sizeof(or_handshake_state_t));
   tor_free(state);
 }
@@ -2013,9 +2014,9 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn)
  * <b>conn</b>'s outbuf.  Right now, this <em>DOES NOT</em> support cells that
  * affect a circuit.
  */
-void
-connection_or_write_var_cell_to_buf(const var_cell_t *cell,
-                                    or_connection_t *conn)
+MOCK_IMPL(void,
+connection_or_write_var_cell_to_buf,(const var_cell_t *cell,
+                                     or_connection_t *conn))
 {
   int n;
   char hdr[VAR_CELL_MAX_HEADER_SIZE];
@@ -2158,8 +2159,8 @@ connection_or_send_versions(or_connection_t *conn, int v3_plus)
 
 /** Send a NETINFO cell on <b>conn</b>, telling the other server what we know
  * about their address, our address, and the current time. */
-int
-connection_or_send_netinfo(or_connection_t *conn)
+MOCK_IMPL(int,
+connection_or_send_netinfo,(or_connection_t *conn))
 {
   cell_t cell;
   time_t now = time(NULL);
@@ -2228,7 +2229,7 @@ connection_or_send_netinfo(or_connection_t *conn)
 int
 connection_or_send_certs_cell(or_connection_t *conn)
 {
-  const tor_cert_t *link_cert = NULL, *id_cert = NULL;
+  const tor_x509_cert_t *link_cert = NULL, *id_cert = NULL;
   const uint8_t *link_encoded = NULL, *id_encoded = NULL;
   size_t link_len, id_len;
   var_cell_t *cell;
@@ -2243,8 +2244,8 @@ connection_or_send_certs_cell(or_connection_t *conn)
   server_mode = ! conn->handshake_state->started_here;
   if (tor_tls_get_my_certs(server_mode, &link_cert, &id_cert) < 0)
     return -1;
-  tor_cert_get_der(link_cert, &link_encoded, &link_len);
-  tor_cert_get_der(id_cert, &id_encoded, &id_len);
+  tor_x509_cert_get_der(link_cert, &link_encoded, &link_len);
+  tor_x509_cert_get_der(id_cert, &id_encoded, &id_len);
 
   cell_len = 1 /* 1 byte: num certs in cell */ +
              2 * ( 1 + 2 ) /* For each cert: 1 byte for type, 2 for length */ +
@@ -2280,28 +2281,37 @@ connection_or_send_certs_cell(or_connection_t *conn)
 int
 connection_or_send_auth_challenge_cell(or_connection_t *conn)
 {
-  var_cell_t *cell;
-  uint8_t *cp;
-  uint8_t challenge[OR_AUTH_CHALLENGE_LEN];
+  var_cell_t *cell = NULL;
+  int r = -1;
   tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3);
 
   if (! conn->handshake_state)
     return -1;
 
-  if (crypto_rand((char*)challenge, OR_AUTH_CHALLENGE_LEN) < 0)
-    return -1;
-  cell = var_cell_new(OR_AUTH_CHALLENGE_LEN + 4);
+  auth_challenge_cell_t *ac = auth_challenge_cell_new();
+
+  if (crypto_rand((char*)ac->challenge, sizeof(ac->challenge)) < 0)
+    goto done;
+
+  auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_TLSSECRET);
+  auth_challenge_cell_set_n_methods(ac,
+                                    auth_challenge_cell_getlen_methods(ac));
+
+  cell = var_cell_new(auth_challenge_cell_encoded_len(ac));
+  ssize_t len = auth_challenge_cell_encode(cell->payload, cell->payload_len,
+                                           ac);
+  if (len != cell->payload_len)
+    goto done;
   cell->command = CELL_AUTH_CHALLENGE;
-  memcpy(cell->payload, challenge, OR_AUTH_CHALLENGE_LEN);
-  cp = cell->payload + OR_AUTH_CHALLENGE_LEN;
-  set_uint16(cp, htons(1)); /* We recognize one authentication type. */
-  set_uint16(cp+2, htons(AUTHTYPE_RSA_SHA256_TLSSECRET));
 
   connection_or_write_var_cell_to_buf(cell, conn);
+  r = 0;
+
+ done:
   var_cell_free(cell);
-  memwipe(challenge, 0, sizeof(challenge));
+  auth_challenge_cell_free(ac);
 
-  return 0;
+  return r;
 }
 
 /** Compute the main body of an AUTHENTICATE cell that a client can use
@@ -2328,28 +2338,28 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
                                              crypto_pk_t *signing_key,
                                              int server)
 {
-  uint8_t *ptr;
+  auth1_t *auth = NULL;
+  auth_ctx_t *ctx = auth_ctx_new();
+  int result;
 
   /* assert state is reasonable XXXX */
 
-  if (outlen < V3_AUTH_FIXED_PART_LEN ||
-      (!server && outlen < V3_AUTH_BODY_LEN))
-    return -1;
+  ctx->is_ed = 0;
 
-  ptr = out;
+  auth = auth1_new();
 
   /* Type: 8 bytes. */
-  memcpy(ptr, "AUTH0001", 8);
-  ptr += 8;
+  memcpy(auth1_getarray_type(auth), "AUTH0001", 8);
 
   {
-    const tor_cert_t *id_cert=NULL, *link_cert=NULL;
+    const tor_x509_cert_t *id_cert=NULL, *link_cert=NULL;
     const digests_t *my_digests, *their_digests;
     const uint8_t *my_id, *their_id, *client_id, *server_id;
     if (tor_tls_get_my_certs(server, &link_cert, &id_cert))
       return -1;
-    my_digests = tor_cert_get_id_digests(id_cert);
-    their_digests = tor_cert_get_id_digests(conn->handshake_state->id_cert);
+    my_digests = tor_x509_cert_get_id_digests(id_cert);
+    their_digests =
+      tor_x509_cert_get_id_digests(conn->handshake_state->id_cert);
     tor_assert(my_digests);
     tor_assert(their_digests);
     my_id = (uint8_t*)my_digests->d[DIGEST_SHA256];
@@ -2359,12 +2369,10 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
     server_id = server ? my_id : their_id;
 
     /* Client ID digest: 32 octets. */
-    memcpy(ptr, client_id, 32);
-    ptr += 32;
+    memcpy(auth->cid, client_id, 32);
 
     /* Server ID digest: 32 octets. */
-    memcpy(ptr, server_id, 32);
-    ptr += 32;
+    memcpy(auth->sid, server_id, 32);
   }
 
   {
@@ -2378,73 +2386,101 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
     }
 
     /* Server log digest : 32 octets */
-    crypto_digest_get_digest(server_d, (char*)ptr, 32);
-    ptr += 32;
+    crypto_digest_get_digest(server_d, (char*)auth->slog, 32);
 
     /* Client log digest : 32 octets */
-    crypto_digest_get_digest(client_d, (char*)ptr, 32);
-    ptr += 32;
+    crypto_digest_get_digest(client_d, (char*)auth->clog, 32);
   }
 
   {
     /* Digest of cert used on TLS link : 32 octets. */
-    const tor_cert_t *cert = NULL;
-    tor_cert_t *freecert = NULL;
+    const tor_x509_cert_t *cert = NULL;
+    tor_x509_cert_t *freecert = NULL;
     if (server) {
       tor_tls_get_my_certs(1, &cert, NULL);
     } else {
       freecert = tor_tls_get_peer_cert(conn->tls);
       cert = freecert;
     }
-    if (!cert)
-      return -1;
-    memcpy(ptr, tor_cert_get_cert_digests(cert)->d[DIGEST_SHA256], 32);
+    if (!cert) {
+      log_warn(LD_OR, "Unable to find cert when making AUTH1 data.");
+      goto err;
+    }
+
+    memcpy(auth->scert,
+           tor_x509_cert_get_cert_digests(cert)->d[DIGEST_SHA256], 32);
 
     if (freecert)
-      tor_cert_free(freecert);
-    ptr += 32;
+      tor_x509_cert_free(freecert);
   }
 
   /* HMAC of clientrandom and serverrandom using master key : 32 octets */
-  tor_tls_get_tlssecrets(conn->tls, ptr);
-  ptr += 32;
-
-  tor_assert(ptr - out == V3_AUTH_FIXED_PART_LEN);
-
-  if (server)
-    return V3_AUTH_FIXED_PART_LEN; // ptr-out
+  tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets);
 
   /* 8 octets were reserved for the current time, but we're trying to get out
    * of the habit of sending time around willynilly.  Fortunately, nothing
    * checks it.  That's followed by 16 bytes of nonce. */
-  crypto_rand((char*)ptr, 24);
-  ptr += 24;
+  crypto_rand((char*)auth->rand, 24);
 
-  tor_assert(ptr - out == V3_AUTH_BODY_LEN);
+  ssize_t len;
+  if ((len = auth1_encode(out, outlen, auth, ctx)) < 0) {
+    log_warn(LD_OR, "Unable to encode signed part of AUTH1 data.");
+    goto err;
+  }
 
-  if (!signing_key)
-    return V3_AUTH_BODY_LEN; // ptr - out
+  if (server) {
+    auth1_t *tmp = NULL;
+    ssize_t len2 = auth1_parse(&tmp, out, len, ctx);
+    if (!tmp) {
+      log_warn(LD_OR, "Unable to parse signed part of AUTH1 data.");
+      goto err;
+    }
+    result = (int) (tmp->end_of_fixed_part - out);
+    auth1_free(tmp);
+    if (len2 != len) {
+      log_warn(LD_OR, "Mismatched length when re-parsing AUTH1 data.");
+      goto err;
+    }
+    goto done;
+  }
+
+  if (signing_key) {
+    auth1_setlen_sig(auth, crypto_pk_keysize(signing_key));
 
-  {
-    int siglen;
     char d[32];
-    crypto_digest256(d, (char*)out, ptr-out, DIGEST_SHA256);
-    siglen = crypto_pk_private_sign(signing_key,
-                                    (char*)ptr, outlen - (ptr-out),
+    crypto_digest256(d, (char*)out, len, DIGEST_SHA256);
+    int siglen = crypto_pk_private_sign(signing_key,
+                                    (char*)auth1_getarray_sig(auth),
+                                    auth1_getlen_sig(auth),
                                     d, 32);
-    if (siglen < 0)
+    if (siglen < 0) {
+      log_warn(LD_OR, "Unable to sign AUTH1 data.");
       return -1;
+    }
 
-    ptr += siglen;
-    tor_assert(ptr <= out+outlen);
-    return (int)(ptr - out);
+    auth1_setlen_sig(auth, siglen);
+
+    len = auth1_encode(out, outlen, auth, ctx);
+    if (len < 0) {
+      log_warn(LD_OR, "Unable to encode signed AUTH1 data.");
+      goto err;
+    }
   }
+  result = (int) len;
+  goto done;
+
+ err:
+  result = -1;
+ done:
+  auth1_free(auth);
+  auth_ctx_free(ctx);
+  return result;
 }
 
 /** Send an AUTHENTICATE cell on the connection <b>conn</b>.  Return 0 on
  * success, -1 on failure */
-int
-connection_or_send_authenticate_cell(or_connection_t *conn, int authtype)
+MOCK_IMPL(int,
+connection_or_send_authenticate_cell,(or_connection_t *conn, int authtype))
 {
   var_cell_t *cell;
   crypto_pk_t *pk = tor_tls_get_my_client_auth_key();

+ 7 - 5
src/or/connection_or.h

@@ -43,7 +43,8 @@ MOCK_DECL(or_connection_t *,
            const char *id_digest, channel_tls_t *chan));
 
 void connection_or_close_normally(or_connection_t *orconn, int flush);
-void connection_or_close_for_error(or_connection_t *orconn, int flush);
+MOCK_DECL(void,connection_or_close_for_error,
+          (or_connection_t *orconn, int flush));
 
 void connection_or_report_broken_states(int severity, int domain);
 
@@ -77,17 +78,18 @@ void or_handshake_state_record_var_cell(or_connection_t *conn,
 int connection_or_set_state_open(or_connection_t *conn);
 void connection_or_write_cell_to_buf(const cell_t *cell,
                                      or_connection_t *conn);
-void connection_or_write_var_cell_to_buf(const var_cell_t *cell,
-                                         or_connection_t *conn);
+MOCK_DECL(void,connection_or_write_var_cell_to_buf,(const var_cell_t *cell,
+                                                   or_connection_t *conn));
 int connection_or_send_versions(or_connection_t *conn, int v3_plus);
-int connection_or_send_netinfo(or_connection_t *conn);
+MOCK_DECL(int,connection_or_send_netinfo,(or_connection_t *conn));
 int connection_or_send_certs_cell(or_connection_t *conn);
 int connection_or_send_auth_challenge_cell(or_connection_t *conn);
 int connection_or_compute_authenticate_cell_body(or_connection_t *conn,
                                                  uint8_t *out, size_t outlen,
                                                  crypto_pk_t *signing_key,
                                                  int server);
-int connection_or_send_authenticate_cell(or_connection_t *conn, int type);
+MOCK_DECL(int,connection_or_send_authenticate_cell,
+          (or_connection_t *conn, int type));
 
 int is_or_protocol_version_known(uint16_t version);
 

+ 257 - 0
src/or/dircollate.c

@@ -0,0 +1,257 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file dircollate.c
+ *
+ * \brief Collation code for figuring out which identities to vote for in
+ *   the directory voting process.
+ */
+
+#define DIRCOLLATE_PRIVATE
+#include "dircollate.h"
+#include "dirvote.h"
+
+static void dircollator_collate_by_rsa(dircollator_t *dc);
+static void dircollator_collate_by_ed25519(dircollator_t *dc);
+
+typedef struct ddmap_entry_s {
+  HT_ENTRY(ddmap_entry_s) node;
+  uint8_t d[DIGEST_LEN + DIGEST256_LEN];
+  vote_routerstatus_t *vrs_lst[FLEXIBLE_ARRAY_MEMBER];
+} ddmap_entry_t;
+
+struct double_digest_map_s *by_both_ids;
+
+static void
+ddmap_entry_free(ddmap_entry_t *e)
+{
+  tor_free(e);
+}
+
+static ddmap_entry_t *
+ddmap_entry_new(int n_votes)
+{
+  return tor_malloc_zero(STRUCT_OFFSET(ddmap_entry_t, vrs_lst) +
+                         sizeof(vote_routerstatus_t *) * n_votes);
+}
+
+static unsigned
+ddmap_entry_hash(const ddmap_entry_t *ent)
+{
+  return (unsigned) siphash24g(ent->d, sizeof(ent->d));
+}
+
+static unsigned
+ddmap_entry_eq(const ddmap_entry_t *a, const ddmap_entry_t *b)
+{
+  return fast_memeq(a->d, b->d, sizeof(a->d));
+}
+
+static void
+ddmap_entry_set_digests(ddmap_entry_t *ent,
+                        const uint8_t *rsa_sha1,
+                        const uint8_t *ed25519)
+{
+  memcpy(ent->d, rsa_sha1, DIGEST_LEN);
+  memcpy(ent->d + DIGEST_LEN, ed25519, DIGEST256_LEN);
+}
+
+HT_PROTOTYPE(double_digest_map, ddmap_entry_s, node, ddmap_entry_hash, ddmap_entry_eq);
+HT_GENERATE2(double_digest_map, ddmap_entry_s, node, ddmap_entry_hash, ddmap_entry_eq, 0.6, tor_reallocarray, tor_free_);
+static void
+dircollator_add_routerstatus(dircollator_t *dc,
+                             int vote_num,
+                             networkstatus_t *vote,
+                             vote_routerstatus_t *vrs)
+{
+  const char *id = vrs->status.identity_digest;
+
+  (void) vote;
+  vote_routerstatus_t **vrs_lst = digestmap_get(dc->by_rsa_sha1, id);
+  if (NULL == vrs_lst) {
+    vrs_lst = tor_calloc(sizeof(vote_routerstatus_t *), dc->n_votes);
+    digestmap_set(dc->by_rsa_sha1, id, vrs_lst);
+  }
+  tor_assert(vrs_lst[vote_num] == NULL);
+  vrs_lst[vote_num] = vrs;
+
+  const uint8_t *ed = vrs->ed25519_id;
+
+  if (tor_mem_is_zero((char*)ed, DIGEST256_LEN))
+    return;
+
+  ddmap_entry_t search, *found;
+  memset(&search, 0, sizeof(search));
+  ddmap_entry_set_digests(&search, (const uint8_t *)id, ed);
+  found = HT_FIND(double_digest_map, &dc->by_both_ids, &search);
+  if (NULL == found) {
+    found = ddmap_entry_new(dc->n_votes);
+    ddmap_entry_set_digests(found, (const uint8_t *)id, ed);
+    HT_INSERT(double_digest_map, &dc->by_both_ids, found);
+  }
+  vrs_lst = found->vrs_lst;
+  tor_assert(vrs_lst[vote_num] == NULL);
+  vrs_lst[vote_num] = vrs;
+}
+
+dircollator_t *
+dircollator_new(int n_votes, int n_authorities)
+{
+  dircollator_t *dc = tor_malloc_zero(sizeof(dircollator_t));
+
+  tor_assert(n_votes <= n_authorities);
+
+  dc->n_votes = n_votes;
+  dc->n_authorities = n_authorities;
+
+  dc->by_rsa_sha1 = digestmap_new();
+  HT_INIT(double_digest_map, &dc->by_both_ids);
+
+  return dc;
+}
+
+void
+dircollator_free(dircollator_t *dc)
+{
+  if (!dc)
+    return;
+
+  if (dc->by_collated_rsa_sha1 != dc->by_rsa_sha1)
+    digestmap_free(dc->by_collated_rsa_sha1, NULL);
+
+  digestmap_free(dc->by_rsa_sha1, tor_free_);
+
+  ddmap_entry_t **e, **next, *this;
+  for (e = HT_START(double_digest_map, &dc->by_both_ids);
+       e != NULL; e = next) {
+    this = *e;
+    next = HT_NEXT_RMV(double_digest_map, &dc->by_both_ids, e);
+    ddmap_entry_free(this);
+  }
+  HT_CLEAR(double_digest_map, &dc->by_both_ids);
+
+  tor_free(dc);
+}
+
+void
+dircollator_add_vote(dircollator_t *dc, networkstatus_t *v)
+{
+  tor_assert(v->type == NS_TYPE_VOTE);
+  tor_assert(dc->next_vote_num < dc->n_votes);
+  tor_assert(!dc->is_collated);
+
+  const int votenum = dc->next_vote_num++;
+
+  SMARTLIST_FOREACH_BEGIN(v->routerstatus_list, vote_routerstatus_t *, vrs) {
+    dircollator_add_routerstatus(dc, votenum, v, vrs);
+  } SMARTLIST_FOREACH_END(vrs);
+}
+
+void
+dircollator_collate(dircollator_t *dc, int consensus_method)
+{
+  tor_assert(!dc->is_collated);
+  dc->all_rsa_sha1_lst = smartlist_new();
+
+  if (consensus_method < MIN_METHOD_FOR_ED25519_ID_VOTING + 10/*XXX*/)
+    dircollator_collate_by_rsa(dc);
+  else
+    dircollator_collate_by_ed25519(dc);
+
+  smartlist_sort_digests(dc->all_rsa_sha1_lst);
+  dc->is_collated = 1;
+}
+
+static void
+dircollator_collate_by_rsa(dircollator_t *dc)
+{
+  const int total_authorities = dc->n_authorities;
+
+  DIGESTMAP_FOREACH(dc->by_rsa_sha1, k, vote_routerstatus_t **, vrs_lst) {
+    int n = 0, i;
+    for (i = 0; i < dc->n_votes; ++i) {
+      if (vrs_lst[i] != NULL)
+        ++n;
+    }
+
+    if (n <= total_authorities / 2)
+      continue;
+
+    smartlist_add(dc->all_rsa_sha1_lst, (char *)k);
+  } DIGESTMAP_FOREACH_END;
+
+  dc->by_collated_rsa_sha1 = dc->by_rsa_sha1;
+}
+
+static void
+dircollator_collate_by_ed25519(dircollator_t *dc)
+{
+  const int total_authorities = dc->n_authorities;
+  digestmap_t *rsa_digests = digestmap_new();
+
+  ddmap_entry_t **iter;
+
+  HT_FOREACH(iter, double_digest_map, &dc->by_both_ids) {
+    ddmap_entry_t *ent = *iter;
+    int n = 0, i;
+    for (i = 0; i < dc->n_votes; ++i) {
+      if (ent->vrs_lst[i] != NULL)
+        ++n;
+    }
+
+    if (n <= total_authorities / 2)
+      continue;
+
+    vote_routerstatus_t **vrs_lst2 = digestmap_get(dc->by_rsa_sha1,
+                                                   (char*)ent->d);
+    tor_assert(vrs_lst2);
+
+    for (i = 0; i < dc->n_votes; ++i) {
+      if (ent->vrs_lst[i] != NULL) {
+        ent->vrs_lst[i]->ed25519_reflects_consensus = 1;
+      } else if (vrs_lst2[i] && ! vrs_lst2[i]->has_ed25519_listing) {
+        ent->vrs_lst[i] = vrs_lst2[i];
+      }
+    }
+
+    digestmap_set(rsa_digests, (char*)ent->d, ent->vrs_lst);
+    smartlist_add(dc->all_rsa_sha1_lst, ent->d);
+  }
+
+  DIGESTMAP_FOREACH(dc->by_rsa_sha1, k, vote_routerstatus_t **, vrs_lst) {
+    if (digestmap_get(rsa_digests, k) != NULL)
+      continue;
+
+    int n = 0, i;
+    for (i = 0; i < dc->n_votes; ++i) {
+      if (vrs_lst[i] != NULL)
+        ++n;
+    }
+
+    if (n <= total_authorities / 2)
+      continue;
+
+    digestmap_set(rsa_digests, k, vrs_lst);
+    smartlist_add(dc->all_rsa_sha1_lst, (char *)k);
+  } DIGESTMAP_FOREACH_END;
+
+  dc->by_collated_rsa_sha1 = rsa_digests;
+}
+
+int
+dircollator_n_routers(dircollator_t *dc)
+{
+  return smartlist_len(dc->all_rsa_sha1_lst);
+}
+
+vote_routerstatus_t **
+dircollator_get_votes_for_router(dircollator_t *dc, int idx)
+{
+  tor_assert(idx < smartlist_len(dc->all_rsa_sha1_lst));
+  return digestmap_get(dc->by_collated_rsa_sha1,
+                       smartlist_get(dc->all_rsa_sha1_lst, idx));
+}
+

+ 49 - 0
src/or/dircollate.h

@@ -0,0 +1,49 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file dirvote.h
+ * \brief Header file for dirvote.c.
+ **/
+
+#ifndef TOR_DIRCOLLATE_H
+#define TOR_DIRCOLLATE_H
+
+#include "testsupport.h"
+#include "or.h"
+
+typedef struct dircollator_s dircollator_t;
+
+dircollator_t *dircollator_new(int n_votes, int n_authorities);
+void dircollator_free(dircollator_t *obj);
+void dircollator_add_vote(dircollator_t *dc, networkstatus_t *v);
+
+void dircollator_collate(dircollator_t *dc, int consensus_method);
+
+int dircollator_n_routers(dircollator_t *dc);
+vote_routerstatus_t **dircollator_get_votes_for_router(dircollator_t *dc,
+                                                       int idx);
+
+#ifdef DIRCOLLATE_PRIVATE
+struct ddmap_entry_s;
+typedef HT_HEAD(double_digest_map, ddmap_entry_s) double_digest_map_t;
+struct dircollator_s {
+  /**DOCDOC */
+  int is_collated;
+  int n_votes;
+  int n_authorities;
+
+  int next_vote_num;
+  digestmap_t *by_rsa_sha1;
+  struct double_digest_map by_both_ids;
+
+  digestmap_t *by_collated_rsa_sha1;
+
+  smartlist_t *all_rsa_sha1_lst;
+};
+#endif
+
+#endif

+ 79 - 0
src/or/dirserv.c

@@ -18,6 +18,7 @@
 #include "dirserv.h"
 #include "dirvote.h"
 #include "hibernate.h"
+#include "keypin.h"
 #include "microdesc.h"
 #include "networkstatus.h"
 #include "nodelist.h"
@@ -27,6 +28,7 @@
 #include "routerlist.h"
 #include "routerparse.h"
 #include "routerset.h"
+#include "torcert.h"
 
 /**
  * \file dirserv.c
@@ -225,6 +227,16 @@ dirserv_load_fingerprint_file(void)
   return 0;
 }
 
+/* If this is set, then we don't allow routers that have advertised an Ed25519
+ * identity to stop doing so.  This is going to be essential for good identity
+ * security: otherwise anybody who can attack RSA-1024 but not Ed25519 could
+ * just sign fake descriptors missing the Ed25519 key.  But we won't actually
+ * be able to prevent that kind of thing until we're confident that there
+ * isn't actually a legit reason to downgrade to 0.2.5.  So for now, we have
+ * to leave this #undef.
+ */
+#undef DISABLE_DISABLING_ED25519
+
 /** Check whether <b>router</b> has a nickname/identity key combination that
  * we recognize from the fingerprint list, or an IP we automatically act on
  * according to our configuration.  Return the appropriate router status.
@@ -243,6 +255,36 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg)
     return FP_REJECT;
   }
 
+  if (router->signing_key_cert) {
+    /* This has an ed25519 identity key. */
+    if (KEYPIN_MISMATCH ==
+        keypin_check((const uint8_t*)router->cache_info.identity_digest,
+                     router->signing_key_cert->signing_key.pubkey)) {
+      if (msg) {
+        *msg = "Ed25519 identity key or RSA identity key has changed.";
+      }
+      log_warn(LD_DIR, "Router %s uploaded a descriptor with a Ed25519 key "
+               "but the <rsa,ed25519> keys don't match what they were before.",
+               router_describe(router));
+      return FP_REJECT;
+    }
+  } else {
+    /* No ed25519 key */
+    if (KEYPIN_MISMATCH == keypin_check_lone_rsa(
+                        (const uint8_t*)router->cache_info.identity_digest)) {
+      log_warn(LD_DIR, "Router %s uploaded a descriptor with no Ed25519 key, "
+               "when we previously knew an Ed25519 for it. Ignoring for now, "
+               "since Tor 0.2.6 is under development.",
+               router_describe(router));
+#ifdef DISABLE_DISABLING_ED25519
+      if (msg) {
+        *msg = "Ed25519 identity key has disappeared.";
+      }
+      return FP_REJECT;
+#endif
+    }
+  }
+
   return dirserv_get_status_impl(d, router->nickname,
                                  router->addr, router->or_port,
                                  router->platform, msg, 1);
@@ -578,6 +620,28 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
     return ROUTER_IS_ALREADY_KNOWN;
   }
 
+  /* Do keypinning again ... this time, to add the pin if appropriate */
+  int keypin_status;
+  if (ri->signing_key_cert) {
+    keypin_status = keypin_check_and_add(
+      (const uint8_t*)ri->cache_info.identity_digest,
+      ri->signing_key_cert->signing_key.pubkey);
+  } else {
+    keypin_status = keypin_check_lone_rsa(
+      (const uint8_t*)ri->cache_info.identity_digest);
+#ifndef DISABLE_DISABLING_ED25519
+    if (keypin_status == KEYPIN_MISMATCH)
+      keypin_status = KEYPIN_NOT_FOUND;
+#endif
+  }
+  if (keypin_status == KEYPIN_MISMATCH) {
+    log_info(LD_DIRSERV, "Dropping descriptor from %s (source: %s) because "
+             "its key did not match an older RSA/Ed25519 keypair",
+             router_describe(ri), source);
+    *msg = "Looks like your keypair does not match its older value.";
+    return ROUTER_AUTHDIR_REJECTS;
+  }
+
   /* Make a copy of desc, since router_add_to_routerlist might free
    * ri and its associated signed_descriptor_t. */
   desc = tor_strndup(ri->cache_info.signed_descriptor_body, desclen);
@@ -1929,6 +1993,16 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
       smartlist_add_asprintf(chunks, "p %s\n", summary);
       tor_free(summary);
     }
+
+    if (format == NS_V3_VOTE && vrs) {
+      if (tor_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) {
+        smartlist_add(chunks, tor_strdup("id ed25519 none\n"));
+      } else {
+        char ed_b64[BASE64_DIGEST256_LEN+1];
+        digest256_to_base64(ed_b64, (const char*)vrs->ed25519_id);
+        smartlist_add_asprintf(chunks, "id ed25519 %s\n", ed_b64);
+      }
+    }
   }
 
  done:
@@ -2751,6 +2825,11 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
                                        listbadexits,
                                        vote_on_hsdirs);
 
+      if (ri->signing_key_cert) {
+        memcpy(vrs->ed25519_id, ri->signing_key_cert->signing_key.pubkey,
+               ED25519_PUBKEY_LEN);
+      }
+
       if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest))
         clear_status_flags_on_sybil(rs);
 

+ 48 - 39
src/or/dirvote.c

@@ -6,6 +6,7 @@
 #define DIRVOTE_PRIVATE
 #include "or.h"
 #include "config.h"
+#include "dircollate.h"
 #include "directory.h"
 #include "dirserv.h"
 #include "dirvote.h"
@@ -17,6 +18,7 @@
 #include "routerlist.h"
 #include "routerparse.h"
 #include "entrynodes.h" /* needed for guardfraction methods */
+#include "torcert.h"
 
 /**
  * \file dirvote.c
@@ -1138,6 +1140,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
   char *params = NULL;
   char *packages = NULL;
   int added_weights = 0;
+  dircollator_t *collator = NULL;
   tor_assert(flavor == FLAV_NS || flavor == FLAV_MICRODESC);
   tor_assert(total_authorities >= smartlist_len(votes));
 
@@ -1493,12 +1496,24 @@ networkstatus_compute_consensus(smartlist_t *votes,
        }
     );
 
+    /* Populate the collator */
+    collator = dircollator_new(smartlist_len(votes), total_authorities);
+    SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+      dircollator_add_vote(collator, v);
+    } SMARTLIST_FOREACH_END(v);
+
+    dircollator_collate(collator, consensus_method);
+
     /* Now go through all the votes */
     flag_counts = tor_calloc(smartlist_len(flags), sizeof(int));
-    while (1) {
+    const int num_routers = dircollator_n_routers(collator);
+    for (i = 0; i < num_routers; ++i) {
+      vote_routerstatus_t **vrs_lst =
+        dircollator_get_votes_for_router(collator, i);
+
       vote_routerstatus_t *rs;
       routerstatus_t rs_out;
-      const char *lowest_id = NULL;
+      const char *current_rsa_id = NULL;
       const char *chosen_version;
       const char *chosen_name = NULL;
       int exitsummary_disagreement = 0;
@@ -1506,23 +1521,9 @@ networkstatus_compute_consensus(smartlist_t *votes,
       int is_guard = 0, is_exit = 0, is_bad_exit = 0;
       int naming_conflict = 0;
       int n_listing = 0;
-      int i;
       char microdesc_digest[DIGEST256_LEN];
       tor_addr_port_t alt_orport = {TOR_ADDR_NULL, 0};
 
-      /* Of the next-to-be-considered digest in each voter, which is first? */
-      SMARTLIST_FOREACH(votes, networkstatus_t *, v, {
-        if (index[v_sl_idx] < size[v_sl_idx]) {
-          rs = smartlist_get(v->routerstatus_list, index[v_sl_idx]);
-          if (!lowest_id ||
-              fast_memcmp(rs->status.identity_digest,
-                          lowest_id, DIGEST_LEN) < 0)
-            lowest_id = rs->status.identity_digest;
-        }
-      });
-      if (!lowest_id) /* we're out of routers. */
-        break;
-
       memset(flag_counts, 0, sizeof(int)*smartlist_len(flags));
       smartlist_clear(matching_descs);
       smartlist_clear(chosen_flags);
@@ -1532,29 +1533,25 @@ networkstatus_compute_consensus(smartlist_t *votes,
       num_guardfraction_inputs = 0;
 
       /* Okay, go through all the entries for this digest. */
-      SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
-        if (index[v_sl_idx] >= size[v_sl_idx])
-          continue; /* out of entries. */
-        rs = smartlist_get(v->routerstatus_list, index[v_sl_idx]);
-        if (fast_memcmp(rs->status.identity_digest, lowest_id, DIGEST_LEN))
-          continue; /* doesn't include this router. */
-        /* At this point, we know that we're looking at a routerstatus with
-         * identity "lowest".
-         */
-        ++index[v_sl_idx];
+      for (int voter_idx = 0; voter_idx < smartlist_len(votes); ++voter_idx) {
+        if (vrs_lst[voter_idx] == NULL)
+          continue; /* This voter had nothig to say about this entry. */
+        rs = vrs_lst[voter_idx];
         ++n_listing;
 
+        current_rsa_id = rs->status.identity_digest;
+
         smartlist_add(matching_descs, rs);
         if (rs->version && rs->version[0])
           smartlist_add(versions, rs->version);
 
         /* Tally up all the flags. */
-        for (i = 0; i < n_voter_flags[v_sl_idx]; ++i) {
-          if (rs->flags & (U64_LITERAL(1) << i))
-            ++flag_counts[flag_map[v_sl_idx][i]];
+        for (int flag = 0; flag < n_voter_flags[voter_idx]; ++flag) {
+          if (rs->flags & (U64_LITERAL(1) << flag))
+            ++flag_counts[flag_map[voter_idx][flag]];
         }
-        if (named_flag[v_sl_idx] >= 0 &&
-            (rs->flags & (U64_LITERAL(1) << named_flag[v_sl_idx]))) {
+        if (named_flag[voter_idx] >= 0 &&
+            (rs->flags & (U64_LITERAL(1) << named_flag[voter_idx]))) {
           if (chosen_name && strcmp(chosen_name, rs->status.nickname)) {
             log_notice(LD_DIR, "Conflict on naming for router: %s vs %s",
                        chosen_name, rs->status.nickname);
@@ -1575,7 +1572,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
 
         if (rs->status.has_bandwidth)
           bandwidths_kb[num_bandwidths++] = rs->status.bandwidth_kb;
-      } SMARTLIST_FOREACH_END(v);
+      }
 
       /* We don't include this router at all unless more than half of
        * the authorities we believe in list it. */
@@ -1589,8 +1586,9 @@ networkstatus_compute_consensus(smartlist_t *votes,
                                           microdesc_digest, &alt_orport);
       /* Copy bits of that into rs_out. */
       memset(&rs_out, 0, sizeof(rs_out));
-      tor_assert(fast_memeq(lowest_id, rs->status.identity_digest,DIGEST_LEN));
-      memcpy(rs_out.identity_digest, lowest_id, DIGEST_LEN);
+      tor_assert(fast_memeq(current_rsa_id,
+                            rs->status.identity_digest,DIGEST_LEN));
+      memcpy(rs_out.identity_digest, current_rsa_id, DIGEST_LEN);
       memcpy(rs_out.descriptor_digest, rs->status.descriptor_digest,
              DIGEST_LEN);
       rs_out.addr = rs->status.addr;
@@ -1614,7 +1612,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
         const char *d = strmap_get_lc(name_to_id_map, rs_out.nickname);
         if (!d) {
           is_named = is_unnamed = 0;
-        } else if (fast_memeq(d, lowest_id, DIGEST_LEN)) {
+        } else if (fast_memeq(d, current_rsa_id, DIGEST_LEN)) {
           is_named = 1; is_unnamed = 0;
         } else {
           is_named = 0; is_unnamed = 1;
@@ -1980,6 +1978,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
 
  done:
 
+  dircollator_free(collator);
   tor_free(client_versions);
   tor_free(server_versions);
   tor_free(packages);
@@ -3487,9 +3486,18 @@ dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method)
   }
 
   if (consensus_method >= MIN_METHOD_FOR_ID_HASH_IN_MD) {
-    char idbuf[BASE64_DIGEST_LEN+1];
-    digest_to_base64(idbuf, ri->cache_info.identity_digest);
-    smartlist_add_asprintf(chunks, "id rsa1024 %s\n", idbuf);
+    char idbuf[ED25519_BASE64_LEN+1];
+    const char *keytype;
+    if (consensus_method >= MIN_METHOD_FOR_ED25519_ID_IN_MD &&
+        ri->signing_key_cert &&
+        ri->signing_key_cert->signing_key_included) {
+      keytype = "ed25519";
+      ed25519_public_to_base64(idbuf, &ri->signing_key_cert->signing_key);
+    } else {
+      keytype = "rsa1024";
+      digest_to_base64(idbuf, ri->cache_info.identity_digest);
+    }
+    smartlist_add_asprintf(chunks, "id %s %s\n", keytype, idbuf);
   }
 
   output = smartlist_join_strings(chunks, "", 0, NULL);
@@ -3562,7 +3570,8 @@ static const struct consensus_method_range_t {
   {MIN_METHOD_FOR_A_LINES, MIN_METHOD_FOR_P6_LINES - 1},
   {MIN_METHOD_FOR_P6_LINES, MIN_METHOD_FOR_NTOR_KEY - 1},
   {MIN_METHOD_FOR_NTOR_KEY, MIN_METHOD_FOR_ID_HASH_IN_MD - 1},
-  {MIN_METHOD_FOR_ID_HASH_IN_MD,  MAX_SUPPORTED_CONSENSUS_METHOD},
+  {MIN_METHOD_FOR_ID_HASH_IN_MD, MIN_METHOD_FOR_ED25519_ID_IN_MD - 1},
+  {MIN_METHOD_FOR_ED25519_ID_IN_MD, MAX_SUPPORTED_CONSENSUS_METHOD},
   {-1, -1}
 };
 

+ 8 - 1
src/or/dirvote.h

@@ -55,7 +55,7 @@
 #define MIN_SUPPORTED_CONSENSUS_METHOD 13
 
 /** The highest consensus method that we currently support. */
-#define MAX_SUPPORTED_CONSENSUS_METHOD 20
+#define MAX_SUPPORTED_CONSENSUS_METHOD 21
 
 /** Lowest consensus method where microdesc consensuses omit any entry
  * with no microdesc. */
@@ -86,6 +86,13 @@
  * GuardFraction information in microdescriptors. */
 #define MIN_METHOD_FOR_GUARDFRACTION 20
 
+/** Lowest consensus method where authorities may include an "id" line for
+ * ed25519 identities in microdescriptors. */
+#define MIN_METHOD_FOR_ED25519_ID_IN_MD 21
+/** Lowest consensus method where authorities vote on ed25519 ids and ensure
+ * ed25519 id consistency. */
+#define MIN_METHOD_FOR_ED25519_ID_VOTING MIN_METHOD_FOR_ED25519_ID_IN_MD
+
 /** Default bandwidth to clip unmeasured bandwidths to using method >=
  * MIN_METHOD_TO_CLIP_UNMEASURED_BW.  (This is not a consensus method; do not
  * get confused with the above macros.) */

+ 12 - 8
src/or/include.am

@@ -43,6 +43,7 @@ LIBTOR_A_SOURCES = \
 	src/or/connection_or.c				\
 	src/or/control.c				\
 	src/or/cpuworker.c				\
+	src/or/dircollate.c				\
 	src/or/directory.c				\
 	src/or/dirserv.c				\
 	src/or/dirvote.c				\
@@ -53,6 +54,7 @@ LIBTOR_A_SOURCES = \
 	src/or/entrynodes.c				\
 	src/or/ext_orport.c				\
 	src/or/hibernate.c				\
+	src/or/keypin.c					\
 	src/or/main.c					\
 	src/or/microdesc.c				\
 	src/or/networkstatus.c				\
@@ -71,12 +73,14 @@ LIBTOR_A_SOURCES = \
 	src/or/rephist.c				\
 	src/or/replaycache.c				\
 	src/or/router.c					\
+	src/or/routerkeys.c				\
 	src/or/routerlist.c				\
 	src/or/routerparse.c				\
 	src/or/routerset.c				\
 	src/or/scheduler.c				\
 	src/or/statefile.c				\
 	src/or/status.c					\
+	src/or/torcert.c				\
 	src/or/onion_ntor.c				\
 	$(evdns_source)					\
 	$(tor_platform_source)
@@ -84,11 +88,6 @@ LIBTOR_A_SOURCES = \
 src_or_libtor_a_SOURCES = $(LIBTOR_A_SOURCES)
 src_or_libtor_testing_a_SOURCES = $(LIBTOR_A_SOURCES)
 
-#libtor_a_LIBADD = $(top_builddir)/common/libor.a \
-#    $(top_builddir)/common/libor-crypto.a \
-#    $(top_builddir)/common/libor-event.a
-
-
 src_or_tor_SOURCES = src/or/tor_main.c
 AM_CPPFLAGS += -I$(srcdir)/src/or -Isrc/or
 
@@ -109,7 +108,7 @@ src_or_libtor_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
 src_or_tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@
 src_or_tor_LDADD = src/or/libtor.a src/common/libor.a \
 	src/common/libor-crypto.a $(LIBDONNA) \
-	src/common/libor-event.a \
+	src/common/libor-event.a src/trunnel/libor-trunnel.a \
 	@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \
 	@TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@
 
@@ -120,7 +119,7 @@ src_or_tor_cov_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
 src_or_tor_cov_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@
 src_or_tor_cov_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \
 	src/common/libor-crypto-testing.a $(LIBDONNA) \
-	src/common/libor-event-testing.a \
+	src/common/libor-event-testing.a src/trunnel/libor-trunnel-testing.a \
 	@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \
 	@TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@
 TESTING_TOR_BINARY = $(top_builddir)/src/or/tor-cov
@@ -148,6 +147,7 @@ ORHEADERS = \
 	src/or/connection_or.h				\
 	src/or/control.h				\
 	src/or/cpuworker.h				\
+	src/or/dircollate.h				\
 	src/or/directory.h				\
 	src/or/dirserv.h				\
 	src/or/dirvote.h				\
@@ -159,6 +159,7 @@ ORHEADERS = \
 	src/or/geoip.h					\
 	src/or/entrynodes.h				\
 	src/or/hibernate.h				\
+	src/or/keypin.h					\
 	src/or/main.h					\
 	src/or/microdesc.h				\
 	src/or/networkstatus.h				\
@@ -180,12 +181,15 @@ ORHEADERS = \
 	src/or/rephist.h				\
 	src/or/replaycache.h				\
 	src/or/router.h					\
+	src/or/routerkeys.h				\
 	src/or/routerlist.h				\
+	src/or/routerkeys.h				\
 	src/or/routerset.h				\
 	src/or/routerparse.h				\
 	src/or/scheduler.h				\
 	src/or/statefile.h				\
-	src/or/status.h
+	src/or/status.h					\
+	src/or/torcert.h
 
 noinst_HEADERS+= $(ORHEADERS) micro-revision.i
 

+ 419 - 0
src/or/keypin.c

@@ -0,0 +1,419 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#define KEYPIN_PRIVATE
+
+#include "orconfig.h"
+#include "compat.h"
+#include "crypto.h"
+#include "di_ops.h"
+#include "ht.h"
+#include "keypin.h"
+#include "siphash.h"
+#include "torint.h"
+#include "torlog.h"
+#include "util.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#ifdef _WIN32
+#include <io.h>
+#endif
+
+/**
+ * @file keypin.c
+ * @brief Key-pinning for RSA and Ed25519 identity keys at directory
+ *  authorities.
+ *
+ * This module implements a key-pinning mechanism to ensure that it's safe
+ * to use RSA keys as identitifers even as we migrate to Ed25519 keys.  It
+ * remembers, for every Ed25519 key we've seen, what the associated Ed25519
+ * key is.  This way, if we see a different Ed25519 key with that RSA key,
+ * we'll know that there's a mismatch.
+ *
+ * We persist these entries to disk using a simple format, where each line
+ * has a base64-encoded RSA SHA1 hash, then a base64-endoded Ed25519 key.
+ * Empty lines, misformed lines, and lines beginning with # are
+ * ignored. Lines beginning with @ are reserved for future extensions.
+ */
+
+static int keypin_journal_append_entry(const uint8_t *rsa_id_digest,
+                                       const uint8_t *ed25519_id_key);
+static int keypin_check_and_add_impl(const uint8_t *rsa_id_digest,
+                                     const uint8_t *ed25519_id_key,
+                                     int do_not_add);
+
+static HT_HEAD(rsamap, keypin_ent_st) the_rsa_map = HT_INITIALIZER();
+static HT_HEAD(edmap, keypin_ent_st) the_ed_map = HT_INITIALIZER();
+
+/** Hashtable helper: compare two keypin table entries and return true iff
+ * they have the same RSA key IDs. */
+static INLINE int
+keypin_ents_eq_rsa(const keypin_ent_t *a, const keypin_ent_t *b)
+{
+  return tor_memeq(a->rsa_id, b->rsa_id, sizeof(a->rsa_id));
+}
+
+/** Hashtable helper: hash a keypin table entries based on its RSA key ID */
+static INLINE unsigned
+keypin_ent_hash_rsa(const keypin_ent_t *a)
+{
+return (unsigned) siphash24g(a->rsa_id, sizeof(a->rsa_id));
+}
+
+/** Hashtable helper: compare two keypin table entries and return true iff
+ * they have the same ed25519 keys */
+static INLINE int
+keypin_ents_eq_ed(const keypin_ent_t *a, const keypin_ent_t *b)
+{
+  return tor_memeq(a->ed25519_key, b->ed25519_key, sizeof(a->ed25519_key));
+}
+
+/** Hashtable helper: hash a keypin table entries based on its ed25519 key */
+static INLINE unsigned
+keypin_ent_hash_ed(const keypin_ent_t *a)
+{
+return (unsigned) siphash24g(a->ed25519_key, sizeof(a->ed25519_key));
+}
+
+HT_PROTOTYPE(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa,
+               keypin_ents_eq_rsa);
+HT_GENERATE2(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa,
+               keypin_ents_eq_rsa, 0.6, tor_reallocarray, tor_free_);
+
+HT_PROTOTYPE(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed,
+               keypin_ents_eq_ed);
+HT_GENERATE2(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed,
+               keypin_ents_eq_ed, 0.6, tor_reallocarray, tor_free_);
+
+/**
+ * Check whether we already have an entry in the key pinning table for a
+ * router with RSA ID digest <b>rsa_id_digest</b> or for ed25519 key
+ * <b>ed25519_id_key</b>.  If we have an entry that matches both keys,
+ * return KEYPIN_FOUND. If we find an entry that matches one key but
+ * not the other, return KEYPIN_MISMATCH.  If we have no entry for either
+ * key, add such an entry to the table and return KEYPIN_ADDED.
+ */
+int
+keypin_check_and_add(const uint8_t *rsa_id_digest,
+                     const uint8_t *ed25519_id_key)
+{
+  return keypin_check_and_add_impl(rsa_id_digest, ed25519_id_key, 0);
+}
+
+/**
+ * As keypin_check_and_add, but do not add.  Return KEYPIN_NOT_FOUND if
+ * we would add.
+ */
+int
+keypin_check(const uint8_t *rsa_id_digest,
+             const uint8_t *ed25519_id_key)
+{
+  return keypin_check_and_add_impl(rsa_id_digest, ed25519_id_key, 1);
+}
+
+/**
+ * Helper: implements keypin_check and keypin_check_and_add.
+ */
+static int
+keypin_check_and_add_impl(const uint8_t *rsa_id_digest,
+                          const uint8_t *ed25519_id_key,
+                          int do_not_add)
+{
+  keypin_ent_t search, *ent;
+  memset(&search, 0, sizeof(search));
+  memcpy(search.rsa_id, rsa_id_digest, sizeof(search.rsa_id));
+  memcpy(search.ed25519_key, ed25519_id_key, sizeof(search.ed25519_key));
+
+  /* Search by RSA key digest first */
+  ent = HT_FIND(rsamap, &the_rsa_map, &search);
+  if (ent) {
+    tor_assert(fast_memeq(ent->rsa_id, rsa_id_digest, sizeof(ent->rsa_id)));
+    if (tor_memeq(ent->ed25519_key, ed25519_id_key,sizeof(ent->ed25519_key))) {
+      return KEYPIN_FOUND; /* Match on both keys. Great. */
+    } else {
+      return KEYPIN_MISMATCH; /* Found RSA with different Ed key */
+    }
+  }
+
+  /* See if we know a different RSA key for this ed key */
+  ent = HT_FIND(edmap, &the_ed_map, &search);
+  if (ent) {
+    /* If we got here, then the ed key matches and the RSA doesn't */
+    tor_assert(fast_memeq(ent->ed25519_key, ed25519_id_key,
+                          sizeof(ent->ed25519_key)));
+    tor_assert(fast_memneq(ent->rsa_id, rsa_id_digest, sizeof(ent->rsa_id)));
+    return KEYPIN_MISMATCH;
+  }
+
+  /* Okay, this one is new to us. */
+  if (do_not_add)
+    return KEYPIN_NOT_FOUND;
+
+  ent = tor_memdup(&search, sizeof(search));
+  keypin_add_entry_to_map(ent);
+  keypin_journal_append_entry(rsa_id_digest, ed25519_id_key);
+  return KEYPIN_ADDED;
+}
+
+/**
+ * Helper: add <b>ent</b> to the hash tables.
+ */
+MOCK_IMPL(STATIC void,
+keypin_add_entry_to_map, (keypin_ent_t *ent))
+{
+  HT_INSERT(rsamap, &the_rsa_map, ent);
+  HT_INSERT(edmap, &the_ed_map, ent);
+}
+
+/**
+ * Check whether we already have an entry in the key pinning table for a
+ * router with RSA ID digest <b>rsa_id_digest</b>.  If we have no such entry,
+ * return KEYPIN_NOT_FOUND.  If we find an entry that matches the RSA key but
+ * which has an ed25519 key, return KEYPIN_MISMATCH.
+ */
+int
+keypin_check_lone_rsa(const uint8_t *rsa_id_digest)
+{
+  keypin_ent_t search, *ent;
+  memset(&search, 0, sizeof(search));
+  memcpy(search.rsa_id, rsa_id_digest, sizeof(search.rsa_id));
+
+  /* Search by RSA key digest first */
+  ent = HT_FIND(rsamap, &the_rsa_map, &search);
+  if (ent) {
+    return KEYPIN_MISMATCH;
+  } else {
+    return KEYPIN_NOT_FOUND;
+  }
+}
+
+/** Open fd to the keypinning journal file. */
+static int keypin_journal_fd = -1;
+
+/** Open the key-pinning journal to append to <b>fname</b>.  Return 0 on
+ * success, -1 on failure. */
+int
+keypin_open_journal(const char *fname)
+{
+  /* O_SYNC ??*/
+  int fd = tor_open_cloexec(fname, O_WRONLY|O_CREAT, 0600);
+  if (fd < 0)
+    goto err;
+
+  if (tor_fd_seekend(fd) < 0)
+    goto err;
+
+  /* Add a newline in case the last line was only partially written */
+  if (write(fd, "\n", 1) < 1)
+    goto err;
+
+  /* Add something about when we opened this file. */
+  char buf[80];
+  char tbuf[ISO_TIME_LEN+1];
+  format_iso_time(tbuf, approx_time());
+  tor_snprintf(buf, sizeof(buf), "@opened-at %s\n", tbuf);
+  if (write_all(fd, buf, strlen(buf), 0) < 0)
+    goto err;
+
+  keypin_journal_fd = fd;
+  return 0;
+ err:
+  if (fd >= 0)
+    close(fd);
+  return -1;
+}
+
+/** Close the keypinning journal file. */
+int
+keypin_close_journal(void)
+{
+  if (keypin_journal_fd >= 0)
+    close(keypin_journal_fd);
+  keypin_journal_fd = -1;
+  return 0;
+}
+
+/** Length of a keypinning journal line, including terminating newline. */
+#define JOURNAL_LINE_LEN (BASE64_DIGEST_LEN + BASE64_DIGEST256_LEN + 2)
+
+/** Add an entry to the keypinning journal to map <b>rsa_id_digest</b> and
+ * <b>ed25519_id_key</b>. */
+static int
+keypin_journal_append_entry(const uint8_t *rsa_id_digest,
+                            const uint8_t *ed25519_id_key)
+{
+  if (keypin_journal_fd == -1)
+    return -1;
+  char line[JOURNAL_LINE_LEN];
+  digest_to_base64(line, (const char*)rsa_id_digest);
+  line[BASE64_DIGEST_LEN] = ' ';
+  digest256_to_base64(line + BASE64_DIGEST_LEN + 1,
+                      (const char*)ed25519_id_key);
+  line[BASE64_DIGEST_LEN+1+BASE64_DIGEST256_LEN] = '\n';
+
+  if (write_all(keypin_journal_fd, line, JOURNAL_LINE_LEN, 0)<0) {
+    log_warn(LD_DIRSERV, "Error while adding a line to the key-pinning "
+             "journal: %s", strerror(errno));
+    keypin_close_journal();
+    return -1;
+  }
+
+  return 0;
+}
+
+/** Load a journal from the <b>size</b>-byte region at <b>data</b>.  Return 0
+ * on success, -1 on failure. */
+STATIC int
+keypin_load_journal_impl(const char *data, size_t size)
+{
+  const char *start = data, *end = data + size, *next;
+
+  int n_corrupt_lines = 0;
+  int n_entries = 0;
+  int n_duplicates = 0;
+  int n_conflicts = 0;
+
+  for (const char *cp = start; cp < end; cp = next) {
+    const char *eol = memchr(cp, '\n', end-cp);
+    const char *eos = eol ? eol : end;
+    const size_t len = eos - cp;
+
+    next = eol ? eol + 1 : end;
+
+    if (len == 0) {
+      continue;
+    }
+
+    if (*cp == '@') {
+      /* Lines that start with @ are reserved. Ignore for now. */
+      continue;
+    }
+    if (*cp == '#') {
+      /* Lines that start with # are comments. */
+      continue;
+    }
+
+    /* Is it the right length?  (The -1 here is for the newline.) */
+    if (len != JOURNAL_LINE_LEN - 1) {
+      /* Lines with a bad length are corrupt unless they are empty.
+       * Ignore them either way */
+      for (const char *s = cp; s < eos; ++s) {
+        if (! TOR_ISSPACE(*s)) {
+          ++n_corrupt_lines;
+          break;
+        }
+      }
+      continue;
+    }
+
+    keypin_ent_t *ent = keypin_parse_journal_line(cp);
+
+    if (ent == NULL) {
+      ++n_corrupt_lines;
+      continue;
+    }
+
+    const keypin_ent_t *ent2;
+    if ((ent2 = HT_FIND(rsamap, &the_rsa_map, ent))) {
+      if (fast_memeq(ent2->ed25519_key, ent->ed25519_key, DIGEST256_LEN)) {
+        ++n_duplicates;
+      } else {
+        ++n_conflicts;
+      }
+      tor_free(ent);
+      continue;
+    } else if (HT_FIND(edmap, &the_ed_map, ent)) {
+      tor_free(ent);
+      ++n_conflicts;
+      continue;
+    }
+
+    keypin_add_entry_to_map(ent);
+    ++n_entries;
+  }
+
+  int severity = (n_corrupt_lines || n_duplicates) ? LOG_WARN : LOG_INFO;
+  tor_log(severity, LD_DIRSERV,
+          "Loaded %d entries from keypin journal. "
+          "Found %d corrupt lines, %d duplicates, and %d conflicts.",
+          n_entries, n_corrupt_lines, n_duplicates, n_conflicts);
+
+  return 0;
+}
+
+/**
+ * Load a journal from the file called <b>fname</b>. Return 0 on success,
+ * -1 on failure.
+ */
+int
+keypin_load_journal(const char *fname)
+{
+  tor_mmap_t *map = tor_mmap_file(fname);
+  if (!map) {
+    if (errno == ENOENT)
+      return 0;
+    else
+      return -1;
+  }
+  int r = keypin_load_journal_impl(map->data, map->size);
+  tor_munmap_file(map);
+  return r;
+}
+
+/** Parse a single keypinning journal line entry from <b>cp</b>.  The input
+ * does not need to be NUL-terminated, but it <em>does</em> need to have
+ * KEYPIN_JOURNAL_LINE_LEN -1 bytes available to read.  Return a new entry
+ * on success, and NULL on failure.
+ */
+STATIC keypin_ent_t *
+keypin_parse_journal_line(const char *cp)
+{
+  /* XXXX assumes !USE_OPENSSL_BASE64 */
+  keypin_ent_t *ent = tor_malloc_zero(sizeof(keypin_ent_t));
+
+  if (base64_decode((char*)ent->rsa_id, sizeof(ent->rsa_id),
+             cp, BASE64_DIGEST_LEN) != DIGEST_LEN ||
+      cp[BASE64_DIGEST_LEN] != ' ' ||
+      base64_decode((char*)ent->ed25519_key, sizeof(ent->ed25519_key),
+             cp+BASE64_DIGEST_LEN+1, BASE64_DIGEST256_LEN) != DIGEST256_LEN) {
+    tor_free(ent);
+    return NULL;
+  } else {
+    return ent;
+  }
+}
+
+/** Remove all entries from the keypinning table.*/
+void
+keypin_clear(void)
+{
+  int bad_entries = 0;
+  {
+    keypin_ent_t **ent, **next, *this;
+    for (ent = HT_START(rsamap, &the_rsa_map); ent != NULL; ent = next) {
+      this = *ent;
+      next = HT_NEXT_RMV(rsamap, &the_rsa_map, ent);
+
+      keypin_ent_t *other_ent = HT_REMOVE(edmap, &the_ed_map, this);
+      bad_entries += (other_ent != this);
+
+      tor_free(this);
+    }
+  }
+  bad_entries += HT_SIZE(&the_ed_map);
+
+  HT_CLEAR(edmap,&the_ed_map);
+  HT_CLEAR(rsamap,&the_rsa_map);
+
+  if (bad_entries) {
+    log_warn(LD_BUG, "Found %d discrepencies in the the keypin database.",
+             bad_entries);
+  }
+}
+

+ 46 - 0
src/or/keypin.h

@@ -0,0 +1,46 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_KEYPIN_H
+#define TOR_KEYPIN_H
+
+#include "testsupport.h"
+
+int keypin_check_and_add(const uint8_t *rsa_id_digest,
+                         const uint8_t *ed25519_id_key);
+int keypin_check(const uint8_t *rsa_id_digest,
+                 const uint8_t *ed25519_id_key);
+
+int keypin_open_journal(const char *fname);
+int keypin_close_journal(void);
+int keypin_load_journal(const char *fname);
+void keypin_clear(void);
+int keypin_check_lone_rsa(const uint8_t *rsa_id_digest);
+
+#define KEYPIN_FOUND 0
+#define KEYPIN_ADDED 1
+#define KEYPIN_MISMATCH -1
+#define KEYPIN_NOT_FOUND -2
+
+#ifdef KEYPIN_PRIVATE
+
+/**
+ * In-memory representation of a key-pinning table entry.
+ */
+typedef struct keypin_ent_st {
+  HT_ENTRY(keypin_ent_st) rsamap_node;
+  HT_ENTRY(keypin_ent_st) edmap_node;
+  /** SHA1 hash of the RSA key */
+  uint8_t rsa_id[DIGEST_LEN];
+  /** Ed2219 key. */
+  uint8_t ed25519_key[DIGEST256_LEN];
+} keypin_ent_t;
+
+STATIC keypin_ent_t * keypin_parse_journal_line(const char *cp);
+STATIC int keypin_load_journal_impl(const char *data, size_t size);
+
+MOCK_DECL(STATIC void, keypin_add_entry_to_map, (keypin_ent_t *ent));
+#endif
+
+#endif
+

+ 37 - 1
src/or/main.c

@@ -37,6 +37,7 @@
 #include "entrynodes.h"
 #include "geoip.h"
 #include "hibernate.h"
+#include "keypin.h"
 #include "main.h"
 #include "microdesc.h"
 #include "networkstatus.h"
@@ -51,6 +52,7 @@
 #include "rendservice.h"
 #include "rephist.h"
 #include "router.h"
+#include "routerkeys.h"
 #include "routerlist.h"
 #include "routerparse.h"
 #include "scheduler.h"
@@ -1223,10 +1225,13 @@ typedef struct {
   time_t check_descriptor;
   /** When do we next launch DNS wildcarding checks? */
   time_t check_for_correct_dns;
+  /** When do we next make sure our Ed25519 keys aren't about to expire? */
+  time_t check_ed_keys;
+
 } time_to_t;
 
 static time_to_t time_to = {
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
 };
 
 /** Reset all the time_to's so we'll do all our actions again as if we
@@ -1297,6 +1302,18 @@ run_scheduled_events(time_t now)
       router_upload_dir_desc_to_dirservers(0);
   }
 
+  if (is_server && time_to.check_ed_keys < now) {
+    if (should_make_new_ed_keys(options, now)) {
+      if (load_ed_keys(options, now) < 0 ||
+          generate_ed_link_cert(options, now)) {
+        log_err(LD_OR, "Unable to update Ed25519 keys!  Exiting.");
+        tor_cleanup();
+        exit(0);
+      }
+    }
+    time_to.check_ed_keys = now + 30;
+  }
+
   if (!should_delay_dir_fetches(options, NULL) &&
       time_to.try_getting_descriptors < now) {
     update_all_descriptor_downloads(now);
@@ -2015,6 +2032,23 @@ do_main_loop(void)
   /* initialize the bootstrap status events to know we're starting up */
   control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0);
 
+  /* Initialize the keypinning log. */
+  if (authdir_mode_v3(get_options())) {
+    char *fname = get_datadir_fname("key-pinning-entries");
+    int r = 0;
+    if (keypin_load_journal(fname)<0) {
+      log_err(LD_DIR, "Error loading key-pinning journal: %s",strerror(errno));
+      r = -1;
+    }
+    if (keypin_open_journal(fname)<0) {
+      log_err(LD_DIR, "Error opening key-pinning journal: %s",strerror(errno));
+      r = -1;
+    }
+    tor_free(fname);
+    if (r)
+      return r;
+  }
+
   if (trusted_dirs_reload_certs()) {
     log_warn(LD_DIR,
              "Couldn't load all cached v3 certificates. Starting anyway.");
@@ -2695,6 +2729,7 @@ tor_free_all(int postfork)
     config_free_all();
     or_state_free_all();
     router_free_all();
+    routerkeys_free_all();
     policies_free_all();
   }
   if (!postfork) {
@@ -2752,6 +2787,7 @@ tor_cleanup(void)
     or_state_save(now);
     if (authdir_mode_tests_reachability(options))
       rep_hist_record_mtbf_data(now, 0);
+    keypin_close_journal();
   }
 #ifdef USE_DMALLOC
   dmalloc_log_stats();

+ 1 - 0
src/or/microdesc.c

@@ -738,6 +738,7 @@ microdesc_free_(microdesc_t *md, const char *fname, int lineno)
   if (md->onion_pkey)
     crypto_pk_free(md->onion_pkey);
   tor_free(md->onion_curve25519_pkey);
+  tor_free(md->ed25519_identity_pkey);
   if (md->body && md->saved_location != SAVED_IN_CACHE)
     tor_free(md->body);
 

+ 39 - 2
src/or/or.h

@@ -96,6 +96,7 @@
 #include "ht.h"
 #include "replaycache.h"
 #include "crypto_curve25519.h"
+#include "crypto_ed25519.h"
 #include "tor_queue.h"
 
 /* These signals are defined to help handle_control_signal work.
@@ -1353,6 +1354,8 @@ typedef struct listener_connection_t {
  * in the v3 handshake.  The subject key must be a 1024-bit RSA key; it
  * must be signed by the identity key */
 #define OR_CERT_TYPE_AUTH_1024 3
+/** DOCDOC */
+#define OR_CERT_TYPE_RSA_ED_CROSSCERT 7
 /**@}*/
 
 /** The one currently supported type of AUTHENTICATE cell.  It contains
@@ -1428,9 +1431,9 @@ typedef struct or_handshake_state_t {
    * @{
    */
   /** The cert for the key that's supposed to sign the AUTHENTICATE cell */
-  tor_cert_t *auth_cert;
+  tor_x509_cert_t *auth_cert;
   /** A self-signed identity certificate */
-  tor_cert_t *id_cert;
+  tor_x509_cert_t *id_cert;
   /**@}*/
 } or_handshake_state_t;
 
@@ -2023,6 +2026,8 @@ typedef int16_t country_t;
 /** Information about another onion router in the network. */
 typedef struct {
   signed_descriptor_t cache_info;
+  /** A SHA256-digest of the extrainfo (if any) */
+  char extra_info_digest256[DIGEST256_LEN];
   char *nickname; /**< Human-readable OR name. */
 
   uint32_t addr; /**< IPv4 address of OR, in host order. */
@@ -2040,6 +2045,11 @@ typedef struct {
   crypto_pk_t *identity_pkey;  /**< Public RSA key for signing. */
   /** Public curve25519 key for onions */
   curve25519_public_key_t *onion_curve25519_pkey;
+  /** Certificate for ed25519 signing key */
+  struct tor_cert_st *signing_key_cert;
+  /** What's the earliest expiration time on all the certs in this
+   * routerinfo? */
+  time_t cert_expiration_time;
 
   char *platform; /**< What software/operating system is this OR using? */
 
@@ -2099,8 +2109,12 @@ typedef struct {
 /** Information needed to keep and cache a signed extra-info document. */
 typedef struct extrainfo_t {
   signed_descriptor_t cache_info;
+  /** SHA256 digest of this document */
+  uint8_t digest256[DIGEST256_LEN];
   /** The router's nickname. */
   char nickname[MAX_NICKNAME_LEN+1];
+  /** Certificate for ed25519 signing key */
+  struct tor_cert_st *signing_key_cert;
   /** True iff we found the right key for this extra-info, verified the
    * signature, and found it to be bad. */
   unsigned int bad_sig : 1;
@@ -2245,6 +2259,8 @@ typedef struct microdesc_t {
   crypto_pk_t *onion_pkey;
   /** As routerinfo_t.onion_curve25519_pkey */
   curve25519_public_key_t *onion_curve25519_pkey;
+  /** Ed25519 identity key, if included. */
+  ed25519_public_key_t *ed25519_identity_pkey;
   /** As routerinfo_t.ipv6_add */
   tor_addr_t ipv6_addr;
   /** As routerinfo_t.ipv6_orport */
@@ -2359,9 +2375,13 @@ typedef struct vote_routerstatus_t {
   char *version; /**< The version that the authority says this router is
                   * running. */
   unsigned int has_measured_bw:1; /**< The vote had a measured bw */
+  unsigned int has_ed25519_listing:1; /** DOCDOC */
+  unsigned int ed25519_reflects_consensus:1; /** DOCDOC */
   uint32_t measured_bw_kb; /**< Measured bandwidth (capacity) of the router */
   /** The hash or hashes that the authority claims this microdesc has. */
   vote_microdesc_hash_t *microdesc;
+  /** Ed25519 identity for this router, or zero if it has none. */
+  uint8_t ed25519_id[ED25519_PUBKEY_LEN];
 } vote_routerstatus_t;
 
 /** A signature of some document by an authority. */
@@ -4261,6 +4281,21 @@ typedef struct {
    * XXXX Eventually, the default will be 0. */
   int ExitRelay;
 
+
+  /** For how long (seconds) do we declare our singning keys to be valid? */
+  int SigningKeyLifetime;
+  /** For how long (seconds) do we declare our link keys to be valid? */
+  int TestingLinkCertLifetime;
+  /** For how long (seconds) do we declare our auth keys to be valid? */
+  int TestingAuthKeyLifetime;
+
+  /** How long before signing keys expire will we try to make a new one? */
+  int TestingSigningKeySlop;
+  /** How long before link keys expire will we try to make a new one? */
+  int TestingLinkKeySlop;
+  /** How long before auth keys expire will we try to make a new one? */
+  int TestingAuthKeySlop;
+
 } or_options_t;
 
 /** Persistent state for an onion router, as saved to disk. */
@@ -5065,6 +5100,8 @@ typedef enum was_router_added_t {
   /* Router descriptor was rejected because it was older than
    * OLD_ROUTER_DESC_MAX_AGE. */
   ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'NOT_NEW' */
+  /* DOCDOC */
+  ROUTER_CERTS_EXPIRED = -8
 } was_router_added_t;
 
 /********************************* routerparse.c ************************/

+ 222 - 20
src/or/router.c

@@ -26,9 +26,11 @@
 #include "relay.h"
 #include "rephist.h"
 #include "router.h"
+#include "routerkeys.h"
 #include "routerlist.h"
 #include "routerparse.h"
 #include "statefile.h"
+#include "torcert.h"
 #include "transports.h"
 #include "routerset.h"
 
@@ -204,6 +206,8 @@ set_server_identity_key(crypto_pk_t *k)
 static void
 assert_identity_keys_ok(void)
 {
+  if (1)
+    return;
   tor_assert(client_identitykey);
   if (public_server_mode(get_options())) {
     /* assert that we have set the client and server keys to be equal */
@@ -863,6 +867,10 @@ init_keys(void)
     set_client_identity_key(prkey);
   }
 
+  /* 1d. Load all ed25519 keys */
+  if (load_ed_keys(options,now) < 0)
+    return -1;
+
   /* 2. Read onion key.  Make it if none is found. */
   keydir = get_datadir_fname2("keys", "secret_onion_key");
   log_info(LD_GENERAL,"Reading/making onion key \"%s\"...",keydir);
@@ -928,6 +936,13 @@ init_keys(void)
     return -1;
   }
 
+  /* 3b. Get an ed25519 link certificate.  Note that we need to do this
+   * after we set up the TLS context */
+  if (generate_ed_link_cert(options, now) < 0) {
+    log_err(LD_GENERAL,"Couldn't make link cert");
+    return -1;
+  }
+
   /* 4. Build our router descriptor. */
   /* Must be called after keys are initialized. */
   mydesc = router_get_my_descriptor();
@@ -1872,6 +1887,8 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
     routerinfo_free(ri);
     return -1;
   }
+  ri->signing_key_cert = tor_cert_dup(get_master_signing_key_cert());
+
   get_platform_str(platform, sizeof(platform));
   ri->platform = tor_strdup(platform);
 
@@ -1962,10 +1979,12 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
   ei->cache_info.is_extrainfo = 1;
   strlcpy(ei->nickname, get_options()->Nickname, sizeof(ei->nickname));
   ei->cache_info.published_on = ri->cache_info.published_on;
+  ei->signing_key_cert = tor_cert_dup(get_master_signing_key_cert());
   memcpy(ei->cache_info.identity_digest, ri->cache_info.identity_digest,
          DIGEST_LEN);
   if (extrainfo_dump_to_string(&ei->cache_info.signed_descriptor_body,
-                               ei, get_server_identity_key()) < 0) {
+                               ei, get_server_identity_key(),
+                               get_master_signing_keypair()) < 0) {
     log_warn(LD_BUG, "Couldn't generate extra-info descriptor.");
     extrainfo_free(ei);
     ei = NULL;
@@ -1975,6 +1994,10 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
     router_get_extrainfo_hash(ei->cache_info.signed_descriptor_body,
                               ei->cache_info.signed_descriptor_len,
                               ei->cache_info.signed_descriptor_digest);
+    crypto_digest256((char*) ei->digest256,
+                     ei->cache_info.signed_descriptor_body,
+                     ei->cache_info.signed_descriptor_len,
+                     DIGEST_SHA256);
   }
 
   /* Now finish the router descriptor. */
@@ -1982,12 +2005,18 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
     memcpy(ri->cache_info.extra_info_digest,
            ei->cache_info.signed_descriptor_digest,
            DIGEST_LEN);
+    memcpy(ri->extra_info_digest256,
+           ei->digest256,
+           DIGEST256_LEN);
   } else {
     /* ri was allocated with tor_malloc_zero, so there is no need to
      * zero ri->cache_info.extra_info_digest here. */
   }
-  if (! (ri->cache_info.signed_descriptor_body = router_dump_router_to_string(
-                                           ri, get_server_identity_key()))) {
+  if (! (ri->cache_info.signed_descriptor_body =
+          router_dump_router_to_string(ri, get_server_identity_key(),
+                                       get_onion_key(),
+                                       get_current_curve25519_keypair(),
+                                       get_master_signing_keypair())) ) {
     log_warn(LD_BUG, "Couldn't generate router descriptor.");
     routerinfo_free(ri);
     extrainfo_free(ei);
@@ -2328,22 +2357,28 @@ get_platform_str(char *platform, size_t len)
  */
 char *
 router_dump_router_to_string(routerinfo_t *router,
-                             crypto_pk_t *ident_key)
+                             const crypto_pk_t *ident_key,
+                             const crypto_pk_t *tap_key,
+                             const curve25519_keypair_t *ntor_keypair,
+                             const ed25519_keypair_t *signing_keypair)
 {
   char *address = NULL;
   char *onion_pkey = NULL; /* Onion key, PEM-encoded. */
   char *identity_pkey = NULL; /* Identity key, PEM-encoded. */
-  char digest[DIGEST_LEN];
+  char digest[DIGEST256_LEN];
   char published[ISO_TIME_LEN+1];
   char fingerprint[FINGERPRINT_LEN+1];
-  int has_extra_info_digest;
-  char extra_info_digest[HEX_DIGEST_LEN+1];
+  char *extra_info_line = NULL;
   size_t onion_pkeylen, identity_pkeylen;
   char *family_line = NULL;
   char *extra_or_address = NULL;
   const or_options_t *options = get_options();
   smartlist_t *chunks = NULL;
   char *output = NULL;
+  const int emit_ed_sigs = signing_keypair && router->signing_key_cert;
+  char *ed_cert_line = NULL;
+  char *rsa_tap_cc_line = NULL;
+  char *ntor_cc_line = NULL;
 
   /* Make sure the identity key matches the one in the routerinfo. */
   if (!crypto_pk_eq_keys(ident_key, router->identity_pkey)) {
@@ -2351,6 +2386,16 @@ router_dump_router_to_string(routerinfo_t *router,
              "match router's public key!");
     goto err;
   }
+  if (emit_ed_sigs) {
+    if (!router->signing_key_cert->signing_key_included ||
+        !ed25519_pubkey_eq(&router->signing_key_cert->signed_key,
+                           &signing_keypair->pubkey)) {
+      log_warn(LD_BUG, "Tried to sign a router descriptor with a mismatched "
+               "ed25519 key chain %d",
+               router->signing_key_cert->signing_key_included);
+      goto err;
+    }
+  }
 
   /* record our fingerprint, so we can include it in the descriptor */
   if (crypto_pk_get_fingerprint(router->identity_pkey, fingerprint, 1)<0) {
@@ -2358,6 +2403,22 @@ router_dump_router_to_string(routerinfo_t *router,
     goto err;
   }
 
+  if (emit_ed_sigs) {
+    /* Encode ed25519 signing cert */
+    char ed_cert_base64[256];
+    if (base64_encode(ed_cert_base64, sizeof(ed_cert_base64),
+                      (const char*)router->signing_key_cert->encoded,
+                      router->signing_key_cert->encoded_len,
+                      BASE64_ENCODE_MULTILINE) < 0) {
+      log_err(LD_BUG,"Couldn't base64-encode signing key certificate!");
+      goto err;
+    }
+    tor_asprintf(&ed_cert_line, "identity-ed25519\n"
+                 "-----BEGIN ED25519 CERT-----\n"
+                 "%s"
+                 "-----END ED25519 CERT-----\n", ed_cert_base64);
+  }
+
   /* PEM-encode the onion key */
   if (crypto_pk_write_public_key_to_string(router->onion_pkey,
                                            &onion_pkey,&onion_pkeylen)<0) {
@@ -2372,6 +2433,69 @@ router_dump_router_to_string(routerinfo_t *router,
     goto err;
   }
 
+  /* Cross-certify with RSA key */
+  if (tap_key && router->signing_key_cert &&
+      router->signing_key_cert->signing_key_included) {
+    char buf[256];
+    int tap_cc_len = 0;
+    uint8_t *tap_cc =
+      make_tap_onion_key_crosscert(tap_key,
+                                   &router->signing_key_cert->signing_key,
+                                   router->identity_pkey,
+                                   &tap_cc_len);
+    if (!tap_cc) {
+      log_warn(LD_BUG,"make_tap_onion_key_crosscert failed!");
+      goto err;
+    }
+
+    if (base64_encode(buf, sizeof(buf), (const char*)tap_cc, tap_cc_len,
+                      BASE64_ENCODE_MULTILINE) < 0) {
+      log_warn(LD_BUG,"base64_encode(rsa_crosscert) failed!");
+      tor_free(tap_cc);
+      goto err;
+    }
+    tor_free(tap_cc);
+
+    tor_asprintf(&rsa_tap_cc_line,
+                 "onion-key-crosscert\n"
+                 "-----BEGIN CROSSCERT-----\n"
+                 "%s"
+                 "-----END CROSSCERT-----\n", buf);
+  }
+
+  /* Cross-certify with onion keys */
+  if (ntor_keypair && router->signing_key_cert &&
+      router->signing_key_cert->signing_key_included) {
+    int sign = 0;
+    char buf[256];
+    /* XXXX Base the expiration date on the actual onion key expiration time?*/
+    tor_cert_t *cert =
+      make_ntor_onion_key_crosscert(ntor_keypair,
+                                &router->signing_key_cert->signing_key,
+                                router->cache_info.published_on,
+                                MIN_ONION_KEY_LIFETIME, &sign);
+    if (!cert) {
+      log_warn(LD_BUG,"make_ntor_onion_key_crosscert failed!");
+      goto err;
+    }
+    tor_assert(sign == 0 || sign == 1);
+
+    if (base64_encode(buf, sizeof(buf),
+                      (const char*)cert->encoded, cert->encoded_len,
+                      BASE64_ENCODE_MULTILINE)<0) {
+      log_warn(LD_BUG,"base64_encode(ntor_crosscert) failed!");
+      tor_cert_free(cert);
+      goto err;
+    }
+    tor_cert_free(cert);
+
+    tor_asprintf(&ntor_cc_line,
+                 "ntor-onion-key-crosscert %d\n"
+                 "-----BEGIN ED25519 CERT-----\n"
+                 "%s"
+                 "-----END ED25519 CERT-----\n", sign, buf);
+  }
+
   /* Encode the publication time. */
   format_iso_time(published, router->cache_info.published_on);
 
@@ -2384,12 +2508,19 @@ router_dump_router_to_string(routerinfo_t *router,
     family_line = tor_strdup("");
   }
 
-  has_extra_info_digest =
-    ! tor_digest_is_zero(router->cache_info.extra_info_digest);
-
-  if (has_extra_info_digest) {
+  if (!tor_digest_is_zero(router->cache_info.extra_info_digest)) {
+    char extra_info_digest[HEX_DIGEST_LEN+1];
     base16_encode(extra_info_digest, sizeof(extra_info_digest),
                   router->cache_info.extra_info_digest, DIGEST_LEN);
+    if (!tor_digest256_is_zero(router->extra_info_digest256)) {
+      char d256_64[BASE64_DIGEST256_LEN+1];
+      digest256_to_base64(d256_64, router->extra_info_digest256);
+      tor_asprintf(&extra_info_line, "extra-info-digest %s %s\n",
+                   extra_info_digest, d256_64);
+    } else {
+      tor_asprintf(&extra_info_line, "extra-info-digest %s\n",
+                   extra_info_digest);
+    }
   }
 
   if (router->ipv6_orport &&
@@ -2411,20 +2542,23 @@ router_dump_router_to_string(routerinfo_t *router,
   smartlist_add_asprintf(chunks,
                     "router %s %s %d 0 %d\n"
                     "%s"
+                    "%s"
                     "platform %s\n"
                     "protocols Link 1 2 Circuit 1\n"
                     "published %s\n"
                     "fingerprint %s\n"
                     "uptime %ld\n"
                     "bandwidth %d %d %d\n"
-                    "%s%s%s%s"
+                    "%s%s"
                     "onion-key\n%s"
                     "signing-key\n%s"
+                    "%s%s"
                     "%s%s%s%s",
     router->nickname,
     address,
     router->or_port,
     decide_to_advertise_dirport(options, router->dir_port),
+    ed_cert_line ? ed_cert_line : "",
     extra_or_address ? extra_or_address : "",
     router->platform,
     published,
@@ -2433,12 +2567,12 @@ router_dump_router_to_string(routerinfo_t *router,
     (int) router->bandwidthrate,
     (int) router->bandwidthburst,
     (int) router->bandwidthcapacity,
-    has_extra_info_digest ? "extra-info-digest " : "",
-    has_extra_info_digest ? extra_info_digest : "",
-    has_extra_info_digest ? "\n" : "",
+    extra_info_line ? extra_info_line : "",
     (options->DownloadExtraInfo || options->V3AuthoritativeDir) ?
                          "caches-extra-info\n" : "",
     onion_pkey, identity_pkey,
+    rsa_tap_cc_line ? rsa_tap_cc_line : "",
+    ntor_cc_line ? ntor_cc_line : "",
     family_line,
     we_are_hibernating() ? "hibernating 1\n" : "",
     options->HidServDirectoryV2 ? "hidden-service-dir\n" : "",
@@ -2481,7 +2615,24 @@ router_dump_router_to_string(routerinfo_t *router,
     tor_free(p6);
   }
 
-  /* Sign the descriptor */
+  /* Sign the descriptor with Ed25519 */
+  if (emit_ed_sigs)  {
+    smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));
+    crypto_digest_smartlist_prefix(digest, DIGEST256_LEN,
+                                   ED_DESC_SIGNATURE_PREFIX,
+                                   chunks, "", DIGEST_SHA256);
+    ed25519_signature_t sig;
+    char buf[ED25519_SIG_BASE64_LEN+1];
+    if (ed25519_sign(&sig, (const uint8_t*)digest, DIGEST256_LEN,
+                     signing_keypair) < 0)
+      goto err;
+    if (ed25519_signature_to_base64(buf, &sig) < 0)
+      goto err;
+
+    smartlist_add_asprintf(chunks, "%s\n", buf);
+  }
+
+  /* Sign the descriptor with RSA */
   smartlist_add(chunks, tor_strdup("router-signature\n"));
 
   crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1);
@@ -2533,6 +2684,10 @@ router_dump_router_to_string(routerinfo_t *router,
   tor_free(onion_pkey);
   tor_free(identity_pkey);
   tor_free(extra_or_address);
+  tor_free(ed_cert_line);
+  tor_free(rsa_tap_cc_line);
+  tor_free(ntor_cc_line);
+  tor_free(extra_info_line);
 
   return output;
 }
@@ -2676,7 +2831,8 @@ load_stats_file(const char *filename, const char *end_line, time_t now,
  * success, negative on failure. */
 int
 extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
-                         crypto_pk_t *ident_key)
+                         crypto_pk_t *ident_key,
+                         const ed25519_keypair_t *signing_keypair)
 {
   const or_options_t *options = get_options();
   char identity[HEX_DIGEST_LEN+1];
@@ -2686,18 +2842,45 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
   int result;
   static int write_stats_to_extrainfo = 1;
   char sig[DIROBJ_MAX_SIG_LEN+1];
-  char *s, *pre, *contents, *cp, *s_dup = NULL;
+  char *s = NULL, *pre, *contents, *cp, *s_dup = NULL;
   time_t now = time(NULL);
   smartlist_t *chunks = smartlist_new();
   extrainfo_t *ei_tmp = NULL;
+  const int emit_ed_sigs = signing_keypair && extrainfo->signing_key_cert;
+  char *ed_cert_line = NULL;
 
   base16_encode(identity, sizeof(identity),
                 extrainfo->cache_info.identity_digest, DIGEST_LEN);
   format_iso_time(published, extrainfo->cache_info.published_on);
   bandwidth_usage = rep_hist_get_bandwidth_lines();
+  if (emit_ed_sigs) {
+    if (!extrainfo->signing_key_cert->signing_key_included ||
+        !ed25519_pubkey_eq(&extrainfo->signing_key_cert->signed_key,
+                           &signing_keypair->pubkey)) {
+      log_warn(LD_BUG, "Tried to sign a extrainfo descriptor with a "
+               "mismatched ed25519 key chain %d",
+               extrainfo->signing_key_cert->signing_key_included);
+      goto err;
+    }
+    char ed_cert_base64[256];
+    if (base64_encode(ed_cert_base64, sizeof(ed_cert_base64),
+                      (const char*)extrainfo->signing_key_cert->encoded,
+                      extrainfo->signing_key_cert->encoded_len,
+                      BASE64_ENCODE_MULTILINE) < 0) {
+      log_err(LD_BUG,"Couldn't base64-encode signing key certificate!");
+      goto err;
+    }
+    tor_asprintf(&ed_cert_line, "identity-ed25519\n"
+                 "-----BEGIN ED25519 CERT-----\n"
+                 "%s"
+                 "-----END ED25519 CERT-----\n", ed_cert_base64);
+  } else {
+    ed_cert_line = tor_strdup("");
+  }
 
-  tor_asprintf(&pre, "extra-info %s %s\npublished %s\n%s",
+  tor_asprintf(&pre, "extra-info %s %s\n%spublished %s\n%s",
                extrainfo->nickname, identity,
+               ed_cert_line,
                published, bandwidth_usage);
   tor_free(bandwidth_usage);
   smartlist_add(chunks, pre);
@@ -2757,6 +2940,23 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
     }
   }
 
+  if (emit_ed_sigs) {
+    char digest[DIGEST256_LEN];
+    smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));
+    crypto_digest_smartlist_prefix(digest, DIGEST256_LEN,
+                                   ED_DESC_SIGNATURE_PREFIX,
+                                   chunks, "", DIGEST_SHA256);
+    ed25519_signature_t sig;
+    char buf[ED25519_SIG_BASE64_LEN+1];
+    if (ed25519_sign(&sig, (const uint8_t*)digest, DIGEST256_LEN,
+                     signing_keypair) < 0)
+      goto err;
+    if (ed25519_signature_to_base64(buf, &sig) < 0)
+      goto err;
+
+    smartlist_add_asprintf(chunks, "%s\n", buf);
+  }
+
   smartlist_add(chunks, tor_strdup("router-signature\n"));
   s = smartlist_join_strings(chunks, "", 0, NULL);
 
@@ -2805,7 +3005,8 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
                            "adding statistics to this or any future "
                            "extra-info descriptors.");
       write_stats_to_extrainfo = 0;
-      result = extrainfo_dump_to_string(s_out, extrainfo, ident_key);
+      result = extrainfo_dump_to_string(s_out, extrainfo, ident_key,
+                                        signing_keypair);
       goto done;
     } else {
       log_warn(LD_BUG, "We just generated an extrainfo descriptor we "
@@ -2827,6 +3028,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
   SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
   smartlist_free(chunks);
   tor_free(s_dup);
+  tor_free(ed_cert_line);
   extrainfo_free(ei_tmp);
 
   return result;

+ 6 - 2
src/or/router.h

@@ -92,7 +92,10 @@ int router_pick_published_address(const or_options_t *options, uint32_t *addr);
 int router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e);
 int router_rebuild_descriptor(int force);
 char *router_dump_router_to_string(routerinfo_t *router,
-                                   crypto_pk_t *ident_key);
+                                   const crypto_pk_t *ident_key,
+                                   const crypto_pk_t *tap_key,
+                                   const curve25519_keypair_t *ntor_keypair,
+                                   const ed25519_keypair_t *signing_keypair);
 char *router_dump_exit_policy_to_string(const routerinfo_t *router,
                                          int include_ipv4,
                                          int include_ipv6);
@@ -107,7 +110,8 @@ int router_has_addr(const routerinfo_t *router, const tor_addr_t *addr);
 int router_has_orport(const routerinfo_t *router,
                       const tor_addr_port_t *orport);
 int extrainfo_dump_to_string(char **s, extrainfo_t *extrainfo,
-                             crypto_pk_t *ident_key);
+                             crypto_pk_t *ident_key,
+                             const ed25519_keypair_t *signing_keypair);
 int is_legal_nickname(const char *s);
 int is_legal_nickname_or_hexdigest(const char *s);
 int is_legal_hexdigest(const char *s);

+ 648 - 0
src/or/routerkeys.c

@@ -0,0 +1,648 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "or.h"
+#include "config.h"
+#include "router.h"
+#include "routerkeys.h"
+#include "torcert.h"
+
+/**
+ * Read an ed25519 key and associated certificates from files beginning with
+ * <b>fname</b>, with certificate type <b>cert_type</b>.  On failure, return
+ * NULL; on success return the keypair.
+ *
+ * If INIT_ED_KEY_CREATE is set in <b>flags</b>, then create the key (and
+ * certificate if requested) if it doesn't exist, and save it to disk.
+ *
+ * If INIT_ED_KEY_NEEDCERT is set in <b>flags</b>, load/create a certificate
+ * too and store it in *<b>cert_out</b>.  Fail if the cert can't be
+ * found/created.  To create a certificate, <b>signing_key</b> must be set to
+ * the key that should sign it; <b>now</b> to the current time, and
+ * <b>lifetime</b> to the lifetime of the key.
+ *
+ * If INIT_ED_KEY_REPLACE is set in <b>flags</b>, then create and save new key
+ * whether we can read the old one or not.
+ *
+ * If INIT_ED_KEY_EXTRA_STRONG is set in <b>flags</b>, set the extra_strong
+ * flag when creating the secret key.
+ *
+ * If INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT is set in <b>flags</b>, and
+ * we create a new certificate, create it with the signing key embedded.
+ *
+ * If INIT_ED_KEY_SPLIT is set in <b>flags</b>, and we create a new key,
+ * store the public key in a separate file from the secret key.
+ *
+ * If INIT_ED_KEY_MISSING_SECRET_OK is set in <b>flags</b>, and we find a
+ * public key file but no secret key file, return successfully anyway.
+ *
+ * If INIT_ED_KEY_OMIT_SECRET is set in <b>flags</b>, do not even try to
+ * load or return a secret key (but create and save on if needed).
+ */
+ed25519_keypair_t *
+ed_key_init_from_file(const char *fname, uint32_t flags,
+                      int severity,
+                      const ed25519_keypair_t *signing_key,
+                      time_t now,
+                      time_t lifetime,
+                      uint8_t cert_type,
+                      struct tor_cert_st **cert_out)
+{
+  char *secret_fname = NULL;
+  char *public_fname = NULL;
+  char *cert_fname = NULL;
+  int created_pk = 0, created_sk = 0, created_cert = 0;
+  const int try_to_load = ! (flags & INIT_ED_KEY_REPLACE);
+
+  char tag[8];
+  tor_snprintf(tag, sizeof(tag), "type%d", (int)cert_type);
+
+  tor_cert_t *cert = NULL;
+  char *got_tag = NULL;
+  ed25519_keypair_t *keypair = tor_malloc_zero(sizeof(ed25519_keypair_t));
+
+  tor_asprintf(&secret_fname, "%s_secret_key", fname);
+  tor_asprintf(&public_fname, "%s_public_key", fname);
+  tor_asprintf(&cert_fname, "%s_cert", fname);
+
+  /* Try to read the secret key. */
+  const int have_secret = try_to_load &&
+    !(flags & INIT_ED_KEY_OMIT_SECRET) &&
+    ed25519_seckey_read_from_file(&keypair->seckey,
+                                  &got_tag, secret_fname) == 0;
+
+  if (have_secret) {
+    if (strcmp(got_tag, tag)) {
+      tor_log(severity, LD_OR, "%s has wrong tag", secret_fname);
+      goto err;
+    }
+    /* Derive the public key */
+    if (ed25519_public_key_generate(&keypair->pubkey, &keypair->seckey)<0) {
+      tor_log(severity, LD_OR, "%s can't produce a public key", secret_fname);
+      goto err;
+    }
+  }
+
+  /* If it's absent and that's okay, try to read the pubkey. */
+  int found_public = 0;
+  if (!have_secret && try_to_load) {
+    tor_free(got_tag);
+    found_public = ed25519_pubkey_read_from_file(&keypair->pubkey,
+                                                 &got_tag, public_fname) == 0;
+    if (found_public && strcmp(got_tag, tag)) {
+      tor_log(severity, LD_OR, "%s has wrong tag", public_fname);
+      goto err;
+    }
+  }
+
+  /* If the secret key is absent and it's not allowed to be, fail. */
+  if (!have_secret && found_public && !(flags & INIT_ED_KEY_MISSING_SECRET_OK))
+    goto err;
+
+  /* If it's absent, and we're not supposed to make a new keypair, fail. */
+  if (!have_secret && !found_public && !(flags & INIT_ED_KEY_CREATE))
+    goto err;
+
+  /* if it's absent, make a new keypair and save it. */
+  if (!have_secret && !found_public) {
+    const int split = !! (flags & INIT_ED_KEY_SPLIT);
+    tor_free(keypair);
+    keypair = ed_key_new(signing_key, flags, now, lifetime,
+                         cert_type, &cert);
+    if (!keypair) {
+      tor_log(severity, LD_OR, "Couldn't create keypair");
+      goto err;
+    }
+
+    created_pk = created_sk = created_cert = 1;
+    if (ed25519_seckey_write_to_file(&keypair->seckey, secret_fname, tag) < 0
+        ||
+        (split &&
+         ed25519_pubkey_write_to_file(&keypair->pubkey, public_fname, tag) < 0)
+        ||
+        (cert &&
+         crypto_write_tagged_contents_to_file(cert_fname, "ed25519v1-cert",
+                                 tag, cert->encoded, cert->encoded_len) < 0)) {
+      tor_log(severity, LD_OR, "Couldn't write keys or cert to file.");
+      goto err;
+    }
+    goto done;
+  }
+
+  /* If we're not supposed to get a cert, we're done. */
+  if (! (flags & INIT_ED_KEY_NEEDCERT))
+    goto done;
+
+  /* Read a cert. */
+  uint8_t certbuf[256];
+  ssize_t cert_body_len = crypto_read_tagged_contents_from_file(
+                 cert_fname, "ed25519v1-cert",
+                 &got_tag, certbuf, sizeof(certbuf));
+  if (cert_body_len >= 0 && !strcmp(got_tag, tag))
+    cert = tor_cert_parse(certbuf, cert_body_len);
+
+  /* If we got it, check it to the extent we can. */
+  if (cert) {
+    int bad_cert = 0;
+
+    if (! cert) {
+      tor_log(severity, LD_OR, "Cert was unparseable");
+      bad_cert = 1;
+    } else if (!tor_memeq(cert->signed_key.pubkey, keypair->pubkey.pubkey,
+                          ED25519_PUBKEY_LEN)) {
+      tor_log(severity, LD_OR, "Cert was for wrong key");
+      bad_cert = 1;
+    } else if (tor_cert_checksig(cert, &signing_key->pubkey, now) < 0 &&
+               (signing_key || cert->cert_expired)) {
+      tor_log(severity, LD_OR, "Can't check certificate");
+      bad_cert = 1;
+    }
+
+    if (bad_cert) {
+      tor_cert_free(cert);
+      cert = NULL;
+    }
+  }
+
+  /* If we got a cert, we're done. */
+  if (cert)
+    goto done;
+
+  /* If we didn't get a cert, and we're not supposed to make one, fail. */
+  if (!signing_key || !(flags & INIT_ED_KEY_CREATE))
+    goto err;
+
+  /* We have keys but not a certificate, so make one. */
+  uint32_t cert_flags = 0;
+  if (flags & INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT)
+    cert_flags |= CERT_FLAG_INCLUDE_SIGNING_KEY;
+  cert = tor_cert_create(signing_key, cert_type,
+                         &keypair->pubkey,
+                         now, lifetime,
+                         cert_flags);
+
+  if (! cert)
+    goto err;
+
+  /* Write it to disk. */
+  created_cert = 1;
+  if (crypto_write_tagged_contents_to_file(cert_fname, "ed25519v1-cert",
+                             tag, cert->encoded, cert->encoded_len) < 0) {
+    tor_log(severity, LD_OR, "Couldn't write cert to disk.");
+    goto err;
+  }
+
+ done:
+  if (cert_out)
+    *cert_out = cert;
+  else
+    tor_cert_free(cert);
+
+  goto cleanup;
+
+ err:
+  memwipe(keypair, 0, sizeof(*keypair));
+  tor_free(keypair);
+  tor_cert_free(cert);
+  if (cert_out)
+    *cert_out = NULL;
+  if (created_sk)
+    unlink(secret_fname);
+  if (created_pk)
+    unlink(public_fname);
+  if (created_cert)
+    unlink(cert_fname);
+
+ cleanup:
+  tor_free(secret_fname);
+  tor_free(public_fname);
+  tor_free(cert_fname);
+
+  return keypair;
+}
+
+/**
+ * Create a new signing key and (optionally) certficiate; do not read or write
+ * from disk.  See ed_key_init_from_file() for more information.
+ */
+ed25519_keypair_t *
+ed_key_new(const ed25519_keypair_t *signing_key,
+           uint32_t flags,
+           time_t now,
+           time_t lifetime,
+           uint8_t cert_type,
+           struct tor_cert_st **cert_out)
+{
+  if (cert_out)
+    *cert_out = NULL;
+
+  const int extra_strong = !! (flags & INIT_ED_KEY_EXTRA_STRONG);
+  ed25519_keypair_t *keypair = tor_malloc_zero(sizeof(ed25519_keypair_t));
+  if (ed25519_keypair_generate(keypair, extra_strong) < 0)
+    goto err;
+
+  if (! (flags & INIT_ED_KEY_NEEDCERT))
+    return keypair;
+
+  tor_assert(signing_key);
+  tor_assert(cert_out);
+  uint32_t cert_flags = 0;
+  if (flags & INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT)
+    cert_flags |= CERT_FLAG_INCLUDE_SIGNING_KEY;
+  tor_cert_t *cert = tor_cert_create(signing_key, cert_type,
+                                     &keypair->pubkey,
+                                     now, lifetime,
+                                     cert_flags);
+  if (! cert)
+    goto err;
+
+  *cert_out = cert;
+  return keypair;
+
+ err:
+  tor_free(keypair);
+  return NULL;
+}
+
+static ed25519_keypair_t *master_identity_key = NULL;
+static ed25519_keypair_t *master_signing_key = NULL;
+static ed25519_keypair_t *current_auth_key = NULL;
+static tor_cert_t *signing_key_cert = NULL;
+static tor_cert_t *link_cert_cert = NULL;
+static tor_cert_t *auth_key_cert = NULL;
+
+static uint8_t *rsa_ed_crosscert = NULL;
+static size_t rsa_ed_crosscert_len = 0;
+
+/**
+ * Running as a server: load, reload, or refresh our ed25519 keys and
+ * certificates, creating and saving new ones as needed.
+ */
+int
+load_ed_keys(const or_options_t *options, time_t now)
+{
+  ed25519_keypair_t *id = NULL;
+  ed25519_keypair_t *sign = NULL;
+  ed25519_keypair_t *auth = NULL;
+  const ed25519_keypair_t *sign_signing_key_with_id = NULL;
+  const ed25519_keypair_t *use_signing = NULL;
+  const tor_cert_t *check_signing_cert = NULL;
+  tor_cert_t *sign_cert = NULL;
+  tor_cert_t *auth_cert = NULL;
+
+#define FAIL(msg) do {                          \
+    log_warn(LD_OR, (msg));                     \
+    goto err;                                   \
+  } while (0)
+#define SET_KEY(key, newval) do {               \
+    ed25519_keypair_free(key);                  \
+    key = (newval);                             \
+  } while (0)
+#define SET_CERT(cert, newval) do {             \
+    tor_cert_free(cert);                        \
+    cert = (newval);                            \
+  } while (0)
+#define EXPIRES_SOON(cert, interval)            \
+  (!(cert) || (cert)->valid_until < now + (interval))
+
+  /* XXXX support encrypted identity keys fully */
+
+  /* First try to get the signing key to see how it is. */
+  if (master_signing_key) {
+    check_signing_cert = signing_key_cert;
+    use_signing = master_signing_key;
+  } else {
+    sign = ed_key_init_from_file(
+               options_get_datadir_fname2(options, "keys", "ed25519_signing"),
+               INIT_ED_KEY_NEEDCERT|
+               INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT,
+               LOG_INFO,
+               NULL, 0, 0, CERT_TYPE_ID_SIGNING, &sign_cert);
+    check_signing_cert = sign_cert;
+    use_signing = sign;
+  }
+
+  const int need_new_signing_key =
+    NULL == use_signing ||
+    EXPIRES_SOON(check_signing_cert, 0);
+  const int want_new_signing_key =
+    need_new_signing_key ||
+    EXPIRES_SOON(check_signing_cert, options->TestingSigningKeySlop);
+
+  {
+    uint32_t flags =
+      (INIT_ED_KEY_CREATE|INIT_ED_KEY_SPLIT|
+       INIT_ED_KEY_EXTRA_STRONG);
+    if (! need_new_signing_key)
+      flags |= INIT_ED_KEY_MISSING_SECRET_OK;
+    if (! want_new_signing_key)
+      flags |= INIT_ED_KEY_OMIT_SECRET;
+
+    id = ed_key_init_from_file(
+             options_get_datadir_fname2(options, "keys", "ed25519_master_id"),
+             flags,
+             LOG_WARN, NULL, 0, 0, 0, NULL);
+    if (!id)
+      FAIL("Missing identity key");
+    if (tor_mem_is_zero((char*)id->seckey.seckey, sizeof(id->seckey)))
+      sign_signing_key_with_id = NULL;
+    else
+      sign_signing_key_with_id = id;
+  }
+
+  if (need_new_signing_key && NULL == sign_signing_key_with_id)
+    FAIL("Can't load master key make a new signing key.");
+
+  if (want_new_signing_key && sign_signing_key_with_id) {
+    uint32_t flags = (INIT_ED_KEY_CREATE|
+                      INIT_ED_KEY_REPLACE|
+                      INIT_ED_KEY_EXTRA_STRONG|
+                      INIT_ED_KEY_NEEDCERT|
+                      INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT);
+    sign = ed_key_init_from_file(
+               options_get_datadir_fname2(options, "keys", "ed25519_signing"),
+                                 flags, LOG_WARN,
+                                 sign_signing_key_with_id, now,
+                                 options->SigningKeyLifetime,
+                                 CERT_TYPE_ID_SIGNING, &sign_cert);
+    if (!sign)
+      FAIL("Missing signing key");
+    use_signing = sign;
+  } else if (want_new_signing_key) {
+    static ratelim_t missing_master = RATELIM_INIT(3600);
+    log_fn_ratelim(&missing_master, LOG_WARN, LD_OR,
+                   "Signing key will expire soon, but I can't load the "
+                   "master key to sign a new one!");
+  }
+
+  tor_assert(use_signing);
+
+  /* At this point we no longer need our secret identity key.  So wipe
+   * it, if we loaded it in the first place. */
+  memwipe(id->seckey.seckey, 0, sizeof(id->seckey));
+
+  if (!rsa_ed_crosscert && server_mode(options)) {
+    uint8_t *crosscert;
+    ssize_t crosscert_len = tor_make_rsa_ed25519_crosscert(&id->pubkey,
+                                                   get_server_identity_key(),
+                                                   now+10*365*86400,/*XXXX*/
+                                                   &crosscert);
+    rsa_ed_crosscert_len = crosscert_len;
+    rsa_ed_crosscert = crosscert;
+  }
+
+  if (!current_auth_key ||
+      EXPIRES_SOON(auth_key_cert, options->TestingAuthKeySlop)) {
+    auth = ed_key_new(use_signing, INIT_ED_KEY_NEEDCERT,
+                      now,
+                      options->TestingAuthKeyLifetime,
+                      CERT_TYPE_SIGNING_AUTH, &auth_cert);
+
+    if (!auth)
+      FAIL("Can't create auth key");
+  }
+
+  /* We've generated or loaded everything.  Put them in memory. */
+
+  if (! master_identity_key) {
+    SET_KEY(master_identity_key, id);
+  } else {
+    tor_free(id);
+  }
+  if (sign) {
+    SET_KEY(master_signing_key, sign);
+    SET_CERT(signing_key_cert, sign_cert);
+  }
+  if (auth) {
+    SET_KEY(current_auth_key, auth);
+    SET_CERT(auth_key_cert, auth_cert);
+  }
+
+  return 0;
+ err:
+  ed25519_keypair_free(id);
+  ed25519_keypair_free(sign);
+  ed25519_keypair_free(auth);
+  tor_cert_free(sign_cert);
+  tor_cert_free(auth_cert);
+  return -1;
+}
+
+/**DOCDOC*/
+int
+generate_ed_link_cert(const or_options_t *options, time_t now)
+{
+  const tor_x509_cert_t *link = NULL, *id = NULL;
+  tor_cert_t *link_cert = NULL;
+
+  if (tor_tls_get_my_certs(1, &link, &id) < 0 || link == NULL) {
+    log_warn(LD_OR, "Can't get my x509 link cert.");
+    return -1;
+  }
+
+  const digests_t *digests = tor_x509_cert_get_cert_digests(link);
+
+  if (link_cert_cert &&
+      ! EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop) &&
+      fast_memeq(digests->d[DIGEST_SHA256], link_cert_cert->signed_key.pubkey,
+                 DIGEST256_LEN)) {
+    return 0;
+  }
+
+  ed25519_public_key_t dummy_key;
+  memcpy(dummy_key.pubkey, digests->d[DIGEST_SHA256], DIGEST256_LEN);
+
+  link_cert = tor_cert_create(get_master_signing_keypair(),
+                              CERT_TYPE_SIGNING_LINK,
+                              &dummy_key,
+                              now,
+                              options->TestingLinkCertLifetime, 0);
+
+  if (link_cert) {
+    SET_CERT(link_cert_cert, link_cert);
+  }
+  return 0;
+}
+
+#undef FAIL
+#undef SET_KEY
+#undef SET_CERT
+
+int
+should_make_new_ed_keys(const or_options_t *options, const time_t now)
+{
+  if (!master_identity_key ||
+      !master_signing_key ||
+      !current_auth_key ||
+      !link_cert_cert ||
+      EXPIRES_SOON(signing_key_cert, options->TestingSigningKeySlop) ||
+      EXPIRES_SOON(auth_key_cert, options->TestingAuthKeySlop) ||
+      EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop))
+    return 1;
+
+  const tor_x509_cert_t *link = NULL, *id = NULL;
+
+  if (tor_tls_get_my_certs(1, &link, &id) < 0 || link == NULL)
+    return 1;
+
+  const digests_t *digests = tor_x509_cert_get_cert_digests(link);
+
+  if (!fast_memeq(digests->d[DIGEST_SHA256],
+                  link_cert_cert->signed_key.pubkey,
+                  DIGEST256_LEN)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+#undef EXPIRES_SOON
+
+const ed25519_public_key_t *
+get_master_identity_key(void)
+{
+  if (!master_identity_key)
+    return NULL;
+  return &master_identity_key->pubkey;
+}
+
+const ed25519_keypair_t *
+get_master_signing_keypair(void)
+{
+  return master_signing_key;
+}
+
+const struct tor_cert_st *
+get_master_signing_key_cert(void)
+{
+  return signing_key_cert;
+}
+
+const ed25519_keypair_t *
+get_current_auth_keypair(void)
+{
+  return current_auth_key;
+}
+
+const tor_cert_t *
+get_current_link_cert_cert(void)
+{
+  return link_cert_cert;
+}
+
+const tor_cert_t *
+get_current_auth_key_cert(void)
+{
+  return auth_key_cert;
+}
+
+void
+get_master_rsa_crosscert(const uint8_t **cert_out,
+                         size_t *size_out)
+{
+  *cert_out = rsa_ed_crosscert;
+  *size_out = rsa_ed_crosscert_len;
+}
+
+/** Construct cross-certification for the master identity key with
+ * the ntor onion key. Store the sign of the corresponding ed25519 public key
+ * in *<b>sign_out</b>. */
+tor_cert_t *
+make_ntor_onion_key_crosscert(const curve25519_keypair_t *onion_key,
+      const ed25519_public_key_t *master_id_key, time_t now, time_t lifetime,
+      int *sign_out)
+{
+  tor_cert_t *cert = NULL;
+  ed25519_keypair_t ed_onion_key;
+
+  if (ed25519_keypair_from_curve25519_keypair(&ed_onion_key, sign_out,
+                                              onion_key) < 0)
+    goto end;
+
+  cert = tor_cert_create(&ed_onion_key, CERT_TYPE_ONION_ID, master_id_key,
+      now, lifetime, 0);
+
+ end:
+  memwipe(&ed_onion_key, 0, sizeof(ed_onion_key));
+  return cert;
+}
+
+/** Construct and return an RSA signature for the TAP onion key to
+ * cross-certify the RSA and Ed25519 identity keys. Set <b>len_out</b> to its
+ * length. */
+uint8_t *
+make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
+                             const ed25519_public_key_t *master_id_key,
+                             const crypto_pk_t *rsa_id_key,
+                             int *len_out)
+{
+  uint8_t signature[PK_BYTES];
+  uint8_t signed_data[DIGEST_LEN + ED25519_PUBKEY_LEN];
+
+  *len_out = 0;
+  crypto_pk_get_digest(rsa_id_key, (char*)signed_data);
+  memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN);
+
+  int r = crypto_pk_private_sign(onion_key,
+                               (char*)signature, sizeof(signature),
+                               (const char*)signed_data, sizeof(signed_data));
+  if (r < 0)
+    return NULL;
+
+  *len_out = r;
+
+  return tor_memdup(signature, r);
+}
+
+/** Check whether an RSA-TAP cross-certification is correct. Return 0 if it
+ * is, -1 if it isn't. */
+int
+check_tap_onion_key_crosscert(const uint8_t *crosscert,
+                              int crosscert_len,
+                              const crypto_pk_t *onion_pkey,
+                              const ed25519_public_key_t *master_id_pkey,
+                              const uint8_t *rsa_id_digest)
+{
+  uint8_t *cc = tor_malloc(crypto_pk_keysize(onion_pkey));
+  int cc_len =
+    crypto_pk_public_checksig(onion_pkey,
+                              (char*)cc,
+                              crypto_pk_keysize(onion_pkey),
+                              (const char*)crosscert,
+                              crosscert_len);
+  if (cc_len < 0) {
+    goto err;
+  }
+  if (cc_len < DIGEST_LEN + ED25519_PUBKEY_LEN) {
+    log_warn(LD_DIR, "Short signature on cross-certification with TAP key");
+    goto err;
+  }
+  if (tor_memneq(cc, rsa_id_digest, DIGEST_LEN) ||
+      tor_memneq(cc + DIGEST_LEN, master_id_pkey->pubkey,
+                 ED25519_PUBKEY_LEN)) {
+    log_warn(LD_DIR, "Incorrect cross-certification with TAP key");
+    goto err;
+  }
+
+  tor_free(cc);
+  return 0;
+ err:
+  tor_free(cc);
+  return -1;
+}
+
+void
+routerkeys_free_all(void)
+{
+  ed25519_keypair_free(master_identity_key);
+  ed25519_keypair_free(master_signing_key);
+  ed25519_keypair_free(current_auth_key);
+  tor_cert_free(signing_key_cert);
+  tor_cert_free(link_cert_cert);
+  tor_cert_free(auth_key_cert);
+
+  master_identity_key = master_signing_key = NULL;
+  current_auth_key = NULL;
+  signing_key_cert = link_cert_cert = auth_key_cert = NULL;
+}
+

+ 67 - 0
src/or/routerkeys.h

@@ -0,0 +1,67 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_ROUTERKEYS_H
+#define TOR_ROUTERKEYS_H
+
+#include "crypto_ed25519.h"
+
+#define INIT_ED_KEY_CREATE                      (1u<<0)
+#define INIT_ED_KEY_REPLACE                     (1u<<1)
+#define INIT_ED_KEY_SPLIT                       (1u<<2)
+#define INIT_ED_KEY_MISSING_SECRET_OK           (1u<<3)
+#define INIT_ED_KEY_NEEDCERT                    (1u<<4)
+#define INIT_ED_KEY_EXTRA_STRONG                (1u<<5)
+#define INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT (1u<<6)
+#define INIT_ED_KEY_OMIT_SECRET                 (1u<<7)
+
+struct tor_cert_st;
+ed25519_keypair_t *ed_key_init_from_file(const char *fname, uint32_t flags,
+                                         int severity,
+                                         const ed25519_keypair_t *signing_key,
+                                         time_t now,
+                                         time_t lifetime,
+                                         uint8_t cert_type,
+                                         struct tor_cert_st **cert_out);
+ed25519_keypair_t *ed_key_new(const ed25519_keypair_t *signing_key,
+                              uint32_t flags,
+                              time_t now,
+                              time_t lifetime,
+                              uint8_t cert_type,
+                              struct tor_cert_st **cert_out);
+const ed25519_public_key_t *get_master_identity_key(void);
+const ed25519_keypair_t *get_master_signing_keypair(void);
+const struct tor_cert_st *get_master_signing_key_cert(void);
+
+const ed25519_keypair_t *get_current_auth_keypair(void);
+const struct tor_cert_st *get_current_link_cert_cert(void);
+const struct tor_cert_st *get_current_auth_key_cert(void);
+
+void get_master_rsa_crosscert(const uint8_t **cert_out,
+                              size_t *size_out);
+
+struct tor_cert_st *make_ntor_onion_key_crosscert(
+                                  const curve25519_keypair_t *onion_key,
+                                  const ed25519_public_key_t *master_id_key,
+                                  time_t now, time_t lifetime,
+                                  int *sign_out);
+uint8_t *make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
+                                  const ed25519_public_key_t *master_id_key,
+                                  const crypto_pk_t *rsa_id_key,
+                                  int *len_out);
+
+int check_tap_onion_key_crosscert(const uint8_t *crosscert,
+                                  int crosscert_len,
+                                  const crypto_pk_t *onion_pkey,
+                                  const ed25519_public_key_t *master_id_pkey,
+                                  const uint8_t *rsa_id_digest);
+
+int load_ed_keys(const or_options_t *options, time_t now);
+int should_make_new_ed_keys(const or_options_t *options, const time_t now);
+
+int generate_ed_link_cert(const or_options_t *options, time_t now);
+
+void routerkeys_free_all(void);
+
+#endif
+

+ 26 - 1
src/or/routerlist.c

@@ -13,6 +13,7 @@
 
 #define ROUTERLIST_PRIVATE
 #include "or.h"
+#include "crypto_ed25519.h"
 #include "circuitstats.h"
 #include "config.h"
 #include "connection.h"
@@ -38,6 +39,8 @@
 #include "routerparse.h"
 #include "routerset.h"
 #include "sandbox.h"
+#include "torcert.h"
+
 // #define DEBUG_ROUTERLIST
 
 /****************************************************************************/
@@ -2660,6 +2663,7 @@ routerinfo_free(routerinfo_t *router)
   tor_free(router->onion_curve25519_pkey);
   if (router->identity_pkey)
     crypto_pk_free(router->identity_pkey);
+  tor_cert_free(router->signing_key_cert);
   if (router->declared_family) {
     SMARTLIST_FOREACH(router->declared_family, char *, s, tor_free(s));
     smartlist_free(router->declared_family);
@@ -2678,6 +2682,7 @@ extrainfo_free(extrainfo_t *extrainfo)
 {
   if (!extrainfo)
     return;
+  tor_cert_free(extrainfo->signing_key_cert);
   tor_free(extrainfo->cache_info.signed_descriptor_body);
   tor_free(extrainfo->pending_sig);
 
@@ -3288,6 +3293,11 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg,
 
   old_router = router_get_mutable_by_digest(id_digest);
 
+  /* Make sure that it isn't expired. */
+  if (router->cert_expiration_time < approx_time()) {
+    return ROUTER_CERTS_EXPIRED;
+  }
+
   /* Make sure that we haven't already got this exact descriptor. */
   if (sdmap_get(routerlist->desc_digest_map,
                 router->cache_info.signed_descriptor_digest)) {
@@ -4894,7 +4904,7 @@ routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
                                        signed_descriptor_t *sd,
                                        const char **msg)
 {
-  int digest_matches, r=1;
+  int digest_matches, digest256_matches, r=1;
   tor_assert(ri);
   tor_assert(ei);
   if (!sd)
@@ -4907,6 +4917,11 @@ routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
 
   digest_matches = tor_memeq(ei->cache_info.signed_descriptor_digest,
                            sd->extra_info_digest, DIGEST_LEN);
+  /* Set digest256_matches to 1 if the digest is correct, or if no
+   * digest256 was in the ri. */
+  digest256_matches = tor_memeq(ei->digest256,
+                                ri->extra_info_digest256, DIGEST256_LEN);
+  digest256_matches |= tor_mem_is_zero(ri->extra_info_digest256, DIGEST256_LEN);
 
   /* The identity must match exactly to have been generated at the same time
    * by the same router. */
@@ -4917,6 +4932,11 @@ routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
     goto err; /* different servers */
   }
 
+  if (! tor_cert_opt_eq(ri->signing_key_cert, ei->signing_key_cert)) {
+    if (msg) *msg = "Extrainfo signing key cert didn't match routerinfo";
+    goto err; /* different servers */
+  }
+
   if (ei->pending_sig) {
     char signed_digest[128];
     if (crypto_pk_public_checksig(ri->identity_pkey,
@@ -4943,6 +4963,11 @@ routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
     goto err;
   }
 
+  if (!digest256_matches) {
+    if (msg) *msg = "Extrainfo digest did not match digest256 from routerdesc";
+    goto err; /* Digest doesn't match declared value. */
+  }
+
   if (!digest_matches) {
     if (msg) *msg = "Extrainfo digest did not match value from routerdesc";
     goto err; /* Digest doesn't match declared value. */

+ 5 - 2
src/or/routerlist.h

@@ -118,13 +118,15 @@ WRA_WAS_ADDED(was_router_added_t s) {
  * - not in the consensus
  * - neither in the consensus nor in any networkstatus document
  * - it was outdated.
+ * - its certificates were expired.
  */
 static INLINE int WRA_WAS_OUTDATED(was_router_added_t s)
 {
   return (s == ROUTER_WAS_TOO_OLD ||
           s == ROUTER_IS_ALREADY_KNOWN ||
           s == ROUTER_NOT_IN_CONSENSUS ||
-          s == ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS);
+          s == ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS ||
+          s == ROUTER_CERTS_EXPIRED);
 }
 /** Return true iff the outcome code in <b>s</b> indicates that the descriptor
  * was flat-out rejected. */
@@ -138,7 +140,8 @@ static INLINE int WRA_NEVER_DOWNLOADABLE(was_router_added_t s)
 {
   return (s == ROUTER_AUTHDIR_REJECTS ||
           s == ROUTER_BAD_EI ||
-          s == ROUTER_WAS_TOO_OLD);
+          s == ROUTER_WAS_TOO_OLD ||
+          s == ROUTER_CERTS_EXPIRED);
 }
 was_router_added_t router_add_to_routerlist(routerinfo_t *router,
                                             const char **msg,

+ 307 - 3
src/or/routerparse.c

@@ -24,8 +24,11 @@
 #include "microdesc.h"
 #include "networkstatus.h"
 #include "rephist.h"
+#include "routerkeys.h"
 #include "routerparse.h"
 #include "entrynodes.h"
+#include "torcert.h"
+
 #undef log
 #include <math.h>
 
@@ -69,6 +72,7 @@ typedef enum {
   K_CLIENT_VERSIONS,
   K_SERVER_VERSIONS,
   K_OR_ADDRESS,
+  K_ID,
   K_P,
   K_P6,
   K_R,
@@ -83,6 +87,10 @@ typedef enum {
   K_HIDDEN_SERVICE_DIR,
   K_ALLOW_SINGLE_HOP_EXITS,
   K_IPV6_POLICY,
+  K_ROUTER_SIG_ED25519,
+  K_IDENTITY_ED25519,
+  K_ONION_KEY_CROSSCERT,
+  K_NTOR_ONION_KEY_CROSSCERT,
 
   K_DIRREQ_END,
   K_DIRREQ_V2_IPS,
@@ -293,6 +301,12 @@ static token_rule_t routerdesc_token_table[] = {
   T01("write-history",       K_WRITE_HISTORY,       ARGS,    NO_OBJ ),
   T01("extra-info-digest",   K_EXTRA_INFO_DIGEST,   GE(1),   NO_OBJ ),
   T01("hidden-service-dir",  K_HIDDEN_SERVICE_DIR,  NO_ARGS, NO_OBJ ),
+  T01("identity-ed25519",    K_IDENTITY_ED25519,    NO_ARGS, NEED_OBJ ),
+  T01("router-sig-ed25519",  K_ROUTER_SIG_ED25519,  GE(1),   NO_OBJ ),
+  T01("onion-key-crosscert", K_ONION_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),
+  T01("ntor-onion-key-crosscert", K_NTOR_ONION_KEY_CROSSCERT,
+                                                    EQ(1),   NEED_OBJ ),
+
   T01("allow-single-hop-exits",K_ALLOW_SINGLE_HOP_EXITS,    NO_ARGS, NO_OBJ ),
 
   T01("family",              K_FAMILY,              ARGS,    NO_OBJ ),
@@ -310,6 +324,8 @@ static token_rule_t routerdesc_token_table[] = {
 static token_rule_t extrainfo_token_table[] = {
   T1_END( "router-signature",    K_ROUTER_SIGNATURE,    NO_ARGS, NEED_OBJ ),
   T1( "published",           K_PUBLISHED,       CONCAT_ARGS, NO_OBJ ),
+  T01("identity-ed25519",    K_IDENTITY_ED25519,    NO_ARGS, NEED_OBJ ),
+  T01("router-sig-ed25519",  K_ROUTER_SIG_ED25519,  GE(1),   NO_OBJ ),
   T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
   T01("read-history",        K_READ_HISTORY,        ARGS,    NO_OBJ ),
   T01("write-history",       K_WRITE_HISTORY,       ARGS,    NO_OBJ ),
@@ -353,6 +369,7 @@ static token_rule_t rtrstatus_token_table[] = {
   T01("v",                   K_V,               CONCAT_ARGS, NO_OBJ ),
   T01("w",                   K_W,                   ARGS,    NO_OBJ ),
   T0N("m",                   K_M,               CONCAT_ARGS, NO_OBJ ),
+  T0N("id",                  K_ID,                  GE(2),   NO_OBJ ),
   T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
   END_OF_TABLE
 };
@@ -490,6 +507,7 @@ static token_rule_t networkstatus_detached_signature_token_table[] = {
 static token_rule_t microdesc_token_table[] = {
   T1_START("onion-key",        K_ONION_KEY,        NO_ARGS,     NEED_KEY_1024),
   T01("ntor-onion-key",        K_ONION_KEY_NTOR,   GE(1),       NO_OBJ ),
+  T0N("id",                    K_ID,               GE(2),       NO_OBJ ),
   T0N("a",                     K_A,                GE(1),       NO_OBJ ),
   T01("family",                K_FAMILY,           ARGS,        NO_OBJ ),
   T01("p",                     K_P,                CONCAT_ARGS, NO_OBJ ),
@@ -506,6 +524,10 @@ static addr_policy_t *router_parse_addr_policy(directory_token_t *tok,
                                                unsigned fmt_flags);
 static addr_policy_t *router_parse_addr_policy_private(directory_token_t *tok);
 
+static int router_get_hash_impl_helper(const char *s, size_t s_len,
+                            const char *start_str,
+                            const char *end_str, char end_c,
+                            const char **start_out, const char **end_out);
 static int router_get_hash_impl(const char *s, size_t s_len, char *digest,
                                 const char *start_str, const char *end_str,
                                 char end_char,
@@ -637,7 +659,7 @@ router_get_extrainfo_hash(const char *s, size_t s_len, char *digest)
 char *
 router_get_dirobj_signature(const char *digest,
                             size_t digest_len,
-                            crypto_pk_t *private_key)
+                            const crypto_pk_t *private_key)
 {
   char *signature;
   size_t i, keysize;
@@ -858,8 +880,8 @@ check_signature_token(const char *digest,
     tor_free(signed_digest);
     return -1;
   }
-//  log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
-//            hex_str(signed_digest,4));
+  //  log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
+  //            hex_str(signed_digest,4));
   if (tor_memneq(digest, signed_digest, digest_len)) {
     log_warn(LD_DIR, "Error reading %s: signature does not match.", doctype);
     tor_free(signed_digest);
@@ -1106,6 +1128,7 @@ router_parse_entry_from_string(const char *s, const char *end,
   size_t prepend_len = prepend_annotations ? strlen(prepend_annotations) : 0;
   int ok = 1;
   memarea_t *area = NULL;
+  tor_cert_t *ntor_cc_cert = NULL;
   /* Do not set this to '1' until we have parsed everything that we intend to
    * parse that's covered by the hash. */
   int can_dl_again = 0;
@@ -1178,9 +1201,11 @@ router_parse_entry_from_string(const char *s, const char *end,
   }
 
   tok = find_by_keyword(tokens, K_ROUTER);
+  const int router_token_pos = smartlist_pos(tokens, tok);
   tor_assert(tok->n_args >= 5);
 
   router = tor_malloc_zero(sizeof(routerinfo_t));
+  router->cert_expiration_time = TIME_MAX;
   router->cache_info.routerlist_index = -1;
   router->cache_info.annotations_len = s-start_of_annotations + prepend_len;
   router->cache_info.signed_descriptor_len = end-s;
@@ -1311,6 +1336,147 @@ router_parse_entry_from_string(const char *s, const char *end,
     log_warn(LD_DIR, "Couldn't calculate key digest"); goto err;
   }
 
+  {
+    directory_token_t *ed_sig_tok, *ed_cert_tok, *cc_tap_tok, *cc_ntor_tok;
+    ed_sig_tok = find_opt_by_keyword(tokens, K_ROUTER_SIG_ED25519);
+    ed_cert_tok = find_opt_by_keyword(tokens, K_IDENTITY_ED25519);
+    cc_tap_tok = find_opt_by_keyword(tokens, K_ONION_KEY_CROSSCERT);
+    cc_ntor_tok = find_opt_by_keyword(tokens, K_NTOR_ONION_KEY_CROSSCERT);
+    int n_ed_toks = !!ed_sig_tok + !!ed_cert_tok +
+      !!cc_tap_tok + !!cc_ntor_tok;
+    if ((n_ed_toks != 0 && n_ed_toks != 4) ||
+        (n_ed_toks == 4 && !router->onion_curve25519_pkey)) {
+      log_warn(LD_DIR, "Router descriptor with only partial ed25519/"
+               "cross-certification support");
+      goto err;
+    }
+    if (ed_sig_tok) {
+      tor_assert(ed_cert_tok && cc_tap_tok && cc_ntor_tok);
+      const int ed_cert_token_pos = smartlist_pos(tokens, ed_cert_tok);
+      if (ed_cert_token_pos == -1 || router_token_pos == -1 ||
+          (ed_cert_token_pos != router_token_pos + 1 &&
+           ed_cert_token_pos != router_token_pos - 1)) {
+        log_warn(LD_DIR, "Ed25519 certificate in wrong position");
+        goto err;
+      }
+      if (ed_sig_tok != smartlist_get(tokens, smartlist_len(tokens)-2)) {
+        log_warn(LD_DIR, "Ed25519 signature in wrong position");
+        goto err;
+      }
+      if (strcmp(ed_cert_tok->object_type, "ED25519 CERT")) {
+        log_warn(LD_DIR, "Wrong object type on identity-ed25519 in decriptor");
+        goto err;
+      }
+      if (strcmp(cc_ntor_tok->object_type, "ED25519 CERT")) {
+        log_warn(LD_DIR, "Wrong object type on ntor-onion-key-crosscert "
+                 "in decriptor");
+        goto err;
+      }
+      if (strcmp(cc_tap_tok->object_type, "CROSSCERT")) {
+        log_warn(LD_DIR, "Wrong object type on onion-key-crosscert "
+                 "in decriptor");
+        goto err;
+      }
+      if (strcmp(cc_ntor_tok->args[0], "0") &&
+          strcmp(cc_ntor_tok->args[0], "1")) {
+        log_warn(LD_DIR, "Bad sign bit on ntor-onion-key-crosscert");
+        goto err;
+      }
+      int ntor_cc_sign_bit = !strcmp(cc_ntor_tok->args[0], "1");
+
+      uint8_t d256[DIGEST256_LEN];
+      const char *signed_start, *signed_end;
+      tor_cert_t *cert = tor_cert_parse(
+                       (const uint8_t*)ed_cert_tok->object_body,
+                       ed_cert_tok->object_size);
+      if (! cert) {
+        log_warn(LD_DIR, "Couldn't parse ed25519 cert");
+        goto err;
+      }
+      router->signing_key_cert = cert; /* makes sure it gets freed. */
+      if (cert->cert_type != CERT_TYPE_ID_SIGNING ||
+          ! cert->signing_key_included) {
+        log_warn(LD_DIR, "Invalid form for ed25519 cert");
+        goto err;
+      }
+
+      ntor_cc_cert = tor_cert_parse((const uint8_t*)cc_ntor_tok->object_body,
+                                    cc_ntor_tok->object_size);
+      if (!ntor_cc_cert) {
+        log_warn(LD_DIR, "Couldn't parse ntor-onion-key-crosscert cert");
+        goto err;
+      }
+      if (ntor_cc_cert->cert_type != CERT_TYPE_ONION_ID ||
+          ! ed25519_pubkey_eq(&ntor_cc_cert->signed_key, &cert->signing_key)) {
+        log_warn(LD_DIR, "Invalid contents for ntor-onion-key-crosscert cert");
+        goto err;
+      }
+
+      ed25519_public_key_t ntor_cc_pk;
+      if (ed25519_public_key_from_curve25519_public_key(&ntor_cc_pk,
+                                            router->onion_curve25519_pkey,
+                                            ntor_cc_sign_bit)<0) {
+        log_warn(LD_DIR, "Error converting onion key to ed25519");
+        goto err;
+      }
+
+      if (router_get_hash_impl_helper(s, end-s, "router ",
+                                      "\nrouter-sig-ed25519",
+                                      ' ', &signed_start, &signed_end) < 0) {
+        log_warn(LD_DIR, "Can't find ed25519-signed portion of descriptor");
+        goto err;
+      }
+      crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256);
+      crypto_digest_add_bytes(d, ED_DESC_SIGNATURE_PREFIX,
+        strlen(ED_DESC_SIGNATURE_PREFIX));
+      crypto_digest_add_bytes(d, signed_start, signed_end-signed_start);
+      crypto_digest_get_digest(d, (char*)d256, sizeof(d256));
+      crypto_digest_free(d);
+
+      ed25519_checkable_t check[3];
+      int check_ok[3];
+      if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) {
+        log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
+        goto err;
+      }
+      if (tor_cert_get_checkable_sig(&check[1],
+                                     ntor_cc_cert, &ntor_cc_pk) < 0) {
+        log_err(LD_BUG, "Couldn't create 'checkable' for ntor_cc_cert.");
+        goto err;
+      }
+
+      if (ed25519_signature_from_base64(&check[2].signature,
+                                        ed_sig_tok->args[0])<0) {
+        log_warn(LD_DIR, "Couldn't decode ed25519 signature");
+        goto err;
+      }
+      check[2].pubkey = &cert->signed_key;
+      check[2].msg = d256;
+      check[2].len = DIGEST256_LEN;
+
+      if (ed25519_checksig_batch(check_ok, check, 3) < 0) {
+        log_warn(LD_DIR, "Incorrect ed25519 signature(s)");
+        goto err;
+      }
+
+      if (check_tap_onion_key_crosscert(
+                      (const uint8_t*)cc_tap_tok->object_body,
+                      (int)cc_tap_tok->object_size,
+                      router->onion_pkey,
+                      &cert->signing_key,
+                      (const uint8_t*)router->cache_info.identity_digest)<0) {
+        log_warn(LD_DIR, "Incorrect TAP cross-verification");
+        goto err;
+      }
+
+      /* We check this before adding it to the routerlist. */
+      if (cert->valid_until < ntor_cc_cert->valid_until)
+        router->cert_expiration_time = cert->valid_until;
+      else
+        router->cert_expiration_time = ntor_cc_cert->valid_until;
+    }
+  }
+
   if ((tok = find_opt_by_keyword(tokens, K_FINGERPRINT))) {
     /* If there's a fingerprint line, it must match the identity digest. */
     char d[DIGEST_LEN];
@@ -1402,6 +1568,14 @@ router_parse_entry_from_string(const char *s, const char *end,
     } else {
       log_warn(LD_DIR, "Invalid extra info digest %s", escaped(tok->args[0]));
     }
+
+    if (tok->n_args >= 2) {
+      if (digest256_from_base64(router->extra_info_digest256, tok->args[1])
+          < 0) {
+        log_warn(LD_DIR, "Invalid extra info digest256 %s",
+                 escaped(tok->args[1]));
+      }
+    }
   }
 
   if (find_opt_by_keyword(tokens, K_HIDDEN_SERVICE_DIR)) {
@@ -1437,6 +1611,7 @@ router_parse_entry_from_string(const char *s, const char *end,
   routerinfo_free(router);
   router = NULL;
  done:
+  tor_cert_free(ntor_cc_cert);
   if (tokens) {
     SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
     smartlist_free(tokens);
@@ -1503,6 +1678,7 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
     goto err;
   }
 
+  /* XXXX Accept this in position 1 too, and ed identity in position 0. */
   tok = smartlist_get(tokens,0);
   if (tok->tp != K_EXTRA_INFO) {
     log_warn(LD_DIR,"Entry does not start with \"extra-info\"");
@@ -1515,6 +1691,7 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
     extrainfo->cache_info.signed_descriptor_body = tor_memdup_nulterm(s,end-s);
   extrainfo->cache_info.signed_descriptor_len = end-s;
   memcpy(extrainfo->cache_info.signed_descriptor_digest, digest, DIGEST_LEN);
+  crypto_digest256((char*)extrainfo->digest256, s, end-s, DIGEST_SHA256);
 
   tor_assert(tok->n_args >= 2);
   if (!is_legal_nickname(tok->args[0])) {
@@ -1537,6 +1714,87 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
     goto err;
   }
 
+  {
+    directory_token_t *ed_sig_tok, *ed_cert_tok;
+    ed_sig_tok = find_opt_by_keyword(tokens, K_ROUTER_SIG_ED25519);
+    ed_cert_tok = find_opt_by_keyword(tokens, K_IDENTITY_ED25519);
+    int n_ed_toks = !!ed_sig_tok + !!ed_cert_tok;
+    if (n_ed_toks != 0 && n_ed_toks != 2) {
+      log_warn(LD_DIR, "Router descriptor with only partial ed25519/"
+               "cross-certification support");
+      goto err;
+    }
+    if (ed_sig_tok) {
+      tor_assert(ed_cert_tok);
+      const int ed_cert_token_pos = smartlist_pos(tokens, ed_cert_tok);
+      if (ed_cert_token_pos != 1) {
+        /* Accept this in position 0 XXXX */
+        log_warn(LD_DIR, "Ed25519 certificate in wrong position");
+        goto err;
+      }
+      if (ed_sig_tok != smartlist_get(tokens, smartlist_len(tokens)-2)) {
+        log_warn(LD_DIR, "Ed25519 signature in wrong position");
+        goto err;
+      }
+      if (strcmp(ed_cert_tok->object_type, "ED25519 CERT")) {
+        log_warn(LD_DIR, "Wrong object type on identity-ed25519 in decriptor");
+        goto err;
+      }
+
+      uint8_t d256[DIGEST256_LEN];
+      const char *signed_start, *signed_end;
+      tor_cert_t *cert = tor_cert_parse(
+                       (const uint8_t*)ed_cert_tok->object_body,
+                       ed_cert_tok->object_size);
+      if (! cert) {
+        log_warn(LD_DIR, "Couldn't parse ed25519 cert");
+        goto err;
+      }
+      extrainfo->signing_key_cert = cert; /* makes sure it gets freed. */
+      if (cert->cert_type != CERT_TYPE_ID_SIGNING ||
+          ! cert->signing_key_included) {
+        log_warn(LD_DIR, "Invalid form for ed25519 cert");
+        goto err;
+      }
+
+      if (router_get_hash_impl_helper(s, end-s, "extra-info ",
+                                      "\nrouter-sig-ed25519",
+                                      ' ', &signed_start, &signed_end) < 0) {
+        log_warn(LD_DIR, "Can't find ed25519-signed portion of extrainfo");
+        goto err;
+      }
+      crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256);
+      crypto_digest_add_bytes(d, ED_DESC_SIGNATURE_PREFIX,
+        strlen(ED_DESC_SIGNATURE_PREFIX));
+      crypto_digest_add_bytes(d, signed_start, signed_end-signed_start);
+      crypto_digest_get_digest(d, (char*)d256, sizeof(d256));
+      crypto_digest_free(d);
+
+      ed25519_checkable_t check[2];
+      int check_ok[2];
+      if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) {
+        log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
+        goto err;
+      }
+
+      if (ed25519_signature_from_base64(&check[1].signature,
+                                        ed_sig_tok->args[0])<0) {
+        log_warn(LD_DIR, "Couldn't decode ed25519 signature");
+        goto err;
+      }
+      check[1].pubkey = &cert->signed_key;
+      check[1].msg = d256;
+      check[1].len = DIGEST256_LEN;
+
+      if (ed25519_checksig_batch(check_ok, check, 2) < 0) {
+        log_warn(LD_DIR, "Incorrect ed25519 signature(s)");
+        goto err;
+      }
+      /* We don't check the certificate expiration time: checking that it
+       * matches the cert in the router descriptor is adequate. */
+    }
+  }
+
   /* We've checked everything that's covered by the hash. */
   can_dl_again = 1;
 
@@ -2089,6 +2347,17 @@ routerstatus_parse_entry_from_string(memarea_t *area,
         line->microdesc_hash_line = tor_strdup(t->args[0]);
         vote_rs->microdesc = line;
       }
+      if (t->tp == K_ID) {
+        tor_assert(t->n_args >= 2);
+        if (!strcmp(t->args[0], "ed25519")) {
+          vote_rs->has_ed25519_listing = 1;
+          if (strcmp(t->args[1], "none") &&
+              digest256_from_base64((char*)vote_rs->ed25519_id, t->args[1])<0) {
+            log_warn(LD_DIR, "Bogus ed25519 key in networkstatus vote");
+            goto err;
+          }
+        }
+      }
     } SMARTLIST_FOREACH_END(t);
   } else if (flav == FLAV_MICRODESC) {
     tok = find_opt_by_keyword(tokens, K_M);
@@ -2913,6 +3182,21 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
       goto err;
     }
   }
+  if (ns_type != NS_TYPE_CONSENSUS) {
+    digest256map_t *ed_id_map = digest256map_new();
+    SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, vote_routerstatus_t *, vrs) {
+      if (! vrs->has_ed25519_listing ||
+          tor_mem_is_zero((const char *)vrs->ed25519_id, DIGEST256_LEN))
+        continue;
+      if (digest256map_get(ed_id_map, vrs->ed25519_id) != NULL) {
+        log_warn(LD_DIR, "Vote networkstatus ed25519 identities were not "
+                 "unique");
+        goto err;
+      }
+      digest256map_set(ed_id_map, vrs->ed25519_id, (void*)1);
+    } SMARTLIST_FOREACH_END(vrs);
+    digest256map_free(ed_id_map, NULL);
+  }
 
   /* Parse footer; check signature. */
   footer_tokens = smartlist_new();
@@ -4210,6 +4494,26 @@ microdescs_parse_from_string(const char *s, const char *eos,
         tor_memdup(&k, sizeof(curve25519_public_key_t));
     }
 
+    smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID);
+    if (id_lines) {
+      SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) {
+        tor_assert(t->n_args >= 2);
+        if (!strcmp(t->args[0], "ed25519")) {
+          if (md->ed25519_identity_pkey) {
+            log_warn(LD_DIR, "Extra ed25519 key in microdesc");
+            goto next;
+          }
+          ed25519_public_key_t k;
+          if (ed25519_public_from_base64(&k, t->args[1])<0) {
+            log_warn(LD_DIR, "Bogus ed25519 key in microdesc");
+            goto next;
+          }
+          md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k));
+        }
+      } SMARTLIST_FOREACH_END(t);
+      smartlist_free(id_lines);
+    }
+
     {
       smartlist_t *a_lines = find_all_by_keyword(tokens, K_A);
       if (a_lines) {

+ 3 - 1
src/or/routerparse.h

@@ -19,7 +19,7 @@ int router_get_extrainfo_hash(const char *s, size_t s_len, char *digest);
 #define DIROBJ_MAX_SIG_LEN 256
 char *router_get_dirobj_signature(const char *digest,
                                   size_t digest_len,
-                                  crypto_pk_t *private_key);
+                                  const crypto_pk_t *private_key);
 int router_append_dirobj_signature(char *buf, size_t buf_len,
                                    const char *digest,
                                    size_t digest_len,
@@ -91,5 +91,7 @@ STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str,
                                             routerstatus_t *rs);
 #endif
 
+#define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1"
+
 #endif
 

+ 280 - 0
src/or/torcert.c

@@ -0,0 +1,280 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "crypto.h"
+#include "torcert.h"
+#include "ed25519_cert.h"
+#include "torlog.h"
+#include "util.h"
+#include "compat.h"
+#include "link_handshake.h"
+
+/** Helper for tor_cert_create(): signs any 32 bytes, not just an ed25519
+ * key.
+ */
+static tor_cert_t *
+tor_cert_sign_impl(const ed25519_keypair_t *signing_key,
+                      uint8_t cert_type,
+                      uint8_t signed_key_type,
+                      const uint8_t signed_key_info[32],
+                      time_t now, time_t lifetime,
+                      uint32_t flags)
+{
+  tor_cert_t *torcert = NULL;
+
+  ed25519_cert_t *cert = ed25519_cert_new();
+  cert->cert_type = cert_type;
+  cert->exp_field = (uint32_t) CEIL_DIV(now + lifetime, 3600);
+  cert->cert_key_type = signed_key_type;
+  memcpy(cert->certified_key, signed_key_info, 32);
+
+  if (flags & CERT_FLAG_INCLUDE_SIGNING_KEY) {
+    ed25519_cert_extension_t *ext = ed25519_cert_extension_new();
+    ext->ext_type = CERTEXT_SIGNED_WITH_KEY;
+    memcpy(ext->un_signing_key, signing_key->pubkey.pubkey, 32);
+    ed25519_cert_add_ext(cert, ext);
+    ++cert->n_extensions;
+  }
+
+  const ssize_t alloc_len = ed25519_cert_encoded_len(cert);
+  tor_assert(alloc_len > 0);
+  uint8_t *encoded = tor_malloc(alloc_len);
+  const ssize_t real_len = ed25519_cert_encode(encoded, alloc_len, cert);
+  if (real_len < 0)
+    goto err;
+  tor_assert(real_len == alloc_len);
+  tor_assert(real_len > ED25519_SIG_LEN);
+  uint8_t *sig = encoded + (real_len - ED25519_SIG_LEN);
+  tor_assert(tor_mem_is_zero((char*)sig, ED25519_SIG_LEN));
+
+  ed25519_signature_t signature;
+  if (ed25519_sign(&signature, encoded,
+                   real_len-ED25519_SIG_LEN, signing_key)<0) {
+    log_warn(LD_BUG, "Can't sign certificate");
+    goto err;
+  }
+  memcpy(sig, signature.sig, ED25519_SIG_LEN);
+
+  torcert = tor_cert_parse(encoded, real_len);
+  if (! torcert) {
+    log_warn(LD_BUG, "Generated a certificate we cannot parse");
+    goto err;
+  }
+
+  if (tor_cert_checksig(torcert, &signing_key->pubkey, now) < 0) {
+    log_warn(LD_BUG, "Generated a certificate whose signature we can't check");
+    goto err;
+  }
+
+  tor_free(encoded);
+
+  return torcert;
+
+ err:
+  tor_cert_free(torcert);
+  ed25519_cert_free(cert);
+  tor_free(encoded);
+  return NULL;
+}
+
+/**
+ * Create and return a new new certificate of type <b>cert_type</b> to
+ * authenticate <b>signed_key</b> using the key <b>signing_key</b>.  The
+ * certificate should remain valid for at least <b>lifetime</b> seconds after
+ * <b>now</b>.
+ *
+ * If CERT_FLAG_INCLUDE_SIGNING_KEY is set in <b>flags</b>, embed
+ * the public part of <b>signing_key</b> in the certificate.
+ */
+tor_cert_t *
+tor_cert_create(const ed25519_keypair_t *signing_key,
+                uint8_t cert_type,
+                const ed25519_public_key_t *signed_key,
+                time_t now, time_t lifetime,
+                uint32_t flags)
+{
+  return tor_cert_sign_impl(signing_key, cert_type,
+                            SIGNED_KEY_TYPE_ED25519, signed_key->pubkey,
+                            now, lifetime, flags);
+}
+
+/** Release all storage held for <b>cert</>. */
+void
+tor_cert_free(tor_cert_t *cert)
+{
+  if (! cert)
+    return;
+
+  if (cert->encoded)
+    memwipe(cert->encoded, 0, cert->encoded_len);
+  tor_free(cert->encoded);
+
+  memwipe(cert, 0, sizeof(tor_cert_t));
+  tor_free(cert);
+}
+
+/** Parse a certificate encoded with <b>len</b> bytes in <b>encoded</b>. */
+tor_cert_t *
+tor_cert_parse(const uint8_t *encoded, const size_t len)
+{
+  tor_cert_t *cert = NULL;
+  ed25519_cert_t *parsed = NULL;
+  ssize_t got_len = ed25519_cert_parse(&parsed, encoded, len);
+  if (got_len < 0 || (size_t) got_len != len)
+    goto err;
+
+  cert = tor_malloc_zero(sizeof(tor_cert_t));
+  cert->encoded = tor_memdup(encoded, len);
+  cert->encoded_len = len;
+
+  memcpy(cert->signed_key.pubkey, parsed->certified_key, 32);
+  cert->valid_until = parsed->exp_field * 3600;
+  cert->cert_type = parsed->cert_type;
+
+  for (unsigned i = 0; i < ed25519_cert_getlen_ext(parsed); ++i) {
+    ed25519_cert_extension_t *ext = ed25519_cert_get_ext(parsed, i);
+    if (ext->ext_type == CERTEXT_SIGNED_WITH_KEY) {
+      if (cert->signing_key_included)
+        goto err;
+
+      cert->signing_key_included = 1;
+      memcpy(cert->signing_key.pubkey, ext->un_signing_key, 32);
+    } else if (ext->ext_flags & CERTEXT_FLAG_AFFECTS_VALIDATION) {
+      /* Unrecognized extension with affects_validation set */
+      goto err;
+    }
+  }
+
+  return cert;
+ err:
+  ed25519_cert_free(parsed);
+  tor_cert_free(cert);
+  return NULL;
+}
+
+/** Fill in <b>checkable_out</b> with the information needed to check
+ * the signature on <b>cert</b> with <b>pubkey</b>. */
+int
+tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out,
+                           const tor_cert_t *cert,
+                           const ed25519_public_key_t *pubkey)
+{
+  if (! pubkey) {
+    if (cert->signing_key_included)
+      pubkey = &cert->signing_key;
+    else
+      return -1;
+  }
+
+  checkable_out->msg = cert->encoded;
+  checkable_out->pubkey = pubkey;
+  tor_assert(cert->encoded_len > ED25519_SIG_LEN);
+  const size_t signed_len = cert->encoded_len - ED25519_SIG_LEN;
+  checkable_out->len = signed_len;
+  memcpy(checkable_out->signature.sig,
+         cert->encoded + signed_len, ED25519_SIG_LEN);
+
+  return 0;
+}
+
+/** Validates the signature on <b>cert</b> with <b>pubkey</b> relative to
+ * the current time <b>now</b>.  Return 0 on success, -1 on failure.
+ * Sets flags in <b>cert</b> as appropriate.
+ */
+int
+tor_cert_checksig(tor_cert_t *cert,
+                  const ed25519_public_key_t *pubkey, time_t now)
+{
+  ed25519_checkable_t checkable;
+  int okay;
+
+  if (now > cert->valid_until) {
+    cert->cert_expired = 1;
+    return -1;
+  }
+
+  if (tor_cert_get_checkable_sig(&checkable, cert, pubkey) < 0)
+    return -1;
+
+  if (ed25519_checksig_batch(&okay, &checkable, 1) < 0) {
+    cert->sig_bad = 1;
+    return -1;
+  } else {
+    cert->sig_ok = 1;
+    memcpy(cert->signing_key.pubkey, checkable.pubkey->pubkey, 32);
+    cert->cert_valid = 1;
+    return 0;
+  }
+}
+
+/** Return a new copy of <b>cert</b> */
+tor_cert_t *
+tor_cert_dup(const tor_cert_t *cert)
+{
+  tor_cert_t *newcert = tor_memdup(cert, sizeof(tor_cert_t));
+  if (cert->encoded)
+    newcert->encoded = tor_memdup(cert->encoded, cert->encoded_len);
+  return newcert;
+}
+
+/** Return true iff cert1 and cert2 are the same cert. */
+int
+tor_cert_eq(const tor_cert_t *cert1, const tor_cert_t *cert2)
+{
+  tor_assert(cert1);
+  tor_assert(cert2);
+  return cert1->encoded_len == cert2->encoded_len &&
+    tor_memeq(cert1->encoded, cert2->encoded, cert1->encoded_len);
+}
+
+/** Return true iff cert1 and cert2 are the same cert, or if they are both
+ * NULL. */
+int
+tor_cert_opt_eq(const tor_cert_t *cert1, const tor_cert_t *cert2)
+{
+  if (cert1 == NULL && cert2 == NULL)
+    return 1;
+  if (!cert1 || !cert2)
+    return 0;
+  return tor_cert_eq(cert1, cert2);
+}
+
+/** Create new cross-certification object to certify <b>ed_key</b> as the
+ * master ed25519 identity key for the RSA identity key <b>rsa_key</b>.
+ * Allocates and stores the encoded certificate in *<b>cert</b>, and returns
+ * the number of bytes stored. Returns negative on error.*/
+ssize_t
+tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key,
+                               const crypto_pk_t *rsa_key,
+                               time_t expires,
+                               uint8_t **cert)
+{
+  uint8_t *res;
+
+  rsa_ed_crosscert_t *cc = rsa_ed_crosscert_new();
+  memcpy(cc->ed_key, ed_key->pubkey, ED25519_PUBKEY_LEN);
+  cc->expiration = (uint32_t) CEIL_DIV(expires, 3600);
+  cc->sig_len = crypto_pk_keysize(rsa_key);
+  rsa_ed_crosscert_setlen_sig(cc, crypto_pk_keysize(rsa_key));
+
+  ssize_t alloc_sz = rsa_ed_crosscert_encoded_len(cc);
+  tor_assert(alloc_sz > 0);
+  res = tor_malloc_zero(alloc_sz);
+  ssize_t sz = rsa_ed_crosscert_encode(res, alloc_sz, cc);
+  tor_assert(sz > 0 && sz <= alloc_sz);
+
+  const int signed_part_len = 32 + 4;
+  int siglen = crypto_pk_private_sign(rsa_key,
+                                      (char*)rsa_ed_crosscert_getarray_sig(cc),
+                                      rsa_ed_crosscert_getlen_sig(cc),
+                                      (char*)res, signed_part_len);
+  tor_assert(siglen > 0 && siglen <= (int)crypto_pk_keysize(rsa_key));
+  tor_assert(siglen <= UINT8_MAX);
+  cc->sig_len = siglen;
+  rsa_ed_crosscert_setlen_sig(cc, siglen);
+
+  sz = rsa_ed_crosscert_encode(res, alloc_sz, cc);
+  rsa_ed_crosscert_free(cc);
+  *cert = res;
+  return sz;
+}

+ 76 - 0
src/or/torcert.h

@@ -0,0 +1,76 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TORCERT_H_INCLUDED
+#define TORCERT_H_INCLUDED
+
+#include "crypto_ed25519.h"
+
+#define SIGNED_KEY_TYPE_ED25519 0x01
+
+#define CERT_TYPE_ID_SIGNING    0x04
+#define CERT_TYPE_SIGNING_LINK  0x05
+#define CERT_TYPE_SIGNING_AUTH  0x06
+#define CERT_TYPE_ONION_ID      0x0A
+
+#define CERT_FLAG_INCLUDE_SIGNING_KEY 0x1
+
+/** An ed25519-signed certificate as used throughout the Tor protocol.
+ **/
+typedef struct tor_cert_st {
+  /** The key authenticated by this certificate */
+  ed25519_public_key_t signed_key;
+  /** The key that signed this certificate. This value may be unset if the
+   * certificate has never been checked, and didn't include its own key. */
+  ed25519_public_key_t signing_key;
+  /** A time after which this certificate will no longer be valid. */
+  time_t valid_until;
+
+  /** The encoded representation of this certificate */
+  uint8_t *encoded;
+  /** The length of <b>encoded</b> */
+  size_t encoded_len;
+
+  /** One of CERT_TYPE_... */
+  uint8_t cert_type;
+  /** True iff we received a signing key embedded in this certificate */
+  unsigned signing_key_included : 1;
+  /** True iff we checked the signature and found it bad */
+  unsigned sig_bad : 1;
+  /** True iff we checked the signature and found it correct */
+  unsigned sig_ok : 1;
+  /** True iff we checked the signature and first found that the cert
+   * had expired */
+  unsigned cert_expired : 1;
+  /** True iff we checked the signature and found the whole cert valid */
+  unsigned cert_valid : 1;
+} tor_cert_t;
+
+tor_cert_t *tor_cert_create(const ed25519_keypair_t *signing_key,
+                            uint8_t cert_type,
+                            const ed25519_public_key_t *signed_key,
+                            time_t now, time_t lifetime,
+                            uint32_t flags);
+
+tor_cert_t *tor_cert_parse(const uint8_t *cert, size_t certlen);
+
+void tor_cert_free(tor_cert_t *cert);
+
+int tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out,
+                                 const tor_cert_t *out,
+                                 const ed25519_public_key_t *pubkey);
+
+int tor_cert_checksig(tor_cert_t *cert,
+                      const ed25519_public_key_t *pubkey, time_t now);
+
+tor_cert_t *tor_cert_dup(const tor_cert_t *cert);
+int tor_cert_eq(const tor_cert_t *cert1, const tor_cert_t *cert2);
+int tor_cert_opt_eq(const tor_cert_t *cert1, const tor_cert_t *cert2);
+
+ssize_t tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key,
+                                       const crypto_pk_t *rsa_key,
+                                       time_t expires,
+                                       uint8_t **cert);
+
+#endif
+

+ 233 - 0
src/test/example_extrainfo.inc

@@ -190,3 +190,236 @@ static const char EX_EI_BAD_PUBLISHED_KEY[] =
   "BvG6303md3INygg+KP49RvWEJR/cU4RZ9QfHpORxH2OocMyRedw2rLex2E7jNNSi\n"
   "52yd1sHFYI8ZQ4aff+ZHUjJUGKRyqpbc8okVbq/Rl7vug0dd12eHAgMBAAE=\n"
   "-----END RSA PUBLIC KEY-----\n";
+
+static const char EX_EI_GOOD_ED_EI[] =
+  "extra-info emma A692FE045C32B5E3A54B52882EF678A9DAC46A73\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AYgHn/OKR8GHBlscN5VkO73wA9jSci8QgTM30615ZT44AQAgBAC08woT\n"
+  "MBZpKzRcaoEJhEG7+RmuYtnB2+nODk9IRIs8ZoyYPTZ6dLzI+MLMmtzUuo/Wmvw0\n"
+  "PflTyCb2RlWitOEhAErWH3Z9UmYGnzM/COId0Fe3ScSriyvRoFnJY1+GVAQ=\n"
+  "-----END ED25519 CERT-----\n"
+  "published 2014-10-05 20:07:00\n"
+  "router-sig-ed25519 a7K8nwfg+HrdlSGQwr9rnLBq0qozkyZZs6d6aiLEiXGdhV1r9KJncmlQ5SNoY/zMQlyQm8EV5rCyBiVliKQ1Bw\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "GvmCmIGgbC1DeawRyRuChy62VmBOG0EviryG/a2qSZiFy0iPPwqSp5ZyZDQEIEId\n"
+  "kkk1zPzK1+S3fmgOAXyXGH0r4YFkoLGnhMk07BoEwi6HEXzjJsabmcNkOHfaOWgs\n"
+  "/5nvnLfcmxL4c6FstZ7t9VQpE06y3GU0zwBeIy1qjp0=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  ;
+const char EX_EI_GOOD_ED_EI_FP[] = "A692FE045C32B5E3A54B52882EF678A9DAC46A73";
+static const char EX_EI_GOOD_ED_EI_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAM3jdYwjwGxDWYj/vyFkQT7RgeCNIn89Ei6D2+L/fdtFnqrMXOreFFHL\n"
+  "C7CK2v2uN3v+uXxfb5lADz3NcalxJrCfGTGtaBk7PwMZraTSh2luFKOvSRBQCmB1\n"
+  "yD5N0QqnIhBJoGr6NITpbWyiTKWvYLjl9PZd9af8e8jQCAa5P1j1AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+
+static const char EX_EI_ED_MISSING_SIG[] =
+  "extra-info rachel 2A7521497B91A8437021515308A47491164EDBA1\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AT2/T71LFYHiI1ppwNiuaewIu2Hq+GWWQ85O8gpWcUxeAQAgBAC2dgYu\n"
+  "moxhtuip7GVlthT9iomZKba1IllVa7uE1u2uO9BUYZQWXciFt7OnNzMH5mlffwxB\n"
+  "1dWCl+G5nbOsV5jYLbfhrF5afZotf+EQTfob4cCH79AV223LPcySbTHTtQ4=\n"
+  "-----END ED25519 CERT-----\n"
+  "published 2014-10-05 20:07:00\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "oypRD2IZQ5EttOE8dvofrW80nnBfijSkvYzBrM6H4KVeayRYvWfmi96dYO6ybMqm\n"
+  "Yp7Gs3ngqeeNdfHtkRPuQVUXUGYZgBTvYItuagnFlFgRqaHy0knwUIVOL35eqWYx\n"
+  "xSbQKA7fglxEDMFs/RK7FRP4dWc731ZMt5wzzfJHZ8E=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  ;
+const char EX_EI_ED_MISSING_SIG_FP[] = "2A7521497B91A8437021515308A47491164EDBA1";
+static const char EX_EI_ED_MISSING_SIG_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAOOB8ccxbtk2dB5FuKFhGndDcO6STNjB6KiG0b9X2QwKrOZMfmXSigto\n"
+  "mtC1JfPTxECayRjLSiP/9UD8iTVvlcnc8mMWBGM12Pa/KoCZRn7McHI3JJ7n9lfn\n"
+  "qw9+iZ9b/rBimzOb3W6k3uxzg9r8secdq4jJwTnwSjTObgxZtC8/AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+
+static const char EX_EI_ED_MISSING_CERT[] =
+  "extra-info lynne E88E43E86015345A323D93D825C33E4AD1028F65\n"
+  "published 2014-10-05 20:07:00\n"
+  "router-sig-ed25519 H4gKIKm5K9Pfkriy7SlMUD6BdYVp6B5mXKzR/rTyYlpH0tEZ4Fx2hlHNfNNdWXJieXzKZQZo8e7SOVzvrAC3CQ\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "dIrbQjK5T9t5KM8CpsMF85hh2i060oPIxzYQMgE1q4j99dtb/n7SE8nhj1Sjij4D\n"
+  "7JvTjGdLHi3bFSxXaSmla0wxD9PUYFN7VsBQmwSaDrqrzJFb1SGwZuzW1IEZ7BBi\n"
+  "H0czsxEteg5hcNRwISj5WVthuWmau9v13MijtZGSK40=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  "\n"
+  ;
+const char EX_EI_ED_MISSING_CERT_FP[] = "E88E43E86015345A323D93D825C33E4AD1028F65";
+static const char EX_EI_ED_MISSING_CERT_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBALjA/geb0TR9rp/UPvLhABQpB0XUDYuZAnLkrv+i7AAV7FemTDveEGnc\n"
+  "XdXNSusO1mHOquvr0YYKPhwauInxD56S8QOzLYiWWajGq8XHARQ33b4/9K2TUrAx\n"
+  "W9HTHV1U1zrPlCJtrkbjxsYoHpUg5ljzM7FGYGY5xuvyHu18SQvzAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+static const char EX_EI_ED_BAD_CERT1[] =
+  "extra-info marcie F78D8A655607D32281D02144817A4F1D26AE520F\n"
+  "identity-ed25519\n"
+  "-----BEGIN PLAGICAL SPELL-----\n"
+  "aaaa\n"
+  "-----END PLAGICAL SPELL\n"
+  "published 2014-10-05 20:07:00\n"
+  "router-sig-ed25519 KQJ+2AH7EkkjrD0RtDtUAIr+Vc7wndwILYnoUxFLSJiTP+5fMi54eFF/f1OgkG8gYyTh8phMij9WOxK/dsOpBg\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "XWD+P25AH6moi79j20Si3hqKGcJDws+FORL1MTu+GeJLV1mp5CR9N83UH4ffulcL\n"
+  "CpSSBDL/j74HqapzW7QvBx3FilaNT55GvcobZDFK4TKkCEyEmcuWKpEceBS7JTTV\n"
+  "SvwZeOObTjWPafELbsc/gI9Rh5Idwu7mZt3ZVntCGaQ=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+const char EX_EI_ED_BAD_CERT1_FP[] = "F78D8A655607D32281D02144817A4F1D26AE520F";
+static const char EX_EI_ED_BAD_CERT1_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMlR46JhxsCmWYtmIB/JjTV2TUYIhJLmHy+X7FfkK3ZVQvvl9/3GSXFL\n"
+  "3USfyf3j34XLh8An7pJBi9LAHkIXgnRbglCud7dXoexabmC+c2mSbw5RnuxDGEwz\n"
+  "krXUph/r2b+2UY1CgEt28nFigaHrIQbCmF4szFX/2GPYCLi5SrRNAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+static const char EX_EI_ED_BAD_CERT2[] =
+  "extra-info jaeger 7C2B42E783C4E0EB0CC3BDB37385D16737BACFBD\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55Acpw27GZBdwGCgawCj2F/DPadt8F/9DnEWywEew1Yi3qAOtLpCB8KXL7\n"
+  "4w5deFW2RBg8qTondNSUvAmwYLbLjNXMmgA3+nkoJOP3fcmQMHz1jm5xzgs2lCVP\n"
+  "t5txApaBIA4=\n"
+  "-----END ED25519 CERT-----\n"
+  "published 2014-10-05 20:07:00\n"
+  "router-sig-ed25519 DRQ4MLOGosBbW8M+17klNu8uWVkPxErmmEYoSo6OuH2Tzrcs6sUY+8Xi2qLoV1SbOugJ214Htl0I+6ceag+vBA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "DfdA+DbuN9nVJNujuSY5wNCDLk7Hfzkrde/sK0hVmZRvivtpF/Fy/dVQHHGNFY5i\n"
+  "L1cESAgq9HLdbHU+hcc08XXxTIaGwvoklcJClcG3ENVBWkTXbJNT+ifr7chEagIi\n"
+  "cVrtU6RVmzldSbyir8V/Z4S/Cm67gYAgjM5gfoFUqDs=\n"
+  "-----END SIGNATURE-----\n"
+  ;
+const char EX_EI_ED_BAD_CERT2_FP[] = "7C2B42E783C4E0EB0CC3BDB37385D16737BACFBD";
+static const char EX_EI_ED_BAD_CERT2_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBALAM1F/0XJEsbxIQqb3+ObX/yGVnq9of8Q9sLsmxffD6hwVpCqnV3lTg\n"
+  "iC6+xZ/bSlTGLPi0k8QLCaTmYxgKwmlMPpbQZ4kpZUrsb9flKdChMN7w8hd48pY9\n"
+  "lu8QiAEgErsl5rCCJIHHjrxxM/Cnd0TnedRnj/Z2YqpNx/ggsmsRAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+static const char EX_EI_ED_BAD_SIG1[] =
+  "extra-info vary 5AC3A538FEEFC6F9FCC5FA0CE64704396C30D62A\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AbPp++GrRb6WphSu+PkMaYsqY/beiLBmtiV3YP5i2JkKAQAgBABKXjg1\n"
+  "aiz2JfQpNOG308i2EojnUAZEk0C0x9g2BAAXGL63sv3eO/qrlytsG1x2hkcamxFn\n"
+  "LmfZBb/prqe1Vy4wABuhqWHAUtM29vXR6lpiCJeddt9Pa8XVy/tgWLX6TAw=\n"
+  "-----END ED25519 CERT-----\n"
+  "published 2014-10-05 20:07:00\n"
+  "router-sig-ed25519 a7K8nwfg+HrdlSGQwr9rnLBq0qozkyZZs6d6aiLEiXGdhV1r9KJncmlQ5SNoY/zMQlyQm8EV5rCyBiVliKQ1Bw\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "xhZX8Qmgft51NJ7eMd4vrESzf/VdxDrBz7hgn8K+5bLtZUksG0s6s7IyGRYWQtp4\n"
+  "/7oc9sYe3lcQiUN2K7DkeBDlL8Pcsl8aIlKuujWomCE3j0TIu+8XK6oJeo7eYic+\n"
+  "IA7EwVbdZsKsW5/eJVzbX2eO0a5zyJ5RIYotFNYNCSE=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+const char EX_EI_ED_BAD_SIG1_FP[] = "5AC3A538FEEFC6F9FCC5FA0CE64704396C30D62A";
+static const char EX_EI_ED_BAD_SIG1_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMvb6SuoIkPfBkJgQuo5aQDepAs1kEETZ9VXotMlhB0JJikrqBrAAz+7\n"
+  "rjIJ4JsBaeQuN0Z5ksXk2ebxtef7oMIUs37NfekLQHbNR0VsXkFXPEGmOAqpZjW0\n"
+  "P524eHqybWYZTckvZtUvKI3xYGD6kEEkz4qmV6dcExU1OiAYO9jrAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+static const char EX_EI_ED_BAD_SIG2[] =
+  "extra-info coward 7F1D4DD477E340C6D6B389FAC26EDC746113082F\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf56AZkSDiFZ1QaiLJhcKdFDE5Kei/sPaPEIEoPMGP4BvOVXAQAgBAAlRLzx\n"
+  "U029tgIL9BRe47MVgcPJGy48db6ntzhjil7iOnWKT70z2LorUD5CZoLJs72TjB6r\n"
+  "8+HYNyFLEM6dvytWZf9NA5gLdhogbFcUk/R3gbNepmCF7XoZjbhPIp8zOwg=\n"
+  "-----END ED25519 CERT-----\n"
+  "published 2014-10-05 20:07:00\n"
+  "router-sig-ed25519 yfV+GySMIP1fw1oVa1C1de4XOWBqT4pUtEmSHq1h+WrLBNCh3/HZWvNC/denf2YVntuQrMLCJEv5ZaFKU+AIDQ\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "g+BWq69i9CP19va2cYMAXCQ6jK3IG0VmNYspjjUFgmFpJKGG6bHeOkuy1GXp47fG\n"
+  "LzZ3OPfJLptxU5AOQDUUYf25hu9uSl6gyknCzsszFs5n6ticuNejvcpzw6UfO1LP\n"
+  "5u+mGJlgpcMtmSraImDZrRipmZ3oRWvEULltlvzGQcQ=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+const char EX_EI_ED_BAD_SIG2_FP[] = "7F1D4DD477E340C6D6B389FAC26EDC746113082F";
+static const char EX_EI_ED_BAD_SIG2_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBALzOyfCEUZnvCyhlyMctPkdXg/XRE3Cr6QgyzdKf5kQbUiu2n0FgSHOX\n"
+  "iP5gfq8sO9eVeTPZtjE7/+KiR8aQJECy+eoye+lpsfm3tXpLxnpOIgL4DlURxlo/\n"
+  "rfCyv30SYBN9j62qgU9m6U2ydI0tH7/9Ep8yIY/QL8me8VAjLbf/AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+
+static const char EX_EI_ED_MISPLACED_CERT[] =
+  "extra-info msselene 3B788BD0CE348BC5CED48313307C78175EB6D0F3\n"
+  "published 2014-10-05 20:07:00\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AWBcqjzLESDuLNGsqQ/tHn32XueXwj2fDlgEy/kQNVf/AQAgBAAFOegg\n"
+  "XY1LR82xE9ohAYJxYpwJJw0YfXsBhGHqfakEoBtSgFJ3cQAUXZQX4lX6G8IxAlQB\n"
+  "7Rj7dPQuQRUmqD1yyKb/ScBgCa8esxlhNlATz47kRNR38A3TcoJ4c1Zv6AE=\n"
+  "-----END ED25519 CERT-----\n"
+  "router-sig-ed25519 Q52JKH9/iMsr1jIPlWHHxakSBvyqjT1gzL944vad4OhzCZuNuAYGWyWSGzTb1DVmBqqbAUq73TiZKAz77YLNCQ\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "YplvAIwExGf5/L8AoroVQXtGm+26EffrxKBArMKn0zS1NOOie1p0oF/+qJg+rNWU\n"
+  "6cv3Anf188EXGlkUOddavgVH8CQbvve2nHSfIAPxjgEX9QNXbM5CiaMwgpCewXnF\n"
+  "UoNBVo5tydeLHVns15MBg/JNIxUQMd6svMoPp2WqmaE=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+const char EX_EI_ED_MISPLACED_CERT_FP[] = "3B788BD0CE348BC5CED48313307C78175EB6D0F3";
+static const char EX_EI_ED_MISPLACED_CERT_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBALTwNqhTprg1oC6bEbDqwIYBoER6prqUXQFbwbFDn+ekXhZj8vltgGwp\n"
+  "aDGl9ceZWDKfi+reR6rZXjAJGctmv0VHkfe7maUX4FC/d2T8N8DvS+3IvJzFMpbT\n"
+  "O0fFrDTrCSnPikqFfQWnlP8yoF5vO7wo0jRRY432fLRXg9WqVzdrAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+static const char EX_EI_ED_MISPLACED_SIG[] =
+  "extra-info grazie 384E40A5DEED4AB1D8A74F1FCBDB18B7C24A8284\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AcGuIBoa6TBqD8Gg5atcwp/+r9ThxIBkULmPv9OSGhv+AQAgBACXH13y\n"
+  "mUvdpcN6oRN1nX6mnH40LyfYR5um8xogJZk3oINse5cRNrfMgVWiBpDlJZAwlDDa\n"
+  "lx99hzuZBong+CiOcnEvLMsBaVJmNTm5mpdetYclZpl0g8QEXznXXeRBMgM=\n"
+  "-----END ED25519 CERT-----\n"
+  "router-sig-ed25519 TxuO86dQ3pUaIY2raQ3hoDBmh4TTPC0OVgY98T5cf6Y+sHyiELCkkKQ3lqqXCjqnbTLr1/4riH980JoWPpR+Dw\n"
+  "published 2014-10-05 20:07:00\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "kV2CtArl1VF1nUSyHL00mO3nEdNxlQU5N7/hZNTd+45lej5Veb+6vb4ujelsFERJ\n"
+  "YoxwIs6SuKAR4orQytCL0e+GgZsrg8zGTveEtMX/+u//OcCwQBYEevR5duBZjVw/\n"
+  "yzpEHwdIdB2PPyDBLkf1VKnP7uDj059tXiQRWl7LXgE=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+const char EX_EI_ED_MISPLACED_SIG_FP[] = "384E40A5DEED4AB1D8A74F1FCBDB18B7C24A8284";
+static const char EX_EI_ED_MISPLACED_SIG_KEY[] =
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAK0HgOCG/6433VCrwz/vhk3cKmyOfenCp0GZ4DIUwPWt4DeyP4nTbN6T\n"
+  "1HJ1H8+hXC9bMuI4m43IWrzgLycQ9UaskUn372ZjHP9InPqHMJU6GQ7vZUe9Tgza\n"
+  "qnBdRPoxnrZzUOzlvatGrePt0hDiOZaMtDAkeEojFp9Wp2ZN7+tZAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  ;
+

+ 901 - 0
src/test/failing_routerdescs.inc

@@ -666,3 +666,904 @@ 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"
+  ;
+
+static const char EX_RI_ED_MISSING_CROSSCERT[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf54AfsyyHhGluzfESzL4LP8AhFEm83+GkFoHbe1KnssVngHAQAgBABNzJRw\n"
+  "BLXT3QMlic0QZ4eG612wkfSRS4yzONIbATKLHIgyzgGiGl4gaSX0JTeHeGfIlu7P\n"
+  "5SKocZVNxm1mp55PG+tgBqHObDRJRSgbOyUbUgfOtcbQGUeVgUlFKWZ9FAY=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMqT7K8cEzWIaPNXbNgvoZ5ejavoszI2OjW9XXetPD/S2f+N7TfQXHBW\n"
+  "bnjpgj87gmk59w0OXTMCv+XofZ0xOy2YR/jG5l1VJIvqgJhhFJ8oSEGVzy+97Ekn\n"
+  "Lb1FEYuVfVxSxnU2jhHW6KPtee/gvuyRI/TvZuwmYWxLRpikVn4pAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAM4nITNe8UykgsIuo5czSSSl3Okr1K+UVWTzDGLznDg77MkLy7mydmk9\n"
+  "vf51OB+ogQhozYKIh9uHvecOzY4EhSIuKhui4hNyQklD9juGoW7RVTSpGdYT1ymp\n"
+  "dDYS30JBPwCZ7KjdMtXiU8ch2WgbzYBuI+JfjwOhfcsuNC9QPfbfAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key lx8o212IYw5Ly2KbH2ua1+fr4YvDq5nKd7LHMdPzTGo=\n"
+  "ntor-onion-key-crosscert 1\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf54AU3MlHAEtdPdAyWJzRBnh4brXbCR9JFLjLM40hsBMoscAJ8cHMIc71+p\n"
+  "Qa+lg5JiYb551mLgtPWLy12xdhog7SXiJl3NvnMgbMZXHDqkU2YZCidnVz+xqMdh\n"
+  "mjQFK4AtRwg=\n"
+  "-----END ED25519 CERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 4DSdPePrToNx3WQ+4GfFelB8IyHu5Z9vTbbLZ02vfYEsCF9QeaeHbYagY/yjdt+9e71jmfM+W5MfRQd8FJ1+Dg\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "cv1yL8HhQzQfjzkSosziu2kMecNUQGle4d103h6tVMoZS1ua1xiDpVKeuWPl9Z0+\n"
+  "wpFwRkOmK0HpNeOXCNHJwfJaWBGQXunB3WQ6Oi1BLilwLtWQixGTYG0hZ6xYLTnX\n"
+  "PdSQIbsohSgCzo9HLTAgTnkyBgklIO1PHJBJsaNOwfI=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+
+static const char EX_RI_ED_MISSING_CROSSCERT2[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf54AXXgm0CUWQr+rxvgdIslqaFdBiwosT+9PaC8zOxYGIsZAQAgBAA6yeH7\n"
+  "3AfGIGuDpVihVUUo0QwguWDPwk2dBJan7B0qgPWF5Y4YL5XDh2nMatskUrtUGCr1\n"
+  "abLYlJPozmYd6QBSv6eyBfITS/oNOMyZpjDiIjcLQD08tVQ2Jho+WmN64wc=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMdyTK/VPZloLUaLsvj1+NOFs33/E9HmA0VgvZ1nNUrR+PxSR71QF7Tw\n"
+  "DKz+/p2rJE+MPfQ/Na3dH0vH4CDZ+FH2m4A8SB9emF8aKxdc/7KCjQNDQCNlEQYn\n"
+  "O9WvZJhbNPHUmX0z4OotI+Sk3qBzVHu0BGDsPYC9gwszIumDUILxAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAL8o6CJiLfW4vdRFvJ2nFt/H/ei0ov83rilOuwSmNORmL9lvnHY++HrD\n"
+  "dmEEvBv74xqWJxGbJ6OQ3VOwRpf2X/cb4gAvsQDqDmNwpJsrPYRQVXp/KY/8z7bJ\n"
+  "dM4CjcsuJHHmj3yc3iCzgqt/Xr6vR24X4bee12/bP7R8IETvWoiHAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key qpNEGrLMVn28Odonk/nDtZq1ljy0fBshwgoAm4X1yzQ=\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "i4RKGIeaUrO6nzfdtb6j+ijYJh1Vgc9bsHMpW9cVCOjoJKFW9xljgl9xp6LytviN\n"
+  "ppKYCt9/JflbZUZjny34ESltPGrdquvHe8TtdQazjiZBWQok/kKnx2i+PioRF/xI\n"
+  "P8D0512kbJjXSuuq9tGl94RKPM/ySGjkTJPevN4TaJE=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 pMAOpepn5Q9MxcV9+Yiftu50oBzBsItQcBV9qdZCIt3lvSFqFY9+wJjaShvW3N9ICHkunrC0h/w5VEfx4SQdDA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "Du5fJYDzvEeGqKTJwgaQsJJgz39K/J4qEM2TZ3Mh0XuDM1ZWDtjyzP03PaPQqbJ1\n"
+  "FsN5IStjOqN3O1IWuLzGaZGpGVuqcyYOxjs7REkGQn2LfqCjpzjaAdcsL0fI4ain\n"
+  "o/in8GQ6S/qhsx8enKlN0tffTmWmH9bmmVz0+yYmBSo=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+static const char EX_RI_ED_MISSING_CROSSCERT_SIGN[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf54AfoVFYuJnDNBWbjbTqfXACUtXWPipmqEYC++Ok/+4VoFAQAgBADH7JzI\n"
+  "fjSMV158AMiftgNY+KyHYIECuL9SnV3CSO+8+I7+r9n+A3DQQmGLULo/uZnkbteJ\n"
+  "+uy6uRG4kW0fnuBlKhseJQm9hjNGWzC8hmebp1M+bxwG41EGI7BZvnTrRgM=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBALEqlijoFIDX1y1i5zfei8DuDIsFtSw56PGgnMRGcybwD1PRQCheCUZM\n"
+  "erQgFCWjgLgvGJERBK/oILW1dFXp4MAR5RgnrPGTfWTinCj32obMLN1gIczpq6a9\n"
+  "P9uv6Cz0ApSxpA/AuvjyAZwQKbUXuMvIY4aTprAKSqqVohk6E+E1AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMZbbBjGV7xPri4XNmejq4add93p+XsWlsfbM930bcC2JZiwg4g4cq6W\n"
+  "idl8VDmCXeaWg5y3kb82Ch/Q9vPG0QYQbXxUA3JxQKKbcEK3QsEvqQh8Nb7krILK\n"
+  "YnSGAnLG2Nc3PnKb7Wpb8M3rAysC5O99Gq1mSfm8ntj3zlIM7NSHAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key CYcpfIF4T9PJcfROfVJTUYl0zNd4Ia5u0L9eng/EBSo=\n"
+  "ntor-onion-key-crosscert\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf54AcfsnMh+NIxXXnwAyJ+2A1j4rIdggQK4v1KdXcJI77z4AMRc2LxiKbyr\n"
+  "fqRVynHuB031C4TN/HAlNPBjVoRvQRgzpiyyoyCqMDxLZdM8KtzdLLeqZJOXtWod\n"
+  "UXbYG3L70go=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "BRwRAK2lWxWGS49k8gXFHLEQ/h4k8gOQxM0WgCaN4LjAOilLHFjsjXkmKgttVpHl\n"
+  "f0V9ebSf+HgkpQnDSD8ittnr/0QaohUbD4lzslW4e/tQYEiM46soSoFft85J6U3G\n"
+  "D3D63+GmaOfIaa4nv7CD0Rw/Jz0zTuyEuARsdJIr1IY=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 7XfV5r7FXbXPEvrxlecWmAJxat/6VT+/4tE5cHrQnvLM4zslysstWH6/AfIfcmUuDlQ0watmfg1MvVnjavcfDA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "eigLL3S/oMGL2tJULt9bl3S0iY+YIxdKeGFCcKZci59zD786m+n+BpGM3yPpvrXr\n"
+  "bGvl4IBqCa1I+TqPP1rM9lIEcUWaBT7Zo5uMcL1o+zZl1ZWPWVVKP5hC5ehDueu8\n"
+  "/blzNhTEFAp23ftDK9PnFf+bXxqbgKkEoZsxnd3e9Ns=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+
+static const char EX_RI_ED_BAD_SIG1[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf54AR8QC+SNBpPOTVY198IQBANNwZjy+SBqQNxfzjEmo204AQAgBABjz4FP\n"
+  "zW/G+fu7YirvANvvqJeb7S1YYJnf6IrPaPsPRzDqJcO3/sTzFC5OSb9iJmzQAWnn\n"
+  "ADPOl+nOJC58XJnJ7CUJdPtyoVdMvUiUT/Jtg4RuCN1iDaDYaTh2VavImAY=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAKuLC0kzCBTV6+WPZcAOQPKjqbjvMIyaehIQS1o90dYM+Tosrhtk3bw8\n"
+  "QBLMaiWL3kfIWPZuWi2ai40dmqAXMrXH3yBgKRNZ6zZSbUUuJ1IknqmrQ2PKjC/p\n"
+  "sIW2awC6Tq+zrZ7vntDb02zY857vP59j8eolTDg1Vvn6l2ieL+WhAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMnBQPOJBQLZ3NAa70n6lGZGvS3DYZFNOZ2QnHVeVvOSFIFsuvHtnUdX\n"
+  "svDafznYAuRFRVqJS2xtKKGu0cmy6ulEbBF+4uAEMwQY7dGRPMgVF1Z33U0CSd08\n"
+  "ChCJGPTE7tGGuoeSIGN3mfC4z2v9SP3McBdAiLHisPzaUjfRTcwRAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key W8fUvBpKBoePmqb70rdJUcRT0NhELDWH7/BSXJtkXS0=\n"
+  "ntor-onion-key-crosscert 1\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf54AWPPgU/Nb8b5+7tiKu8A2++ol5vtLVhgmd/ois9o+w9HAAPwWqmL0HXa\n"
+  "bYKrKPWQYnpQHQ3Ty0MmCgj3ABF940JURnV161RlN8CRAOJaeQ0Z8wBRLFC1NqLT\n"
+  "+GVdtewGeQA=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "x0vT5Wv7Guc0/Vu2BqomWwenh8oda9+8K/7ILi5GQL/WC29Tj51i0EE7PVSnSMJ7\n"
+  "33I/V+N5neauqWnbg7TxYaLsPfr6SpPTpBL1Xt0OiwT1//PvPYZ1gCcF3ig3KcfI\n"
+  "mreQd5C5Vri6ukWkMtz/zNDaDpDanzaNXTdaUXmFHF4=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 4DSdPePrToNx3WQ+4GfFelB8IyHu5Z9vTbbLZ02vfYEsCF9QeaeHbYagY/yjdt+9e71jmfM+W5MfRQd8FJ1+Dg\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "Hci/Br1+NNymDZBmQy1QWMlCeLe8Z1vtZ2ZTj42jDhWg1OC/v72ptI072x4x5cmi\n"
+  "X3EONy8wQUvTNowkfG6/V/B768C7FYJYBId1GAFZZymXnON9zUYnE3z1J20eu6l6\n"
+  "QepmmdvRmteIHMQ7HLSrBuDuXZUDJD0yXm6g8bMT+Ek=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_SIG2[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf54AW8fyx54c7vQQA/AmShAitFP7XI1CLdifEVPSrFKwYq6AQAgBAChqjVA\n"
+  "/wKKJZ30BIQoXe5+QMiPR6meNxF1lBttQ2t5AhauZbH5XzRhZkdGo114wuyPNEM9\n"
+  "PrBwp5akTtari9doVy6gs3McqdoIbRdWevpaGj5g5oOEOtA9b5UNWQSwUAs=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBALp0Croi9zhpGxi9sUj54jr/flZdzxVVS+8VNldJG2c1soSx8kwlwotu\n"
+  "7mGGudJDAzDHGo5F5CCPEfQov2OmDehpefYUz/AaMLly6PrLRJlcUcpLogGf1+KU\n"
+  "1lLwE8kanXUkgvDhVQiFvNjy2Dxxuv3AHH4WdZZfbMbm8FJRGoHzAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMoI9vQT4g2sV2dViGOWOzxckk367T9sMjVwcYfJCmnixGxjWeKScQFB\n"
+  "K9v1uK73cfZR8AxiUGK4/iOX/9en14mJOGF7fftAqypFLAt1TBvb07IgXljOBoHc\n"
+  "Paw4oZoJQzEoazt0Oa181LyNnNIoaZpHVZd1+a1Gs1gKoM4xDBv1AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key KjyvXYkMcpke5ZsUYf2gZAUNeEoz8NAwYoQvvbcDGiw=\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf54AaGqNUD/AoolnfQEhChd7n5AyI9HqZ43EXWUG21Da3kCAI6MRHm7GpCF\n"
+  "/3zDGR/6jKe625uFZX9HpLt6FgAdGSJeMQ9W4Np9VkrFXAB3gvh7xxRzSgZ1rXgR\n"
+  "lUomgi7N1gc=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "xJXvCCpP4ExBuT3OTsdn2HJB0HidupmQq5zBh8fx/ox6+047ZBOM7+hVxxWapcMg\n"
+  "PMXbcLD4L/FCBpA/rjnFUE/9kztdq7FH/rOdi0nB6FZWhwDcsZuyfvbnDTxz5iHJ\n"
+  "87gd5nXA5PE649SRCxW5LX0OtSiPFPazu4KyyBgnTIM=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 4DSdPePrToNx3WQ+4GfFelB8IyHu5Z9vTbbLZ02vfYEsCF9QeaeHbYagY/yjdt+9e71jmfM+W5MfRQd8FJ1+Dgxx\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "tk4kBNYqB8utOmX30HrV8YfnwBXYODIiL3M/juRS6nPn0uvbW7pjoZ3ck/ahgW+6\n"
+  "FNQsgTJnEADCWS1r6v7PcvzQjtrOUUpNxGJxYw1r8yZkvmIxSQD6GMzuTxq7o1VA\n"
+  "/wZYDLonLhCWRdPjxnrl12+z92NdyISJCHMLRVqs2QY=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_SIG3[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf54AYYiKZrFWZ/Cj5mZbfK11MZHYbwchllsUl4qPqY9gfi6AQAgBAB4irxT\n"
+  "86FYA0NbZssSTmfyG6Edcf0ge61OwB4QD35kHCrvuZk2HnmL+63Tj4QoFqIVnwVC\n"
+  "3wRGJGcmS7y+vS64GUXbuyTgqgpl/KuoHo5Aqe6IxJlVWYtU6W0M6FV9tAM=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMUEvXTVTl5xkQ2MTEsB4sXQ3MQkz8sQrU63rlqglpi1yUv24fotjzvE\n"
+  "oJpeKJBwwg5WBW/fW0bUDJF2cOHRHkj/R4Is3m+2PR1Kn3UbYfxNkFkTE11l099V\n"
+  "H6xlsi0TJOJKlgrcbSuB7se2QctZVhwsdsJvFRptC9Qd+klAPb7tAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMooTeSUX7GPoyklSd1/6cF1u8e2LbjOLIpZrMon0Xt7c/aNwlrG9rVo\n"
+  "TSokHs3AQ2H2XIceySVRRWR4AdX9KApO4CX0gGTuVUmq6hFJWMnHdAs2mKL0kt1w\n"
+  "I+YWzjUqn4jIVa2nMbyHVQWzIysWwWiO4yduIjAYpBbWd9Biew4BAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key BN0I+pLmFkDQD5iRsdkcped4eZwGIuXnLiX2K0Zoi2I=\n"
+  "ntor-onion-key-crosscert 1\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf54AXiKvFPzoVgDQ1tmyxJOZ/IboR1x/SB7rU7AHhAPfmQcAOrIvaG/xJqe\n"
+  "adM6mai+FlV8Dbt6QrXTcNHJU1m+CUDthA9TPTAYz9D8W0mTEQ6KEAKGfQrNLy2r\n"
+  "G1B+9wWSpA4=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "BpLBsl6Yo64QzczJn0TjdcXC1Jv9IhUG2m/Re3v0voCELOP+t5vkZXXLoVL23oKv\n"
+  "JheSkWiuAIEPsatb4afXZ8wZxPcQjwy3zTOBM7p9CG5fA+KYpqKTxAi+dhVYlcDo\n"
+  "M7S5nMV63FclkZIT70FFTHwWed1sAKwEO3/Ny24eppc=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 abcdvEzGFYMcJ/Ea7sbessW1qRJmnNNo2Khkkl0rEEgtLX0b4L4MMhK/ktS52Y6jX3PRQWK5PZc6gjV7Jaldh+g0Aw\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "Vyj7g3eQ3K4+tm49fJkAtsAYnYHcEiMnlucYCEPeKojzYStNfZwQO2SG5gsoBIif\n"
+  "urgQZ/heaF4uiGFg64UFw08doXqQkd5SHO3B4astslITvmq0jyaqzSXhdB5uUzvp\n"
+  "QCR0fqGLVS1acUiqGbRr4PiZ9G7OJkm230N3rGdet+0=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_SIG4[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AaEnncX/t0cbLm1xrtlUpkXghaA8fVuV7g1VF3YNfCaIAQAgBAC7Ki3S\n"
+  "zzH9Aezz5X4fbwHeF+BQEDfVasfyTxTI4fhRi7t3RxHzBJd60uEMXy2FchD8VO5d\n"
+  "j4Dl7R4btrohPVSVBQZuemBQSW6g3ufNl0txpFWu0R7vBPTFH6oyXYfY9gQ=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBALGKwzhOui2/jJPjU1ngW5IZRPcoDk7RAfGDO4xaef4VfAFHCV9CQO1c\n"
+  "/wQ09CcRdggTvUcv9hJTGJhSObUUooCkxw4/35f/A6/NoW1Gi0JqF9EsQWHpuAfr\n"
+  "n/ATlJQ9oGdTCNDq/BXSPWXhoI6UhUe0wiD4P4x4QwaYHcZh+lE5AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAOKrizVm2h5/jE/HqqLCBLWJZVVoGspasCtDDqHhSqsPzyjpqa52iMKi\n"
+  "q/deJ92le3J2NJRGKxPmPQqWxwhIjnMS5kUMoW182iLpO/G9qyPZ0dh6jXB0NBLF\n"
+  "ySfW6V2s3h4G4D2P+fqnsnzQnAX7YufkvgDau/qTWi2CqD0CjavDAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key A9h8jY9dPbhHTDbIc/NYWXmRP65wwSMrkY1MN8dV3BM=\n"
+  "ntor-onion-key-crosscert 1\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AbsqLdLPMf0B7PPlfh9vAd4X4FAQN9Vqx/JPFMjh+FGLAN8xr/w3KFVi\n"
+  "yXoP/az6hIbJh0HYCwH8D1rPoQLcdpe8XVwFSrHGarZesdslIwc9dZa/D1dx3OGO\n"
+  "UhJOrdv51QY=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "bLmdO7ME5vq+c9y/Hd8EyBviMBTeo85sHZF/z6Pehc3Wg3i1BJ8DHSd1cK24Pg48\n"
+  "4WUrGTfonewuzJBDd3MLkKe6epXmvUgvuQN5wQszq1+u9ap/mRf6b3nEG0MHxMlO\n"
+  "FLx5MBsScuo+Q+pwXZa8vPuKTtEjqbVZivdKExJuIX0=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  " router-sig-ed25519 4DSdPePrToNx3WQ+4GfFelB8IyHu5Z9vTbbLZ02vfYEsCF9QeaeHbYagY/yjdt+9e71jmfM+W5MfRQd8FJ1+Dgxx\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "LqNGEa10zwSPeomBXTfgvBnnWAdWyiR7KYZq9T++jK4ctR6hUaWngH8qSteUrkMx\n"
+  "gyWb6UMmlxdfOG0sdcU463HsqV7zObaKya8/WwQ9elj3FfsToswUCeOaLR/Rg7wC\n"
+  "zcUjI5VsneQoXT2WVZbZBLsLB3+7QfezVHRMB377GAY=\n"
+  "-----END SIGNATURE-----\n"
+  ;
+
+static const char EX_RI_ED_BAD_CROSSCERT1[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AV1AfOvQWKlWsbzoBdJc5m72ShIJuA8eNV15basjhXYdAQAgBABy+KQK\n"
+  "3oLDGtqL5kwRmjAsls/+C6SAoAALll7U7wNSH7en5RVBal4RUzCf57ea/KG0c9V8\n"
+  "2DmZ3PdOt2aY/M2bWGmmH/tyyapOoV98dhDwFU7zcx/pMfRnJTDRSDwl8QE=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMP6xbqbj+x1mq5XImjeT0rUzqKZTgBd5zvK4Xcy9IifJuFC9+mMzrY4\n"
+  "WhYbdClxKUkDMkit9MVhek+P/w5TSHKl6AuqGaO09ID+hZpoUSdoBUYktynxfGsx\n"
+  "kIDu0XvgtAeSyJaVvoV1SKVChY0IBbzUqbHt4O2Q1BhzFCKEJTEzAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBANwWlBh7e/eSLlhto5YUdj1iGYOq+yAmlosDItVfYrSPJuUfM2ocMBAn\n"
+  "udbRbWiADoqsbKn/gwwHCC/f1HX2FkRXxxnOlJKLo+NEi8tGmOlcQXSQol1pCpvK\n"
+  "sA9TxtYr+Ft4LRpxNrexF+pIBxqzwetqQrZbKYr0CFJi8q1qlMynAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key cs1AP+xF5cXTLuKeOeItdoDAzfALTJkwk9lB4mtC4QI=\n"
+  "ntor-onion-key-crosscert 3\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AXL4pAregsMa2ovmTBGaMCyWz/4LpICgAAuWXtTvA1IfAKo6ANUq+hi+\n"
+  "xb3J4aYafnszlj87oi/DR+SDf29wzwNw8gmaqGzJ5GbfISfABuTUCzlilZyVnLxi\n"
+  "BHcCH6PWiAQ=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "qC9Kph/kGtONR2DxZDoIFFgnDFC+/7H07EgCiYQdIFIROc+gGK9qBOgeFEptrkXF\n"
+  "XdE35xxox5xSASQvp7hjFwxUtJRGOtf2O98regqeeaz6O9VPXHkLf51uqX3bVgq8\n"
+  "KvFAsFFS66GxhtbrVjpyRgIwHAYvse1WVESfLuZZTn0=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 3uW8Q1aetIQLOsqSco128ZUaHlhqdYiBvrxV7x75BGNS5RzIMTEwYDNtEX1LNPFJ5N0YOV0HEEOLhrJUV9QCBA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "WuD7S/saTYBxKvItITbHRi8n+e6g/oVbosicfbRbafYPzPp4Prb+RK03UTafzXrV\n"
+  "QEQIzDNhfePcIMH8qX+qrogLMXFqiXx6TVQ0GqNvqirokk8ar3AgtRtewhChAuAj\n"
+  "8pmQTj2JpZn/iB3PCE2l/93O9LHZfp44hc8QOWKs6BE=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_CROSSCERT4[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AW5TTGF9jCMl7aALZzqypD9Bj8WYnAPIrKCoIJdgMbY0AQAgBAB7eCn8\n"
+  "rukx7t/egZUdqU7+FYqsnO4wdmOkLZkp0+gpF3jjk6N1Q0037NNVNZBjONB0Nm2F\n"
+  "CpB3nWSJliSSKr5tOYsuBPFy5VVGYeKPakpOoxanQ1UcqevMBAQy0zf9hwA=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBALeS5YbeDuKQ5iiuUvh3REoyJ47/YU9lslWmTrVBf9b66pMnYJv/awPu\n"
+  "m2HredUAJ3VzwQ38VJA39w3fQXUhQDnQ0OPpKzeAmIiuG+6WdW/mBSK7uKcezC23\n"
+  "LA1d6Afyl79LjZz/n+ENXqNMlJk4QPcPHuRnAvwBl3t8YVRPJmxhAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAPprokY7utWuO/0252dBB5MCxmVD/dROaIBDyFtpdH+YVv04rkOlDzYD\n"
+  "W4mgHVBMxEm/cspTgQmJ4exRHJPpcSe1RYHt1ONZdLYr6D7OOWf0y1IUrVSzF6K4\n"
+  "lqlmNuH1H4+TKGbkvixYc5GU/2ZmAy6gFEuphYnBbsN2Ywc38mnfAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key Cgo6xniGfEiuYoLSPUdE4Vb2D4zj2NQzC1lRjysRRXs=\n"
+  "ntor-onion-key-crosscert 1\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf54AU3MlHAEtdPdAyWJzRBnh4brXbCR9JFLjLM40hsBMoscAJ8cHMIc71+p\n"
+  "Qa+lg5JiYb551mLgtPWLy12xdhog7SXiJl3NvnMgbMZXHDqkU2YZCidnVz+xqMdh\n"
+  "mjQFK4AtRwg=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "bi4M/AJLZF7/vSNmOj4uhrgKBQA/KfcZy5e58mhGL4owxd9vaWfl3aelvb9jf9zN\n"
+  "Q7FMv8f9aXzeVIoXIpRJxSKIJgBtG2wnMumIc80pqBvTyGInharszb6njfm0bg1u\n"
+  "PfJkbQYyf/dA5l5UwCrjFs06ImDmjFTAdsSWf6DfZ/k=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 4DSdPePrToNx3WQ+4GfFelB8IyHu5Z9vTbbLZ02vfYEsCF9QeaeHbYagY/yjdt+9e71jmfM+W5MfRQd8FJ1+Dgxx\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "io16v+e0pK3sbFzPGnkQrAjrRgIOJHrVZ1RXcxZ1+UNXagWM/MOLhQpkU/cw49Wd\n"
+  "4rQeZD3JQh16330eXbxc97AyDgp0b30He846SI0MfW/DnmGI8ZNeYfLbMv2bmbs9\n"
+  "QULzyIH8C+5mnMI1arcuiAua+Dpa34F79vgqPuvw5fU=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_CROSSCERT3[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AVB+j+B2yPgGywvp7nvejyhMh9ejKmw7LCwufV83Zl9eAQAgBAConA3B\n"
+  "jJ3X2tES40jd94rRUFS2/s/Yv7E4LEQ9z0+jz8horNivzK3O/t7IGxJggi+b41/9\n"
+  "Uaqt+wqtVuKj0xJ9jwBlCXFt28G2P9s4ZyXYgGZqo7MlJlboybnOMvmoTQA=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAPWuEWckT4aYAVNrZzLA8xVwfXp0wzfXeTWBztLS8VzssN6w/+cwXdeY\n"
+  "N1YNc2DiD3u8f+7kmuZIqL1EFQUwTvRwEzQXm2dqGM7qkm5ZGNMb5FKu+QwO2ImI\n"
+  "FLNiO5zO/LqP3cf/2L8/DuvruLenUrhRtecGFaHmhDYl+2brHIiPAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMtHTfk0gDvp9+PtIG8Ks7rgCiJZ2aihSvr6WaKHYuIprgspFuga98cg\n"
+  "D//J80CrgH5Dw68YnkG+gU40IxP7YzhQ4glFlJGu3s2y7Qazcv5ww1XtHur+GDoA\n"
+  "cY0zCLhltNQFxIsoVUepY97XA6Y2ejYJjyqNXQcAmoPNoVhnTdkhAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key ibZf57LptdOK3WpVFXkYMatEEqPhuVWxsnkwF6638V4=\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AaicDcGMndfa0RLjSN33itFQVLb+z9i/sTgsRD3PT6PPAEbkxCdI/bH/\n"
+  "B06DAjRuoDiv1HKsGuW+UN1iGEiWu2ieFzf3m0Z7BL9p2u2zIbHYkP50b3T3sebD\n"
+  "1AksemmMdA0=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "BpLBsl6Yo64QzczJn0TjdcXC1Jv9IhUG2m/Re3v0voCELOP+t5vkZXXLoVL23oKv\n"
+  "JheSkWiuAIEPsatb4afXZ8wZxPcQjwy3zTOBM7p9CG5fA+KYpqKTxAi+dhVYlcDo\n"
+  "M7S5nMV63FclkZIT70FFTHwWed1sAKwEO3/Ny24eppc=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 XS4zVi46Xl3xKhuozPCDlW0QRFD4qUhJmkefonQNsRlMVsrPkALnP2tfnfdfTc69hbNa22pOjJNf6Gm505EnAw\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "Q+R3OpO8VhfvFbXuE5qolhVbgosBHy2A5QS91TMzCbsxa8pBA6Li4QdPR37wvdLq\n"
+  "KayfmmNCMKU5qiZMyXqJZm4fdpxiSi50Z0tYlXM3b2OVfza3+pSOEBl89fN6G4Qc\n"
+  "pAmM14eEo1UzXrqZw76tMS2CwOYF5vR2xFGCYC0b5hM=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_CROSSCERT5[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AaCfOaispi7dJhK0c8HXJHIwoBkMgRpmmHu+3Zce/soMAQAgBAB5bAIo\n"
+  "5i4TSY/bV2KQAyziRwvgJm+nEiECClflPbP9Um+zOzOgxtDmNnR5UFQj+VWNG4uf\n"
+  "5lnaryN+PfUXZMTcs8AARof3fFz9tVPINHDrsGvKt8gpzgZEHkVioAXOFwg=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAL3Fr/ovZ9SMGYrAM24taKBm/NpemZaXdD/JeBXFYm5Zs3szLwJC4Etm\n"
+  "zjNL6tVy+I21O1g3cs16TkflcidsjPXNx//PHAn7bqWMekjrt3SQdkHW2gDPgT2c\n"
+  "zYJ/hBR96JYG796jP3pkfJz6Iz5uT/ci3A/cdaVbzM1uZbMUgYGzAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMHB+1dWa8BBrKE94vTqfbkSEuysG5LyyZF/WrqHq/3W+ocDLz795k8O\n"
+  "2Zvgr9im/Ib4hD7IyrtRexcuBdwujdG7cBALdCcWiUTGAMkl96HNETSX+lUVIpJ9\n"
+  "pMsc9O7+yz+/0Cl2RpILZCdE/7I96qHpZl3tzlRKSu15WeIm5U77AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key GXi0a2VLcRHQMMYys85zu3IPqOn5ZTsOixYyQvTGnQs=\n"
+  "ntor-onion-key-crosscert 1\n"
+  "-----BEGIN BUTTERED CRUMPET-----\n"
+  "AQoABf54AU3MlHAEtdPdAyWJzRBnh4brXbCR9JFLjLM40hsBMoscAJ8cHMIc71+p\n"
+  "Qa+lg5JiYb551mLgtPWLy12xdhog7SXiJl3NvnMgbMZXHDqkU2YZCidnVz+xqMdh\n"
+  "mjQFK4AtRwg=\n"
+  "-----END BUTTERED CRUMPET-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "T9NHMBhuJo+TlfU3TztNgCc9fK1naNRwPOyoqr5R6lJvJ40jkHnIVOFuvuzvZ35O\n"
+  "QgPbyFcMjv6leV5xcW+/I9tWaBUFXiRGI27qjCFth4Gxq2B6B2dIcQliLXSvW9b+\n"
+  "CMTgDwVa4h2R2PMh18TRx1596ywE09YhCgBF3CwYsiM=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 sRpiP9kyW/DGOphp4V2VCtcKNA8i7zGuv2tnljNIPTB7r7KsTvdUk/Ha9ArRQEivO4nC2HHENtknDl3GtWIPCA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "DtORw3+gO/yUUIp70xDaWSOgQZrJAAoZTNCB7q5WCoZOngeaCiC1Gtc+Fmdn7tER\n"
+  "uPqQC5H/Kh3Mi82PCj0JxvNivnNTNY1AZVaIX5YoioXVOkWF0B2pqMvFuDSdm2oJ\n"
+  "29PqSVcklquu19EjJRTopIHvYn3sFhQL4LarMsYY11c=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  "\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_CROSSCERT6[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55ARMMCtQ8pObC5bq02AUE9Lx2bqsZBBkeOsDZVaEq6JavAQAgBABtV0xF\n"
+  "CsWXL/uFIBnoEsnXBeU1MvYRFrj1vR7QHdWXnxywXvBYUAC8lu/uyc8qqLp+aQSJ\n"
+  "5JzpDYlg3hp1fl5k97iv5F9WrR6s554YpmgYy9agFaxZ4LmRgz7n0UJ8mwM=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAO5qd1TndKD2pEs1ZLWsHlvfO/E7cA0H7NKGLSioGpBf4P0rtkueX4ci\n"
+  "kJNa/4Fn/QsLECqEF2lUjkIc8YL+HMS6qteKvN8+nn16DfvnIhPDNZWTJjLl1bOI\n"
+  "sWSSiduhanoWQnhRtl3Rxg3opdNd9ApO0DLUNy4Qy18Ai6SgksfHAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAJkMYNpK7eJJyGwD/xG/iNg6gzzbIwrOSvmtoP7Rot42qtBiQ9A9kdsy\n"
+  "sazwkWkM93U1+1OaAADPYxeHoyHnuia95Cnc5y2lFSH3I7gnGGSPKSTwXtdyvDWZ\n"
+  "P1LbmQ4Bnh5leTCNZ/eFC4/GjNVzqHxjbb8a11dQhA8dOk8PrUq9AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key HdSQOqvLr4YnJE1XzzVIddgKgnjaHKJqnq0GqF4wXDg=\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AW1XTEUKxZcv+4UgGegSydcF5TUy9hEWuPW9HtAd1ZefACVwif1deQry\n"
+  "K5GeemRa32sGzujVDDe75WRiPKFT3l/EtjTq3oeVq2xwbVJklnG3ASejKTr3YcHt\n"
+  "ov0jOl0jywc=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN NAUGHTY MARMOSET-----\n"
+  "BpLBsl6Yo64QzczJn0TjdcXC1Jv9IhUG2m/Re3v0voCELOP+t5vkZXXLoVL23oKv\n"
+  "JheSkWiuAIEPsatb4afXZ8wZxPcQjwy3zTOBM7p9CG5fA+KYpqKTxAi+dhVYlcDo\n"
+  "M7S5nMV63FclkZIT70FFTHwWed1sAKwEO3/Ny24eppc=\n"
+  "-----END NAUGHTY MARMOSET-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 lNY8TRX/FZdH5eFbsBkFHuRi8bPDsE5P+v7zExyD/IXnKS/ffYlP8qw1XIPdEDOIzGQ14+kyPX0SotaAqHRtBA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "BHamS+epF77iozo5cBt+tbs22m9GhwY55DRXpEWAtvn67jsMnmn7qCOLONigK1RT\n"
+  "adZNezIydcCxXltgHTdKaZw4lcqv3s0KL8kI8frbBmm7PjXtWnrdXBYY+YK54MN/\n"
+  "t4N3162o9hzzKSwye0gPjgzpQ1xtEIkzWhBcmE9Vw5s=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_CROSSCERT7[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AfVmH2ReTyatl4VnS5YREtCM2dwikWuAPffq6M5bysZxAQAgBAAXoqE7\n"
+  "taqwLDXLZrZukpF1eBkCwYQK9uzctHTuMdqOHChguvkfX7V4H3O76Ayqvz+Z1ut1\n"
+  "KYRdgiArn3viRaBv3ZKT4Z75suMI3bjqGOSGLAKfOa0uLkOmKblHHhSUkwQ=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAOLNugzUezzzw+N1SuQWzILJYkUJyQDoVXSZjT0dzBplHCjlrv0WZCUP\n"
+  "/pbonE7SlCChIovHcdiASaLj7MVaGgYDq3M1Vtgt5vhgGl10/+evBAD1QEt8AVfr\n"
+  "5+PH/sbZvOWucAhNUhOlqFKAn4vdRY39VEEXC5/Jz5fsk1E/DBu5AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAKxzg1hsYMS+0zAIrgYxSGO0GbKRrL/VhdlMEGu7ACaoqlGnmGQS3B4B\n"
+  "gLk8xDdx9N//8+YTx0hUIxP38w08lubPl1WXMq8s7wAiFd06Nklf65mHs0sXVtS1\n"
+  "EG3f97PQqmBpEJOwYBATNcA9e6F62P8SXNkpSjOzNaE0h9wHNKk7AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key msdr3O4W4bm/xdmZLzj35363ZSFex8yQxLWsV3wRCAQ=\n"
+  "ntor-onion-key-crosscert 1\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "VQoABx54AU3MlHAEtgPdAyWJzRBnh4brXbCR9JFLjLM40hsBMoscAJ8cHMIc71+p\n"
+  "Qa+lg5JiYb551mLgtPWLy12xdhog7SXiJl3NvnMgbMZXHDqkU2YZCidnVz+xqMdh\n"
+  "mjQFK4AtRwg=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "RJJRiU0vjVtRi3bVZru3aTvV5l56X/WOOp/ii316yPAS3aAMpOm1+piFVR5MNqcB\n"
+  "ZGyrA2Kx0hawdL2buU47iZ12GOCi4f1Es4V4N0TQgJICsKX38DsRdct9c1qMcqpp\n"
+  "1aENSRuaw0szTIr9OgR7/8stqR5c3iF1H5fOhmTi6xM=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 4DSdPePrToNx3WQ+4GfFelB8IyHu5Z9vTbbLZ02vfYEsCF9QeaeHbYagY/yjdt+9e71jmfM+W5MfRQd8FJ1+Dgxx\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "F3ZqvsyL6RRhPEnNFFIZY4WJM7LK082rseWzRkGNXjwoEwOWUK8enQ4Wjit+wozW\n"
+  "4HVIY1F+vP7gm6IiOEAFgEpB4C8FGuyoFw2q0ONA2tqTcvBJDDnqbx08FO7v2Dij\n"
+  "d3ucfc5gf7YNaoFCMMuyAzC56eyNk4U+6cSKy6wnJds=\n"
+  "-----END SIGNATURE-----\n"
+  ;
+
+static const char EX_RI_ED_MISPLACED1[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAKT6OIN6TsDB+xcp1uLeE0K3aiHGqa7hdxMBGpvcD0UFSyzpVv1A/fJa\n"
+  "tClDCwTpfTGbyK2L7AO75Ci0c7jf6Pq+V7L6R7o12g6WBTMrgsceC4YqXSKpXNhi\n"
+  "oudJyPfVzBfKcJUSynv89FUQOyul/WRRqWTfv0xUsJ3yjuOESfCNAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AbBV9NVz0Hdl0Uiv87LiXaTAoeSXE+bheNG4Dju1GzQHAQAgBAD16h+T\n"
+  "ygzSgPN4Qat5ITthvm+lvMwMVGbVNWMxNy9i33NGhgp8kqMp2iPAY+LhX8It2b+X\n"
+  "8H9cBmYLO5G7AlMPj7GsuWdCdP/M/ldMvFfznlqeE3pCpRas6W48CFJ+9Ao=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBANMO/MepK3uCkKTLRCwIWc/8URVza2gEmDx6mDTJIB/Mw8U8VRDuu4iJ\n"
+  "v+LL3D8/HGLvT9a8OXbl5525Zszt8XueF3uePBF0Qp0fjGBL8GFqmrmFe6plurPJ\n"
+  "TfrS/m3q+KhXAUowmghciVGDY0kMiDG9X/t/zKLMKWVDYRZk+fupAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key I8yDO62Flx5O/QsFvgb2ArIRqwJLWetHMeZdxngRl2A=\n"
+  "ntor-onion-key-crosscert 1\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AfXqH5PKDNKA83hBq3khO2G+b6W8zAxUZtU1YzE3L2LfAGC1uXxN2KwW\n"
+  "w4PqRidM1UPZ5jVOHceZYNQcTzzzArfBpr9OraOO2up4TGte8GVqjJNxrZc1gfjn\n"
+  "CwPW5WxpFg0=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "jLg3D3VO4i0sN8p2qtB6+5C3tai/K4M89mP7z2abQnUTbynOacPoNXIk4o64DjBJ\n"
+  "kaR42yfA7yQZ8Rj8abwgz0Zz6zbd+JjE+s/EklrEEtOl+jZAl3i+92FaHROJojXq\n"
+  "hw+ZEPOb9zgb1UQ7S1Fo+GoqA5bdGm/Wg1kSQielkNE=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 TRKvIl/wIIRD4Xcmd6HYmy7tD0KhVGgoStpWPtX0zmXGZ7+jugItrY0frDu9n82syiruuA45ZOs1Rfi4CbOSCg\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "NYpRfurB1YhFmDAdRc2Sd77S7By2V/0kgEHpJhtySb7efiQsyOA4ZBr1zEFPAXdp\n"
+  "TviKzyS9kN2fnz3hORoqFul33BDZbiLMNLtt5tzp62TYtmIg9IZdjjczbJUgbVLt\n"
+  "KCJL0vM7fdbXkZX61GIBbMYwzwIiHvVxG7F/AS5RbtE=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+static const char EX_RI_ED_MISPLACED2[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55AfJo9FIePrxeDNnWT6SWkoz0/L27018XjUNWEHfaR06MAQAgBAAMgolK\n"
+  "nLg3ZnVv0skzHCfmX+ZR9Ttwj7FNXfhXCsyr860S79OW5LD0/m1GcS9JflWhP+FO\n"
+  "ng5cRb+aqNc8Ul+/4sQudZRx8w4U3d5rOuMGCqhQXnktH9AFzQHFq0jpAAU=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAPeK/znKLRvSUmCIUiZOgfhiRFt7XGN//C2GFuey4xkKiIr9LWMuVe9m\n"
+  "Wx39Ea2UGEtNGCEVvZdJMDVRl7heFTfJTN4L1YeyWx6iNRWlpAmgQOKII7slHwlq\n"
+  "seEULOLOXc9AsU/v9ba9G54DFbHfe2k44ZOwEmaQZW5VF/I0YMMdAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAKFRzlrqPPxEW0nboAJ1qzKFb/vFtvRW0xNVb8RtbOY/NY5FV1hS8yfH\n"
+  "igtugkrOBmWah7cmJhiON2j+TKeBxEoXwJMZeyV+HLbr7nY/mFhad4BQ3Frkl8d6\n"
+  "1kQMhOJswMdwnnVHPNGUob4YAX0SpFA6MpBVj92zmMBeaihqUS9VAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key br8svioLcJCAQxoo3KvlT288p8rb4lQIZNLlplkIKkw=\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AQyCiUqcuDdmdW/SyTMcJ+Zf5lH1O3CPsU1d+FcKzKvzAG9XqwmRm0uJ\n"
+  "E49NoHcWr9IzdIwSGo+PJSkVpk95a5p2s065BetCWxEEBJQniajQf2hZ36zmV9rq\n"
+  "a6puqkEAKAM=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "d6QGIVAJL5JjHUyV+aicLIdBYyxHwviKpPcp7uldRF8vfDGFpu0qFgJ5KT+3t36w\n"
+  "QY1r75bvUMG/ZzGKDg95dcK0X2AK6GFlcrYyCoQEVOsuPc1QEUeK9P2s7viNQE4V\n"
+  "tRwG/CvJhPfcnxErzVGfXIeYRL1r/hPNFDZSeSxPPM0=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "router-sig-ed25519 ts9pFk8PnDWtXgQad09XC/ZCbruSx1U1pNOMWF9fyoNG0CodxdDH9Vglg+BOS7Nd9fmsINfPWKCVdVuSSM7zCA\n"
+  "reject *:*\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "YMl6mpQm7UCsPQhZKMm0aZ7fzGevWzRbQO+de20HTn7fVqMWQf2hBDJe9QTN/uDK\n"
+  "/VKYT8SnIBexbrSMy1N5q8kNFKxxUtwA9GRtz620Vvc4m+lz/tnT9qucIKCDL5iJ\n"
+  "eRpnls0JoAMIHKl99zdUioYubmOZuqUaRAdT8ulWy+Y=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_CERT1[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AYf+rX8a5rzdTBGPvLdQIP8XcElDDQnJIruGqfDTj+tjAP+3XOL2UTmn\n"
+  "Hu39PbLZV+m9DIj/DvG38M0hP4MmHUjP/iZG5PaCX6/aMe+nQSNuTl0IDGpIo1l8\n"
+  "dZToQTFSzAQ=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAM4o2DrTwn3wrvUMm41S/hFL5ZtRHGRDh26o8htn14AKMC65vpygKFY7\n"
+  "fUQVClAiJthAs5fD/8sE5XDtQrLnFv5OegQx8kSPuwyS/+5pI1bdxRJvKMOUl2Tc\n"
+  "fAUhzeNBmPvW3lMi9Fksw5sCSAKQ5VH/+DlYvBGZIO49pTnOAty1AgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAMzIsJeEWWjN3Lp6qrzaJGn8uhJPJyjy2Wt3sp7z7iD/yBWW6Q7Jku3e\n"
+  "C5QfKmSmNi2pNjS0SqPjqZZNsbcxpq/bEOcZdysZG1lqi/QgxUevk57RWjh3EFsG\n"
+  "TwK3ougKWB5Q6/3m32dNsnnnDqzVapgZo7Zd3V/aCo0BVtL5VXZbAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key W28nwT/5FJ818M78y/5sNOkxhQ7ENBhjVhGG2j6KvFY=\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AYf+rX8a5rzdTBGPvLdQIP8XcElDDQnJIruGqfDTj+tjAP+3XOL2UTmn\n"
+  "Hu39PbLZV+m9DIj/DvG38M0hP4MmHUjP/iZG5PaCX6/aMe+nQSNuTl0IDGpIo1l8\n"
+  "dZToQTFSzAQ=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "FWnEjvFob0ObgqohMT7miwGsAuioCT7Urz6tyWaGWph/TP9hbFWj4MPK5mt998mn\n"
+  "xA8zHSF5n/edu7wVX+rtnPrYPBmg+qN8+Pq6XMg64CwtWu+sqigsi6vtz/TfAIDL\n"
+  "mypENmSY32sWPvy/CA8dAZ2ASh57EH9a+WcFModpXkM=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 88YqJdGJS4O6XiUCNrc9xbOHxujvcN/TkCoRuQQeKfZGHM+4IhI6AcXFlPIfDYq0SAavMhVmzsDDw0ROl7vyCQ\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "cU4WDO3w9ZfVRbNUgxOQMbwS2xWXvaL+cZmIV6AAjAZVWkLEpif4g6uYu+jJUZOS\n"
+  "NUT7lNOMwTu4tE4b1YJpnD9T8iW0DlOXxlvRBMQYmKwhQuYk898BDGTSk+0AY0HJ\n"
+  "vv8wRVewDajNhW7tFY907IdHvPXG0u83GANxkYrRyUg=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_CERT2[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN WOBBLY RUTABAGA-----\n"
+  "helo\n"
+  "-----END WOBBLY RUTABAGA-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBANZvqyqFeiekh8ApqIGK4ZtOqjaX87EzDestvAWwamVOXiPoUrzXgM3O\n"
+  "l8uuTnMA4TfnjLyyA2TnaMzJylOI1OMHuW/D9B/liWDstSxWNNIlKgLQ/Dh9xBS7\n"
+  "uQb2PYlI+iMkPKPyJQSTDdGHE7cdFPewUfhRtJU3F5ztm/3FLBFvAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBANZl8U/Z8KCPS7EBDzt8i9kNETXS7vnp9gnw3BQNXfjiDtDg9eO7ChxY\n"
+  "NBwuOTXmRxfX3W9kvZ0op9Hno6hixIhHzDql+vZ+hN7yPanVVDglSUXcr31yBm5K\n"
+  "kA+ZnRvH3oVQ97E4rRzpi09dtI13Pzu7JS5jRMtH+JF1kQBoNC0dAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key lUrEL+TVXpjjHQ2BIKk34vblyDmoyMro1a6/9hJ4VRc=\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55Abm5E7FBdd3F8N1xuz/vdv03zh2lABrmGjzPQ3AFJtntALNeQTgjv0JL\n"
+  "jON4+SPNi0B2Bva3yKaSsdxiHQ1rIwQqIUVkzXmmX4jmsvJK/9gERAdD7GafTKZQ\n"
+  "BaZbNXBvmQw=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "OxkqFsw1vHUQ9iPYcKC/MHUBtbLPK6JY2i81ccAai2eW118UXcTbeCRccrXyqSkl\n"
+  "RLcooZyli1D6wg9x7O8+2+HXIbUa6WcTOD1Qi7Z9wKZfk4sDUy7QHKENMRfAXwX3\n"
+  "U/gqd4BflMPp4+XrYfPzz+6yQPWp0t9wXbFv5hZ9F3k=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 fW6Bt4R3xVk5KMDyOcYg8n5ANP0OrQq2PQFK2cW0lTAdi+eX+oT/BeWnkrn0uSWOC/t4omCmH4Rdl8M9xtpfBA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "DHxiQXuLxZR0ylqwUGGePgN4KF4ItlOV/DuGmmszCO/Ut0p+5s4FP2v6Mm9M92Wj\n"
+  "75rS9xF/Ts0Kf49dvgc+c5VTvhX5I5SwGQkRk0RNJtNoP0t+qXBHaFV8BlAeaWF6\n"
+  "Lg3O+GUK325fQv9uDPCe37mFQV9jafAzsZUrO/ggb1U=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+static const char EX_RI_ED_BAD_CERT3[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "BVVVnf55AW5TTGF9jCMl7aALZzqypD9Bj8WYnAPIrKCoIJdgMbY0AQAgBAB7eCn8\n"
+  "rukx7t/egZUdqU7+FYqsnO4wdmOkLZkp0+gpF3jjk6N1Q0037NNVNZBjONB0Nm2F\n"
+  "CpB3nWSJliSSKr5tOYsuBPFy5VVGYeKPakpOoxanQ1UcqevMBAQy0zf9hwA=\n"
+  "-----END ED25519 CERT-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAPgeQNbKwpnTU+qW/2djh66hptS9rcy1B4vdyWkDTdREao2ECuCv691Y\n"
+  "oIw3MpTWvpC1qHIKorunusR0FKgwXw3xQTikXbDq/1ptsekzoIA1R/hltQV3UuGH\n"
+  "zdzHuQXAMX7Fdll2gyya03c3Yq5s+xSDvGdkEeaIoctKjwxp4SdNAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAOzWuH4cPW9rIrfi8MrruMUg4IUVHz4BxfY4/szMIUvzeEAdHn4FYkWy\n"
+  "Vt7MDtUELZsmZeFNmkn72kLxnrdZ5XhxZBriq1Fzq11cSWRBF+SyE1MdcouY4GyG\n"
+  "drw6T8xb8ty19q0eO6C/gw27iqXPAp1clvkroLg6Nv9lGZvsedVDAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key /vYZ+9yLqG7yUnutoI57s96JBl36GTz0IDWE244rbzE=\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AZ4zVBWP/fIYEgWmyj0WpO6CkXRJjtrWXtiT02k3IddiAMpYgMemGIpN\n"
+  "xj7TQRULsHHYvo4fLcKrSgndQbUUhfLTUuVhIzbnE2TBLMVOEkpxKU6mTuvTT/3h\n"
+  "MJugrwTWVg4=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "c/Vqu3wtsTsYMdnhTS9Tn1Pq6jDmH4uRD5WmbaCKKrkin2DjuYSMVpypndkdlZDE\n"
+  "He7uF7SUO3QG/UcRIXYOsg9MSLUmvn2kIwef8ykyqlRh95Csjo5DyattUhL2w4QF\n"
+  "tJkJBQAnXWaAVW1O8XimGCAvJ84cxbmZEcpN6WKjrXI=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 Ue7bkPpOoc8ca7cyQj/Vq3BP5X4vwLA5QmpLGw/WfRNVRPojJRxU3RVqWMi3JbsJFRTe6pH6ZHyXER33G5aAAA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "ifKUtbxmqHVs8A0oT5n7Te0c6D/XqWQTc0RxX9OKGspzh6wNX26h0Xa2vpK1Q9Zu\n"
+  "sj61I7vbHuZN6rxiWs9IzJgb//XaNJasX1pd9tbGSXW+yYzc9G9kaa7vp3HcnhIP\n"
+  "XVWzzS8WmOiVNGcF65j6f7yGloTgN7cHMptgJG7pWes=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;
+static const char EX_RI_BAD_EI_DIGEST2[] =
+  "router fred 127.0.0.1 9001 0 9002\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf55ATrK8IVBWLO2yXKCqXLXJOTu89W2b+hREPO+tCrxjVqWAQAgBACG/vVx\n"
+  "NK8wKVZvf34d75ZObSR0ge1N2RrAIKNslNXBq/tcllIrNE4S0ZNcMpA+hxXoVFeo\n"
+  "jbxifYX7nTs5N3GrGPmkiuo82v2X6ZwoIXJGFnvWMxCjsYsUVDDxoT6h/w8=\n"
+  "-----END ED25519 CERT-----\n"
+  "extra-info-digest E5FAC29E766D63F96AD175069640E803F2723765 99oo\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAK9wHSdRalxkuAybrSCA3dlEC1ZGc7oHOzXRGLg+z6batuiCdQtus1Rk\n"
+  "LP821eZJtEMAE56aewCIHDcTiCxVa6DMqmxRjm5pfW4G5H5QCPYT6Fu0RoYck3Ef\n"
+  "vkgits5/fNYGPPVC7k8AdGax5dKj5oFVGq+JWolYFRv6tyR9AThvAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAKxjxTQ/T/MHpFbk7/zwA7l5b3IW3yVcyVe6eIGFoYun8FI0fbYRmR4M\n"
+  "G5Asu07gP9Bbgt3AFPuEqrjg4u+lIkgqTcCgKWJbAgm7fslwaDTXQ36A7I1M95PD\n"
+  "GJ10Dk5v4dVbrqwoF7MSrQPFtMO91RP11nGPSvDqXZJ4XpwqwdxpAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key LuVmHxpj4F5mPXGNi4MtxbIbLMav6frJRBsRgAvpdzo=\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf55AYb+9XE0rzApVm9/fh3vlk5tJHSB7U3ZGsAgo2yU1cGrAKBcSzwi4lY/\n"
+  "salCELOLdeZzOjDNnBd6cKp2WJg7Yz5zFlbVbyNk0iwfGmucHk8vQZe5BS0Oq/Pz\n"
+  "B1u/BcJv8gk=\n"
+  "-----END ED25519 CERT-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "QsAQVdDVHtasDbhrZG4ZxImdTTMY7fz3vouAiGyZx6/jCCB5v0gHwTn4xo6pgLEW\n"
+  "LQfMhQZIr76Ky67c0hAN2hihuDlfvhfVe9c2c5UOH1BOhq3llE3Hc3xGyEy3rw7r\n"
+  "5y38YGi759CvsP2/L8JfXMuBg89OcgJYFa27Q6e6MdQ=\n"
+  "-----END CROSSCERT-----\n"
+  "published 2014-10-05 12:00:00\n"
+  "bandwidth 1000 1000 1000\n"
+  "reject *:*\n"
+  "router-sig-ed25519 5zoQ0dufeeOJ/tE/BgcWgM8JpfW1ELSXLz4dI+K8YRH/gUtaPmYJgU2QfeUHD0oy1iwv4Qvl8Ferga7aBk1+DA\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "D6KRMwkb6JmVEnpZ825SD3LMB84UmVy0i94xk44OwhoWNKLXhaSTWJgf6AqnPG5o\n"
+  "QrCypSb44bYLn+VaDN5LVUl36jeZqCT4xd+4ZwIRdPOUj7vcVmyUDg3lXcAIk97Q\n"
+  "E5PrQY1mQuLSIjjKInAR2NRBumNJtRw31Y/DTB7tODU=\n"
+  "-----END SIGNATURE-----\n"
+  "\n"
+  ;

+ 5 - 1
src/test/include.am

@@ -35,6 +35,8 @@ src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \
         -DLOCALSTATEDIR="\"$(localstatedir)\"" \
         -DBINDIR="\"$(bindir)\""	       \
 	-I"$(top_srcdir)/src/or" -I"$(top_srcdir)/src/ext" \
+	-I"$(top_srcdir)/src/trunnel" \
+	-I"$(top_srcdir)/src/ext/trunnel" \
 	-DTOR_UNIT_TESTS
 
 # -L flags need to go in LDFLAGS. -l flags need to go in LDADD.
@@ -67,6 +69,8 @@ src_test_test_SOURCES = \
 	src/test/test_extorport.c \
 	src/test/test_hs.c \
 	src/test/test_introduce.c \
+	src/test/test_keypin.c \
+	src/test/test_link_handshake.c \
 	src/test/test_logging.c \
 	src/test/test_microdesc.c \
 	src/test/test_nodelist.c \
@@ -135,7 +139,7 @@ src_test_bench_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \
         @TOR_LDFLAGS_libevent@
 src_test_bench_LDADD = src/or/libtor.a src/common/libor.a \
 	src/common/libor-crypto.a $(LIBDONNA) \
-	src/common/libor-event.a \
+	src/common/libor-event.a src/trunnel/libor-trunnel.a \
 	@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \
 	@TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \
 	@TOR_SYSTEMD_LIBS@

+ 4 - 0
src/test/test.c

@@ -1137,6 +1137,8 @@ extern struct testcase_t guardfraction_tests[];
 extern struct testcase_t extorport_tests[];
 extern struct testcase_t hs_tests[];
 extern struct testcase_t introduce_tests[];
+extern struct testcase_t keypin_tests[];
+extern struct testcase_t link_handshake_tests[];
 extern struct testcase_t logging_tests[];
 extern struct testcase_t microdesc_tests[];
 extern struct testcase_t nodelist_tests[];
@@ -1183,6 +1185,8 @@ struct testgroup_t testgroups[] = {
   { "extorport/", extorport_tests },
   { "hs/", hs_tests },
   { "introduce/", introduce_tests },
+  { "keypin/", keypin_tests },
+  { "link-handshake/", link_handshake_tests },
   { "nodelist/", nodelist_tests },
   { "oom/", oom_tests },
   { "options/", options_tests },

+ 38 - 0
src/test/test_containers.c

@@ -495,6 +495,43 @@ test_container_smartlist_join(void *arg)
   tor_free(joined);
 }
 
+static void
+test_container_smartlist_pos(void *arg)
+{
+  (void) arg;
+  smartlist_t *sl = smartlist_new();
+
+  smartlist_add(sl, tor_strdup("This"));
+  smartlist_add(sl, tor_strdup("is"));
+  smartlist_add(sl, tor_strdup("a"));
+  smartlist_add(sl, tor_strdup("test"));
+  smartlist_add(sl, tor_strdup("for"));
+  smartlist_add(sl, tor_strdup("a"));
+  smartlist_add(sl, tor_strdup("function"));
+
+  /* Test string_pos */
+  tt_int_op(smartlist_string_pos(NULL, "Fred"), ==, -1);
+  tt_int_op(smartlist_string_pos(sl, "Fred"), ==, -1);
+  tt_int_op(smartlist_string_pos(sl, "This"), ==, 0);
+  tt_int_op(smartlist_string_pos(sl, "a"), ==, 2);
+  tt_int_op(smartlist_string_pos(sl, "function"), ==, 6);
+
+  /* Test pos */
+  tt_int_op(smartlist_pos(NULL, "Fred"), ==, -1);
+  tt_int_op(smartlist_pos(sl, "Fred"), ==, -1);
+  tt_int_op(smartlist_pos(sl, "This"), ==, -1);
+  tt_int_op(smartlist_pos(sl, "a"), ==, -1);
+  tt_int_op(smartlist_pos(sl, "function"), ==, -1);
+  tt_int_op(smartlist_pos(sl, smartlist_get(sl,0)), ==, 0);
+  tt_int_op(smartlist_pos(sl, smartlist_get(sl,2)), ==, 2);
+  tt_int_op(smartlist_pos(sl, smartlist_get(sl,5)), ==, 5);
+  tt_int_op(smartlist_pos(sl, smartlist_get(sl,6)), ==, 6);
+
+ done:
+  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
+  smartlist_free(sl);
+}
+
 static void
 test_container_smartlist_ints_eq(void *arg)
 {
@@ -1053,6 +1090,7 @@ struct testcase_t container_tests[] = {
   CONTAINER_LEGACY(smartlist_overlap),
   CONTAINER_LEGACY(smartlist_digests),
   CONTAINER_LEGACY(smartlist_join),
+  CONTAINER_LEGACY(smartlist_pos),
   CONTAINER(smartlist_ints_eq, 0),
   CONTAINER_LEGACY(bitarray),
   CONTAINER_LEGACY(digestset),

+ 19 - 1
src/test/test_crypto.c

@@ -698,9 +698,18 @@ test_crypto_formats(void *arg)
   for (idx = 0; idx < 10; ++idx) {
     i = base64_encode(data2, 1024, data1, idx, 0);
     tt_int_op(i, OP_GE, 0);
+    tt_int_op(i, OP_EQ, strlen(data2));
     j = base64_decode(data3, 1024, data2, i);
     tt_int_op(j,OP_EQ, idx);
     tt_mem_op(data3,OP_EQ, data1, idx);
+
+    i = base64_encode_nopad(data2, 1024, (uint8_t*)data1, idx);
+    tt_int_op(i, OP_GE, 0);
+    tt_int_op(i, OP_EQ, strlen(data2));
+    tt_assert(! strchr(data2, '='));
+    j = base64_decode_nopad((uint8_t*)data3, 1024, data2, i);
+    tt_int_op(j, OP_EQ, idx);
+    tt_mem_op(data3,OP_EQ, data1, idx);
   }
 
   strlcpy(data1, "Test string that contains 35 chars.", 1024);
@@ -1264,6 +1273,8 @@ test_crypto_ed25519_simple(void *arg)
   tt_int_op(0, OP_EQ, ed25519_public_key_generate(&pub2, &sec1));
 
   tt_mem_op(pub1.pubkey, OP_EQ, pub2.pubkey, sizeof(pub1.pubkey));
+  tt_assert(ed25519_pubkey_eq(&pub1, &pub2));
+  tt_assert(ed25519_pubkey_eq(&pub1, &pub1));
 
   memcpy(&kp1.pubkey, &pub1, sizeof(pub1));
   memcpy(&kp1.seckey, &sec1, sizeof(sec1));
@@ -1283,6 +1294,7 @@ test_crypto_ed25519_simple(void *arg)
   /* Wrong public key doesn't work. */
   tt_int_op(0, OP_EQ, ed25519_public_key_generate(&pub2, &sec2));
   tt_int_op(-1, OP_EQ, ed25519_checksig(&sig2, msg, msg_len, &pub2));
+  tt_assert(! ed25519_pubkey_eq(&pub1, &pub2));
 
   /* Wrong message doesn't work. */
   tt_int_op(0, OP_EQ, ed25519_checksig(&sig2, msg, msg_len, &pub1));
@@ -1421,9 +1433,10 @@ test_crypto_ed25519_test_vectors(void *arg)
 static void
 test_crypto_ed25519_encode(void *arg)
 {
-  char buf[ED25519_BASE64_LEN+1];
+  char buf[ED25519_SIG_BASE64_LEN+1];
   ed25519_keypair_t kp;
   ed25519_public_key_t pk;
+  ed25519_signature_t sig1, sig2;
   char *mem_op_hex_tmp = NULL;
   (void) arg;
 
@@ -1434,6 +1447,11 @@ test_crypto_ed25519_encode(void *arg)
   tt_int_op(0, OP_EQ, ed25519_public_from_base64(&pk, buf));
   tt_mem_op(kp.pubkey.pubkey, OP_EQ, pk.pubkey, ED25519_PUBKEY_LEN);
 
+  tt_int_op(0, OP_EQ, ed25519_sign(&sig1, (const uint8_t*)"ABC", 3, &kp));
+  tt_int_op(0, OP_EQ, ed25519_signature_to_base64(buf, &sig1));
+  tt_int_op(0, OP_EQ, ed25519_signature_from_base64(&sig2, buf));
+  tt_mem_op(sig1.sig, OP_EQ, sig2.sig, ED25519_SIG_LEN);
+
   /* Test known value. */
   tt_int_op(0, OP_EQ, ed25519_public_from_base64(&pk,
                              "lVIuIctLjbGZGU5wKMNXxXlSE3cW4kaqkqm04u6pxvM"));

+ 121 - 12
src/test/test_dir.c

@@ -14,15 +14,18 @@
 #define NETWORKSTATUS_PRIVATE
 #include "or.h"
 #include "config.h"
+#include "crypto_ed25519.h"
 #include "directory.h"
 #include "dirserv.h"
 #include "dirvote.h"
 #include "hibernate.h"
 #include "networkstatus.h"
 #include "router.h"
+#include "routerkeys.h"
 #include "routerlist.h"
 #include "routerparse.h"
 #include "test.h"
+#include "torcert.h"
 
 static void
 test_dir_nicknames(void *arg)
@@ -87,8 +90,11 @@ test_dir_formats(void *arg)
   routerinfo_t *rp1 = NULL, *rp2 = NULL;
   addr_policy_t *ex1, *ex2;
   routerlist_t *dir1 = NULL, *dir2 = NULL;
+  uint8_t *rsa_cc = NULL;
+  tor_cert_t *ntor_cc = NULL;
   or_options_t *options = get_options_mutable();
   const addr_policy_t *p;
+  time_t now = time(NULL);
 
   (void)arg;
   pk1 = pk_generate(0);
@@ -127,14 +133,33 @@ test_dir_formats(void *arg)
   ex2->prt_min = ex2->prt_max = 24;
   r2 = tor_malloc_zero(sizeof(routerinfo_t));
   r2->addr = 0x0a030201u; /* 10.3.2.1 */
+  ed25519_keypair_t kp1, kp2;
+  ed25519_secret_key_from_seed(&kp1.seckey,
+                          (const uint8_t*)"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY");
+  ed25519_public_key_generate(&kp1.pubkey, &kp1.seckey);
+  ed25519_secret_key_from_seed(&kp2.seckey,
+                          (const uint8_t*)"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
+  ed25519_public_key_generate(&kp2.pubkey, &kp2.seckey);
+  r2->signing_key_cert = tor_cert_create(&kp1,
+                                         CERT_TYPE_ID_SIGNING,
+                                         &kp2.pubkey,
+                                         now, 86400,
+                                         CERT_FLAG_INCLUDE_SIGNING_KEY);
+  char cert_buf[256];
+  base64_encode(cert_buf, sizeof(cert_buf),
+                (const char*)r2->signing_key_cert->encoded,
+                r2->signing_key_cert->encoded_len,
+                BASE64_ENCODE_MULTILINE);
   r2->platform = tor_strdup(platform);
   r2->cache_info.published_on = 5;
   r2->or_port = 9005;
   r2->dir_port = 0;
   r2->onion_pkey = crypto_pk_dup_key(pk2);
   r2->onion_curve25519_pkey = tor_malloc_zero(sizeof(curve25519_public_key_t));
-  curve25519_public_from_base64(r2->onion_curve25519_pkey,
-                                "skyinAnvardNostarsNomoonNowindormistsorsnow");
+  curve25519_keypair_t r2_onion_keypair;
+  curve25519_keypair_generate(&r2_onion_keypair, 0);
+  r2->onion_curve25519_pkey = tor_memdup(&r2_onion_keypair.pubkey,
+                                         sizeof(curve25519_public_key_t));
   r2->identity_pkey = crypto_pk_dup_key(pk1);
   r2->bandwidthrate = r2->bandwidthburst = r2->bandwidthcapacity = 3000;
   r2->exit_policy = smartlist_new();
@@ -150,7 +175,7 @@ test_dir_formats(void *arg)
   /* XXXX025 router_dump_to_string should really take this from ri.*/
   options->ContactInfo = tor_strdup("Magri White "
                                     "<magri@elsewhere.example.com>");
-  buf = router_dump_router_to_string(r1, pk2);
+  buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL);
   tor_free(options->ContactInfo);
   tt_assert(buf);
 
@@ -183,7 +208,7 @@ test_dir_formats(void *arg)
   tt_str_op(buf,OP_EQ, buf2);
   tor_free(buf);
 
-  buf = router_dump_router_to_string(r1, pk2);
+  buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL);
   tt_assert(buf);
   cp = buf;
   rp1 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL);
@@ -201,6 +226,10 @@ test_dir_formats(void *arg)
 
   strlcpy(buf2,
           "router Fred 10.3.2.1 9005 0 0\n"
+          "identity-ed25519\n"
+          "-----BEGIN ED25519 CERT-----\n", sizeof(buf2));
+  strlcat(buf2, cert_buf, sizeof(buf2));
+  strlcat(buf2, "-----END ED25519 CERT-----\n"
           "platform Tor "VERSION" on ", sizeof(buf2));
   strlcat(buf2, get_uname(), sizeof(buf2));
   strlcat(buf2, "\n"
@@ -215,19 +244,52 @@ test_dir_formats(void *arg)
   strlcat(buf2, pk2_str, sizeof(buf2));
   strlcat(buf2, "signing-key\n", sizeof(buf2));
   strlcat(buf2, pk1_str, sizeof(buf2));
+  int rsa_cc_len;
+  rsa_cc = make_tap_onion_key_crosscert(pk2,
+                                        &kp1.pubkey,
+                                        pk1,
+                                        &rsa_cc_len);
+  tt_assert(rsa_cc);
+  base64_encode(cert_buf, sizeof(cert_buf), (char*)rsa_cc, rsa_cc_len,
+                BASE64_ENCODE_MULTILINE);
+  strlcat(buf2, "onion-key-crosscert\n"
+          "-----BEGIN CROSSCERT-----\n", sizeof(buf2));
+  strlcat(buf2, cert_buf, sizeof(buf2));
+  strlcat(buf2, "-----END CROSSCERT-----\n", sizeof(buf2));
+  int ntor_cc_sign;
+  ntor_cc = make_ntor_onion_key_crosscert(&r2_onion_keypair,
+                                          &kp1.pubkey,
+                                          r2->cache_info.published_on,
+                                          MIN_ONION_KEY_LIFETIME,
+                                          &ntor_cc_sign);
+  tt_assert(ntor_cc);
+  base64_encode(cert_buf, sizeof(cert_buf),
+                (char*)ntor_cc->encoded, ntor_cc->encoded_len,
+                BASE64_ENCODE_MULTILINE);
+  tor_snprintf(buf2+strlen(buf2), sizeof(buf2)-strlen(buf2),
+               "ntor-onion-key-crosscert %d\n"
+               "-----BEGIN ED25519 CERT-----\n"
+               "%s"
+               "-----END ED25519 CERT-----\n", ntor_cc_sign, cert_buf);
+
   strlcat(buf2, "hidden-service-dir\n", sizeof(buf2));
-  strlcat(buf2, "ntor-onion-key "
-          "skyinAnvardNostarsNomoonNowindormistsorsnow=\n", sizeof(buf2));
+  strlcat(buf2, "ntor-onion-key ", sizeof(buf2));
+  base64_encode(cert_buf, sizeof(cert_buf),
+                (const char*)r2_onion_keypair.pubkey.public_key, 32,
+                BASE64_ENCODE_MULTILINE);
+  strlcat(buf2, cert_buf, sizeof(buf2));
   strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2));
-  strlcat(buf2, "router-signature\n", sizeof(buf2));
+  strlcat(buf2, "router-sig-ed25519 ", sizeof(buf2));
 
-  buf = router_dump_router_to_string(r2, pk1);
+  buf = router_dump_router_to_string(r2, pk1, pk2, &r2_onion_keypair, &kp2);
+  tt_assert(buf);
   buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same
                              * twice */
-  tt_str_op(buf,OP_EQ, buf2);
+
+  tt_str_op(buf, OP_EQ, buf2);
   tor_free(buf);
 
-  buf = router_dump_router_to_string(r2, pk1);
+  buf = router_dump_router_to_string(r2, pk1, NULL, NULL, NULL);
   cp = buf;
   rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL);
   tt_assert(rp2);
@@ -280,6 +342,7 @@ test_dir_formats(void *arg)
   if (rp2)
     routerinfo_free(rp2);
 
+  tor_free(rsa_cc);
   tor_free(buf);
   tor_free(pk1_str);
   tor_free(pk2_str);
@@ -293,7 +356,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;
 
@@ -318,6 +381,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,
@@ -376,8 +441,28 @@ test_dir_routerparse_bad(void *arg)
   CHECK_FAIL(EX_RI_BAD_FAMILY, 0);
   CHECK_FAIL(EX_RI_ZERO_ORPORT, 0);
 
+  CHECK_FAIL(EX_RI_ED_MISSING_CROSSCERT, 0);
+  CHECK_FAIL(EX_RI_ED_MISSING_CROSSCERT2, 0);
+  CHECK_FAIL(EX_RI_ED_MISSING_CROSSCERT_SIGN, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_SIG1, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_SIG2, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_SIG3, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_SIG4, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CROSSCERT1, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CROSSCERT3, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CROSSCERT4, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CROSSCERT5, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CROSSCERT6, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CROSSCERT7, 0);
+  CHECK_FAIL(EX_RI_ED_MISPLACED1, 0);
+  CHECK_FAIL(EX_RI_ED_MISPLACED2, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CERT1, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CERT2, 0);
+  CHECK_FAIL(EX_RI_ED_BAD_CERT3, 0);
+
   /* This is allowed; we just ignore it. */
   CHECK_OK(EX_RI_BAD_EI_DIGEST);
+  CHECK_OK(EX_RI_BAD_EI_DIGEST2);
 
 #undef CHECK_FAIL
 #undef CHECK_OK
@@ -433,20 +518,34 @@ test_dir_extrainfo_parsing(void *arg)
   tt_assert(ei->pending_sig);
   CHECK_OK(EX_EI_MAXIMAL);
   tt_assert(ei->pending_sig);
+  CHECK_OK(EX_EI_GOOD_ED_EI);
+  tt_assert(ei->pending_sig);
 
   map = (struct digest_ri_map_t *)digestmap_new();
   ADD(EX_EI_MINIMAL);
   ADD(EX_EI_MAXIMAL);
+  ADD(EX_EI_GOOD_ED_EI);
   ADD(EX_EI_BAD_FP);
   ADD(EX_EI_BAD_NICKNAME);
   ADD(EX_EI_BAD_TOKENS);
   ADD(EX_EI_BAD_START);
   ADD(EX_EI_BAD_PUBLISHED);
 
+  ADD(EX_EI_ED_MISSING_SIG);
+  ADD(EX_EI_ED_MISSING_CERT);
+  ADD(EX_EI_ED_BAD_CERT1);
+  ADD(EX_EI_ED_BAD_CERT2);
+  ADD(EX_EI_ED_BAD_SIG1);
+  ADD(EX_EI_ED_BAD_SIG2);
+  ADD(EX_EI_ED_MISPLACED_CERT);
+  ADD(EX_EI_ED_MISPLACED_SIG);
+
   CHECK_OK(EX_EI_MINIMAL);
   tt_assert(!ei->pending_sig);
   CHECK_OK(EX_EI_MAXIMAL);
   tt_assert(!ei->pending_sig);
+  CHECK_OK(EX_EI_GOOD_ED_EI);
+  tt_assert(!ei->pending_sig);
 
   CHECK_FAIL(EX_EI_BAD_SIG1,1);
   CHECK_FAIL(EX_EI_BAD_SIG2,1);
@@ -457,6 +556,15 @@ test_dir_extrainfo_parsing(void *arg)
   CHECK_FAIL(EX_EI_BAD_START,0);
   CHECK_FAIL(EX_EI_BAD_PUBLISHED,0);
 
+  CHECK_FAIL(EX_EI_ED_MISSING_SIG,0);
+  CHECK_FAIL(EX_EI_ED_MISSING_CERT,0);
+  CHECK_FAIL(EX_EI_ED_BAD_CERT1,0);
+  CHECK_FAIL(EX_EI_ED_BAD_CERT2,0);
+  CHECK_FAIL(EX_EI_ED_BAD_SIG1,0);
+  CHECK_FAIL(EX_EI_ED_BAD_SIG2,0);
+  CHECK_FAIL(EX_EI_ED_MISPLACED_CERT,0);
+  CHECK_FAIL(EX_EI_ED_MISPLACED_SIG,0);
+
 #undef CHECK_OK
 #undef CHECK_FAIL
 
@@ -1394,6 +1502,7 @@ generate_ri_from_rs(const vote_routerstatus_t *vrs)
   static time_t published = 0;
 
   r = tor_malloc_zero(sizeof(routerinfo_t));
+  r->cert_expiration_time = TIME_MAX;
   memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN);
   memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest,
          DIGEST_LEN);
@@ -3108,7 +3217,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),

+ 255 - 0
src/test/test_keypin.c

@@ -0,0 +1,255 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#define KEYPIN_PRIVATE
+#include "or.h"
+#include "keypin.h"
+#include "util.h"
+
+#include "test.h"
+
+static void
+test_keypin_parse_line(void *arg)
+{
+  (void)arg;
+  keypin_ent_t *ent = NULL;
+
+  /* Good line */
+  ent = keypin_parse_journal_line(
+                "aGVyZSBpcyBhIGdvb2Qgc2hhMSE "
+                "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4");
+  tt_assert(ent);
+  tt_mem_op(ent->rsa_id, ==, "here is a good sha1!", 20);
+  tt_mem_op(ent->ed25519_key, ==, "This ed25519 scoffs at the sha1.", 32);
+  tor_free(ent); ent = NULL;
+
+  /* Good line with extra stuff we will ignore. */
+  ent = keypin_parse_journal_line(
+                "aGVyZSBpcyBhIGdvb2Qgc2hhMSE "
+                "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4helloworld");
+  tt_assert(ent);
+  tt_mem_op(ent->rsa_id, ==, "here is a good sha1!", 20);
+  tt_mem_op(ent->ed25519_key, ==, "This ed25519 scoffs at the sha1.", 32);
+  tor_free(ent); ent = NULL;
+
+  /* Bad line: no space in the middle. */
+  ent = keypin_parse_journal_line(
+                "aGVyZSBpcyBhIGdvb2Qgc2hhMSE?"
+                "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4");
+  tt_assert(! ent);
+
+  /* Bad line: bad base64 in RSA ID */
+  ent = keypin_parse_journal_line(
+                "aGVyZSBpcyBhIGdv!2Qgc2hhMSE "
+                "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4");
+  tt_assert(! ent);
+
+  /* Bad line: bad base64 in Ed25519 */
+  ent = keypin_parse_journal_line(
+                "aGVyZSBpcyBhIGdvb2Qgc2hhMSE "
+                "VGhpcyBlZDI1NTE5IHNjb2ZmcyB!dCB0aGUgc2hhMS4");
+  tt_assert(! ent);
+
+ done:
+  tor_free(ent);
+}
+
+static smartlist_t *mock_addent_got = NULL;
+static void
+mock_addent(keypin_ent_t *ent)
+{
+  smartlist_add(mock_addent_got, ent);
+  keypin_add_entry_to_map__real(ent);
+}
+
+static void
+test_keypin_parse_file(void *arg)
+{
+  (void)arg;
+
+  mock_addent_got = smartlist_new();
+  MOCK(keypin_add_entry_to_map, mock_addent);
+
+  /* Simple, minimal, correct example. */
+  const char data1[] =
+"PT09PT09PT09PT09PT09PT09PT0 PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0\n"
+"TG9yYXggaXBzdW0gZ3J1dnZ1bHU cyB0aG5lZWQgYW1ldCwgc25lcmdlbGx5IG9uY2UtbGU\n"
+"ciBsZXJraW0sIHNlZCBkbyBiYXI YmFsb290IHRlbXBvciBnbHVwcGl0dXMgdXQgbGFib3I\n"
+"ZSBldCB0cnVmZnVsYSBtYWduYSA YWxpcXVhLiBVdCBlbmltIGFkIGdyaWNrbGUtZ3Jhc3M\n"
+"dmVuaWFtLCBxdWlzIG1pZmYtbXU ZmZlcmVkIGdhLXp1bXBjbyBsYWJvcmlzIG5pc2kgdXQ\n"
+"Y3J1ZmZ1bHVzIGV4IGVhIHNjaGw b3BwaXR5IGNvbnNlcXVhdC4gRHVpcyBhdXRlIHNuYXI\n"
+"Z2dsZSBpbiBzd29tZWVzd2FucyA aW4gdm9sdXB0YXRlIGF4ZS1oYWNrZXIgZXNzZSByaXA\n"
+"cHVsdXMgY3J1bW1paSBldSBtb28 ZiBudWxsYSBzbnV2di5QTFVHSFBMT1ZFUlhZWlpZLi4\n";
+
+  tt_int_op(0, ==, keypin_load_journal_impl(data1, strlen(data1)));
+  tt_int_op(8, ==, smartlist_len(mock_addent_got));
+  keypin_ent_t *ent = smartlist_get(mock_addent_got, 2);
+  tt_mem_op(ent->rsa_id, ==, "r lerkim, sed do bar", 20);
+  tt_mem_op(ent->ed25519_key, ==, "baloot tempor gluppitus ut labor", 32);
+
+  /* More complex example: weird lines, bogus lines,
+     duplicate/conflicting lines */
+  const char data2[] =
+    "PT09PT09PT09PT09PT09PT09PT0 PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0\n"
+    "# This is a comment.\n"
+    "     \n"
+    "QXQgdGhlIGVuZCBvZiB0aGUgeWU YXIgS3VycmVta2FybWVycnVrIHNhaWQgdG8gaGltLCA\n"
+    "IllvdSBoYXZlIG1hZGUgYSBnb28 ZCBiZWdpbm5pbmcuIiBCdXQgbm8gbW9yZS4gV2l6YXI\n"
+    "\n"
+    "ZHMgc3BlYWsgdHJ1dGgsIGFuZCA aXQgd2FzIHRydWUgdGhhdCBhbGwgdGhlIG1hc3Rlcgo\n"
+    "@reserved for a future extension \n"
+    "eSBvZiBOYW1lcyB0aGF0IEdlZCA aGFkIHRvaWxlZCbyB3aW4gdGhhdCB5ZWFyIHdhcyA\n"
+    "eSBvZiBOYW1lcyB0aGF0IEdlZCA aGFkIHRvaWxlZCbyB3aW4gdGhhdCB5ZWFyIHdhcy"
+              "A line too long\n"
+    "dGhlIG1lcmUgc3RhcnQgb2Ygd2g YXQgaGUgbXVzdCBnbyBvb!BsZWFybmluZy4uLi4uLi4\n"
+    "ZHMgc3BlYWsgdaJ1dGgsIGFuZCA aXQgd2FzIHRydWUgdGhhdCBhbGwgdGhlIG1hc3Rlcgo\n"
+    "ZHMgc3BlYWsgdHJ1dGgsIGFuZCA aXQgd2FzIHRydaUgdGhhdCBhbGwgdGhlIG1hc3Rlcgo\n"
+    ;
+
+  tt_int_op(0, ==, keypin_load_journal_impl(data2, strlen(data2)));
+  tt_int_op(11, ==, smartlist_len(mock_addent_got));
+  ent = smartlist_get(mock_addent_got, 9);
+  tt_mem_op(ent->rsa_id, ==, "\"You have made a goo", 20);
+  tt_mem_op(ent->ed25519_key, ==, "d beginning.\" But no more. Wizar", 32);
+
+  ent = smartlist_get(mock_addent_got, 10);
+  tt_mem_op(ent->rsa_id, ==, "ds speak truth, and ", 20);
+  tt_mem_op(ent->ed25519_key, ==, "it was true that all the master\n", 32);
+
+  /* File truncated before NL */
+  const char data3[] =
+    "Tm8gZHJhZ29uIGNhbiByZXNpc3Q IHRoZSBmYXNjaW5hdGlvbiBvZiByaWRkbGluZyB0YWw";
+  tt_int_op(0, ==, keypin_load_journal_impl(data3, strlen(data3)));
+  tt_int_op(12, ==, smartlist_len(mock_addent_got));
+  ent = smartlist_get(mock_addent_got, 11);
+  tt_mem_op(ent->rsa_id, ==, "No dragon can resist", 20);
+  tt_mem_op(ent->ed25519_key, ==, " the fascination of riddling tal", 32);
+
+ done:
+  keypin_clear();
+  smartlist_free(mock_addent_got);
+}
+
+#define ADD(a,b) keypin_check_and_add((const uint8_t*)(a),(const uint8_t*)(b))
+#define LONE_RSA(a) keypin_check_lone_rsa((const uint8_t*)(a))
+
+static void
+test_keypin_add_entry(void *arg)
+{
+  (void)arg;
+  keypin_clear();
+
+  tt_int_op(KEYPIN_ADDED, ==, ADD("ambassadors-at-large",
+                                  "bread-and-butter thing-in-itself"));
+  tt_int_op(KEYPIN_ADDED, ==, ADD("gentleman-adventurer",
+                                  "cloak-and-dagger what's-his-face"));
+
+  tt_int_op(KEYPIN_FOUND, ==, ADD("ambassadors-at-large",
+                                  "bread-and-butter thing-in-itself"));
+  tt_int_op(KEYPIN_FOUND, ==, ADD("ambassadors-at-large",
+                                  "bread-and-butter thing-in-itself"));
+  tt_int_op(KEYPIN_FOUND, ==, ADD("gentleman-adventurer",
+                                  "cloak-and-dagger what's-his-face"));
+
+  tt_int_op(KEYPIN_ADDED, ==, ADD("Johnnies-come-lately",
+                                  "run-of-the-mill root-mean-square"));
+
+  tt_int_op(KEYPIN_MISMATCH, ==, ADD("gentleman-adventurer",
+                                     "hypersentimental closefistedness"));
+
+  tt_int_op(KEYPIN_MISMATCH, ==, ADD("disestablismentarian",
+                                     "cloak-and-dagger what's-his-face"));
+
+  tt_int_op(KEYPIN_FOUND, ==, ADD("gentleman-adventurer",
+                                  "cloak-and-dagger what's-his-face"));
+
+  tt_int_op(KEYPIN_NOT_FOUND, ==, LONE_RSA("Llanfairpwllgwyngyll"));
+  tt_int_op(KEYPIN_MISMATCH, ==, LONE_RSA("Johnnies-come-lately"));
+
+ done:
+  keypin_clear();
+}
+
+static void
+test_keypin_journal(void *arg)
+{
+  (void)arg;
+  char *contents = NULL;
+  const char *fname = get_fname("keypin-journal");
+
+  tt_int_op(0, ==, keypin_load_journal(fname)); /* ENOENT is okay */
+  update_approx_time(1217709000);
+  tt_int_op(0, ==, keypin_open_journal(fname));
+
+  tt_int_op(KEYPIN_ADDED, ==, ADD("king-of-the-herrings",
+                                  "good-for-nothing attorney-at-law"));
+  tt_int_op(KEYPIN_ADDED, ==, ADD("yellowish-red-yellow",
+                                  "salt-and-pepper high-muck-a-muck"));
+  tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow",
+                                  "salt-and-pepper high-muck-a-muck"));
+  keypin_close_journal();
+  keypin_clear();
+
+  tt_int_op(0, ==, keypin_load_journal(fname));
+  update_approx_time(1231041600);
+  tt_int_op(0, ==, keypin_open_journal(fname));
+  tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow",
+                                  "salt-and-pepper high-muck-a-muck"));
+  tt_int_op(KEYPIN_ADDED, ==, ADD("theatre-in-the-round",
+                                  "holier-than-thou jack-in-the-box"));
+  tt_int_op(KEYPIN_ADDED, ==, ADD("no-deposit-no-return",
+                                  "across-the-board will-o-the-wisp"));
+  tt_int_op(KEYPIN_MISMATCH, ==, ADD("intellectualizations",
+                                     "salt-and-pepper high-muck-a-muck"));
+  keypin_close_journal();
+  keypin_clear();
+
+  tt_int_op(0, ==, keypin_load_journal(fname));
+  update_approx_time(1412278354);
+  tt_int_op(0, ==, keypin_open_journal(fname));
+  tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow",
+                                  "salt-and-pepper high-muck-a-muck"));
+  tt_int_op(KEYPIN_MISMATCH, ==, ADD("intellectualizations",
+                                     "salt-and-pepper high-muck-a-muck"));
+  tt_int_op(KEYPIN_FOUND, ==, ADD("theatre-in-the-round",
+                                  "holier-than-thou jack-in-the-box"));
+  tt_int_op(KEYPIN_MISMATCH, ==, ADD("counterrevolutionary",
+                                     "holier-than-thou jack-in-the-box"));
+  tt_int_op(KEYPIN_MISMATCH, ==, ADD("no-deposit-no-return",
+                                     "floccinaucinihilipilificationism"));
+  keypin_close_journal();
+
+  contents = read_file_to_str(fname, RFTS_BIN, NULL);
+  tt_assert(contents);
+  tt_str_op(contents,==,
+    "\n"
+    "@opened-at 2008-08-02 20:30:00\n"
+    "a2luZy1vZi10aGUtaGVycmluZ3M Z29vZC1mb3Itbm90aGluZyBhdHRvcm5leS1hdC1sYXc\n"
+    "eWVsbG93aXNoLXJlZC15ZWxsb3c c2FsdC1hbmQtcGVwcGVyIGhpZ2gtbXVjay1hLW11Y2s\n"
+    "\n"
+    "@opened-at 2009-01-04 04:00:00\n"
+    "dGhlYXRyZS1pbi10aGUtcm91bmQ aG9saWVyLXRoYW4tdGhvdSBqYWNrLWluLXRoZS1ib3g\n"
+    "bm8tZGVwb3NpdC1uby1yZXR1cm4 YWNyb3NzLXRoZS1ib2FyZCB3aWxsLW8tdGhlLXdpc3A\n"
+    "\n"
+    "@opened-at 2014-10-02 19:32:34\n");
+
+ done:
+  tor_free(contents);
+  keypin_clear();
+}
+
+#undef ADD
+#undef LONE_RSA
+
+#define TEST(name, flags)                                       \
+  { #name , test_keypin_ ## name, (flags), NULL, NULL }
+
+struct testcase_t keypin_tests[] = {
+  TEST( parse_line, 0 ),
+  TEST( parse_file, TT_FORK ),
+  TEST( add_entry, TT_FORK ),
+  TEST( journal, TT_FORK ),
+  END_OF_TESTCASES
+};
+

+ 914 - 0
src/test/test_link_handshake.c

@@ -0,0 +1,914 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+
+#define CHANNELTLS_PRIVATE
+#define CONNECTION_PRIVATE
+#define TOR_CHANNEL_INTERNAL_
+#include "or.h"
+#include "config.h"
+#include "connection.h"
+#include "connection_or.h"
+#include "channeltls.h"
+#include "link_handshake.h"
+#include "scheduler.h"
+
+#include "test.h"
+
+var_cell_t *mock_got_var_cell = NULL;
+
+static void
+mock_write_var_cell(const var_cell_t *vc, or_connection_t *conn)
+{
+  (void)conn;
+
+  var_cell_t *newcell = var_cell_new(vc->payload_len);
+  memcpy(newcell, vc, sizeof(var_cell_t));
+  memcpy(newcell->payload, vc->payload, vc->payload_len);
+
+  mock_got_var_cell = newcell;
+}
+static int
+mock_tls_cert_matches_key(const tor_tls_t *tls, const tor_x509_cert_t *cert)
+{
+  (void) tls;
+  (void) cert; // XXXX look at this.
+  return 1;
+}
+
+static int mock_send_netinfo_called = 0;
+static int
+mock_send_netinfo(or_connection_t *conn)
+{
+  (void) conn;
+  ++mock_send_netinfo_called;// XXX check_this
+  return 0;
+}
+
+static int mock_close_called = 0;
+static void
+mock_close_for_err(or_connection_t *orconn, int flush)
+{
+  (void)orconn;
+  (void)flush;
+  ++mock_close_called;
+}
+
+static int mock_send_authenticate_called = 0;
+static int
+mock_send_authenticate(or_connection_t *conn, int type)
+{
+  (void) conn;
+  (void) type;
+  ++mock_send_authenticate_called;// XXX check_this
+  return 0;
+}
+
+/* Test good certs cells */
+static void
+test_link_handshake_certs_ok(void *arg)
+{
+  (void) arg;
+
+  or_connection_t *c1 = or_connection_new(CONN_TYPE_OR, AF_INET);
+  or_connection_t *c2 = or_connection_new(CONN_TYPE_OR, AF_INET);
+  var_cell_t *cell1 = NULL, *cell2 = NULL;
+  certs_cell_t *cc1 = NULL, *cc2 = NULL;
+  channel_tls_t *chan1 = NULL, *chan2 = NULL;
+  crypto_pk_t *key1 = NULL, *key2 = NULL;
+
+  scheduler_init();
+
+  MOCK(tor_tls_cert_matches_key, mock_tls_cert_matches_key);
+  MOCK(connection_or_write_var_cell_to_buf, mock_write_var_cell);
+  MOCK(connection_or_send_netinfo, mock_send_netinfo);
+
+  key1 = pk_generate(2);
+  key2 = pk_generate(3);
+
+  /* We need to make sure that our TLS certificates are set up before we can
+   * actually generate a CERTS cell.
+   */
+  tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER,
+                                 key1, key2, 86400), ==, 0);
+
+  c1->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3;
+  c1->link_proto = 3;
+  tt_int_op(connection_init_or_handshake_state(c1, 1), ==, 0);
+
+  c2->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3;
+  c2->link_proto = 3;
+  tt_int_op(connection_init_or_handshake_state(c2, 0), ==, 0);
+
+  tt_int_op(0, ==, connection_or_send_certs_cell(c1));
+  tt_assert(mock_got_var_cell);
+  cell1 = mock_got_var_cell;
+
+  tt_int_op(0, ==, connection_or_send_certs_cell(c2));
+  tt_assert(mock_got_var_cell);
+  cell2 = mock_got_var_cell;
+
+  tt_int_op(cell1->command, ==, CELL_CERTS);
+  tt_int_op(cell1->payload_len, >, 1);
+
+  tt_int_op(cell2->command, ==, CELL_CERTS);
+  tt_int_op(cell2->payload_len, >, 1);
+
+  tt_int_op(cell1->payload_len, ==,
+            certs_cell_parse(&cc1, cell1->payload, cell1->payload_len));
+  tt_int_op(cell2->payload_len, ==,
+            certs_cell_parse(&cc2, cell2->payload, cell2->payload_len));
+
+  tt_int_op(2, ==, cc1->n_certs);
+  tt_int_op(2, ==, cc2->n_certs);
+
+  tt_int_op(certs_cell_get_certs(cc1, 0)->cert_type, ==,
+            CERTTYPE_RSA1024_ID_AUTH);
+  tt_int_op(certs_cell_get_certs(cc1, 1)->cert_type, ==,
+            CERTTYPE_RSA1024_ID_ID);
+
+  tt_int_op(certs_cell_get_certs(cc2, 0)->cert_type, ==,
+            CERTTYPE_RSA1024_ID_LINK);
+  tt_int_op(certs_cell_get_certs(cc2, 1)->cert_type, ==,
+            CERTTYPE_RSA1024_ID_ID);
+
+  chan1 = tor_malloc_zero(sizeof(*chan1));
+  channel_tls_common_init(chan1);
+  c1->chan = chan1;
+  chan1->conn = c1;
+  c1->base_.address = tor_strdup("C1");
+  c1->tls = tor_tls_new(-1, 0);
+  c1->link_proto = 4;
+  c1->base_.conn_array_index = -1;
+  crypto_pk_get_digest(key2, c1->identity_digest);
+
+  channel_tls_process_certs_cell(cell2, chan1);
+
+  tt_assert(c1->handshake_state->received_certs_cell);
+  tt_assert(c1->handshake_state->auth_cert == NULL);
+  tt_assert(c1->handshake_state->id_cert);
+  tt_assert(! tor_mem_is_zero(
+                  (char*)c1->handshake_state->authenticated_peer_id, 20));
+
+  chan2 = tor_malloc_zero(sizeof(*chan2));
+  channel_tls_common_init(chan2);
+  c2->chan = chan2;
+  chan2->conn = c2;
+  c2->base_.address = tor_strdup("C2");
+  c2->tls = tor_tls_new(-1, 1);
+  c2->link_proto = 4;
+  c2->base_.conn_array_index = -1;
+  crypto_pk_get_digest(key1, c2->identity_digest);
+
+  channel_tls_process_certs_cell(cell1, chan2);
+
+  tt_assert(c2->handshake_state->received_certs_cell);
+  tt_assert(c2->handshake_state->auth_cert);
+  tt_assert(c2->handshake_state->id_cert);
+  tt_assert(tor_mem_is_zero(
+                (char*)c2->handshake_state->authenticated_peer_id, 20));
+
+ done:
+  UNMOCK(tor_tls_cert_matches_key);
+  UNMOCK(connection_or_write_var_cell_to_buf);
+  UNMOCK(connection_or_send_netinfo);
+  connection_free_(TO_CONN(c1));
+  connection_free_(TO_CONN(c2));
+  tor_free(cell1);
+  tor_free(cell2);
+  certs_cell_free(cc1);
+  certs_cell_free(cc2);
+  circuitmux_free(chan1->base_.cmux);
+  tor_free(chan1);
+  circuitmux_free(chan2->base_.cmux);
+  tor_free(chan2);
+  crypto_pk_free(key1);
+  crypto_pk_free(key2);
+}
+
+typedef struct certs_data_s {
+  or_connection_t *c;
+  channel_tls_t *chan;
+  certs_cell_t *ccell;
+  var_cell_t *cell;
+  crypto_pk_t *key1, *key2;
+} certs_data_t;
+
+
+static int
+recv_certs_cleanup(const struct testcase_t *test, void *obj)
+{
+  (void)test;
+  certs_data_t *d = obj;
+  UNMOCK(tor_tls_cert_matches_key);
+  UNMOCK(connection_or_send_netinfo);
+  UNMOCK(connection_or_close_for_error);
+
+  if (d) {
+    tor_free(d->cell);
+    certs_cell_free(d->ccell);
+    connection_free_(TO_CONN(d->c));
+    circuitmux_free(d->chan->base_.cmux);
+    tor_free(d->chan);
+    crypto_pk_free(d->key1);
+    crypto_pk_free(d->key2);
+    tor_free(d);
+  }
+  return 1;
+}
+
+static void *
+recv_certs_setup(const struct testcase_t *test)
+{
+  (void)test;
+  certs_data_t *d = tor_malloc_zero(sizeof(*d));
+  certs_cell_cert_t *ccc1 = NULL;
+  certs_cell_cert_t *ccc2 = NULL;
+  ssize_t n;
+
+  d->c = or_connection_new(CONN_TYPE_OR, AF_INET);
+  d->chan = tor_malloc_zero(sizeof(*d->chan));
+  d->c->chan = d->chan;
+  d->c->base_.address = tor_strdup("HaveAnAddress");
+  d->c->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3;
+  d->chan->conn = d->c;
+  tt_int_op(connection_init_or_handshake_state(d->c, 1), ==, 0);
+  d->c->link_proto = 4;
+
+  d->key1 = pk_generate(2);
+  d->key2 = pk_generate(3);
+
+  tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER,
+                                 d->key1, d->key2, 86400), ==, 0);
+  d->ccell = certs_cell_new();
+  ccc1 = certs_cell_cert_new();
+  certs_cell_add_certs(d->ccell, ccc1);
+  ccc2 = certs_cell_cert_new();
+  certs_cell_add_certs(d->ccell, ccc2);
+  d->ccell->n_certs = 2;
+  ccc1->cert_type = 1;
+  ccc2->cert_type = 2;
+
+  const tor_x509_cert_t *a,*b;
+  const uint8_t *enca, *encb;
+  size_t lena, lenb;
+  tor_tls_get_my_certs(1, &a, &b);
+  tor_x509_cert_get_der(a, &enca, &lena);
+  tor_x509_cert_get_der(b, &encb, &lenb);
+  certs_cell_cert_setlen_body(ccc1, lena);
+  ccc1->cert_len = lena;
+  certs_cell_cert_setlen_body(ccc2, lenb);
+  ccc2->cert_len = lenb;
+
+  memcpy(certs_cell_cert_getarray_body(ccc1), enca, lena);
+  memcpy(certs_cell_cert_getarray_body(ccc2), encb, lenb);
+
+  d->cell = var_cell_new(4096);
+  d->cell->command = CELL_CERTS;
+
+  n = certs_cell_encode(d->cell->payload, 4096, d->ccell);
+  tt_int_op(n, >, 0);
+  d->cell->payload_len = n;
+
+  MOCK(tor_tls_cert_matches_key, mock_tls_cert_matches_key);
+  MOCK(connection_or_send_netinfo, mock_send_netinfo);
+  MOCK(connection_or_close_for_error, mock_close_for_err);
+
+  tt_int_op(0, ==, d->c->handshake_state->received_certs_cell);
+  tt_int_op(0, ==, mock_send_authenticate_called);
+  tt_int_op(0, ==, mock_send_netinfo_called);
+
+  return d;
+ done:
+  recv_certs_cleanup(test, d);
+  return NULL;
+}
+
+static struct testcase_setup_t setup_recv_certs = {
+  .setup_fn = recv_certs_setup,
+  .cleanup_fn = recv_certs_cleanup
+};
+
+static void
+test_link_handshake_recv_certs_ok(void *arg)
+{
+  certs_data_t *d = arg;
+  channel_tls_process_certs_cell(d->cell, d->chan);
+  tt_int_op(0, ==, mock_close_called);
+  tt_int_op(d->c->handshake_state->authenticated, ==, 1);
+  tt_int_op(d->c->handshake_state->received_certs_cell, ==, 1);
+  tt_assert(d->c->handshake_state->id_cert != NULL);
+  tt_assert(d->c->handshake_state->auth_cert == NULL);
+
+ done:
+  ;
+}
+
+static void
+test_link_handshake_recv_certs_ok_server(void *arg)
+{
+  certs_data_t *d = arg;
+  d->c->handshake_state->started_here = 0;
+  certs_cell_get_certs(d->ccell, 0)->cert_type = 3;
+  certs_cell_get_certs(d->ccell, 1)->cert_type = 2;
+  ssize_t n = certs_cell_encode(d->cell->payload, 2048, d->ccell);
+  tt_int_op(n, >, 0);
+  d->cell->payload_len = n;
+  channel_tls_process_certs_cell(d->cell, d->chan);
+  tt_int_op(0, ==, mock_close_called);
+  tt_int_op(d->c->handshake_state->authenticated, ==, 0);
+  tt_int_op(d->c->handshake_state->received_certs_cell, ==, 1);
+  tt_assert(d->c->handshake_state->id_cert != NULL);
+  tt_assert(d->c->handshake_state->auth_cert != NULL);
+
+ done:
+  ;
+}
+
+#define CERTS_FAIL(name, code)                          \
+  static void                                                           \
+  test_link_handshake_recv_certs_ ## name (void *arg)                   \
+  {                                                                     \
+    certs_data_t *d = arg;                                              \
+    { code ; }                                                          \
+    channel_tls_process_certs_cell(d->cell, d->chan);                   \
+    tt_int_op(1, ==, mock_close_called);                                \
+    tt_int_op(0, ==, mock_send_authenticate_called);                    \
+    tt_int_op(0, ==, mock_send_netinfo_called);                         \
+  done:                                                                 \
+    ;                                                                   \
+  }
+
+CERTS_FAIL(badstate, d->c->base_.state = OR_CONN_STATE_CONNECTING)
+CERTS_FAIL(badproto, d->c->link_proto = 2)
+CERTS_FAIL(duplicate, d->c->handshake_state->received_certs_cell = 1)
+CERTS_FAIL(already_authenticated,
+           d->c->handshake_state->authenticated = 1)
+CERTS_FAIL(empty, d->cell->payload_len = 0)
+CERTS_FAIL(bad_circid, d->cell->circ_id = 1)
+CERTS_FAIL(truncated_1, d->cell->payload[0] = 5)
+CERTS_FAIL(truncated_2, {
+           d->cell->payload_len = 4;
+           memcpy(d->cell->payload, "\x01\x01\x00\x05", 4);})
+CERTS_FAIL(truncated_3, {
+           d->cell->payload_len = 7;
+           memcpy(d->cell->payload, "\x01\x01\x00\x05""abc", 7);})
+#define REENCODE() do {                                                 \
+    ssize_t n = certs_cell_encode(d->cell->payload, 4096, d->ccell);    \
+    tt_int_op(n, >, 0);                                                 \
+    d->cell->payload_len = n;                                           \
+  } while (0)
+
+CERTS_FAIL(not_x509, {
+    certs_cell_cert_setlen_body(certs_cell_get_certs(d->ccell, 0), 3);
+    certs_cell_get_certs(d->ccell, 0)->cert_len = 3;
+    REENCODE();
+    })
+CERTS_FAIL(both_link, {
+    certs_cell_get_certs(d->ccell, 0)->cert_type = 1;
+    certs_cell_get_certs(d->ccell, 1)->cert_type = 1;
+    REENCODE();
+    })
+CERTS_FAIL(both_id_rsa, {
+    certs_cell_get_certs(d->ccell, 0)->cert_type = 2;
+    certs_cell_get_certs(d->ccell, 1)->cert_type = 2;
+    REENCODE();
+    })
+CERTS_FAIL(both_auth, {
+    certs_cell_get_certs(d->ccell, 0)->cert_type = 3;
+    certs_cell_get_certs(d->ccell, 1)->cert_type = 3;
+    REENCODE();
+    })
+CERTS_FAIL(wrong_labels_1, {
+    certs_cell_get_certs(d->ccell, 0)->cert_type = 2;
+    certs_cell_get_certs(d->ccell, 1)->cert_type = 1;
+    REENCODE();
+    })
+CERTS_FAIL(wrong_labels_2, {
+    const tor_x509_cert_t *a;
+    const tor_x509_cert_t *b;
+    const uint8_t *enca;
+    size_t lena;
+    tor_tls_get_my_certs(1, &a, &b);
+    tor_x509_cert_get_der(a, &enca, &lena);
+    certs_cell_cert_setlen_body(certs_cell_get_certs(d->ccell, 1), lena);
+    memcpy(certs_cell_cert_getarray_body(certs_cell_get_certs(d->ccell, 1)),
+           enca, lena);
+    certs_cell_get_certs(d->ccell, 1)->cert_len = lena;
+    REENCODE();
+  })
+CERTS_FAIL(wrong_labels_3, {
+    certs_cell_get_certs(d->ccell, 0)->cert_type = 2;
+    certs_cell_get_certs(d->ccell, 1)->cert_type = 3;
+    REENCODE();
+    })
+CERTS_FAIL(server_missing_certs, {
+  d->c->handshake_state->started_here = 0;
+  })
+CERTS_FAIL(server_wrong_labels_1, {
+  d->c->handshake_state->started_here = 0;
+  certs_cell_get_certs(d->ccell, 0)->cert_type = 2;
+  certs_cell_get_certs(d->ccell, 1)->cert_type = 3;
+  REENCODE();
+  })
+
+static void
+test_link_handshake_send_authchallenge(void *arg)
+{
+  (void)arg;
+
+  or_connection_t *c1 = or_connection_new(CONN_TYPE_OR, AF_INET);
+  var_cell_t *cell1=NULL, *cell2=NULL;
+
+  MOCK(connection_or_write_var_cell_to_buf, mock_write_var_cell);
+
+  tt_int_op(connection_init_or_handshake_state(c1, 0), ==, 0);
+  c1->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3;
+  tt_assert(! mock_got_var_cell);
+  tt_int_op(0, ==, connection_or_send_auth_challenge_cell(c1));
+  cell1 = mock_got_var_cell;
+  tt_int_op(0, ==, connection_or_send_auth_challenge_cell(c1));
+  cell2 = mock_got_var_cell;
+  tt_int_op(36, ==, cell1->payload_len);
+  tt_int_op(36, ==, cell2->payload_len);
+  tt_int_op(0, ==, cell1->circ_id);
+  tt_int_op(0, ==, cell2->circ_id);
+  tt_int_op(CELL_AUTH_CHALLENGE, ==, cell1->command);
+  tt_int_op(CELL_AUTH_CHALLENGE, ==, cell2->command);
+
+  tt_mem_op("\x00\x01\x00\x01", ==, cell1->payload + 32, 4);
+  tt_mem_op("\x00\x01\x00\x01", ==, cell2->payload + 32, 4);
+  tt_mem_op(cell1->payload, !=, cell2->payload, 32);
+
+ done:
+  UNMOCK(connection_or_write_var_cell_to_buf);
+  connection_free_(TO_CONN(c1));
+  tor_free(cell1);
+  tor_free(cell2);
+}
+
+typedef struct authchallenge_data_s {
+  or_connection_t *c;
+  channel_tls_t *chan;
+  var_cell_t *cell;
+} authchallenge_data_t;
+
+static int
+recv_authchallenge_cleanup(const struct testcase_t *test, void *obj)
+{
+  (void)test;
+  authchallenge_data_t *d = obj;
+
+  UNMOCK(connection_or_send_netinfo);
+  UNMOCK(connection_or_close_for_error);
+  UNMOCK(connection_or_send_authenticate_cell);
+
+  if (d) {
+    tor_free(d->cell);
+    connection_free_(TO_CONN(d->c));
+    circuitmux_free(d->chan->base_.cmux);
+    tor_free(d->chan);
+    tor_free(d);
+  }
+  return 1;
+}
+
+static void *
+recv_authchallenge_setup(const struct testcase_t *test)
+{
+  (void)test;
+  authchallenge_data_t *d = tor_malloc_zero(sizeof(*d));
+  d->c = or_connection_new(CONN_TYPE_OR, AF_INET);
+  d->chan = tor_malloc_zero(sizeof(*d->chan));
+  d->c->chan = d->chan;
+  d->c->base_.address = tor_strdup("HaveAnAddress");
+  d->c->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3;
+  d->chan->conn = d->c;
+  tt_int_op(connection_init_or_handshake_state(d->c, 1), ==, 0);
+  d->c->link_proto = 4;
+  d->c->handshake_state->received_certs_cell = 1;
+  d->cell = var_cell_new(128);
+  d->cell->payload_len = 38;
+  d->cell->payload[33] = 2;
+  d->cell->payload[35] = 7;
+  d->cell->payload[37] = 1;
+  d->cell->command = CELL_AUTH_CHALLENGE;
+
+  get_options_mutable()->ORPort_set = 1;
+
+  MOCK(connection_or_close_for_error, mock_close_for_err);
+  MOCK(connection_or_send_netinfo, mock_send_netinfo);
+  MOCK(connection_or_send_authenticate_cell, mock_send_authenticate);
+
+  tt_int_op(0, ==, d->c->handshake_state->received_auth_challenge);
+  tt_int_op(0, ==, mock_send_authenticate_called);
+  tt_int_op(0, ==, mock_send_netinfo_called);
+
+  return d;
+ done:
+  recv_authchallenge_cleanup(test, d);
+  return NULL;
+}
+
+static struct testcase_setup_t setup_recv_authchallenge = {
+  .setup_fn = recv_authchallenge_setup,
+  .cleanup_fn = recv_authchallenge_cleanup
+};
+
+static void
+test_link_handshake_recv_authchallenge_ok(void *arg)
+{
+  authchallenge_data_t *d = arg;
+
+  channel_tls_process_auth_challenge_cell(d->cell, d->chan);
+  tt_int_op(0, ==, mock_close_called);
+  tt_int_op(1, ==, d->c->handshake_state->received_auth_challenge);
+  tt_int_op(1, ==, mock_send_authenticate_called);
+  tt_int_op(1, ==, mock_send_netinfo_called);
+ done:
+  ;
+}
+
+static void
+test_link_handshake_recv_authchallenge_ok_noserver(void *arg)
+{
+  authchallenge_data_t *d = arg;
+  get_options_mutable()->ORPort_set = 0;
+
+  channel_tls_process_auth_challenge_cell(d->cell, d->chan);
+  tt_int_op(0, ==, mock_close_called);
+  tt_int_op(1, ==, d->c->handshake_state->received_auth_challenge);
+  tt_int_op(0, ==, mock_send_authenticate_called);
+  tt_int_op(0, ==, mock_send_netinfo_called);
+ done:
+  ;
+}
+
+static void
+test_link_handshake_recv_authchallenge_ok_unrecognized(void *arg)
+{
+  authchallenge_data_t *d = arg;
+  d->cell->payload[37] = 99;
+
+  channel_tls_process_auth_challenge_cell(d->cell, d->chan);
+  tt_int_op(0, ==, mock_close_called);
+  tt_int_op(1, ==, d->c->handshake_state->received_auth_challenge);
+  tt_int_op(0, ==, mock_send_authenticate_called);
+  tt_int_op(1, ==, mock_send_netinfo_called);
+ done:
+  ;
+}
+
+#define AUTHCHALLENGE_FAIL(name, code)                          \
+  static void                                                           \
+  test_link_handshake_recv_authchallenge_ ## name (void *arg)           \
+  {                                                                     \
+    authchallenge_data_t *d = arg;                                      \
+    { code ; }                                                          \
+    channel_tls_process_auth_challenge_cell(d->cell, d->chan);          \
+    tt_int_op(1, ==, mock_close_called);                                \
+    tt_int_op(0, ==, mock_send_authenticate_called);                    \
+    tt_int_op(0, ==, mock_send_netinfo_called);                         \
+  done:                                                                 \
+    ;                                                                   \
+  }
+
+AUTHCHALLENGE_FAIL(badstate,
+                   d->c->base_.state = OR_CONN_STATE_CONNECTING)
+AUTHCHALLENGE_FAIL(badproto,
+                   d->c->link_proto = 2)
+AUTHCHALLENGE_FAIL(as_server,
+                   d->c->handshake_state->started_here = 0;)
+AUTHCHALLENGE_FAIL(duplicate,
+                   d->c->handshake_state->received_auth_challenge = 1)
+AUTHCHALLENGE_FAIL(nocerts,
+                   d->c->handshake_state->received_certs_cell = 0)
+AUTHCHALLENGE_FAIL(tooshort,
+                   d->cell->payload_len = 33)
+AUTHCHALLENGE_FAIL(truncated,
+                   d->cell->payload_len = 34)
+AUTHCHALLENGE_FAIL(nonzero_circid,
+                   d->cell->circ_id = 1337)
+
+
+static tor_x509_cert_t *mock_peer_cert = NULL;
+static tor_x509_cert_t *
+mock_get_peer_cert(tor_tls_t *tls)
+{
+  (void)tls;
+  return mock_peer_cert;
+}
+
+static int
+mock_get_tlssecrets(tor_tls_t *tls, uint8_t *secrets_out)
+{
+  (void)tls;
+  memcpy(secrets_out, "int getRandomNumber(){return 4;}", 32);
+  return 0;
+}
+
+static void
+mock_set_circid_type(channel_t *chan,
+                     crypto_pk_t *identity_rcvd,
+                     int consider_identity)
+{
+  (void) chan;
+  (void) identity_rcvd;
+  (void) consider_identity;
+}
+
+typedef struct authenticate_data_s {
+  or_connection_t *c1, *c2;
+  channel_tls_t *chan2;
+  var_cell_t *cell;
+  crypto_pk_t *key1, *key2;
+} authenticate_data_t;
+
+static int
+authenticate_data_cleanup(const struct testcase_t *test, void *arg)
+{
+  (void) test;
+  UNMOCK(connection_or_write_var_cell_to_buf);
+  UNMOCK(tor_tls_get_peer_cert);
+  UNMOCK(tor_tls_get_tlssecrets);
+  UNMOCK(connection_or_close_for_error);
+  UNMOCK(channel_set_circid_type);
+  authenticate_data_t *d = arg;
+  if (d) {
+    tor_free(d->cell);
+    connection_free_(TO_CONN(d->c1));
+    connection_free_(TO_CONN(d->c2));
+    circuitmux_free(d->chan2->base_.cmux);
+    tor_free(d->chan2);
+    crypto_pk_free(d->key1);
+    crypto_pk_free(d->key2);
+    tor_free(d);
+  }
+  mock_peer_cert = NULL;
+
+  return 1;
+}
+
+static void *
+authenticate_data_setup(const struct testcase_t *test)
+{
+  authenticate_data_t *d = tor_malloc_zero(sizeof(*d));
+
+  scheduler_init();
+
+  MOCK(connection_or_write_var_cell_to_buf, mock_write_var_cell);
+  MOCK(tor_tls_get_peer_cert, mock_get_peer_cert);
+  MOCK(tor_tls_get_tlssecrets, mock_get_tlssecrets);
+  MOCK(connection_or_close_for_error, mock_close_for_err);
+  MOCK(channel_set_circid_type, mock_set_circid_type);
+  d->c1 = or_connection_new(CONN_TYPE_OR, AF_INET);
+  d->c2 = or_connection_new(CONN_TYPE_OR, AF_INET);
+
+  d->key1 = pk_generate(2);
+  d->key2 = pk_generate(3);
+  tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER,
+                                 d->key1, d->key2, 86400), ==, 0);
+
+  d->c1->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3;
+  d->c1->link_proto = 3;
+  tt_int_op(connection_init_or_handshake_state(d->c1, 1), ==, 0);
+
+  d->c2->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3;
+  d->c2->link_proto = 3;
+  tt_int_op(connection_init_or_handshake_state(d->c2, 0), ==, 0);
+  var_cell_t *cell = var_cell_new(16);
+  cell->command = CELL_CERTS;
+  or_handshake_state_record_var_cell(d->c1, d->c1->handshake_state, cell, 1);
+  or_handshake_state_record_var_cell(d->c2, d->c2->handshake_state, cell, 0);
+  memset(cell->payload, 0xf0, 16);
+  or_handshake_state_record_var_cell(d->c1, d->c1->handshake_state, cell, 0);
+  or_handshake_state_record_var_cell(d->c2, d->c2->handshake_state, cell, 1);
+  tor_free(cell);
+
+  d->chan2 = tor_malloc_zero(sizeof(*d->chan2));
+  channel_tls_common_init(d->chan2);
+  d->c2->chan = d->chan2;
+  d->chan2->conn = d->c2;
+  d->c2->base_.address = tor_strdup("C2");
+  d->c2->tls = tor_tls_new(-1, 1);
+  d->c2->handshake_state->received_certs_cell = 1;
+
+  const tor_x509_cert_t *id_cert=NULL, *link_cert=NULL, *auth_cert=NULL;
+  tt_assert(! tor_tls_get_my_certs(1, &link_cert, &id_cert));
+
+  const uint8_t *der;
+  size_t sz;
+  tor_x509_cert_get_der(id_cert, &der, &sz);
+  d->c1->handshake_state->id_cert = tor_x509_cert_decode(der, sz);
+  d->c2->handshake_state->id_cert = tor_x509_cert_decode(der, sz);
+
+  tor_x509_cert_get_der(link_cert, &der, &sz);
+  mock_peer_cert = tor_x509_cert_decode(der, sz);
+  tt_assert(mock_peer_cert);
+  tt_assert(! tor_tls_get_my_certs(0, &auth_cert, &id_cert));
+  tor_x509_cert_get_der(auth_cert, &der, &sz);
+  d->c2->handshake_state->auth_cert = tor_x509_cert_decode(der, sz);
+
+  /* Make an authenticate cell ... */
+  tt_int_op(0, ==, connection_or_send_authenticate_cell(d->c1,
+                                             AUTHTYPE_RSA_SHA256_TLSSECRET));
+  tt_assert(mock_got_var_cell);
+  d->cell = mock_got_var_cell;
+  mock_got_var_cell = NULL;
+
+  return d;
+ done:
+  authenticate_data_cleanup(test, d);
+  return NULL;
+}
+
+static struct testcase_setup_t setup_authenticate = {
+  .setup_fn = authenticate_data_setup,
+  .cleanup_fn = authenticate_data_cleanup
+};
+
+static void
+test_link_handshake_auth_cell(void *arg)
+{
+  authenticate_data_t *d = arg;
+  auth1_t *auth1 = NULL;
+  crypto_pk_t *auth_pubkey = NULL;
+
+  /* Is the cell well-formed on the outer layer? */
+  tt_int_op(d->cell->command, ==, CELL_AUTHENTICATE);
+  tt_int_op(d->cell->payload[0], ==, 0);
+  tt_int_op(d->cell->payload[1], ==, 1);
+  tt_int_op(ntohs(get_uint16(d->cell->payload + 2)), ==,
+            d->cell->payload_len - 4);
+
+  /* Check it out for plausibility... */
+  auth_ctx_t ctx;
+  ctx.is_ed = 0;
+  tt_int_op(d->cell->payload_len-4, ==, auth1_parse(&auth1,
+                                             d->cell->payload+4,
+                                             d->cell->payload_len - 4, &ctx));
+  tt_assert(auth1);
+
+  tt_mem_op(auth1->type, ==, "AUTH0001", 8);
+  tt_mem_op(auth1->tlssecrets, ==, "int getRandomNumber(){return 4;}", 32);
+  tt_int_op(auth1_getlen_sig(auth1), >, 120);
+
+  /* Is the signature okay? */
+  uint8_t sig[128];
+  uint8_t digest[32];
+
+  auth_pubkey = tor_tls_cert_get_key(d->c2->handshake_state->auth_cert);
+  int n = crypto_pk_public_checksig(
+              auth_pubkey,
+              (char*)sig, sizeof(sig), (char*)auth1_getarray_sig(auth1),
+              auth1_getlen_sig(auth1));
+  tt_int_op(n, ==, 32);
+  const uint8_t *start = d->cell->payload+4, *end = auth1->end_of_signed;
+  crypto_digest256((char*)digest,
+                   (const char*)start, end-start, DIGEST_SHA256);
+  tt_mem_op(sig, ==, digest, 32);
+
+  /* Then feed it to c2. */
+  tt_int_op(d->c2->handshake_state->authenticated, ==, 0);
+  channel_tls_process_authenticate_cell(d->cell, d->chan2);
+  tt_int_op(mock_close_called, ==, 0);
+  tt_int_op(d->c2->handshake_state->authenticated, ==, 1);
+
+ done:
+  auth1_free(auth1);
+  crypto_pk_free(auth_pubkey);
+}
+
+#define AUTHENTICATE_FAIL(name, code)                           \
+  static void                                                   \
+  test_link_handshake_auth_ ## name (void *arg)                 \
+  {                                                             \
+    authenticate_data_t *d = arg;                               \
+    { code ; }                                                  \
+    tt_int_op(d->c2->handshake_state->authenticated, ==, 0);    \
+    channel_tls_process_authenticate_cell(d->cell, d->chan2);   \
+    tt_int_op(mock_close_called, ==, 1);                        \
+    tt_int_op(d->c2->handshake_state->authenticated, ==, 0);    \
+   done:                                                        \
+   ;                                                            \
+  }
+
+AUTHENTICATE_FAIL(badstate,
+                  d->c2->base_.state = OR_CONN_STATE_CONNECTING)
+AUTHENTICATE_FAIL(badproto,
+                  d->c2->link_proto = 2)
+AUTHENTICATE_FAIL(atclient,
+                  d->c2->handshake_state->started_here = 1)
+AUTHENTICATE_FAIL(duplicate,
+                  d->c2->handshake_state->received_authenticate = 1)
+static void
+test_link_handshake_auth_already_authenticated(void *arg)
+{
+  authenticate_data_t *d = arg;
+  d->c2->handshake_state->authenticated = 1;
+  channel_tls_process_authenticate_cell(d->cell, d->chan2);
+  tt_int_op(mock_close_called, ==, 1);
+  tt_int_op(d->c2->handshake_state->authenticated, ==, 1);
+ done:
+  ;
+}
+AUTHENTICATE_FAIL(nocerts,
+                  d->c2->handshake_state->received_certs_cell = 0)
+AUTHENTICATE_FAIL(noidcert,
+                  tor_x509_cert_free(d->c2->handshake_state->id_cert);
+                  d->c2->handshake_state->id_cert = NULL)
+AUTHENTICATE_FAIL(noauthcert,
+                  tor_x509_cert_free(d->c2->handshake_state->auth_cert);
+                  d->c2->handshake_state->auth_cert = NULL)
+AUTHENTICATE_FAIL(tooshort,
+                  d->cell->payload_len = 3)
+AUTHENTICATE_FAIL(badtype,
+                  d->cell->payload[0] = 0xff)
+AUTHENTICATE_FAIL(truncated_1,
+                  d->cell->payload[2]++)
+AUTHENTICATE_FAIL(truncated_2,
+                  d->cell->payload[3]++)
+AUTHENTICATE_FAIL(tooshort_1,
+                  tt_int_op(d->cell->payload_len, >=, 260);
+                  d->cell->payload[2] -= 1;
+                  d->cell->payload_len -= 256;)
+AUTHENTICATE_FAIL(badcontent,
+                  d->cell->payload[10] ^= 0xff)
+AUTHENTICATE_FAIL(badsig_1,
+                  d->cell->payload[d->cell->payload_len - 5] ^= 0xff)
+
+#define TEST(name, flags)                                       \
+  { #name , test_link_handshake_ ## name, (flags), NULL, NULL }
+
+#define TEST_RCV_AUTHCHALLENGE(name)                            \
+  { "recv_authchallenge/" #name ,                               \
+    test_link_handshake_recv_authchallenge_ ## name, TT_FORK,   \
+      &setup_recv_authchallenge, NULL }
+
+#define TEST_RCV_CERTS(name)                                    \
+  { "recv_certs/" #name ,                                       \
+      test_link_handshake_recv_certs_ ## name, TT_FORK,         \
+      &setup_recv_certs, NULL }
+
+#define TEST_AUTHENTICATE(name)                                         \
+  { "authenticate/" #name , test_link_handshake_auth_ ## name, TT_FORK, \
+      &setup_authenticate, NULL }
+
+struct testcase_t link_handshake_tests[] = {
+  TEST(certs_ok, TT_FORK),
+  //TEST(certs_bad, TT_FORK),
+  TEST_RCV_CERTS(ok),
+  TEST_RCV_CERTS(ok_server),
+  TEST_RCV_CERTS(badstate),
+  TEST_RCV_CERTS(badproto),
+  TEST_RCV_CERTS(duplicate),
+  TEST_RCV_CERTS(already_authenticated),
+  TEST_RCV_CERTS(empty),
+  TEST_RCV_CERTS(bad_circid),
+  TEST_RCV_CERTS(truncated_1),
+  TEST_RCV_CERTS(truncated_2),
+  TEST_RCV_CERTS(truncated_3),
+  TEST_RCV_CERTS(not_x509),
+  TEST_RCV_CERTS(both_link),
+  TEST_RCV_CERTS(both_id_rsa),
+  TEST_RCV_CERTS(both_auth),
+  TEST_RCV_CERTS(wrong_labels_1),
+  TEST_RCV_CERTS(wrong_labels_2),
+  TEST_RCV_CERTS(wrong_labels_3),
+  TEST_RCV_CERTS(server_missing_certs),
+  TEST_RCV_CERTS(server_wrong_labels_1),
+
+  TEST(send_authchallenge, TT_FORK),
+  TEST_RCV_AUTHCHALLENGE(ok),
+  TEST_RCV_AUTHCHALLENGE(ok_noserver),
+  TEST_RCV_AUTHCHALLENGE(ok_unrecognized),
+  TEST_RCV_AUTHCHALLENGE(badstate),
+  TEST_RCV_AUTHCHALLENGE(badproto),
+  TEST_RCV_AUTHCHALLENGE(as_server),
+  TEST_RCV_AUTHCHALLENGE(duplicate),
+  TEST_RCV_AUTHCHALLENGE(nocerts),
+  TEST_RCV_AUTHCHALLENGE(tooshort),
+  TEST_RCV_AUTHCHALLENGE(truncated),
+  TEST_RCV_AUTHCHALLENGE(nonzero_circid),
+
+  TEST_AUTHENTICATE(cell),
+  TEST_AUTHENTICATE(badstate),
+  TEST_AUTHENTICATE(badproto),
+  TEST_AUTHENTICATE(atclient),
+  TEST_AUTHENTICATE(duplicate),
+  TEST_AUTHENTICATE(already_authenticated),
+  TEST_AUTHENTICATE(nocerts),
+  TEST_AUTHENTICATE(noidcert),
+  TEST_AUTHENTICATE(noauthcert),
+  TEST_AUTHENTICATE(tooshort),
+  TEST_AUTHENTICATE(badtype),
+  TEST_AUTHENTICATE(truncated_1),
+  TEST_AUTHENTICATE(truncated_2),
+  TEST_AUTHENTICATE(tooshort_1),
+  TEST_AUTHENTICATE(badcontent),
+  TEST_AUTHENTICATE(badsig_1),
+  //TEST_AUTHENTICATE(),
+
+  END_OF_TESTCASES
+};

+ 93 - 0
src/test/test_microdesc.c

@@ -10,6 +10,7 @@
 #include "networkstatus.h"
 #include "routerlist.h"
 #include "routerparse.h"
+#include "torcert.h"
 
 #include "test.h"
 
@@ -335,6 +336,59 @@ static const char test_ri[] =
   "t0xkIE39ss/EwmQr7iIgkdVH4oRIMsjYnFFJBG26nYY=\n"
   "-----END SIGNATURE-----\n";
 
+static const char test_ri2[] =
+  "router test001a 127.0.0.1 5001 0 7001\n"
+  "identity-ed25519\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQQABf/FAf5iDuKCZP2VxnAaQWdklilAh6kaEeFX4z8261Yx2T1/AQAgBADCp8vO\n"
+  "B8K1F9g2DzwuwvVCnPFLSK1qknVqPpNucHLH9DY7fuIYogBAdz4zHv1qC7RKaMNG\n"
+  "Jux/tMO2tzPcm62Ky5PjClMQplKUOnZNQ+RIpA3wYCIfUDy/cQnY7XWgNQ0=\n"
+  "-----END ED25519 CERT-----\n"
+  "platform Tor 0.2.6.0-alpha-dev on Darwin\n"
+  "protocols Link 1 2 Circuit 1\n"
+  "published 2014-10-08 12:58:04\n"
+  "fingerprint B7E2 7F10 4213 C36F 13E7 E982 9182 845E 4959 97A0\n"
+  "uptime 0\n"
+  "bandwidth 1073741824 1073741824 0\n"
+  "extra-info-digest 568F27331B6D8C73E7024F1EF5D097B90DFC7CDB\n"
+  "caches-extra-info\n"
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAL2R8EfubUcahxha4u02P4VAR0llQIMwFAmrHPjzcK7apcQgDOf2ovOA\n"
+  "+YQnJFxlpBmCoCZC6ssCi+9G0mqo650lFuTMP5I90BdtjotfzESfTykHLiChyvhd\n"
+  "l0dlqclb2SU/GKem/fLRXH16aNi72CdSUu/1slKs/70ILi34QixRAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "signing-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAN8+78KUVlgHXdMMkYJxcwh1Zv2y+Gb5eWUyltUaQRajhrT9ij2T5JZs\n"
+  "M0g85xTcuM3jNVVpV79+33hiTohdC6UZ+Bk4USQ7WBFzRbVFSXoVKLBJFkCOIexg\n"
+  "SMGNd5WEDtHWrXl58mizmPFu1eG6ZxHzt7RuLSol5cwBvawXPNkFAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "onion-key-crosscert\n"
+  "-----BEGIN CROSSCERT-----\n"
+  "ETFDzU49bvNfoZnKK1j6JeBP2gDirgj6bBCgWpUYs663OO9ypbZRO0JwWANssKl6\n"
+  "oaq9vKTsKGRsaNnqnz/JGMhehymakjjNtqg7crWwsahe8+7Pw9GKmW+YjFtcOkUf\n"
+  "KfOn2bmKBa1FoJb4yW3oXzHcdlLSRuCciKqPn+Hky5o=\n"
+  "-----END CROSSCERT-----\n"
+  "ntor-onion-key-crosscert 0\n"
+  "-----BEGIN ED25519 CERT-----\n"
+  "AQoABf2dAcKny84HwrUX2DYPPC7C9UKc8UtIrWqSdWo+k25wcsf0AFohutG+xI06\n"
+  "Ef21c5Zl1j8Hw6DzHDjYyJevXLFuOneaL3zcH2Ldn4sjrG3kc5UuVvRfTvV120UO\n"
+  "xk4f5s5LGwY=\n"
+  "-----END ED25519 CERT-----\n"
+  "hidden-service-dir\n"
+  "contact auth1@test.test\n"
+  "ntor-onion-key hbxdRnfVUJJY7+KcT4E3Rs7/zuClbN3hJrjSBiEGMgI=\n"
+  "reject *:*\n"
+  "router-sig-ed25519 5aQXyTif7PExIuL2di37UvktmJECKnils2OWz2vDi"
+                     "hFxi+5TTAAPxYkS5clhc/Pjvw34itfjGmTKFic/8httAQ\n"
+  "router-signature\n"
+  "-----BEGIN SIGNATURE-----\n"
+  "BaUB+aFPQbb3BwtdzKsKqV3+6cRlSqJF5bI3UTmwRoJk+Z5Pz+W5NWokNI0xArHM\n"
+  "T4T5FZCCP9350jXsUCIvzyIyktU6aVRCGFt76rFlo1OETpN8GWkMnQU0w18cxvgS\n"
+  "cf34GXHv61XReJF3AlzNHFpbrPOYmowmhrTULKyMqow=\n"
+  "-----END SIGNATURE-----\n";
+
 static const char test_md_8[] =
   "onion-key\n"
   "-----BEGIN RSA PUBLIC KEY-----\n"
@@ -365,6 +419,26 @@ static const char test_md_18[] =
   "p reject 25,119,135-139,445,563,1214,4661-4666,6346-6429,6699,6881-6999\n"
   "id rsa1024 Cd47okjCHD83YGzThGBDptXs9Z4\n";
 
+static const char test_md2_18[] =
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAL2R8EfubUcahxha4u02P4VAR0llQIMwFAmrHPjzcK7apcQgDOf2ovOA\n"
+  "+YQnJFxlpBmCoCZC6ssCi+9G0mqo650lFuTMP5I90BdtjotfzESfTykHLiChyvhd\n"
+  "l0dlqclb2SU/GKem/fLRXH16aNi72CdSUu/1slKs/70ILi34QixRAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key hbxdRnfVUJJY7+KcT4E3Rs7/zuClbN3hJrjSBiEGMgI=\n"
+  "id rsa1024 t+J/EEITw28T5+mCkYKEXklZl6A\n";
+
+static const char test_md2_21[] =
+  "onion-key\n"
+  "-----BEGIN RSA PUBLIC KEY-----\n"
+  "MIGJAoGBAL2R8EfubUcahxha4u02P4VAR0llQIMwFAmrHPjzcK7apcQgDOf2ovOA\n"
+  "+YQnJFxlpBmCoCZC6ssCi+9G0mqo650lFuTMP5I90BdtjotfzESfTykHLiChyvhd\n"
+  "l0dlqclb2SU/GKem/fLRXH16aNi72CdSUu/1slKs/70ILi34QixRAgMBAAE=\n"
+  "-----END RSA PUBLIC KEY-----\n"
+  "ntor-onion-key hbxdRnfVUJJY7+KcT4E3Rs7/zuClbN3hJrjSBiEGMgI=\n"
+  "id ed25519 wqfLzgfCtRfYNg88LsL1QpzxS0itapJ1aj6TbnByx/Q\n";
+
 static void
 test_md_generate(void *arg)
 {
@@ -391,6 +465,25 @@ test_md_generate(void *arg)
   md = dirvote_create_microdescriptor(ri, 18);
   tt_str_op(md->body, OP_EQ, test_md_18);
 
+  microdesc_free(md);
+  md = NULL;
+  md = dirvote_create_microdescriptor(ri, 21);
+  tt_str_op(md->body, ==, test_md_18);
+
+  ri = router_parse_entry_from_string(test_ri2, NULL, 0, 0, NULL, NULL);
+
+  microdesc_free(md);
+  md = NULL;
+  md = dirvote_create_microdescriptor(ri, 18);
+  tt_str_op(md->body, ==, test_md2_18);
+
+  microdesc_free(md);
+  md = NULL;
+  md = dirvote_create_microdescriptor(ri, 21);
+  tt_str_op(md->body, ==, test_md2_21);
+  tt_assert(ed25519_pubkey_eq(md->ed25519_identity_pkey,
+                              &ri->signing_key_cert->signing_key));
+
  done:
   microdesc_free(md);
   routerinfo_free(ri);

+ 539 - 1
src/test/test_routerkeys.c

@@ -8,11 +8,17 @@
 #include "or.h"
 #include "config.h"
 #include "router.h"
+#include "routerkeys.h"
 #include "util.h"
 #include "crypto.h"
-
+#include "torcert.h"
 #include "test.h"
 
+#ifdef _WIN32
+/* For mkdir() */
+#include <direct.h>
+#endif
+
 static void
 test_routerkeys_write_fingerprint(void *arg)
 {
@@ -75,11 +81,543 @@ test_routerkeys_write_fingerprint(void *arg)
   tor_free(cp2);
 }
 
+static void
+test_routerkeys_ed_certs(void *args)
+{
+  (void)args;
+  ed25519_keypair_t kp1, kp2;
+  tor_cert_t *cert[2] = {NULL, NULL};
+  tor_cert_t *parsed_cert[2] = {NULL, NULL};
+  time_t now = 1412094534;
+  uint8_t *junk = NULL;
+  char *base64 = NULL;
+
+  tt_int_op(0,==,ed25519_keypair_generate(&kp1, 0));
+  tt_int_op(0,==,ed25519_keypair_generate(&kp2, 0));
+
+  for (int i = 0; i <= 1; ++i) {
+    uint32_t flags = i ? CERT_FLAG_INCLUDE_SIGNING_KEY : 0;
+
+    cert[i] = tor_cert_create(&kp1, 5, &kp2.pubkey, now, 10000, flags);
+    tt_assert(cert[i]);
+
+    tt_assert(cert[i]->sig_bad == 0);
+    tt_assert(cert[i]->sig_ok == 1);
+    tt_assert(cert[i]->cert_expired == 0);
+    tt_assert(cert[i]->cert_valid == 1);
+    tt_int_op(cert[i]->cert_type, ==, 5);
+    tt_mem_op(cert[i]->signed_key.pubkey, ==, &kp2.pubkey.pubkey, 32);
+    tt_mem_op(cert[i]->signing_key.pubkey, ==, &kp1.pubkey.pubkey, 32);
+    tt_int_op(cert[i]->signing_key_included, ==, i);
+
+    tt_assert(cert[i]->encoded);
+    tt_int_op(cert[i]->encoded_len, ==, 104 + 36 * i);
+    tt_int_op(cert[i]->encoded[0], ==, 1);
+    tt_int_op(cert[i]->encoded[1], ==, 5);
+
+    parsed_cert[i] = tor_cert_parse(cert[i]->encoded, cert[i]->encoded_len);
+    tt_assert(parsed_cert[i]);
+    tt_int_op(cert[i]->encoded_len, ==, parsed_cert[i]->encoded_len);
+    tt_mem_op(cert[i]->encoded, ==, parsed_cert[i]->encoded,
+              cert[i]->encoded_len);
+    tt_assert(parsed_cert[i]->sig_bad == 0);
+    tt_assert(parsed_cert[i]->sig_ok == 0);
+    tt_assert(parsed_cert[i]->cert_expired == 0);
+    tt_assert(parsed_cert[i]->cert_valid == 0);
+
+    /* Expired */
+    tt_int_op(tor_cert_checksig(parsed_cert[i], &kp1.pubkey, now + 30000),
+              <, 0);
+    tt_assert(parsed_cert[i]->cert_expired == 1);
+    parsed_cert[i]->cert_expired = 0;
+
+    /* Wrong key */
+    tt_int_op(tor_cert_checksig(parsed_cert[i], &kp2.pubkey, now), <, 0);
+    tt_assert(parsed_cert[i]->sig_bad== 1);
+    parsed_cert[i]->sig_bad = 0;
+
+    /* Missing key */
+    int ok = tor_cert_checksig(parsed_cert[i], NULL, now);
+    tt_int_op(ok < 0, ==, i == 0);
+    tt_assert(parsed_cert[i]->sig_bad == 0);
+    tt_assert(parsed_cert[i]->sig_ok == (i != 0));
+    tt_assert(parsed_cert[i]->cert_valid == (i != 0));
+    parsed_cert[i]->sig_bad = 0;
+    parsed_cert[i]->sig_ok = 0;
+    parsed_cert[i]->cert_valid = 0;
+
+    /* Right key */
+    tt_int_op(tor_cert_checksig(parsed_cert[i], &kp1.pubkey, now), ==, 0);
+    tt_assert(parsed_cert[i]->sig_bad == 0);
+    tt_assert(parsed_cert[i]->sig_ok == 1);
+    tt_assert(parsed_cert[i]->cert_expired == 0);
+    tt_assert(parsed_cert[i]->cert_valid == 1);
+  }
+
+  /* Now try some junky certs. */
+  /* - Truncated */
+  tt_ptr_op(NULL, ==,tor_cert_parse(cert[0]->encoded, cert[0]->encoded_len-1));
+
+  /* - First byte modified */
+  cert[0]->encoded[0] = 99;
+  tt_ptr_op(NULL, ==,tor_cert_parse(cert[0]->encoded, cert[0]->encoded_len));
+  cert[0]->encoded[0] = 1;
+
+  /* - Extra byte at the end*/
+  junk = tor_malloc_zero(cert[0]->encoded_len + 1);
+  memcpy(junk, cert[0]->encoded, cert[0]->encoded_len);
+  tt_ptr_op(NULL, ==, tor_cert_parse(junk, cert[0]->encoded_len+1));
+
+  /* - Multiple signing key instances */
+  tor_free(junk);
+  junk = tor_malloc_zero(104 + 36 * 2);
+  junk[0] = 1; /* version */
+  junk[1] = 5; /* cert type */
+  junk[6] = 1; /* key type */
+  junk[39] = 2; /* n_extensions */
+  junk[41] = 32; /* extlen */
+  junk[42] = 4; /* exttype */
+  junk[77] = 32; /* extlen */
+  junk[78] = 4; /* exttype */
+  tt_ptr_op(NULL, ==, tor_cert_parse(junk, 104 + 36 * 2));
+
+ done:
+  tor_cert_free(cert[0]);
+  tor_cert_free(cert[1]);
+  tor_cert_free(parsed_cert[0]);
+  tor_cert_free(parsed_cert[1]);
+  tor_free(junk);
+  tor_free(base64);
+}
+
+static void
+test_routerkeys_ed_key_create(void *arg)
+{
+  (void)arg;
+  tor_cert_t *cert = NULL;
+  ed25519_keypair_t *kp1 = NULL, *kp2 = NULL;
+  time_t now = time(NULL);
+
+  /* This is a simple alias for 'make a new keypair' */
+  kp1 = ed_key_new(NULL, 0, 0, 0, 0, &cert);
+  tt_assert(kp1);
+
+  /* Create a new certificate signed by kp1. */
+  kp2 = ed_key_new(kp1, INIT_ED_KEY_NEEDCERT, now, 3600, 4, &cert);
+  tt_assert(kp2);
+  tt_assert(cert);
+  tt_mem_op(&cert->signed_key, ==, &kp2->pubkey, sizeof(ed25519_public_key_t));
+  tt_assert(! cert->signing_key_included);
+
+  tt_int_op(cert->valid_until, >=, now);
+  tt_int_op(cert->valid_until, <=, now+7200);
+
+  /* Create a new key-including certificate signed by kp1 */
+  ed25519_keypair_free(kp2);
+  tor_cert_free(cert);
+  cert = NULL; kp2 = NULL;
+  kp2 = ed_key_new(kp1, (INIT_ED_KEY_NEEDCERT|
+                         INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT),
+                   now, 3600, 4, &cert);
+  tt_assert(kp2);
+  tt_assert(cert);
+  tt_assert(cert->signing_key_included);
+  tt_mem_op(&cert->signed_key, ==, &kp2->pubkey, sizeof(ed25519_public_key_t));
+  tt_mem_op(&cert->signing_key, ==, &kp1->pubkey,sizeof(ed25519_public_key_t));
+
+ done:
+  ed25519_keypair_free(kp1);
+  ed25519_keypair_free(kp2);
+  tor_cert_free(cert);
+}
+
+static void
+test_routerkeys_ed_key_init_basic(void *arg)
+{
+  (void) arg;
+
+  tor_cert_t *cert = NULL, *cert2 = NULL;
+  ed25519_keypair_t *kp1 = NULL, *kp2 = NULL, *kp3 = NULL;
+  time_t now = time(NULL);
+  char *fname1 = tor_strdup(get_fname("test_ed_key_1"));
+  char *fname2 = tor_strdup(get_fname("test_ed_key_2"));
+  struct stat st;
+
+  unlink(fname1);
+  unlink(fname2);
+
+  /* Fail to load a key that isn't there. */
+  kp1 = ed_key_init_from_file(fname1, 0, LOG_INFO, NULL, now, 0, 7, &cert);
+  tt_assert(kp1 == NULL);
+  tt_assert(cert == NULL);
+
+  /* Create the key if requested to do so. */
+  kp1 = ed_key_init_from_file(fname1, INIT_ED_KEY_CREATE, LOG_INFO,
+                              NULL, now, 0, 7, &cert);
+  tt_assert(kp1 != NULL);
+  tt_assert(cert == NULL);
+  tt_int_op(stat(get_fname("test_ed_key_1_cert"), &st), <, 0);
+  tt_int_op(stat(get_fname("test_ed_key_1_secret_key"), &st), ==, 0);
+
+  /* Fail to load if we say we need a cert */
+  kp2 = ed_key_init_from_file(fname1, INIT_ED_KEY_NEEDCERT, LOG_INFO,
+                              NULL, now, 0, 7, &cert);
+  tt_assert(kp2 == NULL);
+
+  /* Fail to load if we say the wrong key type */
+  kp2 = ed_key_init_from_file(fname1, 0, LOG_INFO,
+                              NULL, now, 0, 6, &cert);
+  tt_assert(kp2 == NULL);
+
+  /* Load successfully if we're not picky, whether we say "create" or not. */
+  kp2 = ed_key_init_from_file(fname1, INIT_ED_KEY_CREATE, LOG_INFO,
+                              NULL, now, 0, 7, &cert);
+  tt_assert(kp2 != NULL);
+  tt_assert(cert == NULL);
+  tt_mem_op(kp1, ==, kp2, sizeof(*kp1));
+  ed25519_keypair_free(kp2); kp2 = NULL;
+
+  kp2 = ed_key_init_from_file(fname1, 0, LOG_INFO,
+                              NULL, now, 0, 7, &cert);
+  tt_assert(kp2 != NULL);
+  tt_assert(cert == NULL);
+  tt_mem_op(kp1, ==, kp2, sizeof(*kp1));
+  ed25519_keypair_free(kp2); kp2 = NULL;
+
+  /* Now create a key with a cert. */
+  kp2 = ed_key_init_from_file(fname2, (INIT_ED_KEY_CREATE|
+                                       INIT_ED_KEY_NEEDCERT),
+                              LOG_INFO, kp1, now, 7200, 7, &cert);
+  tt_assert(kp2 != NULL);
+  tt_assert(cert != NULL);
+  tt_mem_op(kp1, !=, kp2, sizeof(*kp1));
+  tt_int_op(stat(get_fname("test_ed_key_2_cert"), &st), ==, 0);
+  tt_int_op(stat(get_fname("test_ed_key_2_secret_key"), &st), ==, 0);
+
+  tt_assert(cert->cert_valid == 1);
+  tt_mem_op(&cert->signed_key, ==, &kp2->pubkey, 32);
+
+  /* Now verify we can load the cert... */
+  kp3 = ed_key_init_from_file(fname2, (INIT_ED_KEY_CREATE|
+                                       INIT_ED_KEY_NEEDCERT),
+                              LOG_INFO, kp1, now, 7200, 7, &cert2);
+  tt_mem_op(kp2, ==, kp3, sizeof(*kp2));
+  tt_mem_op(cert2->encoded, ==, cert->encoded, cert->encoded_len);
+  ed25519_keypair_free(kp3); kp3 = NULL;
+  tor_cert_free(cert2); cert2 = NULL;
+
+  /* ... even without create... */
+  kp3 = ed_key_init_from_file(fname2, INIT_ED_KEY_NEEDCERT,
+                              LOG_INFO, kp1, now, 7200, 7, &cert2);
+  tt_mem_op(kp2, ==, kp3, sizeof(*kp2));
+  tt_mem_op(cert2->encoded, ==, cert->encoded, cert->encoded_len);
+  ed25519_keypair_free(kp3); kp3 = NULL;
+  tor_cert_free(cert2); cert2 = NULL;
+
+  /* ... but that we don't crash or anything if we say we don't want it. */
+  kp3 = ed_key_init_from_file(fname2, INIT_ED_KEY_NEEDCERT,
+                              LOG_INFO, kp1, now, 7200, 7, NULL);
+  tt_mem_op(kp2, ==, kp3, sizeof(*kp2));
+  ed25519_keypair_free(kp3); kp3 = NULL;
+
+  /* Fail if we're told the wrong signing key */
+  kp3 = ed_key_init_from_file(fname2, INIT_ED_KEY_NEEDCERT,
+                              LOG_INFO, kp2, now, 7200, 7, &cert2);
+  tt_assert(kp3 == NULL);
+  tt_assert(cert2 == NULL);
+
+ done:
+  ed25519_keypair_free(kp1);
+  ed25519_keypair_free(kp2);
+  ed25519_keypair_free(kp3);
+  tor_cert_free(cert);
+  tor_cert_free(cert2);
+  tor_free(fname1);
+  tor_free(fname2);
+}
+
+static void
+test_routerkeys_ed_key_init_split(void *arg)
+{
+  (void) arg;
+
+  tor_cert_t *cert = NULL;
+  ed25519_keypair_t *kp1 = NULL, *kp2 = NULL;
+  time_t now = time(NULL);
+  char *fname1 = tor_strdup(get_fname("test_ed_key_3"));
+  char *fname2 = tor_strdup(get_fname("test_ed_key_4"));
+  struct stat st;
+  const uint32_t flags = INIT_ED_KEY_SPLIT|INIT_ED_KEY_MISSING_SECRET_OK;
+
+  unlink(fname1);
+  unlink(fname2);
+
+  /* Can't load key that isn't there. */
+  kp1 = ed_key_init_from_file(fname1, flags, LOG_INFO, NULL, now, 0, 7, &cert);
+  tt_assert(kp1 == NULL);
+  tt_assert(cert == NULL);
+
+  /* Create a split key */
+  kp1 = ed_key_init_from_file(fname1, flags|INIT_ED_KEY_CREATE,
+                              LOG_INFO, NULL, now, 0, 7, &cert);
+  tt_assert(kp1 != NULL);
+  tt_assert(cert == NULL);
+  tt_int_op(stat(get_fname("test_ed_key_3_cert"), &st), <, 0);
+  tt_int_op(stat(get_fname("test_ed_key_3_secret_key"), &st), ==, 0);
+  tt_int_op(stat(get_fname("test_ed_key_3_public_key"), &st), ==, 0);
+
+  /* Load it. */
+  kp2 = ed_key_init_from_file(fname1, flags|INIT_ED_KEY_CREATE,
+                              LOG_INFO, NULL, now, 0, 7, &cert);
+  tt_assert(kp2 != NULL);
+  tt_assert(cert == NULL);
+  tt_mem_op(kp1, ==, kp2, sizeof(*kp2));
+  ed25519_keypair_free(kp2); kp2 = NULL;
+
+  /* Okay, try killing the secret key and loading it. */
+  unlink(get_fname("test_ed_key_3_secret_key"));
+  kp2 = ed_key_init_from_file(fname1, flags,
+                              LOG_INFO, NULL, now, 0, 7, &cert);
+  tt_assert(kp2 != NULL);
+  tt_assert(cert == NULL);
+  tt_mem_op(&kp1->pubkey, ==, &kp2->pubkey, sizeof(kp2->pubkey));
+  tt_assert(tor_mem_is_zero((char*)kp2->seckey.seckey,
+                            sizeof(kp2->seckey.seckey)));
+  ed25519_keypair_free(kp2); kp2 = NULL;
+
+  /* Even when we're told to "create", don't create if there's a public key */
+  kp2 = ed_key_init_from_file(fname1, flags|INIT_ED_KEY_CREATE,
+                              LOG_INFO, NULL, now, 0, 7, &cert);
+  tt_assert(kp2 != NULL);
+  tt_assert(cert == NULL);
+  tt_mem_op(&kp1->pubkey, ==, &kp2->pubkey, sizeof(kp2->pubkey));
+  tt_assert(tor_mem_is_zero((char*)kp2->seckey.seckey,
+                            sizeof(kp2->seckey.seckey)));
+  ed25519_keypair_free(kp2); kp2 = NULL;
+
+  /* Make sure we fail on a tag mismatch, though */
+  kp2 = ed_key_init_from_file(fname1, flags,
+                              LOG_INFO, NULL, now, 0, 99, &cert);
+  tt_assert(kp2 == NULL);
+
+ done:
+  ed25519_keypair_free(kp1);
+  ed25519_keypair_free(kp2);
+  tor_cert_free(cert);
+  tor_free(fname1);
+  tor_free(fname2);
+}
+
+static void
+test_routerkeys_ed_keys_init_all(void *arg)
+{
+  (void)arg;
+  char *dir = tor_strdup(get_fname("test_ed_keys_init_all"));
+  or_options_t *options = tor_malloc_zero(sizeof(or_options_t));
+  time_t now = time(NULL);
+  ed25519_public_key_t id;
+  ed25519_keypair_t sign, auth;
+  tor_cert_t *link_cert = NULL;
+
+  get_options_mutable()->ORPort_set = 1;
+
+  crypto_pk_t *rsa = pk_generate(0);
+
+  set_server_identity_key(rsa);
+  set_client_identity_key(rsa);
+
+  router_initialize_tls_context();
+
+  options->SigningKeyLifetime = 30*86400;
+  options->TestingAuthKeyLifetime = 2*86400;
+  options->TestingLinkCertLifetime = 2*86400;
+  options->TestingSigningKeySlop = 2*86400;
+  options->TestingAuthKeySlop = 2*3600;
+  options->TestingLinkKeySlop = 2*3600;
+
+#ifdef _WIN32
+  mkdir(dir);
+  mkdir(get_fname("test_ed_keys_init_all/keys"));
+#else
+  mkdir(dir, 0700);
+  mkdir(get_fname("test_ed_keys_init_all/keys"), 0700);
+#endif
+
+  options->DataDirectory = dir;
+
+  tt_int_op(0, ==, load_ed_keys(options, now));
+  tt_int_op(0, ==, generate_ed_link_cert(options, now));
+  tt_assert(get_master_identity_key());
+  tt_assert(get_master_identity_key());
+  tt_assert(get_master_signing_keypair());
+  tt_assert(get_current_auth_keypair());
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_cert_cert());
+  tt_assert(get_current_auth_key_cert());
+  memcpy(&id, get_master_identity_key(), sizeof(id));
+  memcpy(&sign, get_master_signing_keypair(), sizeof(sign));
+  memcpy(&auth, get_current_auth_keypair(), sizeof(auth));
+  link_cert = tor_cert_dup(get_current_link_cert_cert());
+
+  /* Call load_ed_keys again, but nothing has changed. */
+  tt_int_op(0, ==, load_ed_keys(options, now));
+  tt_int_op(0, ==, generate_ed_link_cert(options, now));
+  tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id));
+  tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign));
+  tt_mem_op(&auth, ==, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(tor_cert_eq(link_cert, get_current_link_cert_cert()));
+
+  /* Force a reload: we make new link/auth keys. */
+  routerkeys_free_all();
+  tt_int_op(0, ==, load_ed_keys(options, now));
+  tt_int_op(0, ==, generate_ed_link_cert(options, now));
+  tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id));
+  tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign));
+  tt_assert(tor_cert_eq(link_cert, get_current_link_cert_cert()));
+  tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_cert_cert());
+  tt_assert(get_current_auth_key_cert());
+  tor_cert_free(link_cert);
+  link_cert = tor_cert_dup(get_current_link_cert_cert());
+  memcpy(&auth, get_current_auth_keypair(), sizeof(auth));
+
+  /* Force a link/auth-key regeneration by advancing time. */
+  tt_int_op(0, ==, load_ed_keys(options, now+3*86400));
+  tt_int_op(0, ==, generate_ed_link_cert(options, now+3*86400));
+  tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id));
+  tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign));
+  tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert()));
+  tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_cert_cert());
+  tt_assert(get_current_auth_key_cert());
+  tor_cert_free(link_cert);
+  link_cert = tor_cert_dup(get_current_link_cert_cert());
+  memcpy(&auth, get_current_auth_keypair(), sizeof(auth));
+
+  /* Force a signing-key regeneration by advancing time. */
+  tt_int_op(0, ==, load_ed_keys(options, now+100*86400));
+  tt_int_op(0, ==, generate_ed_link_cert(options, now+100*86400));
+  tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id));
+  tt_mem_op(&sign, !=, get_master_signing_keypair(), sizeof(sign));
+  tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert()));
+  tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_cert_cert());
+  tt_assert(get_current_auth_key_cert());
+  memcpy(&sign, get_master_signing_keypair(), sizeof(sign));
+  tor_cert_free(link_cert);
+  link_cert = tor_cert_dup(get_current_link_cert_cert());
+  memcpy(&auth, get_current_auth_keypair(), sizeof(auth));
+
+  /* Demonstrate that we can start up with no secret identity key */
+  routerkeys_free_all();
+  unlink(get_fname("test_ed_keys_init_all/keys/"
+                   "ed25519_master_id_secret_key"));
+  tt_int_op(0, ==, load_ed_keys(options, now));
+  tt_int_op(0, ==, generate_ed_link_cert(options, now));
+  tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id));
+  tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign));
+  tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert()));
+  tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_cert_cert());
+  tt_assert(get_current_auth_key_cert());
+
+  /* But we're in trouble if we have no id key and our signing key has
+     expired. */
+  log_global_min_severity_ = LOG_ERR; /* Suppress warnings.
+                                       * XXX (better way to do this)? */
+  routerkeys_free_all();
+  tt_int_op(-1, ==, load_ed_keys(options, now+200*86400));
+
+ done:
+  tor_free(dir);
+  tor_free(options);
+  routerkeys_free_all();
+}
+
+static void
+test_routerkeys_cross_certify_ntor(void *args)
+{
+  (void) args;
+
+  tor_cert_t *cert = NULL;
+  curve25519_keypair_t onion_keys;
+  ed25519_public_key_t master_key;
+  ed25519_public_key_t onion_check_key;
+  time_t now = time(NULL);
+  int sign;
+
+  tt_int_op(0, ==, ed25519_public_from_base64(&master_key,
+                               "IamwritingthesetestsOnARainyAfternoonin2014"));
+  tt_int_op(0, ==, curve25519_keypair_generate(&onion_keys, 0));
+  cert = make_ntor_onion_key_crosscert(&onion_keys,
+                                       &master_key,
+                                       now, 10000,
+                                       &sign);
+  tt_assert(cert);
+  tt_assert(sign == 0 || sign == 1);
+  tt_int_op(cert->cert_type, ==, CERT_TYPE_ONION_ID);
+  tt_int_op(1, ==, ed25519_pubkey_eq(&cert->signed_key, &master_key));
+  tt_int_op(0, ==, ed25519_public_key_from_curve25519_public_key(
+                               &onion_check_key, &onion_keys.pubkey, sign));
+  tt_int_op(0, ==, tor_cert_checksig(cert, &onion_check_key, now));
+
+ done:
+  tor_cert_free(cert);
+}
+
+static void
+test_routerkeys_cross_certify_tap(void *args)
+{
+  (void)args;
+  uint8_t *cc = NULL;
+  int cc_len;
+  ed25519_public_key_t master_key;
+  crypto_pk_t *onion_key = pk_generate(2), *id_key = pk_generate(1);
+  char digest[20];
+  char buf[128];
+  int n;
+
+  tt_int_op(0, ==, ed25519_public_from_base64(&master_key,
+                               "IAlreadyWroteTestsForRouterdescsUsingTheseX"));
+
+  cc = make_tap_onion_key_crosscert(onion_key,
+                                    &master_key,
+                                    id_key, &cc_len);
+  tt_assert(cc);
+  tt_assert(cc_len);
+
+  n = crypto_pk_public_checksig(onion_key, buf, sizeof(buf),
+                                (char*)cc, cc_len);
+  tt_int_op(n,>,0);
+  tt_int_op(n,==,52);
+
+  crypto_pk_get_digest(id_key, digest);
+  tt_mem_op(buf,==,digest,20);
+  tt_mem_op(buf+20,==,master_key.pubkey,32);
+
+  tt_int_op(0, ==, check_tap_onion_key_crosscert(cc, cc_len,
+                                    onion_key, &master_key, (uint8_t*)digest));
+
+ done:
+  tor_free(cc);
+}
+
 #define TEST(name, flags)                                       \
   { #name , test_routerkeys_ ## name, (flags), NULL, NULL }
 
 struct testcase_t routerkeys_tests[] = {
   TEST(write_fingerprint, TT_FORK),
+  TEST(ed_certs, TT_FORK),
+  TEST(ed_key_create, TT_FORK),
+  TEST(ed_key_init_basic, TT_FORK),
+  TEST(ed_key_init_split, TT_FORK),
+  TEST(ed_keys_init_all, TT_FORK),
+  TEST(cross_certify_ntor, 0),
+  TEST(cross_certify_tap, 0),
   END_OF_TESTCASES
 };
 

+ 887 - 0
src/trunnel/ed25519_cert.c

@@ -0,0 +1,887 @@
+/* ed25519_cert.c -- generated by Trunnel v1.2.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#include <stdlib.h>
+#include "trunnel-impl.h"
+
+#include "ed25519_cert.h"
+
+#define TRUNNEL_SET_ERROR_CODE(obj) \
+  do {                              \
+    (obj)->trunnel_error_code_ = 1; \
+  } while (0)
+
+#if defined(__COVERITY__) || defined(__clang_analyzer__)
+/* If we're runnning a static analysis tool, we don't want it to complain
+ * that some of our remaining-bytes checks are dead-code. */
+int edcert_deadcode_dummy__ = 0;
+#define OR_DEADCODE_DUMMY || edcert_deadcode_dummy__
+#else
+#define OR_DEADCODE_DUMMY
+#endif
+
+#define CHECK_REMAINING(nbytes, label)                           \
+  do {                                                           \
+    if (remaining < (nbytes) OR_DEADCODE_DUMMY) {                \
+      goto label;                                                \
+    }                                                            \
+  } while (0)
+
+ed25519_cert_extension_t *
+ed25519_cert_extension_new(void)
+{
+  ed25519_cert_extension_t *val = trunnel_calloc(1, sizeof(ed25519_cert_extension_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+ed25519_cert_extension_clear(ed25519_cert_extension_t *obj)
+{
+  (void) obj;
+  TRUNNEL_DYNARRAY_WIPE(&obj->un_unparsed);
+  TRUNNEL_DYNARRAY_CLEAR(&obj->un_unparsed);
+}
+
+void
+ed25519_cert_extension_free(ed25519_cert_extension_t *obj)
+{
+  if (obj == NULL)
+    return;
+  ed25519_cert_extension_clear(obj);
+  trunnel_memwipe(obj, sizeof(ed25519_cert_extension_t));
+  trunnel_free_(obj);
+}
+
+uint16_t
+ed25519_cert_extension_get_ext_length(ed25519_cert_extension_t *inp)
+{
+  return inp->ext_length;
+}
+int
+ed25519_cert_extension_set_ext_length(ed25519_cert_extension_t *inp, uint16_t val)
+{
+  inp->ext_length = val;
+  return 0;
+}
+uint8_t
+ed25519_cert_extension_get_ext_type(ed25519_cert_extension_t *inp)
+{
+  return inp->ext_type;
+}
+int
+ed25519_cert_extension_set_ext_type(ed25519_cert_extension_t *inp, uint8_t val)
+{
+  inp->ext_type = val;
+  return 0;
+}
+uint8_t
+ed25519_cert_extension_get_ext_flags(ed25519_cert_extension_t *inp)
+{
+  return inp->ext_flags;
+}
+int
+ed25519_cert_extension_set_ext_flags(ed25519_cert_extension_t *inp, uint8_t val)
+{
+  inp->ext_flags = val;
+  return 0;
+}
+size_t
+ed25519_cert_extension_getlen_un_signing_key(const ed25519_cert_extension_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+ed25519_cert_extension_get_un_signing_key(const ed25519_cert_extension_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->un_signing_key[idx];
+}
+
+int
+ed25519_cert_extension_set_un_signing_key(ed25519_cert_extension_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->un_signing_key[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+ed25519_cert_extension_getarray_un_signing_key(ed25519_cert_extension_t *inp)
+{
+  return inp->un_signing_key;
+}
+size_t
+ed25519_cert_extension_getlen_un_unparsed(const ed25519_cert_extension_t *inp)
+{
+  return TRUNNEL_DYNARRAY_LEN(&inp->un_unparsed);
+}
+
+uint8_t
+ed25519_cert_extension_get_un_unparsed(ed25519_cert_extension_t *inp, size_t idx)
+{
+  return TRUNNEL_DYNARRAY_GET(&inp->un_unparsed, idx);
+}
+
+int
+ed25519_cert_extension_set_un_unparsed(ed25519_cert_extension_t *inp, size_t idx, uint8_t elt)
+{
+  TRUNNEL_DYNARRAY_SET(&inp->un_unparsed, idx, elt);
+  return 0;
+}
+int
+ed25519_cert_extension_add_un_unparsed(ed25519_cert_extension_t *inp, uint8_t elt)
+{
+  TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->un_unparsed, elt, {});
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+
+uint8_t *
+ed25519_cert_extension_getarray_un_unparsed(ed25519_cert_extension_t *inp)
+{
+  return inp->un_unparsed.elts_;
+}
+int
+ed25519_cert_extension_setlen_un_unparsed(ed25519_cert_extension_t *inp, size_t newlen)
+{
+  uint8_t *newptr;
+  newptr = trunnel_dynarray_setlen(&inp->un_unparsed.allocated_,
+                 &inp->un_unparsed.n_, inp->un_unparsed.elts_, newlen,
+                 sizeof(inp->un_unparsed.elts_[0]), (trunnel_free_fn_t) NULL,
+                 &inp->trunnel_error_code_);
+  if (newptr == NULL)
+    goto trunnel_alloc_failed;
+  inp->un_unparsed.elts_ = newptr;
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+const char *
+ed25519_cert_extension_check(const ed25519_cert_extension_t *obj)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  switch (obj->ext_type) {
+
+    case CERTEXT_SIGNED_WITH_KEY:
+      break;
+
+    default:
+      break;
+  }
+  return NULL;
+}
+
+ssize_t
+ed25519_cert_extension_encoded_len(const ed25519_cert_extension_t *obj)
+{
+  ssize_t result = 0;
+
+  if (NULL != ed25519_cert_extension_check(obj))
+     return -1;
+
+
+  /* Length of u16 ext_length */
+  result += 2;
+
+  /* Length of u8 ext_type */
+  result += 1;
+
+  /* Length of u8 ext_flags */
+  result += 1;
+  switch (obj->ext_type) {
+
+    case CERTEXT_SIGNED_WITH_KEY:
+
+      /* Length of u8 un_signing_key[32] */
+      result += 32;
+      break;
+
+    default:
+
+      /* Length of u8 un_unparsed[] */
+      result += TRUNNEL_DYNARRAY_LEN(&obj->un_unparsed);
+      break;
+  }
+  return result;
+}
+int
+ed25519_cert_extension_clear_errors(ed25519_cert_extension_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+ed25519_cert_extension_encode(uint8_t *output, const size_t avail, const ed25519_cert_extension_t *obj)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = ed25519_cert_extension_encoded_len(obj);
+#endif
+
+  uint8_t *backptr_ext_length = NULL;
+
+  if (NULL != (msg = ed25519_cert_extension_check(obj)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u16 ext_length */
+  backptr_ext_length = ptr;
+  trunnel_assert(written <= avail);
+  if (avail - written < 2)
+    goto truncated;
+  trunnel_set_uint16(ptr, trunnel_htons(obj->ext_length));
+  written += 2; ptr += 2;
+
+  /* Encode u8 ext_type */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->ext_type));
+  written += 1; ptr += 1;
+
+  /* Encode u8 ext_flags */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->ext_flags));
+  written += 1; ptr += 1;
+  {
+    size_t written_before_union = written;
+
+    /* Encode union un[ext_type] */
+    trunnel_assert(written <= avail);
+    switch (obj->ext_type) {
+
+      case CERTEXT_SIGNED_WITH_KEY:
+
+        /* Encode u8 un_signing_key[32] */
+        trunnel_assert(written <= avail);
+        if (avail - written < 32)
+          goto truncated;
+        memcpy(ptr, obj->un_signing_key, 32);
+        written += 32; ptr += 32;
+        break;
+
+      default:
+
+        /* Encode u8 un_unparsed[] */
+        {
+          size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->un_unparsed);
+          trunnel_assert(written <= avail);
+          if (avail - written < elt_len)
+            goto truncated;
+          memcpy(ptr, obj->un_unparsed.elts_, elt_len);
+          written += elt_len; ptr += elt_len;
+        }
+        break;
+    }
+    /* Write the length field back to ext_length */
+    trunnel_assert(written >= written_before_union);
+#if UINT16_MAX < SIZE_MAX
+    if (written - written_before_union > UINT16_MAX)
+      goto check_failed;
+#endif
+    trunnel_set_uint16(backptr_ext_length, trunnel_htons(written - written_before_union));
+  }
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As ed25519_cert_extension_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+ed25519_cert_extension_parse_into(ed25519_cert_extension_t *obj, const uint8_t *input, const size_t len_in)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+
+  /* Parse u16 ext_length */
+  CHECK_REMAINING(2, truncated);
+  obj->ext_length = trunnel_ntohs(trunnel_get_uint16(ptr));
+  remaining -= 2; ptr += 2;
+
+  /* Parse u8 ext_type */
+  CHECK_REMAINING(1, truncated);
+  obj->ext_type = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+
+  /* Parse u8 ext_flags */
+  CHECK_REMAINING(1, truncated);
+  obj->ext_flags = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+  {
+    size_t remaining_after;
+    CHECK_REMAINING(obj->ext_length, truncated);
+    remaining_after = remaining - obj->ext_length;
+    remaining = obj->ext_length;
+
+    /* Parse union un[ext_type] */
+    switch (obj->ext_type) {
+
+      case CERTEXT_SIGNED_WITH_KEY:
+
+        /* Parse u8 un_signing_key[32] */
+        CHECK_REMAINING(32, fail);
+        memcpy(obj->un_signing_key, ptr, 32);
+        remaining -= 32; ptr += 32;
+        break;
+
+      default:
+
+        /* Parse u8 un_unparsed[] */
+        TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->un_unparsed, remaining, {});
+        obj->un_unparsed.n_ = remaining;
+        memcpy(obj->un_unparsed.elts_, ptr, remaining);
+        ptr += remaining; remaining -= remaining;
+        break;
+    }
+    if (remaining != 0)
+      goto fail;
+    remaining = remaining_after;
+  }
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+ trunnel_alloc_failed:
+  return -1;
+ fail:
+  result = -1;
+  return result;
+}
+
+ssize_t
+ed25519_cert_extension_parse(ed25519_cert_extension_t **output, const uint8_t *input, const size_t len_in)
+{
+  ssize_t result;
+  *output = ed25519_cert_extension_new();
+  if (NULL == *output)
+    return -1;
+  result = ed25519_cert_extension_parse_into(*output, input, len_in);
+  if (result < 0) {
+    ed25519_cert_extension_free(*output);
+    *output = NULL;
+  }
+  return result;
+}
+ed25519_cert_t *
+ed25519_cert_new(void)
+{
+  ed25519_cert_t *val = trunnel_calloc(1, sizeof(ed25519_cert_t));
+  if (NULL == val)
+    return NULL;
+  val->version = 1;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+ed25519_cert_clear(ed25519_cert_t *obj)
+{
+  (void) obj;
+  {
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->ext); ++idx) {
+      ed25519_cert_extension_free(TRUNNEL_DYNARRAY_GET(&obj->ext, idx));
+    }
+  }
+  TRUNNEL_DYNARRAY_WIPE(&obj->ext);
+  TRUNNEL_DYNARRAY_CLEAR(&obj->ext);
+}
+
+void
+ed25519_cert_free(ed25519_cert_t *obj)
+{
+  if (obj == NULL)
+    return;
+  ed25519_cert_clear(obj);
+  trunnel_memwipe(obj, sizeof(ed25519_cert_t));
+  trunnel_free_(obj);
+}
+
+uint8_t
+ed25519_cert_get_version(ed25519_cert_t *inp)
+{
+  return inp->version;
+}
+int
+ed25519_cert_set_version(ed25519_cert_t *inp, uint8_t val)
+{
+  if (! ((val == 1))) {
+     TRUNNEL_SET_ERROR_CODE(inp);
+     return -1;
+  }
+  inp->version = val;
+  return 0;
+}
+uint8_t
+ed25519_cert_get_cert_type(ed25519_cert_t *inp)
+{
+  return inp->cert_type;
+}
+int
+ed25519_cert_set_cert_type(ed25519_cert_t *inp, uint8_t val)
+{
+  inp->cert_type = val;
+  return 0;
+}
+uint32_t
+ed25519_cert_get_exp_field(ed25519_cert_t *inp)
+{
+  return inp->exp_field;
+}
+int
+ed25519_cert_set_exp_field(ed25519_cert_t *inp, uint32_t val)
+{
+  inp->exp_field = val;
+  return 0;
+}
+uint8_t
+ed25519_cert_get_cert_key_type(ed25519_cert_t *inp)
+{
+  return inp->cert_key_type;
+}
+int
+ed25519_cert_set_cert_key_type(ed25519_cert_t *inp, uint8_t val)
+{
+  inp->cert_key_type = val;
+  return 0;
+}
+size_t
+ed25519_cert_getlen_certified_key(const ed25519_cert_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+ed25519_cert_get_certified_key(const ed25519_cert_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->certified_key[idx];
+}
+
+int
+ed25519_cert_set_certified_key(ed25519_cert_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->certified_key[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+ed25519_cert_getarray_certified_key(ed25519_cert_t *inp)
+{
+  return inp->certified_key;
+}
+uint8_t
+ed25519_cert_get_n_extensions(ed25519_cert_t *inp)
+{
+  return inp->n_extensions;
+}
+int
+ed25519_cert_set_n_extensions(ed25519_cert_t *inp, uint8_t val)
+{
+  inp->n_extensions = val;
+  return 0;
+}
+size_t
+ed25519_cert_getlen_ext(const ed25519_cert_t *inp)
+{
+  return TRUNNEL_DYNARRAY_LEN(&inp->ext);
+}
+
+struct ed25519_cert_extension_st *
+ed25519_cert_get_ext(ed25519_cert_t *inp, size_t idx)
+{
+  return TRUNNEL_DYNARRAY_GET(&inp->ext, idx);
+}
+
+int
+ed25519_cert_set_ext(ed25519_cert_t *inp, size_t idx, struct ed25519_cert_extension_st * elt)
+{
+  ed25519_cert_extension_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->ext, idx);
+  if (oldval && oldval != elt)
+    ed25519_cert_extension_free(oldval);
+  return ed25519_cert_set0_ext(inp, idx, elt);
+}
+int
+ed25519_cert_set0_ext(ed25519_cert_t *inp, size_t idx, struct ed25519_cert_extension_st * elt)
+{
+  TRUNNEL_DYNARRAY_SET(&inp->ext, idx, elt);
+  return 0;
+}
+int
+ed25519_cert_add_ext(ed25519_cert_t *inp, struct ed25519_cert_extension_st * elt)
+{
+#if SIZE_MAX >= UINT8_MAX
+  if (inp->ext.n_ == UINT8_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  TRUNNEL_DYNARRAY_ADD(struct ed25519_cert_extension_st *, &inp->ext, elt, {});
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+
+struct ed25519_cert_extension_st * *
+ed25519_cert_getarray_ext(ed25519_cert_t *inp)
+{
+  return inp->ext.elts_;
+}
+int
+ed25519_cert_setlen_ext(ed25519_cert_t *inp, size_t newlen)
+{
+  struct ed25519_cert_extension_st * *newptr;
+#if UINT8_MAX < SIZE_MAX
+  if (newlen > UINT8_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  newptr = trunnel_dynarray_setlen(&inp->ext.allocated_,
+                 &inp->ext.n_, inp->ext.elts_, newlen,
+                 sizeof(inp->ext.elts_[0]), (trunnel_free_fn_t) ed25519_cert_extension_free,
+                 &inp->trunnel_error_code_);
+  if (newptr == NULL)
+    goto trunnel_alloc_failed;
+  inp->ext.elts_ = newptr;
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+size_t
+ed25519_cert_getlen_signature(const ed25519_cert_t *inp)
+{
+  (void)inp;  return 64;
+}
+
+uint8_t
+ed25519_cert_get_signature(const ed25519_cert_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 64);
+  return inp->signature[idx];
+}
+
+int
+ed25519_cert_set_signature(ed25519_cert_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 64);
+  inp->signature[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+ed25519_cert_getarray_signature(ed25519_cert_t *inp)
+{
+  return inp->signature;
+}
+const char *
+ed25519_cert_check(const ed25519_cert_t *obj)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  if (! (obj->version == 1))
+    return "Integer out of bounds";
+  {
+    const char *msg;
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->ext); ++idx) {
+      if (NULL != (msg = ed25519_cert_extension_check(TRUNNEL_DYNARRAY_GET(&obj->ext, idx))))
+        return msg;
+    }
+  }
+  if (TRUNNEL_DYNARRAY_LEN(&obj->ext) != obj->n_extensions)
+    return "Length mismatch for ext";
+  return NULL;
+}
+
+ssize_t
+ed25519_cert_encoded_len(const ed25519_cert_t *obj)
+{
+  ssize_t result = 0;
+
+  if (NULL != ed25519_cert_check(obj))
+     return -1;
+
+
+  /* Length of u8 version IN [1] */
+  result += 1;
+
+  /* Length of u8 cert_type */
+  result += 1;
+
+  /* Length of u32 exp_field */
+  result += 4;
+
+  /* Length of u8 cert_key_type */
+  result += 1;
+
+  /* Length of u8 certified_key[32] */
+  result += 32;
+
+  /* Length of u8 n_extensions */
+  result += 1;
+
+  /* Length of struct ed25519_cert_extension ext[n_extensions] */
+  {
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->ext); ++idx) {
+      result += ed25519_cert_extension_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->ext, idx));
+    }
+  }
+
+  /* Length of u8 signature[64] */
+  result += 64;
+  return result;
+}
+int
+ed25519_cert_clear_errors(ed25519_cert_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+ed25519_cert_encode(uint8_t *output, const size_t avail, const ed25519_cert_t *obj)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = ed25519_cert_encoded_len(obj);
+#endif
+
+  if (NULL != (msg = ed25519_cert_check(obj)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u8 version IN [1] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->version));
+  written += 1; ptr += 1;
+
+  /* Encode u8 cert_type */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->cert_type));
+  written += 1; ptr += 1;
+
+  /* Encode u32 exp_field */
+  trunnel_assert(written <= avail);
+  if (avail - written < 4)
+    goto truncated;
+  trunnel_set_uint32(ptr, trunnel_htonl(obj->exp_field));
+  written += 4; ptr += 4;
+
+  /* Encode u8 cert_key_type */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->cert_key_type));
+  written += 1; ptr += 1;
+
+  /* Encode u8 certified_key[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->certified_key, 32);
+  written += 32; ptr += 32;
+
+  /* Encode u8 n_extensions */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->n_extensions));
+  written += 1; ptr += 1;
+
+  /* Encode struct ed25519_cert_extension ext[n_extensions] */
+  {
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->ext); ++idx) {
+      trunnel_assert(written <= avail);
+      result = ed25519_cert_extension_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->ext, idx));
+      if (result < 0)
+        goto fail; /* XXXXXXX !*/
+      written += result; ptr += result;
+    }
+  }
+
+  /* Encode u8 signature[64] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 64)
+    goto truncated;
+  memcpy(ptr, obj->signature, 64);
+  written += 64; ptr += 64;
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As ed25519_cert_parse(), but do not allocate the output object.
+ */
+static ssize_t
+ed25519_cert_parse_into(ed25519_cert_t *obj, const uint8_t *input, const size_t len_in)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+
+  /* Parse u8 version IN [1] */
+  CHECK_REMAINING(1, truncated);
+  obj->version = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+  if (! (obj->version == 1))
+    goto fail;
+
+  /* Parse u8 cert_type */
+  CHECK_REMAINING(1, truncated);
+  obj->cert_type = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+
+  /* Parse u32 exp_field */
+  CHECK_REMAINING(4, truncated);
+  obj->exp_field = trunnel_ntohl(trunnel_get_uint32(ptr));
+  remaining -= 4; ptr += 4;
+
+  /* Parse u8 cert_key_type */
+  CHECK_REMAINING(1, truncated);
+  obj->cert_key_type = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+
+  /* Parse u8 certified_key[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->certified_key, ptr, 32);
+  remaining -= 32; ptr += 32;
+
+  /* Parse u8 n_extensions */
+  CHECK_REMAINING(1, truncated);
+  obj->n_extensions = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+
+  /* Parse struct ed25519_cert_extension ext[n_extensions] */
+  TRUNNEL_DYNARRAY_EXPAND(ed25519_cert_extension_t *, &obj->ext, obj->n_extensions, {});
+  {
+    ed25519_cert_extension_t * elt;
+    unsigned idx;
+    for (idx = 0; idx < obj->n_extensions; ++idx) {
+      result = ed25519_cert_extension_parse(&elt, ptr, remaining);
+      if (result < 0)
+        goto relay_fail;
+      trunnel_assert((size_t)result <= remaining);
+      remaining -= result; ptr += result;
+      TRUNNEL_DYNARRAY_ADD(ed25519_cert_extension_t *, &obj->ext, elt, {ed25519_cert_extension_free(elt);});
+    }
+  }
+
+  /* Parse u8 signature[64] */
+  CHECK_REMAINING(64, truncated);
+  memcpy(obj->signature, ptr, 64);
+  remaining -= 64; ptr += 64;
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+ relay_fail:
+  if (result >= 0) result = -1;
+  return result;
+ trunnel_alloc_failed:
+  return -1;
+ fail:
+  result = -1;
+  return result;
+}
+
+ssize_t
+ed25519_cert_parse(ed25519_cert_t **output, const uint8_t *input, const size_t len_in)
+{
+  ssize_t result;
+  *output = ed25519_cert_new();
+  if (NULL == *output)
+    return -1;
+  result = ed25519_cert_parse_into(*output, input, len_in);
+  if (result < 0) {
+    ed25519_cert_free(*output);
+    *output = NULL;
+  }
+  return result;
+}

+ 288 - 0
src/trunnel/ed25519_cert.h

@@ -0,0 +1,288 @@
+/* ed25519_cert.h -- generated by by Trunnel v1.2.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#ifndef TRUNNEL_ED25519_CERT_H
+#define TRUNNEL_ED25519_CERT_H
+
+#include <stdint.h>
+#include "trunnel.h"
+
+#define CERTEXT_SIGNED_WITH_KEY 4
+#define CERTEXT_FLAG_AFFECTS_VALIDATION 1
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_CERT_EXTENSION)
+struct ed25519_cert_extension_st {
+  uint16_t ext_length;
+  uint8_t ext_type;
+  uint8_t ext_flags;
+  uint8_t un_signing_key[32];
+  TRUNNEL_DYNARRAY_HEAD(, uint8_t) un_unparsed;
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct ed25519_cert_extension_st ed25519_cert_extension_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_CERT)
+struct ed25519_cert_st {
+  uint8_t version;
+  uint8_t cert_type;
+  uint32_t exp_field;
+  uint8_t cert_key_type;
+  uint8_t certified_key[32];
+  uint8_t n_extensions;
+  TRUNNEL_DYNARRAY_HEAD(, struct ed25519_cert_extension_st *) ext;
+  uint8_t signature[64];
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct ed25519_cert_st ed25519_cert_t;
+/** Return a newly allocated ed25519_cert_extension with all elements
+ * set to zero.
+ */
+ed25519_cert_extension_t *ed25519_cert_extension_new(void);
+/** Release all storage held by the ed25519_cert_extension in
+ * 'victim'. (Do nothing if 'victim' is NULL.)
+ */
+void ed25519_cert_extension_free(ed25519_cert_extension_t *victim);
+/** Try to parse a ed25519_cert_extension from the buffer in 'input',
+ * using up to 'len_in' bytes from the input buffer. On success,
+ * return the number of bytes consumed and set *output to the newly
+ * allocated ed25519_cert_extension_t. On failure, return -2 if the
+ * input appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t ed25519_cert_extension_parse(ed25519_cert_extension_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * ed25519_cert_extension in 'obj'. On failure, return a negative
+ * value. Note that this value may be an overestimate, and can even be
+ * an underestimate for certain unencodeable objects.
+ */
+ssize_t ed25519_cert_extension_encoded_len(const ed25519_cert_extension_t *obj);
+/** Try to encode the ed25519_cert_extension from 'input' into the
+ * buffer at 'output', using up to 'avail' bytes of the output buffer.
+ * On success, return the number of bytes used. On failure, return -2
+ * if the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t ed25519_cert_extension_encode(uint8_t *output, const size_t avail, const ed25519_cert_extension_t *input);
+/** Check whether the internal state of the ed25519_cert_extension in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *ed25519_cert_extension_check(const ed25519_cert_extension_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int ed25519_cert_extension_clear_errors(ed25519_cert_extension_t *obj);
+/** Return the value of the ext_length field of the
+ * ed25519_cert_extension_t in 'inp'
+ */
+uint16_t ed25519_cert_extension_get_ext_length(ed25519_cert_extension_t *inp);
+/** Set the value of the ext_length field of the
+ * ed25519_cert_extension_t in 'inp' to 'val'. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int ed25519_cert_extension_set_ext_length(ed25519_cert_extension_t *inp, uint16_t val);
+/** Return the value of the ext_type field of the
+ * ed25519_cert_extension_t in 'inp'
+ */
+uint8_t ed25519_cert_extension_get_ext_type(ed25519_cert_extension_t *inp);
+/** Set the value of the ext_type field of the
+ * ed25519_cert_extension_t in 'inp' to 'val'. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int ed25519_cert_extension_set_ext_type(ed25519_cert_extension_t *inp, uint8_t val);
+/** Return the value of the ext_flags field of the
+ * ed25519_cert_extension_t in 'inp'
+ */
+uint8_t ed25519_cert_extension_get_ext_flags(ed25519_cert_extension_t *inp);
+/** Set the value of the ext_flags field of the
+ * ed25519_cert_extension_t in 'inp' to 'val'. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int ed25519_cert_extension_set_ext_flags(ed25519_cert_extension_t *inp, uint8_t val);
+/** Return the (constant) length of the array holding the
+ * un_signing_key field of the ed25519_cert_extension_t in 'inp'.
+ */
+size_t ed25519_cert_extension_getlen_un_signing_key(const ed25519_cert_extension_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * un_signing_key of the ed25519_cert_extension_t in 'inp'.
+ */
+uint8_t ed25519_cert_extension_get_un_signing_key(const ed25519_cert_extension_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * un_signing_key of the ed25519_cert_extension_t in 'inp', so that it
+ * will hold the value 'elt'.
+ */
+int ed25519_cert_extension_set_un_signing_key(ed25519_cert_extension_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field un_signing_key of
+ * 'inp'.
+ */
+uint8_t * ed25519_cert_extension_getarray_un_signing_key(ed25519_cert_extension_t *inp);
+/** Return the length of the dynamic array holding the un_unparsed
+ * field of the ed25519_cert_extension_t in 'inp'.
+ */
+size_t ed25519_cert_extension_getlen_un_unparsed(const ed25519_cert_extension_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * un_unparsed of the ed25519_cert_extension_t in 'inp'.
+ */
+uint8_t ed25519_cert_extension_get_un_unparsed(ed25519_cert_extension_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * un_unparsed of the ed25519_cert_extension_t in 'inp', so that it
+ * will hold the value 'elt'.
+ */
+int ed25519_cert_extension_set_un_unparsed(ed25519_cert_extension_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field un_unparsed
+ * of the ed25519_cert_extension_t in 'inp'.
+ */
+int ed25519_cert_extension_add_un_unparsed(ed25519_cert_extension_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field un_unparsed of
+ * 'inp'.
+ */
+uint8_t * ed25519_cert_extension_getarray_un_unparsed(ed25519_cert_extension_t *inp);
+/** Change the length of the variable-length array field un_unparsed
+ * of 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on
+ * success; return -1 and set the error code on 'inp' on failure.
+ */
+int ed25519_cert_extension_setlen_un_unparsed(ed25519_cert_extension_t *inp, size_t newlen);
+/** Return a newly allocated ed25519_cert with all elements set to
+ * zero.
+ */
+ed25519_cert_t *ed25519_cert_new(void);
+/** Release all storage held by the ed25519_cert in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void ed25519_cert_free(ed25519_cert_t *victim);
+/** Try to parse a ed25519_cert from the buffer in 'input', using up
+ * to 'len_in' bytes from the input buffer. On success, return the
+ * number of bytes consumed and set *output to the newly allocated
+ * ed25519_cert_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t ed25519_cert_parse(ed25519_cert_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * ed25519_cert in 'obj'. On failure, return a negative value. Note
+ * that this value may be an overestimate, and can even be an
+ * underestimate for certain unencodeable objects.
+ */
+ssize_t ed25519_cert_encoded_len(const ed25519_cert_t *obj);
+/** Try to encode the ed25519_cert from 'input' into the buffer at
+ * 'output', using up to 'avail' bytes of the output buffer. On
+ * success, return the number of bytes used. On failure, return -2 if
+ * the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t ed25519_cert_encode(uint8_t *output, const size_t avail, const ed25519_cert_t *input);
+/** Check whether the internal state of the ed25519_cert in 'obj' is
+ * consistent. Return NULL if it is, and a short message if it is not.
+ */
+const char *ed25519_cert_check(const ed25519_cert_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int ed25519_cert_clear_errors(ed25519_cert_t *obj);
+/** Return the value of the version field of the ed25519_cert_t in
+ * 'inp'
+ */
+uint8_t ed25519_cert_get_version(ed25519_cert_t *inp);
+/** Set the value of the version field of the ed25519_cert_t in 'inp'
+ * to 'val'. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int ed25519_cert_set_version(ed25519_cert_t *inp, uint8_t val);
+/** Return the value of the cert_type field of the ed25519_cert_t in
+ * 'inp'
+ */
+uint8_t ed25519_cert_get_cert_type(ed25519_cert_t *inp);
+/** Set the value of the cert_type field of the ed25519_cert_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int ed25519_cert_set_cert_type(ed25519_cert_t *inp, uint8_t val);
+/** Return the value of the exp_field field of the ed25519_cert_t in
+ * 'inp'
+ */
+uint32_t ed25519_cert_get_exp_field(ed25519_cert_t *inp);
+/** Set the value of the exp_field field of the ed25519_cert_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int ed25519_cert_set_exp_field(ed25519_cert_t *inp, uint32_t val);
+/** Return the value of the cert_key_type field of the ed25519_cert_t
+ * in 'inp'
+ */
+uint8_t ed25519_cert_get_cert_key_type(ed25519_cert_t *inp);
+/** Set the value of the cert_key_type field of the ed25519_cert_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int ed25519_cert_set_cert_key_type(ed25519_cert_t *inp, uint8_t val);
+/** Return the (constant) length of the array holding the
+ * certified_key field of the ed25519_cert_t in 'inp'.
+ */
+size_t ed25519_cert_getlen_certified_key(const ed25519_cert_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * certified_key of the ed25519_cert_t in 'inp'.
+ */
+uint8_t ed25519_cert_get_certified_key(const ed25519_cert_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * certified_key of the ed25519_cert_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int ed25519_cert_set_certified_key(ed25519_cert_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field certified_key of
+ * 'inp'.
+ */
+uint8_t * ed25519_cert_getarray_certified_key(ed25519_cert_t *inp);
+/** Return the value of the n_extensions field of the ed25519_cert_t
+ * in 'inp'
+ */
+uint8_t ed25519_cert_get_n_extensions(ed25519_cert_t *inp);
+/** Set the value of the n_extensions field of the ed25519_cert_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int ed25519_cert_set_n_extensions(ed25519_cert_t *inp, uint8_t val);
+/** Return the length of the dynamic array holding the ext field of
+ * the ed25519_cert_t in 'inp'.
+ */
+size_t ed25519_cert_getlen_ext(const ed25519_cert_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * ext of the ed25519_cert_t in 'inp'.
+ */
+struct ed25519_cert_extension_st * ed25519_cert_get_ext(ed25519_cert_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * ext of the ed25519_cert_t in 'inp', so that it will hold the value
+ * 'elt'. Free the previous value, if any.
+ */
+int ed25519_cert_set_ext(ed25519_cert_t *inp, size_t idx, struct ed25519_cert_extension_st * elt);
+/** As ed25519_cert_set_ext, but does not free the previous value.
+ */
+int ed25519_cert_set0_ext(ed25519_cert_t *inp, size_t idx, struct ed25519_cert_extension_st * elt);
+/** Append a new element 'elt' to the dynamic array field ext of the
+ * ed25519_cert_t in 'inp'.
+ */
+int ed25519_cert_add_ext(ed25519_cert_t *inp, struct ed25519_cert_extension_st * elt);
+/** Return a pointer to the variable-length array field ext of 'inp'.
+ */
+struct ed25519_cert_extension_st * * ed25519_cert_getarray_ext(ed25519_cert_t *inp);
+/** Change the length of the variable-length array field ext of 'inp'
+ * to 'newlen'.Fill extra elements with NULL; free removed elements.
+ * Return 0 on success; return -1 and set the error code on 'inp' on
+ * failure.
+ */
+int ed25519_cert_setlen_ext(ed25519_cert_t *inp, size_t newlen);
+/** Return the (constant) length of the array holding the signature
+ * field of the ed25519_cert_t in 'inp'.
+ */
+size_t ed25519_cert_getlen_signature(const ed25519_cert_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * signature of the ed25519_cert_t in 'inp'.
+ */
+uint8_t ed25519_cert_get_signature(const ed25519_cert_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * signature of the ed25519_cert_t in 'inp', so that it will hold the
+ * value 'elt'.
+ */
+int ed25519_cert_set_signature(ed25519_cert_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 64-element array field signature of 'inp'.
+ */
+uint8_t * ed25519_cert_getarray_signature(ed25519_cert_t *inp);
+
+
+#endif

+ 76 - 0
src/trunnel/ed25519_cert.trunnel

@@ -0,0 +1,76 @@
+
+struct ed25519_cert {
+  u8 version IN [1];
+  u8 cert_type;
+  u32 exp_field;
+  u8 cert_key_type;
+  u8 certified_key[32];
+  u8 n_extensions;
+  struct ed25519_cert_extension ext[n_extensions];
+  u8 signature[64];
+}
+
+const CERTEXT_SIGNED_WITH_KEY = 4;
+const CERTEXT_FLAG_AFFECTS_VALIDATION = 1;
+
+struct ed25519_cert_extension {
+  u16 ext_length;
+  u8 ext_type;
+  u8 ext_flags;
+  union un[ext_type] with length ext_length {
+    CERTEXT_SIGNED_WITH_KEY : u8 signing_key[32];
+    default: u8 unparsed[];
+  };
+}
+
+/*
+struct cert_revocation {
+  u8 prefix[8];
+  u8 version IN [1];
+  u8 keytype;
+  u8 identity_key[32];
+  u8 revoked_key[32];
+  u64 published;
+  u8 n_extensions;
+  struct cert_extension ext[n_extensions];
+  u8 signature[64];
+}
+
+struct crosscert_ed_rsa {
+  u8 ed_key[32];
+  u32 expiration_date;
+  u8 signature[128];
+}
+
+struct auth02_cell {
+  u8 type[8];
+  u8 cid[32];
+  u8 sid[32];
+  u8 cid_ed[32];
+  u8 sid_ed[32];
+  u8 slog[32];
+  u8 clog[32];
+  u8 scert[32];
+  u8 tlssecrets[32];
+  u8 rand[24];
+  u8 sig[64];
+}
+
+const LS_IPV4 = 0x00;
+const LS_IPV6 = 0x01;
+const LS_LEGACY_ID = 0x02;
+const LS_ED25519_ID = 0x03;
+
+// amended from tor.trunnel
+struct link_specifier {
+  u8 ls_type;
+  u8 ls_len;
+  union un[ls_type] with length ls_len {
+    LS_IPV4: u32 ipv4_addr; u16 ipv4_port;
+    LS_IPV6: u8 ipv6_addr[16]; u16 ipv6_port;
+    LS_LEGACY_ID: u8 legacy_id[20];
+    LS_ED25519_ID: u8 ed25519_id[32];
+    default: u8 unrecognized[];
+  };
+}
+*/

+ 15 - 6
src/trunnel/include.am

@@ -9,15 +9,24 @@ endif
 
 AM_CPPFLAGS += -I$(srcdir)/src/ext/trunnel -I$(srcdir)/src/trunnel
 
+TRUNNELINPUTS = \
+	src/trunnel/ed25519_cert.trunnel \
+	src/trunnel/link_handshake.trunnel \
+	src/trunnel/pwbox.trunnel
+
 TRUNNELSOURCES = \
-  src/ext/trunnel/trunnel.c \
-  src/trunnel/pwbox.c
+	src/ext/trunnel/trunnel.c \
+	src/trunnel/ed25519_cert.c \
+	src/trunnel/link_handshake.c \
+	src/trunnel/pwbox.c
 
 TRUNNELHEADERS = \
-  src/ext/trunnel/trunnel.h \
-  src/ext/trunnel/trunnel-impl.h \
-  src/trunnel/trunnel-local.h \
-  src/trunnel/pwbox.h
+	src/ext/trunnel/trunnel.h		\
+	src/ext/trunnel/trunnel-impl.h		\
+	src/trunnel/trunnel-local.h 		\
+	src/trunnel/ed25519_cert.h		\
+	src/trunnel/link_handshake.h	 	\
+	src/trunnel/pwbox.h
 
 src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES)
 src_trunnel_libor_trunnel_a_CPPFLAGS = -DTRUNNEL_LOCAL_H $(AM_CPPFLAGS)

+ 1885 - 0
src/trunnel/link_handshake.c

@@ -0,0 +1,1885 @@
+/* link_handshake.c -- generated by Trunnel v1.4-pre.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#include <stdlib.h>
+#include "trunnel-impl.h"
+
+#include "link_handshake.h"
+
+#define TRUNNEL_SET_ERROR_CODE(obj) \
+  do {                              \
+    (obj)->trunnel_error_code_ = 1; \
+  } while (0)
+
+#if defined(__COVERITY__) || defined(__clang_analyzer__)
+/* If we're runnning a static analysis tool, we don't want it to complain
+ * that some of our remaining-bytes checks are dead-code. */
+int linkhandshake_deadcode_dummy__ = 0;
+#define OR_DEADCODE_DUMMY || linkhandshake_deadcode_dummy__
+#else
+#define OR_DEADCODE_DUMMY
+#endif
+
+#define CHECK_REMAINING(nbytes, label)                           \
+  do {                                                           \
+    if (remaining < (nbytes) OR_DEADCODE_DUMMY) {                \
+      goto label;                                                \
+    }                                                            \
+  } while (0)
+
+auth_challenge_cell_t *
+auth_challenge_cell_new(void)
+{
+  auth_challenge_cell_t *val = trunnel_calloc(1, sizeof(auth_challenge_cell_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+auth_challenge_cell_clear(auth_challenge_cell_t *obj)
+{
+  (void) obj;
+  TRUNNEL_DYNARRAY_WIPE(&obj->methods);
+  TRUNNEL_DYNARRAY_CLEAR(&obj->methods);
+}
+
+void
+auth_challenge_cell_free(auth_challenge_cell_t *obj)
+{
+  if (obj == NULL)
+    return;
+  auth_challenge_cell_clear(obj);
+  trunnel_memwipe(obj, sizeof(auth_challenge_cell_t));
+  trunnel_free_(obj);
+}
+
+size_t
+auth_challenge_cell_getlen_challenge(const auth_challenge_cell_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth_challenge_cell_get_challenge(const auth_challenge_cell_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->challenge[idx];
+}
+
+int
+auth_challenge_cell_set_challenge(auth_challenge_cell_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->challenge[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth_challenge_cell_getarray_challenge(auth_challenge_cell_t *inp)
+{
+  return inp->challenge;
+}
+uint16_t
+auth_challenge_cell_get_n_methods(auth_challenge_cell_t *inp)
+{
+  return inp->n_methods;
+}
+int
+auth_challenge_cell_set_n_methods(auth_challenge_cell_t *inp, uint16_t val)
+{
+  inp->n_methods = val;
+  return 0;
+}
+size_t
+auth_challenge_cell_getlen_methods(const auth_challenge_cell_t *inp)
+{
+  return TRUNNEL_DYNARRAY_LEN(&inp->methods);
+}
+
+uint16_t
+auth_challenge_cell_get_methods(auth_challenge_cell_t *inp, size_t idx)
+{
+  return TRUNNEL_DYNARRAY_GET(&inp->methods, idx);
+}
+
+int
+auth_challenge_cell_set_methods(auth_challenge_cell_t *inp, size_t idx, uint16_t elt)
+{
+  TRUNNEL_DYNARRAY_SET(&inp->methods, idx, elt);
+  return 0;
+}
+int
+auth_challenge_cell_add_methods(auth_challenge_cell_t *inp, uint16_t elt)
+{
+#if SIZE_MAX >= UINT16_MAX
+  if (inp->methods.n_ == UINT16_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  TRUNNEL_DYNARRAY_ADD(uint16_t, &inp->methods, elt, {});
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+
+uint16_t *
+auth_challenge_cell_getarray_methods(auth_challenge_cell_t *inp)
+{
+  return inp->methods.elts_;
+}
+int
+auth_challenge_cell_setlen_methods(auth_challenge_cell_t *inp, size_t newlen)
+{
+  uint16_t *newptr;
+#if UINT16_MAX < SIZE_MAX
+  if (newlen > UINT16_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  newptr = trunnel_dynarray_setlen(&inp->methods.allocated_,
+                 &inp->methods.n_, inp->methods.elts_, newlen,
+                 sizeof(inp->methods.elts_[0]), (trunnel_free_fn_t) NULL,
+                 &inp->trunnel_error_code_);
+  if (newptr == NULL)
+    goto trunnel_alloc_failed;
+  inp->methods.elts_ = newptr;
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+const char *
+auth_challenge_cell_check(const auth_challenge_cell_t *obj)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  if (TRUNNEL_DYNARRAY_LEN(&obj->methods) != obj->n_methods)
+    return "Length mismatch for methods";
+  return NULL;
+}
+
+ssize_t
+auth_challenge_cell_encoded_len(const auth_challenge_cell_t *obj)
+{
+  ssize_t result = 0;
+
+  if (NULL != auth_challenge_cell_check(obj))
+     return -1;
+
+
+  /* Length of u8 challenge[32] */
+  result += 32;
+
+  /* Length of u16 n_methods */
+  result += 2;
+
+  /* Length of u16 methods[n_methods] */
+  result += 2 * TRUNNEL_DYNARRAY_LEN(&obj->methods);
+  return result;
+}
+int
+auth_challenge_cell_clear_errors(auth_challenge_cell_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+auth_challenge_cell_encode(uint8_t *output, const size_t avail, const auth_challenge_cell_t *obj)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = auth_challenge_cell_encoded_len(obj);
+#endif
+
+  if (NULL != (msg = auth_challenge_cell_check(obj)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u8 challenge[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->challenge, 32);
+  written += 32; ptr += 32;
+
+  /* Encode u16 n_methods */
+  trunnel_assert(written <= avail);
+  if (avail - written < 2)
+    goto truncated;
+  trunnel_set_uint16(ptr, trunnel_htons(obj->n_methods));
+  written += 2; ptr += 2;
+
+  /* Encode u16 methods[n_methods] */
+  {
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->methods); ++idx) {
+      trunnel_assert(written <= avail);
+      if (avail - written < 2)
+        goto truncated;
+      trunnel_set_uint16(ptr, trunnel_htons(TRUNNEL_DYNARRAY_GET(&obj->methods, idx)));
+      written += 2; ptr += 2;
+    }
+  }
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As auth_challenge_cell_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+auth_challenge_cell_parse_into(auth_challenge_cell_t *obj, const uint8_t *input, const size_t len_in)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+
+  /* Parse u8 challenge[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->challenge, ptr, 32);
+  remaining -= 32; ptr += 32;
+
+  /* Parse u16 n_methods */
+  CHECK_REMAINING(2, truncated);
+  obj->n_methods = trunnel_ntohs(trunnel_get_uint16(ptr));
+  remaining -= 2; ptr += 2;
+
+  /* Parse u16 methods[n_methods] */
+  TRUNNEL_DYNARRAY_EXPAND(uint16_t, &obj->methods, obj->n_methods, {});
+  {
+    uint16_t elt;
+    unsigned idx;
+    for (idx = 0; idx < obj->n_methods; ++idx) {
+      CHECK_REMAINING(2, truncated);
+      elt = trunnel_ntohs(trunnel_get_uint16(ptr));
+      remaining -= 2; ptr += 2;
+      TRUNNEL_DYNARRAY_ADD(uint16_t, &obj->methods, elt, {});
+    }
+  }
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+ trunnel_alloc_failed:
+  return -1;
+}
+
+ssize_t
+auth_challenge_cell_parse(auth_challenge_cell_t **output, const uint8_t *input, const size_t len_in)
+{
+  ssize_t result;
+  *output = auth_challenge_cell_new();
+  if (NULL == *output)
+    return -1;
+  result = auth_challenge_cell_parse_into(*output, input, len_in);
+  if (result < 0) {
+    auth_challenge_cell_free(*output);
+    *output = NULL;
+  }
+  return result;
+}
+auth_ctx_t *
+auth_ctx_new(void)
+{
+  auth_ctx_t *val = trunnel_calloc(1, sizeof(auth_ctx_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+auth_ctx_clear(auth_ctx_t *obj)
+{
+  (void) obj;
+}
+
+void
+auth_ctx_free(auth_ctx_t *obj)
+{
+  if (obj == NULL)
+    return;
+  auth_ctx_clear(obj);
+  trunnel_memwipe(obj, sizeof(auth_ctx_t));
+  trunnel_free_(obj);
+}
+
+uint8_t
+auth_ctx_get_is_ed(auth_ctx_t *inp)
+{
+  return inp->is_ed;
+}
+int
+auth_ctx_set_is_ed(auth_ctx_t *inp, uint8_t val)
+{
+  inp->is_ed = val;
+  return 0;
+}
+certs_cell_cert_t *
+certs_cell_cert_new(void)
+{
+  certs_cell_cert_t *val = trunnel_calloc(1, sizeof(certs_cell_cert_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+certs_cell_cert_clear(certs_cell_cert_t *obj)
+{
+  (void) obj;
+  TRUNNEL_DYNARRAY_WIPE(&obj->body);
+  TRUNNEL_DYNARRAY_CLEAR(&obj->body);
+}
+
+void
+certs_cell_cert_free(certs_cell_cert_t *obj)
+{
+  if (obj == NULL)
+    return;
+  certs_cell_cert_clear(obj);
+  trunnel_memwipe(obj, sizeof(certs_cell_cert_t));
+  trunnel_free_(obj);
+}
+
+uint8_t
+certs_cell_cert_get_cert_type(certs_cell_cert_t *inp)
+{
+  return inp->cert_type;
+}
+int
+certs_cell_cert_set_cert_type(certs_cell_cert_t *inp, uint8_t val)
+{
+  inp->cert_type = val;
+  return 0;
+}
+uint16_t
+certs_cell_cert_get_cert_len(certs_cell_cert_t *inp)
+{
+  return inp->cert_len;
+}
+int
+certs_cell_cert_set_cert_len(certs_cell_cert_t *inp, uint16_t val)
+{
+  inp->cert_len = val;
+  return 0;
+}
+size_t
+certs_cell_cert_getlen_body(const certs_cell_cert_t *inp)
+{
+  return TRUNNEL_DYNARRAY_LEN(&inp->body);
+}
+
+uint8_t
+certs_cell_cert_get_body(certs_cell_cert_t *inp, size_t idx)
+{
+  return TRUNNEL_DYNARRAY_GET(&inp->body, idx);
+}
+
+int
+certs_cell_cert_set_body(certs_cell_cert_t *inp, size_t idx, uint8_t elt)
+{
+  TRUNNEL_DYNARRAY_SET(&inp->body, idx, elt);
+  return 0;
+}
+int
+certs_cell_cert_add_body(certs_cell_cert_t *inp, uint8_t elt)
+{
+#if SIZE_MAX >= UINT16_MAX
+  if (inp->body.n_ == UINT16_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->body, elt, {});
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+
+uint8_t *
+certs_cell_cert_getarray_body(certs_cell_cert_t *inp)
+{
+  return inp->body.elts_;
+}
+int
+certs_cell_cert_setlen_body(certs_cell_cert_t *inp, size_t newlen)
+{
+  uint8_t *newptr;
+#if UINT16_MAX < SIZE_MAX
+  if (newlen > UINT16_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  newptr = trunnel_dynarray_setlen(&inp->body.allocated_,
+                 &inp->body.n_, inp->body.elts_, newlen,
+                 sizeof(inp->body.elts_[0]), (trunnel_free_fn_t) NULL,
+                 &inp->trunnel_error_code_);
+  if (newptr == NULL)
+    goto trunnel_alloc_failed;
+  inp->body.elts_ = newptr;
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+const char *
+certs_cell_cert_check(const certs_cell_cert_t *obj)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  if (TRUNNEL_DYNARRAY_LEN(&obj->body) != obj->cert_len)
+    return "Length mismatch for body";
+  return NULL;
+}
+
+ssize_t
+certs_cell_cert_encoded_len(const certs_cell_cert_t *obj)
+{
+  ssize_t result = 0;
+
+  if (NULL != certs_cell_cert_check(obj))
+     return -1;
+
+
+  /* Length of u8 cert_type */
+  result += 1;
+
+  /* Length of u16 cert_len */
+  result += 2;
+
+  /* Length of u8 body[cert_len] */
+  result += TRUNNEL_DYNARRAY_LEN(&obj->body);
+  return result;
+}
+int
+certs_cell_cert_clear_errors(certs_cell_cert_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+certs_cell_cert_encode(uint8_t *output, const size_t avail, const certs_cell_cert_t *obj)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = certs_cell_cert_encoded_len(obj);
+#endif
+
+  if (NULL != (msg = certs_cell_cert_check(obj)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u8 cert_type */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->cert_type));
+  written += 1; ptr += 1;
+
+  /* Encode u16 cert_len */
+  trunnel_assert(written <= avail);
+  if (avail - written < 2)
+    goto truncated;
+  trunnel_set_uint16(ptr, trunnel_htons(obj->cert_len));
+  written += 2; ptr += 2;
+
+  /* Encode u8 body[cert_len] */
+  {
+    size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->body);
+    trunnel_assert(obj->cert_len == elt_len);
+    trunnel_assert(written <= avail);
+    if (avail - written < elt_len)
+      goto truncated;
+    memcpy(ptr, obj->body.elts_, elt_len);
+    written += elt_len; ptr += elt_len;
+  }
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As certs_cell_cert_parse(), but do not allocate the output object.
+ */
+static ssize_t
+certs_cell_cert_parse_into(certs_cell_cert_t *obj, const uint8_t *input, const size_t len_in)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+
+  /* Parse u8 cert_type */
+  CHECK_REMAINING(1, truncated);
+  obj->cert_type = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+
+  /* Parse u16 cert_len */
+  CHECK_REMAINING(2, truncated);
+  obj->cert_len = trunnel_ntohs(trunnel_get_uint16(ptr));
+  remaining -= 2; ptr += 2;
+
+  /* Parse u8 body[cert_len] */
+  CHECK_REMAINING(obj->cert_len, truncated);
+  TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->body, obj->cert_len, {});
+  obj->body.n_ = obj->cert_len;
+  memcpy(obj->body.elts_, ptr, obj->cert_len);
+  ptr += obj->cert_len; remaining -= obj->cert_len;
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+ trunnel_alloc_failed:
+  return -1;
+}
+
+ssize_t
+certs_cell_cert_parse(certs_cell_cert_t **output, const uint8_t *input, const size_t len_in)
+{
+  ssize_t result;
+  *output = certs_cell_cert_new();
+  if (NULL == *output)
+    return -1;
+  result = certs_cell_cert_parse_into(*output, input, len_in);
+  if (result < 0) {
+    certs_cell_cert_free(*output);
+    *output = NULL;
+  }
+  return result;
+}
+rsa_ed_crosscert_t *
+rsa_ed_crosscert_new(void)
+{
+  rsa_ed_crosscert_t *val = trunnel_calloc(1, sizeof(rsa_ed_crosscert_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+rsa_ed_crosscert_clear(rsa_ed_crosscert_t *obj)
+{
+  (void) obj;
+  TRUNNEL_DYNARRAY_WIPE(&obj->sig);
+  TRUNNEL_DYNARRAY_CLEAR(&obj->sig);
+}
+
+void
+rsa_ed_crosscert_free(rsa_ed_crosscert_t *obj)
+{
+  if (obj == NULL)
+    return;
+  rsa_ed_crosscert_clear(obj);
+  trunnel_memwipe(obj, sizeof(rsa_ed_crosscert_t));
+  trunnel_free_(obj);
+}
+
+size_t
+rsa_ed_crosscert_getlen_ed_key(const rsa_ed_crosscert_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+rsa_ed_crosscert_get_ed_key(const rsa_ed_crosscert_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->ed_key[idx];
+}
+
+int
+rsa_ed_crosscert_set_ed_key(rsa_ed_crosscert_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->ed_key[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+rsa_ed_crosscert_getarray_ed_key(rsa_ed_crosscert_t *inp)
+{
+  return inp->ed_key;
+}
+uint32_t
+rsa_ed_crosscert_get_expiration(rsa_ed_crosscert_t *inp)
+{
+  return inp->expiration;
+}
+int
+rsa_ed_crosscert_set_expiration(rsa_ed_crosscert_t *inp, uint32_t val)
+{
+  inp->expiration = val;
+  return 0;
+}
+const uint8_t *
+rsa_ed_crosscert_get_end_of_signed(const rsa_ed_crosscert_t *inp)
+{
+  return inp->end_of_signed;
+}
+uint8_t
+rsa_ed_crosscert_get_sig_len(rsa_ed_crosscert_t *inp)
+{
+  return inp->sig_len;
+}
+int
+rsa_ed_crosscert_set_sig_len(rsa_ed_crosscert_t *inp, uint8_t val)
+{
+  inp->sig_len = val;
+  return 0;
+}
+size_t
+rsa_ed_crosscert_getlen_sig(const rsa_ed_crosscert_t *inp)
+{
+  return TRUNNEL_DYNARRAY_LEN(&inp->sig);
+}
+
+uint8_t
+rsa_ed_crosscert_get_sig(rsa_ed_crosscert_t *inp, size_t idx)
+{
+  return TRUNNEL_DYNARRAY_GET(&inp->sig, idx);
+}
+
+int
+rsa_ed_crosscert_set_sig(rsa_ed_crosscert_t *inp, size_t idx, uint8_t elt)
+{
+  TRUNNEL_DYNARRAY_SET(&inp->sig, idx, elt);
+  return 0;
+}
+int
+rsa_ed_crosscert_add_sig(rsa_ed_crosscert_t *inp, uint8_t elt)
+{
+#if SIZE_MAX >= UINT8_MAX
+  if (inp->sig.n_ == UINT8_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->sig, elt, {});
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+
+uint8_t *
+rsa_ed_crosscert_getarray_sig(rsa_ed_crosscert_t *inp)
+{
+  return inp->sig.elts_;
+}
+int
+rsa_ed_crosscert_setlen_sig(rsa_ed_crosscert_t *inp, size_t newlen)
+{
+  uint8_t *newptr;
+#if UINT8_MAX < SIZE_MAX
+  if (newlen > UINT8_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  newptr = trunnel_dynarray_setlen(&inp->sig.allocated_,
+                 &inp->sig.n_, inp->sig.elts_, newlen,
+                 sizeof(inp->sig.elts_[0]), (trunnel_free_fn_t) NULL,
+                 &inp->trunnel_error_code_);
+  if (newptr == NULL)
+    goto trunnel_alloc_failed;
+  inp->sig.elts_ = newptr;
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+const char *
+rsa_ed_crosscert_check(const rsa_ed_crosscert_t *obj)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  if (TRUNNEL_DYNARRAY_LEN(&obj->sig) != obj->sig_len)
+    return "Length mismatch for sig";
+  return NULL;
+}
+
+ssize_t
+rsa_ed_crosscert_encoded_len(const rsa_ed_crosscert_t *obj)
+{
+  ssize_t result = 0;
+
+  if (NULL != rsa_ed_crosscert_check(obj))
+     return -1;
+
+
+  /* Length of u8 ed_key[32] */
+  result += 32;
+
+  /* Length of u32 expiration */
+  result += 4;
+
+  /* Length of u8 sig_len */
+  result += 1;
+
+  /* Length of u8 sig[sig_len] */
+  result += TRUNNEL_DYNARRAY_LEN(&obj->sig);
+  return result;
+}
+int
+rsa_ed_crosscert_clear_errors(rsa_ed_crosscert_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+rsa_ed_crosscert_encode(uint8_t *output, const size_t avail, const rsa_ed_crosscert_t *obj)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = rsa_ed_crosscert_encoded_len(obj);
+#endif
+
+  if (NULL != (msg = rsa_ed_crosscert_check(obj)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u8 ed_key[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->ed_key, 32);
+  written += 32; ptr += 32;
+
+  /* Encode u32 expiration */
+  trunnel_assert(written <= avail);
+  if (avail - written < 4)
+    goto truncated;
+  trunnel_set_uint32(ptr, trunnel_htonl(obj->expiration));
+  written += 4; ptr += 4;
+
+  /* Encode u8 sig_len */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->sig_len));
+  written += 1; ptr += 1;
+
+  /* Encode u8 sig[sig_len] */
+  {
+    size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->sig);
+    trunnel_assert(obj->sig_len == elt_len);
+    trunnel_assert(written <= avail);
+    if (avail - written < elt_len)
+      goto truncated;
+    memcpy(ptr, obj->sig.elts_, elt_len);
+    written += elt_len; ptr += elt_len;
+  }
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As rsa_ed_crosscert_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+rsa_ed_crosscert_parse_into(rsa_ed_crosscert_t *obj, const uint8_t *input, const size_t len_in)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+
+  /* Parse u8 ed_key[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->ed_key, ptr, 32);
+  remaining -= 32; ptr += 32;
+
+  /* Parse u32 expiration */
+  CHECK_REMAINING(4, truncated);
+  obj->expiration = trunnel_ntohl(trunnel_get_uint32(ptr));
+  remaining -= 4; ptr += 4;
+  obj->end_of_signed = ptr;
+
+  /* Parse u8 sig_len */
+  CHECK_REMAINING(1, truncated);
+  obj->sig_len = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+
+  /* Parse u8 sig[sig_len] */
+  CHECK_REMAINING(obj->sig_len, truncated);
+  TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->sig, obj->sig_len, {});
+  obj->sig.n_ = obj->sig_len;
+  memcpy(obj->sig.elts_, ptr, obj->sig_len);
+  ptr += obj->sig_len; remaining -= obj->sig_len;
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+ trunnel_alloc_failed:
+  return -1;
+}
+
+ssize_t
+rsa_ed_crosscert_parse(rsa_ed_crosscert_t **output, const uint8_t *input, const size_t len_in)
+{
+  ssize_t result;
+  *output = rsa_ed_crosscert_new();
+  if (NULL == *output)
+    return -1;
+  result = rsa_ed_crosscert_parse_into(*output, input, len_in);
+  if (result < 0) {
+    rsa_ed_crosscert_free(*output);
+    *output = NULL;
+  }
+  return result;
+}
+auth1_t *
+auth1_new(void)
+{
+  auth1_t *val = trunnel_calloc(1, sizeof(auth1_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+auth1_clear(auth1_t *obj)
+{
+  (void) obj;
+  TRUNNEL_DYNARRAY_WIPE(&obj->sig);
+  TRUNNEL_DYNARRAY_CLEAR(&obj->sig);
+}
+
+void
+auth1_free(auth1_t *obj)
+{
+  if (obj == NULL)
+    return;
+  auth1_clear(obj);
+  trunnel_memwipe(obj, sizeof(auth1_t));
+  trunnel_free_(obj);
+}
+
+size_t
+auth1_getlen_type(const auth1_t *inp)
+{
+  (void)inp;  return 8;
+}
+
+uint8_t
+auth1_get_type(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 8);
+  return inp->type[idx];
+}
+
+int
+auth1_set_type(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 8);
+  inp->type[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_type(auth1_t *inp)
+{
+  return inp->type;
+}
+size_t
+auth1_getlen_cid(const auth1_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth1_get_cid(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->cid[idx];
+}
+
+int
+auth1_set_cid(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->cid[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_cid(auth1_t *inp)
+{
+  return inp->cid;
+}
+size_t
+auth1_getlen_sid(const auth1_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth1_get_sid(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->sid[idx];
+}
+
+int
+auth1_set_sid(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->sid[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_sid(auth1_t *inp)
+{
+  return inp->sid;
+}
+size_t
+auth1_getlen_u1_cid_ed(const auth1_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth1_get_u1_cid_ed(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->u1_cid_ed[idx];
+}
+
+int
+auth1_set_u1_cid_ed(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->u1_cid_ed[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_u1_cid_ed(auth1_t *inp)
+{
+  return inp->u1_cid_ed;
+}
+size_t
+auth1_getlen_u1_sid_ed(const auth1_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth1_get_u1_sid_ed(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->u1_sid_ed[idx];
+}
+
+int
+auth1_set_u1_sid_ed(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->u1_sid_ed[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_u1_sid_ed(auth1_t *inp)
+{
+  return inp->u1_sid_ed;
+}
+size_t
+auth1_getlen_slog(const auth1_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth1_get_slog(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->slog[idx];
+}
+
+int
+auth1_set_slog(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->slog[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_slog(auth1_t *inp)
+{
+  return inp->slog;
+}
+size_t
+auth1_getlen_clog(const auth1_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth1_get_clog(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->clog[idx];
+}
+
+int
+auth1_set_clog(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->clog[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_clog(auth1_t *inp)
+{
+  return inp->clog;
+}
+size_t
+auth1_getlen_scert(const auth1_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth1_get_scert(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->scert[idx];
+}
+
+int
+auth1_set_scert(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->scert[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_scert(auth1_t *inp)
+{
+  return inp->scert;
+}
+size_t
+auth1_getlen_tlssecrets(const auth1_t *inp)
+{
+  (void)inp;  return 32;
+}
+
+uint8_t
+auth1_get_tlssecrets(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 32);
+  return inp->tlssecrets[idx];
+}
+
+int
+auth1_set_tlssecrets(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 32);
+  inp->tlssecrets[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_tlssecrets(auth1_t *inp)
+{
+  return inp->tlssecrets;
+}
+const uint8_t *
+auth1_get_end_of_fixed_part(const auth1_t *inp)
+{
+  return inp->end_of_fixed_part;
+}
+size_t
+auth1_getlen_rand(const auth1_t *inp)
+{
+  (void)inp;  return 24;
+}
+
+uint8_t
+auth1_get_rand(const auth1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < 24);
+  return inp->rand[idx];
+}
+
+int
+auth1_set_rand(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < 24);
+  inp->rand[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+auth1_getarray_rand(auth1_t *inp)
+{
+  return inp->rand;
+}
+const uint8_t *
+auth1_get_end_of_signed(const auth1_t *inp)
+{
+  return inp->end_of_signed;
+}
+size_t
+auth1_getlen_sig(const auth1_t *inp)
+{
+  return TRUNNEL_DYNARRAY_LEN(&inp->sig);
+}
+
+uint8_t
+auth1_get_sig(auth1_t *inp, size_t idx)
+{
+  return TRUNNEL_DYNARRAY_GET(&inp->sig, idx);
+}
+
+int
+auth1_set_sig(auth1_t *inp, size_t idx, uint8_t elt)
+{
+  TRUNNEL_DYNARRAY_SET(&inp->sig, idx, elt);
+  return 0;
+}
+int
+auth1_add_sig(auth1_t *inp, uint8_t elt)
+{
+  TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->sig, elt, {});
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+
+uint8_t *
+auth1_getarray_sig(auth1_t *inp)
+{
+  return inp->sig.elts_;
+}
+int
+auth1_setlen_sig(auth1_t *inp, size_t newlen)
+{
+  uint8_t *newptr;
+  newptr = trunnel_dynarray_setlen(&inp->sig.allocated_,
+                 &inp->sig.n_, inp->sig.elts_, newlen,
+                 sizeof(inp->sig.elts_[0]), (trunnel_free_fn_t) NULL,
+                 &inp->trunnel_error_code_);
+  if (newptr == NULL)
+    goto trunnel_alloc_failed;
+  inp->sig.elts_ = newptr;
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+const char *
+auth1_check(const auth1_t *obj, const auth_ctx_t *auth_ctx_ctx)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  if (auth_ctx_ctx == NULL)
+    return "Context was NULL";
+  switch (auth_ctx_ctx->is_ed) {
+
+    case 0:
+      break;
+
+    case 1:
+      break;
+
+    default:
+        return "Bad tag for union";
+      break;
+  }
+  return NULL;
+}
+
+ssize_t
+auth1_encoded_len(const auth1_t *obj, const auth_ctx_t *auth_ctx_ctx)
+{
+  ssize_t result = 0;
+
+  if (NULL != auth1_check(obj, auth_ctx_ctx))
+     return -1;
+
+
+  /* Length of u8 type[8] */
+  result += 8;
+
+  /* Length of u8 cid[32] */
+  result += 32;
+
+  /* Length of u8 sid[32] */
+  result += 32;
+  switch (auth_ctx_ctx->is_ed) {
+
+    case 0:
+      break;
+
+    case 1:
+
+      /* Length of u8 u1_cid_ed[32] */
+      result += 32;
+
+      /* Length of u8 u1_sid_ed[32] */
+      result += 32;
+      break;
+
+    default:
+      trunnel_assert(0);
+      break;
+  }
+
+  /* Length of u8 slog[32] */
+  result += 32;
+
+  /* Length of u8 clog[32] */
+  result += 32;
+
+  /* Length of u8 scert[32] */
+  result += 32;
+
+  /* Length of u8 tlssecrets[32] */
+  result += 32;
+
+  /* Length of u8 rand[24] */
+  result += 24;
+
+  /* Length of u8 sig[] */
+  result += TRUNNEL_DYNARRAY_LEN(&obj->sig);
+  return result;
+}
+int
+auth1_clear_errors(auth1_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+auth1_encode(uint8_t *output, const size_t avail, const auth1_t *obj, const auth_ctx_t *auth_ctx_ctx)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = auth1_encoded_len(obj, auth_ctx_ctx);
+#endif
+
+  if (NULL != (msg = auth1_check(obj, auth_ctx_ctx)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u8 type[8] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 8)
+    goto truncated;
+  memcpy(ptr, obj->type, 8);
+  written += 8; ptr += 8;
+
+  /* Encode u8 cid[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->cid, 32);
+  written += 32; ptr += 32;
+
+  /* Encode u8 sid[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->sid, 32);
+  written += 32; ptr += 32;
+
+  /* Encode union u1[auth_ctx.is_ed] */
+  trunnel_assert(written <= avail);
+  switch (auth_ctx_ctx->is_ed) {
+
+    case 0:
+      break;
+
+    case 1:
+
+      /* Encode u8 u1_cid_ed[32] */
+      trunnel_assert(written <= avail);
+      if (avail - written < 32)
+        goto truncated;
+      memcpy(ptr, obj->u1_cid_ed, 32);
+      written += 32; ptr += 32;
+
+      /* Encode u8 u1_sid_ed[32] */
+      trunnel_assert(written <= avail);
+      if (avail - written < 32)
+        goto truncated;
+      memcpy(ptr, obj->u1_sid_ed, 32);
+      written += 32; ptr += 32;
+      break;
+
+    default:
+      trunnel_assert(0);
+      break;
+  }
+
+  /* Encode u8 slog[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->slog, 32);
+  written += 32; ptr += 32;
+
+  /* Encode u8 clog[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->clog, 32);
+  written += 32; ptr += 32;
+
+  /* Encode u8 scert[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->scert, 32);
+  written += 32; ptr += 32;
+
+  /* Encode u8 tlssecrets[32] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 32)
+    goto truncated;
+  memcpy(ptr, obj->tlssecrets, 32);
+  written += 32; ptr += 32;
+
+  /* Encode u8 rand[24] */
+  trunnel_assert(written <= avail);
+  if (avail - written < 24)
+    goto truncated;
+  memcpy(ptr, obj->rand, 24);
+  written += 24; ptr += 24;
+
+  /* Encode u8 sig[] */
+  {
+    size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->sig);
+    trunnel_assert(written <= avail);
+    if (avail - written < elt_len)
+      goto truncated;
+    memcpy(ptr, obj->sig.elts_, elt_len);
+    written += elt_len; ptr += elt_len;
+  }
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As auth1_parse(), but do not allocate the output object.
+ */
+static ssize_t
+auth1_parse_into(auth1_t *obj, const uint8_t *input, const size_t len_in, const auth_ctx_t *auth_ctx_ctx)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+  if (auth_ctx_ctx == NULL)
+    return -1;
+
+  /* Parse u8 type[8] */
+  CHECK_REMAINING(8, truncated);
+  memcpy(obj->type, ptr, 8);
+  remaining -= 8; ptr += 8;
+
+  /* Parse u8 cid[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->cid, ptr, 32);
+  remaining -= 32; ptr += 32;
+
+  /* Parse u8 sid[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->sid, ptr, 32);
+  remaining -= 32; ptr += 32;
+
+  /* Parse union u1[auth_ctx.is_ed] */
+  switch (auth_ctx_ctx->is_ed) {
+
+    case 0:
+      break;
+
+    case 1:
+
+      /* Parse u8 u1_cid_ed[32] */
+      CHECK_REMAINING(32, truncated);
+      memcpy(obj->u1_cid_ed, ptr, 32);
+      remaining -= 32; ptr += 32;
+
+      /* Parse u8 u1_sid_ed[32] */
+      CHECK_REMAINING(32, truncated);
+      memcpy(obj->u1_sid_ed, ptr, 32);
+      remaining -= 32; ptr += 32;
+      break;
+
+    default:
+      goto fail;
+      break;
+  }
+
+  /* Parse u8 slog[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->slog, ptr, 32);
+  remaining -= 32; ptr += 32;
+
+  /* Parse u8 clog[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->clog, ptr, 32);
+  remaining -= 32; ptr += 32;
+
+  /* Parse u8 scert[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->scert, ptr, 32);
+  remaining -= 32; ptr += 32;
+
+  /* Parse u8 tlssecrets[32] */
+  CHECK_REMAINING(32, truncated);
+  memcpy(obj->tlssecrets, ptr, 32);
+  remaining -= 32; ptr += 32;
+  obj->end_of_fixed_part = ptr;
+
+  /* Parse u8 rand[24] */
+  CHECK_REMAINING(24, truncated);
+  memcpy(obj->rand, ptr, 24);
+  remaining -= 24; ptr += 24;
+  obj->end_of_signed = ptr;
+
+  /* Parse u8 sig[] */
+  TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->sig, remaining, {});
+  obj->sig.n_ = remaining;
+  memcpy(obj->sig.elts_, ptr, remaining);
+  ptr += remaining; remaining -= remaining;
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+ trunnel_alloc_failed:
+  return -1;
+ fail:
+  result = -1;
+  return result;
+}
+
+ssize_t
+auth1_parse(auth1_t **output, const uint8_t *input, const size_t len_in, const auth_ctx_t *auth_ctx_ctx)
+{
+  ssize_t result;
+  *output = auth1_new();
+  if (NULL == *output)
+    return -1;
+  result = auth1_parse_into(*output, input, len_in, auth_ctx_ctx);
+  if (result < 0) {
+    auth1_free(*output);
+    *output = NULL;
+  }
+  return result;
+}
+certs_cell_t *
+certs_cell_new(void)
+{
+  certs_cell_t *val = trunnel_calloc(1, sizeof(certs_cell_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+certs_cell_clear(certs_cell_t *obj)
+{
+  (void) obj;
+  {
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->certs); ++idx) {
+      certs_cell_cert_free(TRUNNEL_DYNARRAY_GET(&obj->certs, idx));
+    }
+  }
+  TRUNNEL_DYNARRAY_WIPE(&obj->certs);
+  TRUNNEL_DYNARRAY_CLEAR(&obj->certs);
+}
+
+void
+certs_cell_free(certs_cell_t *obj)
+{
+  if (obj == NULL)
+    return;
+  certs_cell_clear(obj);
+  trunnel_memwipe(obj, sizeof(certs_cell_t));
+  trunnel_free_(obj);
+}
+
+uint8_t
+certs_cell_get_n_certs(certs_cell_t *inp)
+{
+  return inp->n_certs;
+}
+int
+certs_cell_set_n_certs(certs_cell_t *inp, uint8_t val)
+{
+  inp->n_certs = val;
+  return 0;
+}
+size_t
+certs_cell_getlen_certs(const certs_cell_t *inp)
+{
+  return TRUNNEL_DYNARRAY_LEN(&inp->certs);
+}
+
+struct certs_cell_cert_st *
+certs_cell_get_certs(certs_cell_t *inp, size_t idx)
+{
+  return TRUNNEL_DYNARRAY_GET(&inp->certs, idx);
+}
+
+int
+certs_cell_set_certs(certs_cell_t *inp, size_t idx, struct certs_cell_cert_st * elt)
+{
+  certs_cell_cert_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->certs, idx);
+  if (oldval && oldval != elt)
+    certs_cell_cert_free(oldval);
+  return certs_cell_set0_certs(inp, idx, elt);
+}
+int
+certs_cell_set0_certs(certs_cell_t *inp, size_t idx, struct certs_cell_cert_st * elt)
+{
+  TRUNNEL_DYNARRAY_SET(&inp->certs, idx, elt);
+  return 0;
+}
+int
+certs_cell_add_certs(certs_cell_t *inp, struct certs_cell_cert_st * elt)
+{
+#if SIZE_MAX >= UINT8_MAX
+  if (inp->certs.n_ == UINT8_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  TRUNNEL_DYNARRAY_ADD(struct certs_cell_cert_st *, &inp->certs, elt, {});
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+
+struct certs_cell_cert_st * *
+certs_cell_getarray_certs(certs_cell_t *inp)
+{
+  return inp->certs.elts_;
+}
+int
+certs_cell_setlen_certs(certs_cell_t *inp, size_t newlen)
+{
+  struct certs_cell_cert_st * *newptr;
+#if UINT8_MAX < SIZE_MAX
+  if (newlen > UINT8_MAX)
+    goto trunnel_alloc_failed;
+#endif
+  newptr = trunnel_dynarray_setlen(&inp->certs.allocated_,
+                 &inp->certs.n_, inp->certs.elts_, newlen,
+                 sizeof(inp->certs.elts_[0]), (trunnel_free_fn_t) certs_cell_cert_free,
+                 &inp->trunnel_error_code_);
+  if (newptr == NULL)
+    goto trunnel_alloc_failed;
+  inp->certs.elts_ = newptr;
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+const char *
+certs_cell_check(const certs_cell_t *obj)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  {
+    const char *msg;
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->certs); ++idx) {
+      if (NULL != (msg = certs_cell_cert_check(TRUNNEL_DYNARRAY_GET(&obj->certs, idx))))
+        return msg;
+    }
+  }
+  if (TRUNNEL_DYNARRAY_LEN(&obj->certs) != obj->n_certs)
+    return "Length mismatch for certs";
+  return NULL;
+}
+
+ssize_t
+certs_cell_encoded_len(const certs_cell_t *obj)
+{
+  ssize_t result = 0;
+
+  if (NULL != certs_cell_check(obj))
+     return -1;
+
+
+  /* Length of u8 n_certs */
+  result += 1;
+
+  /* Length of struct certs_cell_cert certs[n_certs] */
+  {
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->certs); ++idx) {
+      result += certs_cell_cert_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->certs, idx));
+    }
+  }
+  return result;
+}
+int
+certs_cell_clear_errors(certs_cell_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+certs_cell_encode(uint8_t *output, const size_t avail, const certs_cell_t *obj)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = certs_cell_encoded_len(obj);
+#endif
+
+  if (NULL != (msg = certs_cell_check(obj)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u8 n_certs */
+  trunnel_assert(written <= avail);
+  if (avail - written < 1)
+    goto truncated;
+  trunnel_set_uint8(ptr, (obj->n_certs));
+  written += 1; ptr += 1;
+
+  /* Encode struct certs_cell_cert certs[n_certs] */
+  {
+
+    unsigned idx;
+    for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->certs); ++idx) {
+      trunnel_assert(written <= avail);
+      result = certs_cell_cert_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->certs, idx));
+      if (result < 0)
+        goto fail; /* XXXXXXX !*/
+      written += result; ptr += result;
+    }
+  }
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As certs_cell_parse(), but do not allocate the output object.
+ */
+static ssize_t
+certs_cell_parse_into(certs_cell_t *obj, const uint8_t *input, const size_t len_in)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+
+  /* Parse u8 n_certs */
+  CHECK_REMAINING(1, truncated);
+  obj->n_certs = (trunnel_get_uint8(ptr));
+  remaining -= 1; ptr += 1;
+
+  /* Parse struct certs_cell_cert certs[n_certs] */
+  TRUNNEL_DYNARRAY_EXPAND(certs_cell_cert_t *, &obj->certs, obj->n_certs, {});
+  {
+    certs_cell_cert_t * elt;
+    unsigned idx;
+    for (idx = 0; idx < obj->n_certs; ++idx) {
+      result = certs_cell_cert_parse(&elt, ptr, remaining);
+      if (result < 0)
+        goto relay_fail;
+      trunnel_assert((size_t)result <= remaining);
+      remaining -= result; ptr += result;
+      TRUNNEL_DYNARRAY_ADD(certs_cell_cert_t *, &obj->certs, elt, {certs_cell_cert_free(elt);});
+    }
+  }
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+ relay_fail:
+  if (result >= 0) result = -1;
+  return result;
+ trunnel_alloc_failed:
+  return -1;
+}
+
+ssize_t
+certs_cell_parse(certs_cell_t **output, const uint8_t *input, const size_t len_in)
+{
+  ssize_t result;
+  *output = certs_cell_new();
+  if (NULL == *output)
+    return -1;
+  result = certs_cell_parse_into(*output, input, len_in);
+  if (result < 0) {
+    certs_cell_free(*output);
+    *output = NULL;
+  }
+  return result;
+}

+ 654 - 0
src/trunnel/link_handshake.h

@@ -0,0 +1,654 @@
+/* link_handshake.h -- generated by by Trunnel v1.4-pre.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#ifndef TRUNNEL_LINK_HANDSHAKE_H
+#define TRUNNEL_LINK_HANDSHAKE_H
+
+#include <stdint.h>
+#include "trunnel.h"
+
+#define CERTTYPE_RSA1024_ID_LINK 1
+#define CERTTYPE_RSA1024_ID_ID 2
+#define CERTTYPE_RSA1024_ID_AUTH 3
+#define CERTTYPE_ED_ID_SIGN 4
+#define CERTTYPE_ED_SIGN_LINK 5
+#define CERTTYPE_ED_SIGN_AUTH 6
+#define CERTTYPE_RSA1024_ID_EDID 7
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_AUTH_CHALLENGE_CELL)
+struct auth_challenge_cell_st {
+  uint8_t challenge[32];
+  uint16_t n_methods;
+  TRUNNEL_DYNARRAY_HEAD(, uint16_t) methods;
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct auth_challenge_cell_st auth_challenge_cell_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_AUTH_CTX)
+struct auth_ctx_st {
+  uint8_t is_ed;
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct auth_ctx_st auth_ctx_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CERTS_CELL_CERT)
+struct certs_cell_cert_st {
+  uint8_t cert_type;
+  uint16_t cert_len;
+  TRUNNEL_DYNARRAY_HEAD(, uint8_t) body;
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct certs_cell_cert_st certs_cell_cert_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_RSA_ED_CROSSCERT)
+struct rsa_ed_crosscert_st {
+  uint8_t ed_key[32];
+  uint32_t expiration;
+  const uint8_t *end_of_signed;
+  uint8_t sig_len;
+  TRUNNEL_DYNARRAY_HEAD(, uint8_t) sig;
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct rsa_ed_crosscert_st rsa_ed_crosscert_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_AUTH1)
+struct auth1_st {
+  uint8_t type[8];
+  uint8_t cid[32];
+  uint8_t sid[32];
+  uint8_t u1_cid_ed[32];
+  uint8_t u1_sid_ed[32];
+  uint8_t slog[32];
+  uint8_t clog[32];
+  uint8_t scert[32];
+  uint8_t tlssecrets[32];
+  const uint8_t *end_of_fixed_part;
+  uint8_t rand[24];
+  const uint8_t *end_of_signed;
+  TRUNNEL_DYNARRAY_HEAD(, uint8_t) sig;
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct auth1_st auth1_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CERTS_CELL)
+struct certs_cell_st {
+  uint8_t n_certs;
+  TRUNNEL_DYNARRAY_HEAD(, struct certs_cell_cert_st *) certs;
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct certs_cell_st certs_cell_t;
+/** Return a newly allocated auth_challenge_cell with all elements set
+ * to zero.
+ */
+auth_challenge_cell_t *auth_challenge_cell_new(void);
+/** Release all storage held by the auth_challenge_cell in 'victim'.
+ * (Do nothing if 'victim' is NULL.)
+ */
+void auth_challenge_cell_free(auth_challenge_cell_t *victim);
+/** Try to parse a auth_challenge_cell from the buffer in 'input',
+ * using up to 'len_in' bytes from the input buffer. On success,
+ * return the number of bytes consumed and set *output to the newly
+ * allocated auth_challenge_cell_t. On failure, return -2 if the input
+ * appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t auth_challenge_cell_parse(auth_challenge_cell_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * auth_challenge_cell in 'obj'. On failure, return a negative value.
+ * Note that this value may be an overestimate, and can even be an
+ * underestimate for certain unencodeable objects.
+ */
+ssize_t auth_challenge_cell_encoded_len(const auth_challenge_cell_t *obj);
+/** Try to encode the auth_challenge_cell from 'input' into the buffer
+ * at 'output', using up to 'avail' bytes of the output buffer. On
+ * success, return the number of bytes used. On failure, return -2 if
+ * the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t auth_challenge_cell_encode(uint8_t *output, const size_t avail, const auth_challenge_cell_t *input);
+/** Check whether the internal state of the auth_challenge_cell in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *auth_challenge_cell_check(const auth_challenge_cell_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int auth_challenge_cell_clear_errors(auth_challenge_cell_t *obj);
+/** Return the (constant) length of the array holding the challenge
+ * field of the auth_challenge_cell_t in 'inp'.
+ */
+size_t auth_challenge_cell_getlen_challenge(const auth_challenge_cell_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * challenge of the auth_challenge_cell_t in 'inp'.
+ */
+uint8_t auth_challenge_cell_get_challenge(const auth_challenge_cell_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * challenge of the auth_challenge_cell_t in 'inp', so that it will
+ * hold the value 'elt'.
+ */
+int auth_challenge_cell_set_challenge(auth_challenge_cell_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field challenge of 'inp'.
+ */
+uint8_t * auth_challenge_cell_getarray_challenge(auth_challenge_cell_t *inp);
+/** Return the value of the n_methods field of the
+ * auth_challenge_cell_t in 'inp'
+ */
+uint16_t auth_challenge_cell_get_n_methods(auth_challenge_cell_t *inp);
+/** Set the value of the n_methods field of the auth_challenge_cell_t
+ * in 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int auth_challenge_cell_set_n_methods(auth_challenge_cell_t *inp, uint16_t val);
+/** Return the length of the dynamic array holding the methods field
+ * of the auth_challenge_cell_t in 'inp'.
+ */
+size_t auth_challenge_cell_getlen_methods(const auth_challenge_cell_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * methods of the auth_challenge_cell_t in 'inp'.
+ */
+uint16_t auth_challenge_cell_get_methods(auth_challenge_cell_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * methods of the auth_challenge_cell_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int auth_challenge_cell_set_methods(auth_challenge_cell_t *inp, size_t idx, uint16_t elt);
+/** Append a new element 'elt' to the dynamic array field methods of
+ * the auth_challenge_cell_t in 'inp'.
+ */
+int auth_challenge_cell_add_methods(auth_challenge_cell_t *inp, uint16_t elt);
+/** Return a pointer to the variable-length array field methods of
+ * 'inp'.
+ */
+uint16_t * auth_challenge_cell_getarray_methods(auth_challenge_cell_t *inp);
+/** Change the length of the variable-length array field methods of
+ * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int auth_challenge_cell_setlen_methods(auth_challenge_cell_t *inp, size_t newlen);
+/** Return a newly allocated auth_ctx with all elements set to zero.
+ */
+auth_ctx_t *auth_ctx_new(void);
+/** Release all storage held by the auth_ctx in 'victim'. (Do nothing
+ * if 'victim' is NULL.)
+ */
+void auth_ctx_free(auth_ctx_t *victim);
+/** Return the value of the is_ed field of the auth_ctx_t in 'inp'
+ */
+uint8_t auth_ctx_get_is_ed(auth_ctx_t *inp);
+/** Set the value of the is_ed field of the auth_ctx_t in 'inp' to
+ * 'val'. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int auth_ctx_set_is_ed(auth_ctx_t *inp, uint8_t val);
+/** Return a newly allocated certs_cell_cert with all elements set to
+ * zero.
+ */
+certs_cell_cert_t *certs_cell_cert_new(void);
+/** Release all storage held by the certs_cell_cert in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void certs_cell_cert_free(certs_cell_cert_t *victim);
+/** Try to parse a certs_cell_cert from the buffer in 'input', using
+ * up to 'len_in' bytes from the input buffer. On success, return the
+ * number of bytes consumed and set *output to the newly allocated
+ * certs_cell_cert_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t certs_cell_cert_parse(certs_cell_cert_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * certs_cell_cert in 'obj'. On failure, return a negative value. Note
+ * that this value may be an overestimate, and can even be an
+ * underestimate for certain unencodeable objects.
+ */
+ssize_t certs_cell_cert_encoded_len(const certs_cell_cert_t *obj);
+/** Try to encode the certs_cell_cert from 'input' into the buffer at
+ * 'output', using up to 'avail' bytes of the output buffer. On
+ * success, return the number of bytes used. On failure, return -2 if
+ * the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t certs_cell_cert_encode(uint8_t *output, const size_t avail, const certs_cell_cert_t *input);
+/** Check whether the internal state of the certs_cell_cert in 'obj'
+ * is consistent. Return NULL if it is, and a short message if it is
+ * not.
+ */
+const char *certs_cell_cert_check(const certs_cell_cert_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int certs_cell_cert_clear_errors(certs_cell_cert_t *obj);
+/** Return the value of the cert_type field of the certs_cell_cert_t
+ * in 'inp'
+ */
+uint8_t certs_cell_cert_get_cert_type(certs_cell_cert_t *inp);
+/** Set the value of the cert_type field of the certs_cell_cert_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int certs_cell_cert_set_cert_type(certs_cell_cert_t *inp, uint8_t val);
+/** Return the value of the cert_len field of the certs_cell_cert_t in
+ * 'inp'
+ */
+uint16_t certs_cell_cert_get_cert_len(certs_cell_cert_t *inp);
+/** Set the value of the cert_len field of the certs_cell_cert_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int certs_cell_cert_set_cert_len(certs_cell_cert_t *inp, uint16_t val);
+/** Return the length of the dynamic array holding the body field of
+ * the certs_cell_cert_t in 'inp'.
+ */
+size_t certs_cell_cert_getlen_body(const certs_cell_cert_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * body of the certs_cell_cert_t in 'inp'.
+ */
+uint8_t certs_cell_cert_get_body(certs_cell_cert_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * body of the certs_cell_cert_t in 'inp', so that it will hold the
+ * value 'elt'.
+ */
+int certs_cell_cert_set_body(certs_cell_cert_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field body of the
+ * certs_cell_cert_t in 'inp'.
+ */
+int certs_cell_cert_add_body(certs_cell_cert_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field body of 'inp'.
+ */
+uint8_t * certs_cell_cert_getarray_body(certs_cell_cert_t *inp);
+/** Change the length of the variable-length array field body of 'inp'
+ * to 'newlen'.Fill extra elements with 0. Return 0 on success; return
+ * -1 and set the error code on 'inp' on failure.
+ */
+int certs_cell_cert_setlen_body(certs_cell_cert_t *inp, size_t newlen);
+/** Return a newly allocated rsa_ed_crosscert with all elements set to
+ * zero.
+ */
+rsa_ed_crosscert_t *rsa_ed_crosscert_new(void);
+/** Release all storage held by the rsa_ed_crosscert in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void rsa_ed_crosscert_free(rsa_ed_crosscert_t *victim);
+/** Try to parse a rsa_ed_crosscert from the buffer in 'input', using
+ * up to 'len_in' bytes from the input buffer. On success, return the
+ * number of bytes consumed and set *output to the newly allocated
+ * rsa_ed_crosscert_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t rsa_ed_crosscert_parse(rsa_ed_crosscert_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * rsa_ed_crosscert in 'obj'. On failure, return a negative value.
+ * Note that this value may be an overestimate, and can even be an
+ * underestimate for certain unencodeable objects.
+ */
+ssize_t rsa_ed_crosscert_encoded_len(const rsa_ed_crosscert_t *obj);
+/** Try to encode the rsa_ed_crosscert from 'input' into the buffer at
+ * 'output', using up to 'avail' bytes of the output buffer. On
+ * success, return the number of bytes used. On failure, return -2 if
+ * the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t rsa_ed_crosscert_encode(uint8_t *output, const size_t avail, const rsa_ed_crosscert_t *input);
+/** Check whether the internal state of the rsa_ed_crosscert in 'obj'
+ * is consistent. Return NULL if it is, and a short message if it is
+ * not.
+ */
+const char *rsa_ed_crosscert_check(const rsa_ed_crosscert_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int rsa_ed_crosscert_clear_errors(rsa_ed_crosscert_t *obj);
+/** Return the (constant) length of the array holding the ed_key field
+ * of the rsa_ed_crosscert_t in 'inp'.
+ */
+size_t rsa_ed_crosscert_getlen_ed_key(const rsa_ed_crosscert_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * ed_key of the rsa_ed_crosscert_t in 'inp'.
+ */
+uint8_t rsa_ed_crosscert_get_ed_key(const rsa_ed_crosscert_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * ed_key of the rsa_ed_crosscert_t in 'inp', so that it will hold the
+ * value 'elt'.
+ */
+int rsa_ed_crosscert_set_ed_key(rsa_ed_crosscert_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field ed_key of 'inp'.
+ */
+uint8_t * rsa_ed_crosscert_getarray_ed_key(rsa_ed_crosscert_t *inp);
+/** Return the value of the expiration field of the rsa_ed_crosscert_t
+ * in 'inp'
+ */
+uint32_t rsa_ed_crosscert_get_expiration(rsa_ed_crosscert_t *inp);
+/** Set the value of the expiration field of the rsa_ed_crosscert_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int rsa_ed_crosscert_set_expiration(rsa_ed_crosscert_t *inp, uint32_t val);
+/** Return the position for end_of_signed when we parsed this object
+ */
+const uint8_t * rsa_ed_crosscert_get_end_of_signed(const rsa_ed_crosscert_t *inp);
+/** Return the value of the sig_len field of the rsa_ed_crosscert_t in
+ * 'inp'
+ */
+uint8_t rsa_ed_crosscert_get_sig_len(rsa_ed_crosscert_t *inp);
+/** Set the value of the sig_len field of the rsa_ed_crosscert_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int rsa_ed_crosscert_set_sig_len(rsa_ed_crosscert_t *inp, uint8_t val);
+/** Return the length of the dynamic array holding the sig field of
+ * the rsa_ed_crosscert_t in 'inp'.
+ */
+size_t rsa_ed_crosscert_getlen_sig(const rsa_ed_crosscert_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * sig of the rsa_ed_crosscert_t in 'inp'.
+ */
+uint8_t rsa_ed_crosscert_get_sig(rsa_ed_crosscert_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * sig of the rsa_ed_crosscert_t in 'inp', so that it will hold the
+ * value 'elt'.
+ */
+int rsa_ed_crosscert_set_sig(rsa_ed_crosscert_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field sig of the
+ * rsa_ed_crosscert_t in 'inp'.
+ */
+int rsa_ed_crosscert_add_sig(rsa_ed_crosscert_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field sig of 'inp'.
+ */
+uint8_t * rsa_ed_crosscert_getarray_sig(rsa_ed_crosscert_t *inp);
+/** Change the length of the variable-length array field sig of 'inp'
+ * to 'newlen'.Fill extra elements with 0. Return 0 on success; return
+ * -1 and set the error code on 'inp' on failure.
+ */
+int rsa_ed_crosscert_setlen_sig(rsa_ed_crosscert_t *inp, size_t newlen);
+/** Return a newly allocated auth1 with all elements set to zero.
+ */
+auth1_t *auth1_new(void);
+/** Release all storage held by the auth1 in 'victim'. (Do nothing if
+ * 'victim' is NULL.)
+ */
+void auth1_free(auth1_t *victim);
+/** Try to parse a auth1 from the buffer in 'input', using up to
+ * 'len_in' bytes from the input buffer. On success, return the number
+ * of bytes consumed and set *output to the newly allocated auth1_t.
+ * On failure, return -2 if the input appears truncated, and -1 if the
+ * input is otherwise invalid.
+ */
+ssize_t auth1_parse(auth1_t **output, const uint8_t *input, const size_t len_in, const auth_ctx_t *auth_ctx_ctx);
+/** Return the number of bytes we expect to need to encode the auth1
+ * in 'obj'. On failure, return a negative value. Note that this value
+ * may be an overestimate, and can even be an underestimate for
+ * certain unencodeable objects.
+ */
+ssize_t auth1_encoded_len(const auth1_t *obj, const auth_ctx_t *auth_ctx_ctx);
+/** Try to encode the auth1 from 'input' into the buffer at 'output',
+ * using up to 'avail' bytes of the output buffer. On success, return
+ * the number of bytes used. On failure, return -2 if the buffer was
+ * not long enough, and -1 if the input was invalid.
+ */
+ssize_t auth1_encode(uint8_t *output, const size_t avail, const auth1_t *input, const auth_ctx_t *auth_ctx_ctx);
+/** Check whether the internal state of the auth1 in 'obj' is
+ * consistent. Return NULL if it is, and a short message if it is not.
+ */
+const char *auth1_check(const auth1_t *obj, const auth_ctx_t *auth_ctx_ctx);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int auth1_clear_errors(auth1_t *obj);
+/** Return the (constant) length of the array holding the type field
+ * of the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_type(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field type
+ * of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_type(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field type
+ * of the auth1_t in 'inp', so that it will hold the value 'elt'.
+ */
+int auth1_set_type(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 8-element array field type of 'inp'.
+ */
+uint8_t * auth1_getarray_type(auth1_t *inp);
+/** Return the (constant) length of the array holding the cid field of
+ * the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_cid(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field cid
+ * of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_cid(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field cid
+ * of the auth1_t in 'inp', so that it will hold the value 'elt'.
+ */
+int auth1_set_cid(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field cid of 'inp'.
+ */
+uint8_t * auth1_getarray_cid(auth1_t *inp);
+/** Return the (constant) length of the array holding the sid field of
+ * the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_sid(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field sid
+ * of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_sid(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field sid
+ * of the auth1_t in 'inp', so that it will hold the value 'elt'.
+ */
+int auth1_set_sid(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field sid of 'inp'.
+ */
+uint8_t * auth1_getarray_sid(auth1_t *inp);
+/** Return the (constant) length of the array holding the u1_cid_ed
+ * field of the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_u1_cid_ed(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * u1_cid_ed of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_u1_cid_ed(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * u1_cid_ed of the auth1_t in 'inp', so that it will hold the value
+ * 'elt'.
+ */
+int auth1_set_u1_cid_ed(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field u1_cid_ed of 'inp'.
+ */
+uint8_t * auth1_getarray_u1_cid_ed(auth1_t *inp);
+/** Return the (constant) length of the array holding the u1_sid_ed
+ * field of the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_u1_sid_ed(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * u1_sid_ed of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_u1_sid_ed(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * u1_sid_ed of the auth1_t in 'inp', so that it will hold the value
+ * 'elt'.
+ */
+int auth1_set_u1_sid_ed(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field u1_sid_ed of 'inp'.
+ */
+uint8_t * auth1_getarray_u1_sid_ed(auth1_t *inp);
+/** Return the (constant) length of the array holding the slog field
+ * of the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_slog(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field slog
+ * of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_slog(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field slog
+ * of the auth1_t in 'inp', so that it will hold the value 'elt'.
+ */
+int auth1_set_slog(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field slog of 'inp'.
+ */
+uint8_t * auth1_getarray_slog(auth1_t *inp);
+/** Return the (constant) length of the array holding the clog field
+ * of the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_clog(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field clog
+ * of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_clog(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field clog
+ * of the auth1_t in 'inp', so that it will hold the value 'elt'.
+ */
+int auth1_set_clog(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field clog of 'inp'.
+ */
+uint8_t * auth1_getarray_clog(auth1_t *inp);
+/** Return the (constant) length of the array holding the scert field
+ * of the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_scert(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * scert of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_scert(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * scert of the auth1_t in 'inp', so that it will hold the value
+ * 'elt'.
+ */
+int auth1_set_scert(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field scert of 'inp'.
+ */
+uint8_t * auth1_getarray_scert(auth1_t *inp);
+/** Return the (constant) length of the array holding the tlssecrets
+ * field of the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_tlssecrets(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * tlssecrets of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_tlssecrets(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * tlssecrets of the auth1_t in 'inp', so that it will hold the value
+ * 'elt'.
+ */
+int auth1_set_tlssecrets(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field tlssecrets of
+ * 'inp'.
+ */
+uint8_t * auth1_getarray_tlssecrets(auth1_t *inp);
+/** Return the position for end_of_fixed_part when we parsed this
+ * object
+ */
+const uint8_t * auth1_get_end_of_fixed_part(const auth1_t *inp);
+/** Return the (constant) length of the array holding the rand field
+ * of the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_rand(const auth1_t *inp);
+/** Return the element at position 'idx' of the fixed array field rand
+ * of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_rand(const auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field rand
+ * of the auth1_t in 'inp', so that it will hold the value 'elt'.
+ */
+int auth1_set_rand(auth1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 24-element array field rand of 'inp'.
+ */
+uint8_t * auth1_getarray_rand(auth1_t *inp);
+/** Return the position for end_of_signed when we parsed this object
+ */
+const uint8_t * auth1_get_end_of_signed(const auth1_t *inp);
+/** Return the length of the dynamic array holding the sig field of
+ * the auth1_t in 'inp'.
+ */
+size_t auth1_getlen_sig(const auth1_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * sig of the auth1_t in 'inp'.
+ */
+uint8_t auth1_get_sig(auth1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * sig of the auth1_t in 'inp', so that it will hold the value 'elt'.
+ */
+int auth1_set_sig(auth1_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field sig of the
+ * auth1_t in 'inp'.
+ */
+int auth1_add_sig(auth1_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field sig of 'inp'.
+ */
+uint8_t * auth1_getarray_sig(auth1_t *inp);
+/** Change the length of the variable-length array field sig of 'inp'
+ * to 'newlen'.Fill extra elements with 0. Return 0 on success; return
+ * -1 and set the error code on 'inp' on failure.
+ */
+int auth1_setlen_sig(auth1_t *inp, size_t newlen);
+/** Return a newly allocated certs_cell with all elements set to zero.
+ */
+certs_cell_t *certs_cell_new(void);
+/** Release all storage held by the certs_cell in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void certs_cell_free(certs_cell_t *victim);
+/** Try to parse a certs_cell from the buffer in 'input', using up to
+ * 'len_in' bytes from the input buffer. On success, return the number
+ * of bytes consumed and set *output to the newly allocated
+ * certs_cell_t. On failure, return -2 if the input appears truncated,
+ * and -1 if the input is otherwise invalid.
+ */
+ssize_t certs_cell_parse(certs_cell_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * certs_cell in 'obj'. On failure, return a negative value. Note that
+ * this value may be an overestimate, and can even be an underestimate
+ * for certain unencodeable objects.
+ */
+ssize_t certs_cell_encoded_len(const certs_cell_t *obj);
+/** Try to encode the certs_cell from 'input' into the buffer at
+ * 'output', using up to 'avail' bytes of the output buffer. On
+ * success, return the number of bytes used. On failure, return -2 if
+ * the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t certs_cell_encode(uint8_t *output, const size_t avail, const certs_cell_t *input);
+/** Check whether the internal state of the certs_cell in 'obj' is
+ * consistent. Return NULL if it is, and a short message if it is not.
+ */
+const char *certs_cell_check(const certs_cell_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int certs_cell_clear_errors(certs_cell_t *obj);
+/** Return the value of the n_certs field of the certs_cell_t in 'inp'
+ */
+uint8_t certs_cell_get_n_certs(certs_cell_t *inp);
+/** Set the value of the n_certs field of the certs_cell_t in 'inp' to
+ * 'val'. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int certs_cell_set_n_certs(certs_cell_t *inp, uint8_t val);
+/** Return the length of the dynamic array holding the certs field of
+ * the certs_cell_t in 'inp'.
+ */
+size_t certs_cell_getlen_certs(const certs_cell_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * certs of the certs_cell_t in 'inp'.
+ */
+struct certs_cell_cert_st * certs_cell_get_certs(certs_cell_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * certs of the certs_cell_t in 'inp', so that it will hold the value
+ * 'elt'. Free the previous value, if any.
+ */
+int certs_cell_set_certs(certs_cell_t *inp, size_t idx, struct certs_cell_cert_st * elt);
+/** As certs_cell_set_certs, but does not free the previous value.
+ */
+int certs_cell_set0_certs(certs_cell_t *inp, size_t idx, struct certs_cell_cert_st * elt);
+/** Append a new element 'elt' to the dynamic array field certs of the
+ * certs_cell_t in 'inp'.
+ */
+int certs_cell_add_certs(certs_cell_t *inp, struct certs_cell_cert_st * elt);
+/** Return a pointer to the variable-length array field certs of
+ * 'inp'.
+ */
+struct certs_cell_cert_st * * certs_cell_getarray_certs(certs_cell_t *inp);
+/** Change the length of the variable-length array field certs of
+ * 'inp' to 'newlen'.Fill extra elements with NULL; free removed
+ * elements. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int certs_cell_setlen_certs(certs_cell_t *inp, size_t newlen);
+
+
+#endif

+ 57 - 0
src/trunnel/link_handshake.trunnel

@@ -0,0 +1,57 @@
+
+struct certs_cell {
+  u8 n_certs;
+  struct certs_cell_cert certs[n_certs];
+}
+
+const CERTTYPE_RSA1024_ID_LINK = 1;
+const CERTTYPE_RSA1024_ID_ID   = 2;
+const CERTTYPE_RSA1024_ID_AUTH = 3;
+const CERTTYPE_ED_ID_SIGN      = 4;
+const CERTTYPE_ED_SIGN_LINK    = 5;
+const CERTTYPE_ED_SIGN_AUTH    = 6;
+const CERTTYPE_RSA1024_ID_EDID = 7;
+
+struct certs_cell_cert {
+  u8 cert_type;
+  u16 cert_len;
+  u8 body[cert_len];
+}
+
+struct rsa_ed_crosscert {
+  u8 ed_key[32];
+  u32 expiration;
+  @ptr end_of_signed;
+  u8 sig_len;
+  u8 sig[sig_len]; // mismatches spec.
+}
+
+struct auth_challenge_cell {
+  u8 challenge[32];
+  u16 n_methods;
+  u16 methods[n_methods];
+}
+
+context auth_ctx {
+  u8 is_ed;
+}
+
+struct auth1 with context auth_ctx {
+  u8 type[8];
+  u8 cid[32];
+  u8 sid[32];
+  union u1[auth_ctx.is_ed] {
+    0 : ;
+    1 : u8 cid_ed[32];
+        u8 sid_ed[32];
+    default: fail;
+  };
+  u8 slog[32];
+  u8 clog[32];
+  u8 scert[32];
+  u8 tlssecrets[32];
+  @ptr end_of_fixed_part;
+  u8 rand[24];
+  @ptr end_of_signed;
+  u8 sig[];
+}