[tor-commits] [tor/master] prop220: Implement certificates and key storage/creation

nickm at torproject.org nickm at torproject.org
Thu May 28 15:06:54 UTC 2015


commit 818e6f939d4bd241e762970da4c6360858993cd5
Author: Nick Mathewson <nickm at torproject.org>
Date:   Tue Sep 30 16:00:17 2014 -0400

    prop220: Implement certificates and key storage/creation
    
    For prop220, we have a new ed25519 certificate type. This patch
    implements the code to create, parse, and validate those, along with
    code for routers to maintain their own sets of certificates and
    keys.  (Some parts of master identity key encryption are done, but
    the implementation of that isn't finished)
---
 src/common/crypto_ed25519.c      |   10 +
 src/common/crypto_ed25519.h      |    5 +-
 src/or/config.h                  |    4 +
 src/or/include.am                |   13 +-
 src/or/main.c                    |    2 +
 src/or/or.h                      |    1 +
 src/or/router.c                  |    5 +
 src/or/routerkeys.c              |  453 +++++++++++++++++++
 src/or/routerkeys.h              |   44 ++
 src/or/torcert.c                 |  208 +++++++++
 src/or/torcert.h                 |   66 +++
 src/test/include.am              |    2 +-
 src/test/test_routerkeys.c       |  444 ++++++++++++++++++-
 src/trunnel/ed25519_cert.c       |  887 ++++++++++++++++++++++++++++++++++++++
 src/trunnel/ed25519_cert.h       |  288 +++++++++++++
 src/trunnel/ed25519_cert.trunnel |   76 ++++
 src/trunnel/include.am           |   18 +-
 17 files changed, 2513 insertions(+), 13 deletions(-)

diff --git a/src/common/crypto_ed25519.c b/src/common/crypto_ed25519.c
index f2e6945..7e8b00a 100644
--- a/src/common/crypto_ed25519.c
+++ b/src/common/crypto_ed25519.c
@@ -351,3 +351,13 @@ ed25519_pubkey_read_from_file(ed25519_public_key_t *pubkey_out,
   return 0;
 }
 
+void
+ed25519_keypair_free(ed25519_keypair_t *kp)
+{
+  if (! kp)
+    return;
+
+  memwipe(kp, 0, sizeof(*kp));
+  tor_free(kp);
+}
+
diff --git a/src/common/crypto_ed25519.h b/src/common/crypto_ed25519.h
index 7efa74b..8e06191 100644
--- a/src/common/crypto_ed25519.h
+++ b/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. */
@@ -109,5 +110,7 @@ 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);
+
 #endif
 
diff --git a/src/or/config.h b/src/or/config.h
index b064f05..e972485 100644
--- a/src/or/config.h
+++ b/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) \
diff --git a/src/or/include.am b/src/or/include.am
index b44e109..a85b0dd 100644
--- a/src/or/include.am
+++ b/src/or/include.am
@@ -71,12 +71,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)				\
@@ -87,7 +89,7 @@ src_or_libtor_testing_a_SOURCES = $(LIBTOR_A_SOURCES)
 
 #libtor_a_LIBADD = ../common/libor.a ../common/libor-crypto.a \
 #	../common/libor-event.a
-
+#src_or_libtor_a_LIBADD = src/trunnel/libor-trunnel.a
 
 src_or_tor_SOURCES = src/or/tor_main.c
 AM_CPPFLAGS += -I$(srcdir)/src/or -Isrc/or
@@ -109,7 +111,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 +122,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 = ./src/or/tor-cov
@@ -180,12 +182,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
 
diff --git a/src/or/main.c b/src/or/main.c
index bc89458..8b82a31 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -51,6 +51,7 @@
 #include "rendservice.h"
 #include "rephist.h"
 #include "router.h"
+#include "routerkeys.h"
 #include "routerlist.h"
 #include "routerparse.h"
 #include "scheduler.h"
@@ -2648,6 +2649,7 @@ tor_free_all(int postfork)
     config_free_all();
     or_state_free_all();
     router_free_all();
+    routerkeys_free_all();
     policies_free_all();
   }
   if (!postfork) {
diff --git a/src/or/or.h b/src/or/or.h
index 19694a0..2044e84 100644
--- a/src/or/or.h
+++ b/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.
diff --git a/src/or/router.c b/src/or/router.c
index 2ddaa89..1355613 100644
--- a/src/or/router.c
+++ b/src/or/router.c
@@ -26,6 +26,7 @@
 #include "relay.h"
 #include "rephist.h"
 #include "router.h"
+#include "routerkeys.h"
 #include "routerlist.h"
 #include "routerparse.h"
 #include "statefile.h"
@@ -861,6 +862,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);
diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c
new file mode 100644
index 0000000..fb61c31
--- /dev/null
+++ b/src/or/routerkeys.c
@@ -0,0 +1,453 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "or.h"
+#include "config.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.
+ */
+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 &&
+    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 && (flags & INIT_ED_KEY_MISSING_SECRET_OK)) {
+    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 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_link_key = NULL;
+static ed25519_keypair_t *current_auth_key = NULL;
+static tor_cert_t *signing_key_cert = NULL;
+static tor_cert_t *link_key_cert = NULL;
+static tor_cert_t *auth_key_cert = NULL;
+
+/**
+ * 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 *link = NULL;
+  ed25519_keypair_t *auth = NULL;
+  const ed25519_keypair_t *use_signing = NULL;
+  tor_cert_t *sign_cert = NULL;
+  tor_cert_t *link_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 */
+
+  /* XXXX use options. */
+  (void) options;
+
+  id = ed_key_init_from_file(
+               options_get_datadir_fname2(options, "keys", "ed25519_master_id"),
+                             (INIT_ED_KEY_CREATE|INIT_ED_KEY_SPLIT|
+                              INIT_ED_KEY_MISSING_SECRET_OK|
+                              INIT_ED_KEY_EXTRA_STRONG),
+                             LOG_WARN, NULL, 0, 0, 0, NULL);
+  if (!id)
+    FAIL("Missing identity key");
+
+  if (!master_signing_key || EXPIRES_SOON(signing_key_cert, 86400/*???*/)) {
+    uint32_t flags = (INIT_ED_KEY_CREATE|
+                      INIT_ED_KEY_EXTRA_STRONG|
+                      INIT_ED_KEY_NEEDCERT|
+                      INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT);
+    const ed25519_keypair_t *sign_with_id = id;
+    if (master_signing_key) {
+      flags |= INIT_ED_KEY_REPLACE; /* it's expired, so force-replace it. */
+    }
+    if (tor_mem_is_zero((char*)id->seckey.seckey, sizeof(id->seckey))) {
+      sign_with_id = NULL;
+      flags &= ~INIT_ED_KEY_CREATE;
+    }
+    sign = ed_key_init_from_file(
+               options_get_datadir_fname2(options, "keys", "ed25519_signing"),
+                                 flags, LOG_WARN,
+                                 sign_with_id, now, 30*86400/*XXX option*/,
+                                 CERT_TYPE_ID_SIGNING, &sign_cert);
+    if (!sign)
+      FAIL("Missing signing key");
+    use_signing = sign;
+  } else {
+    use_signing = master_signing_key;
+  }
+
+  /* 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 (!current_link_key || EXPIRES_SOON(link_key_cert, 7200/*???*/)) {
+    link = ed_key_new(use_signing, INIT_ED_KEY_NEEDCERT,
+                      now, 2*86400/*XXX option??*/,
+                      CERT_TYPE_SIGNING_LINK, &link_cert);
+
+    if (!link)
+      FAIL("Can't create link key");
+  }
+
+  if (!current_auth_key || EXPIRES_SOON(auth_key_cert, 7200)/*???*/) {
+    auth = ed_key_new(use_signing, INIT_ED_KEY_NEEDCERT,
+                      now, 2*86400/*XXX option??*/,
+                      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 (link) {
+    SET_KEY(current_link_key, link);
+    SET_CERT(link_key_cert, link_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(link);
+  ed25519_keypair_free(auth);
+  tor_cert_free(sign_cert);
+  tor_cert_free(link_cert);
+  tor_cert_free(auth_cert);
+  return -1;
+#undef FAIL
+#undef SET_KEY
+#undef SET_CERT
+#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_link_keypair(void)
+{
+  return current_link_key;
+}
+
+const ed25519_keypair_t *
+get_current_auth_keypair(void)
+{
+  return current_auth_key;
+}
+
+const tor_cert_t *
+get_current_link_key_cert(void)
+{
+  return link_key_cert;
+}
+
+const tor_cert_t *
+get_current_auth_key_cert(void)
+{
+  return auth_key_cert;
+}
+
+void
+routerkeys_free_all(void)
+{
+  ed25519_keypair_free(master_identity_key);
+  ed25519_keypair_free(master_signing_key);
+  ed25519_keypair_free(current_link_key);
+  ed25519_keypair_free(current_auth_key);
+  tor_cert_free(signing_key_cert);
+  tor_cert_free(link_key_cert);
+  tor_cert_free(auth_key_cert);
+
+  master_identity_key = master_signing_key = NULL;
+  current_link_key = current_auth_key = NULL;
+  signing_key_cert = link_key_cert = auth_key_cert = NULL;
+}
+
diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h
new file mode 100644
index 0000000..eb21401
--- /dev/null
+++ b/src/or/routerkeys.h
@@ -0,0 +1,44 @@
+/* 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)
+
+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_link_keypair(void);
+const ed25519_keypair_t *get_current_auth_keypair(void);
+const struct tor_cert_st *get_current_link_key_cert(void);
+const struct tor_cert_st *get_current_auth_key_cert(void);
+
+int load_ed_keys(const or_options_t *options, time_t now);
+void routerkeys_free_all(void);
+
+#endif
+
diff --git a/src/or/torcert.c b/src/or/torcert.c
new file mode 100644
index 0000000..478ec4d
--- /dev/null
+++ b/src/or/torcert.c
@@ -0,0 +1,208 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "torcert.h"
+#include "crypto.h"
+#include "ed25519_cert.h"
+#include "torlog.h"
+#include "util.h"
+#include "compat.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;
+  }
+}
+
diff --git a/src/or/torcert.h b/src/or/torcert.h
new file mode 100644
index 0000000..7e9c3f5
--- /dev/null
+++ b/src/or/torcert.h
@@ -0,0 +1,66 @@
+/* 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_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);
+
+#endif
+
diff --git a/src/test/include.am b/src/test/include.am
index d20d2f6..44f63a7 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -104,7 +104,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@
diff --git a/src/test/test_routerkeys.c b/src/test/test_routerkeys.c
index 60b6bb5..63284b7 100644
--- a/src/test/test_routerkeys.c
+++ b/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,447 @@ 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;
+
+  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);
+}
+
+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, link, auth;
+  //  tor_cert_t *cert_is, *cert_sl, *cert_auth;
+
+#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_assert(get_master_identity_key());
+  tt_assert(get_master_identity_key());
+  tt_assert(get_master_signing_keypair());
+  tt_assert(get_current_link_keypair());
+  tt_assert(get_current_auth_keypair());
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_key_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(&link, get_current_link_keypair(), sizeof(link));
+  memcpy(&auth, get_current_auth_keypair(), sizeof(auth));
+
+  /* Call load_ed_keys again, but nothing has changed. */
+  tt_int_op(0, ==, load_ed_keys(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(&link, ==, get_current_link_keypair(), sizeof(link));
+  tt_mem_op(&auth, ==, get_current_auth_keypair(), sizeof(auth));
+
+  /* Force a reload: we make new link/auth keys. */
+  routerkeys_free_all();
+  tt_int_op(0, ==, load_ed_keys(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(&link, !=, get_current_link_keypair(), sizeof(link));
+  tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_key_cert());
+  tt_assert(get_current_auth_key_cert());
+  memcpy(&link, get_current_link_keypair(), sizeof(link));
+  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_mem_op(&id, ==, get_master_identity_key(), sizeof(id));
+  tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign));
+  tt_mem_op(&link, !=, get_current_link_keypair(), sizeof(link));
+  tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_key_cert());
+  tt_assert(get_current_auth_key_cert());
+  memcpy(&link, get_current_link_keypair(), sizeof(link));
+  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_mem_op(&id, ==, get_master_identity_key(), sizeof(id));
+  tt_mem_op(&sign, !=, get_master_signing_keypair(), sizeof(sign));
+  tt_mem_op(&link, !=, get_current_link_keypair(), sizeof(link));
+  tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_key_cert());
+  tt_assert(get_current_auth_key_cert());
+  memcpy(&sign, get_master_signing_keypair(), sizeof(sign));
+  memcpy(&link, get_current_link_keypair(), sizeof(link));
+  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_mem_op(&id, ==, get_master_identity_key(), sizeof(id));
+  tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign));
+  tt_mem_op(&link, !=, get_current_link_keypair(), sizeof(link));
+  tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth));
+  tt_assert(get_master_signing_key_cert());
+  tt_assert(get_current_link_key_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();
+}
+
 #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),
   END_OF_TESTCASES
 };
 
diff --git a/src/trunnel/ed25519_cert.c b/src/trunnel/ed25519_cert.c
new file mode 100644
index 0000000..2a84e4b
--- /dev/null
+++ b/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;
+}
diff --git a/src/trunnel/ed25519_cert.h b/src/trunnel/ed25519_cert.h
new file mode 100644
index 0000000..3ddf95e
--- /dev/null
+++ b/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
diff --git a/src/trunnel/ed25519_cert.trunnel b/src/trunnel/ed25519_cert.trunnel
new file mode 100644
index 0000000..c46f1b6
--- /dev/null
+++ b/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[];
+  };
+}
+*/
\ No newline at end of file
diff --git a/src/trunnel/include.am b/src/trunnel/include.am
index c7ac167..6e7851a 100644
--- a/src/trunnel/include.am
+++ b/src/trunnel/include.am
@@ -9,15 +9,21 @@ endif
 
 AM_CPPFLAGS += -I$(srcdir)/src/ext/trunnel -I$(srcdir)/src/trunnel
 
+TRUNNELINPUTS = \
+	src/trunnel/ed25519_cert.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/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/pwbox.h
 
 src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES)
 src_trunnel_libor_trunnel_a_CPPFLAGS = -DTRUNNEL_LOCAL_H $(AM_CPPFLAGS)





More information about the tor-commits mailing list