[tor-commits] [tor/master] prop224: Descriptor encoding implementation

nickm at torproject.org nickm at torproject.org
Fri Nov 4 18:48:11 UTC 2016


commit 91b5d0789ff5606cf97346590567857bfd78f9ad
Author: David Goulet <dgoulet at ev0ke.net>
Date:   Tue Mar 8 15:51:53 2016 -0500

    prop224: Descriptor encoding implementation
    
    Add hs_descriptor.{c|h} with the needed ABI to represent a descriptor and
    needed component.
    
    Signed-off-by: David Goulet <dgoulet at torproject.org>
    Signed-off-by: George Kadianakis <desnacked at riseup.net>
---
 src/or/hs_descriptor.c | 768 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/or/hs_descriptor.h | 169 +++++++++++
 src/or/include.am      |   2 +
 src/or/torcert.h       |   2 +
 4 files changed, 941 insertions(+)

diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c
new file mode 100644
index 0000000..a349297
--- /dev/null
+++ b/src/or/hs_descriptor.c
@@ -0,0 +1,768 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_descriptor.c
+ * \brief Handle hidden service descriptor encoding/decoding.
+ **/
+
+#include "hs_descriptor.h"
+
+#include "or.h"
+#include "ed25519_cert.h" /* Trunnel interface. */
+
+/* Constant string value used for the descriptor format. */
+static const char *str_hs_desc = "hs-descriptor";
+static const char *str_desc_cert = "descriptor-signing-key-cert";
+static const char *str_rev_counter = "revision-counter";
+static const char *str_encrypted = "encrypted";
+static const char *str_signature = "signature";
+static const char *str_lifetime = "descriptor-lifetime";
+/* Constant string value for the encrypted part of the descriptor. */
+static const char *str_create2_formats = "create2-formats";
+static const char *str_auth_required = "authentication-required";
+static const char *str_intro_point = "introduction-point";
+static const char *str_ip_auth_key = "auth-key";
+static const char *str_ip_enc_key = "enc-key";
+static const char *str_ip_enc_key_cert = "enc-key-certification";
+/* Constant string value for the construction to encrypt the encrypted data
+ * section. */
+static const char *str_enc_hsdir_data = "hsdir-encrypted-data";
+
+/* Encode the ed25519 certificate <b>cert</b> and put the newly allocated
+ * string in <b>cert_str_out</b>. Return 0 on success else a negative value. */
+static int
+encode_cert(const tor_cert_t *cert, char **cert_str_out)
+{
+  int ret = -1;
+  char *ed_cert_b64 = NULL;
+  size_t ed_cert_b64_len;
+
+  tor_assert(cert);
+  tor_assert(cert_str_out);
+
+  /* Get the encoded size and add the NUL byte. */
+  ed_cert_b64_len = base64_encode_size(cert->encoded_len,
+                                       BASE64_ENCODE_MULTILINE) + 1;
+  ed_cert_b64 = tor_malloc_zero(ed_cert_b64_len);
+
+  /* Base64 encode the encoded certificate. */
+  if (base64_encode(ed_cert_b64, ed_cert_b64_len,
+                    (const char *) cert->encoded, cert->encoded_len,
+                    BASE64_ENCODE_MULTILINE) < 0) {
+    log_err(LD_BUG, "Couldn't base64-encode descriptor signing key cert!");
+    goto err;
+  }
+
+  /* Put everything together in a NUL terminated string. */
+  tor_asprintf(cert_str_out,
+               "-----BEGIN ED25519 CERT-----\n"
+               "%s"
+               "-----END ED25519 CERT-----",
+               ed_cert_b64);
+  /* Success! */
+  ret = 0;
+
+err:
+  tor_free(ed_cert_b64);
+  return ret;
+}
+
+/* Encode the given link specifier objects into a newly allocated string.
+ * This can't fail so caller can always assume a valid string being
+ * returned. */
+static char *
+encode_link_specifiers(const smartlist_t *specs)
+{
+  char *encoded_b64 = NULL;
+  link_specifier_list_t *lslist = link_specifier_list_new();
+
+  tor_assert(specs);
+  /* No link specifiers is a code flow error, can't happen. */
+  tor_assert(smartlist_len(specs) > 0);
+  tor_assert(smartlist_len(specs) <= UINT8_MAX);
+
+  link_specifier_list_set_n_spec(lslist, smartlist_len(specs));
+
+  SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *,
+                          spec) {
+    link_specifier_t *ls = link_specifier_new();
+    link_specifier_set_ls_type(ls, spec->type);
+
+    switch (spec->type) {
+    case LS_IPV4:
+      link_specifier_set_un_ipv4_addr(ls,
+                                      tor_addr_to_ipv4h(&spec->u.ap.addr));
+      link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
+      /* Four bytes IPv4 and two bytes port. */
+      link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
+                                    sizeof(spec->u.ap.port));
+      break;
+    case LS_IPV6:
+    {
+      size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
+      const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
+      uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
+      memcpy(ipv6_array, in6_addr, addr_len);
+      link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
+      /* Sixteen bytes IPv6 and two bytes port. */
+      link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
+      break;
+    }
+    case LS_LEGACY_ID:
+    {
+      size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
+      uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
+      memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
+      link_specifier_set_ls_len(ls, legacy_id_len);
+      break;
+    }
+    default:
+      tor_assert(0);
+    }
+
+    link_specifier_list_add_spec(lslist, ls);
+  } SMARTLIST_FOREACH_END(spec);
+
+  {
+    uint8_t *encoded;
+    ssize_t encoded_len, encoded_b64_len, ret;
+
+    encoded_len = link_specifier_list_encoded_len(lslist);
+    tor_assert(encoded_len > 0);
+    encoded = tor_malloc_zero(encoded_len);
+    ret = link_specifier_list_encode(encoded, encoded_len, lslist);
+    tor_assert(ret == encoded_len);
+
+    /* Base64 encode our binary format. Add extra NUL byte for the base64
+     * encoded value. */
+    encoded_b64_len = base64_encode_size(encoded_len, 0) + 1;
+    encoded_b64 = tor_malloc_zero(encoded_b64_len);
+    ret = base64_encode(encoded_b64, encoded_b64_len, (const char *) encoded,
+                        encoded_len, 0);
+    tor_assert(ret == (encoded_b64_len - 1));
+    tor_free(encoded);
+  }
+
+  link_specifier_list_free(lslist);
+  return encoded_b64;
+}
+
+/* Encode an introduction point encryption key and return a newly allocated
+ * string with it. On failure, return NULL. */
+static char *
+encode_enc_key(const ed25519_keypair_t *sig_key,
+               const hs_desc_intro_point_t *ip)
+{
+  char *encoded = NULL;
+  time_t now = time(NULL);
+
+  tor_assert(sig_key);
+  tor_assert(ip);
+
+  switch (ip->enc_key_type) {
+  case HS_DESC_KEY_TYPE_LEGACY:
+  {
+    char *key_str, b64_cert[256];
+    ssize_t cert_len;
+    size_t key_str_len;
+    uint8_t *cert_data;
+
+    /* Create cross certification cert. */
+    cert_len = tor_make_rsa_ed25519_crosscert(&sig_key->pubkey,
+                                              ip->enc_key.legacy,
+                                              now + HS_DESC_CERT_LIFETIME,
+                                              &cert_data);
+    if (cert_len < 0) {
+      log_warn(LD_REND, "Unable to create legacy crosscert.");
+      goto err;
+    }
+    /* Encode cross cert. */
+    if (base64_encode(b64_cert, sizeof(b64_cert), (const char *) cert_data,
+                      cert_len, BASE64_ENCODE_MULTILINE) < 0) {
+      log_warn(LD_REND, "Unable to encode legacy crosscert.");
+      goto err;
+    }
+    /* Convert the encryption key to a string. */
+    if (crypto_pk_write_public_key_to_string(ip->enc_key.legacy, &key_str,
+                                             &key_str_len) < 0) {
+      log_warn(LD_REND, "Unable to encode legacy encryption key.");
+      goto err;
+    }
+    tor_asprintf(&encoded,
+                 "%s legacy\n%s"  /* Newline is added by the call above. */
+                 "%s\n"
+                 "-----BEGIN CROSSCERT-----\n"
+                 "%s"
+                 "-----END CROSSCERT-----",
+                 str_ip_enc_key, key_str,
+                 str_ip_enc_key_cert, b64_cert);
+    tor_free(key_str);
+    break;
+  }
+  case HS_DESC_KEY_TYPE_CURVE25519:
+  {
+    int signbit;
+    char *encoded_cert, key_fp_b64[CURVE25519_BASE64_PADDED_LEN + 1];
+    ed25519_keypair_t curve_kp;
+
+    if (ed25519_keypair_from_curve25519_keypair(&curve_kp, &signbit,
+                                                &ip->enc_key.curve25519)) {
+      goto err;
+    }
+    tor_cert_t *cross_cert = tor_cert_create(&curve_kp, CERT_TYPE_HS_IP_ENC,
+                                             &sig_key->pubkey, now,
+                                             HS_DESC_CERT_LIFETIME,
+                                             CERT_FLAG_INCLUDE_SIGNING_KEY);
+    memwipe(&curve_kp, 0, sizeof(curve_kp));
+    if (!cross_cert) {
+      goto err;
+    }
+    if (encode_cert(cross_cert, &encoded_cert)) {
+      goto err;
+    }
+    if (curve25519_public_to_base64(key_fp_b64,
+                                    &ip->enc_key.curve25519.pubkey) < 0) {
+      tor_free(encoded_cert);
+      goto err;
+    }
+    tor_asprintf(&encoded,
+                 "%s ntor %s\n"
+                 "%s\n%s",
+                 str_ip_enc_key, key_fp_b64,
+                 str_ip_enc_key_cert, encoded_cert);
+    tor_free(encoded_cert);
+    break;
+  }
+  default:
+    tor_assert(0);
+  }
+
+ err:
+  return encoded;
+}
+
+/* Encode an introduction point object and return a newly allocated string
+ * with it. On failure, return NULL. */
+static char *
+encode_intro_point(const ed25519_keypair_t *sig_key,
+                   const hs_desc_intro_point_t *ip)
+{
+  char *encoded_ip = NULL;
+  smartlist_t *lines = smartlist_new();
+
+  tor_assert(ip);
+  tor_assert(sig_key);
+
+  /* Encode link specifier. */
+  {
+    char *ls_str = encode_link_specifiers(ip->link_specifiers);
+    smartlist_add_asprintf(lines, "%s %s", str_intro_point, ls_str);
+    tor_free(ls_str);
+  }
+
+  /* Authentication key encoding. */
+  {
+    char *encoded_cert;
+    if (encode_cert(ip->auth_key_cert, &encoded_cert) < 0) {
+      goto err;
+    }
+    smartlist_add_asprintf(lines, "%s\n%s", str_ip_auth_key, encoded_cert);
+    tor_free(encoded_cert);
+  }
+
+  /* Encryption key encoding. */
+  {
+    char *encoded_enc_key = encode_enc_key(sig_key, ip);
+    if (encoded_enc_key == NULL) {
+      goto err;
+    }
+    smartlist_add_asprintf(lines, "%s", encoded_enc_key);
+    tor_free(encoded_enc_key);
+  }
+
+  /* Join them all in one blob of text. */
+  encoded_ip = smartlist_join_strings(lines, "\n", 1, NULL);
+
+ err:
+  SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+  smartlist_free(lines);
+  return encoded_ip;
+}
+
+/* Using a given decriptor object, build the secret input needed for the
+ * KDF and put it in the dst pointer which is an already allocated buffer
+ * of size dstlen. */
+static void
+build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen)
+{
+  size_t offset = 0;
+
+  tor_assert(desc);
+  tor_assert(dst);
+  tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen);
+
+  /* XXX use the destination length as the memcpy length */
+  /* Copy blinded public key. */
+  memcpy(dst, desc->plaintext_data.blinded_kp.pubkey.pubkey,
+         sizeof(desc->plaintext_data.blinded_kp.pubkey.pubkey));
+  offset += sizeof(desc->plaintext_data.blinded_kp.pubkey.pubkey);
+  /* Copy subcredential. */
+  memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential));
+  offset += sizeof(desc->subcredential);
+  /* Copy revision counter value. */
+  set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter));
+  offset += sizeof(uint64_t);
+  tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset);
+}
+
+/* Do the KDF construction and put the resulting data in key_out which is of
+ * key_out_len length. It uses SHAKE-256 as specified in the spec. */
+static void
+build_kdf_key(const hs_descriptor_t *desc,
+              const uint8_t *salt, size_t salt_len,
+              uint8_t *key_out, size_t key_out_len)
+{
+  uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN];
+  crypto_xof_t *xof;
+
+  tor_assert(desc);
+  tor_assert(salt);
+  tor_assert(key_out);
+
+  /* Build the secret input for the KDF computation. */
+  build_secret_input(desc, secret_input, sizeof(secret_input));
+
+  xof = crypto_xof_new();
+  /* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */
+  crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input));
+  crypto_xof_add_bytes(xof, salt, salt_len);
+  crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_hsdir_data,
+                       strlen(str_enc_hsdir_data));
+  /* Eat from our KDF. */
+  crypto_xof_squeeze_bytes(xof, key_out, key_out_len);
+  crypto_xof_free(xof);
+  memwipe(secret_input,  0, sizeof(secret_input));
+}
+
+/* Using the given descriptor and salt, run it through our KDF function and
+ * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out.
+ * This function can't fail. */
+static void
+build_secret_key_iv_mac(const hs_descriptor_t *desc,
+                        const uint8_t *salt, size_t salt_len,
+                        uint8_t *key_out, size_t key_len,
+                        uint8_t *iv_out, size_t iv_len,
+                        uint8_t *mac_out, size_t mac_len)
+{
+  size_t offset = 0;
+  uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN];
+
+  tor_assert(desc);
+  tor_assert(salt);
+  tor_assert(key_out);
+  tor_assert(iv_out);
+  tor_assert(mac_out);
+
+  build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key));
+  /* Copy the bytes we need for both the secret key and IV. */
+  memcpy(key_out, kdf_key, key_len);
+  offset += key_len;
+  memcpy(iv_out, kdf_key + offset, iv_len);
+  offset += iv_len;
+  memcpy(mac_out, kdf_key + offset, mac_len);
+  /* Extra precaution to make sure we are not out of bound. */
+  tor_assert((offset + mac_len) == sizeof(kdf_key));
+  memwipe(kdf_key, 0, sizeof(kdf_key));
+}
+
+/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out.
+ * The length of the mac key and salt must be fixed and if not, you can't rely
+ * on the result to be a valid MAC. We use SHA3-256 for the MAC computation.
+ * This function can't fail. */
+static void
+build_mac(const uint8_t *mac_key, size_t mac_key_len,
+          const uint8_t *salt, size_t salt_len,
+          const uint8_t *encrypted, size_t encrypted_len,
+          uint8_t *mac_out, size_t mac_len)
+{
+  crypto_digest_t *digest;
+
+  tor_assert(mac_key);
+  tor_assert(salt);
+  tor_assert(encrypted);
+  tor_assert(mac_out);
+
+  digest = crypto_digest256_new(DIGEST_SHA3_256);
+  /* As specified in section 2.5 of proposal 224, first add the mac key
+   * then add the salt first and then the encrypted section. */
+  crypto_digest_add_bytes(digest, (const char *) mac_key, mac_key_len);
+  crypto_digest_add_bytes(digest, (const char *) salt, salt_len);
+  crypto_digest_add_bytes(digest, (const char *) encrypted, encrypted_len);
+  crypto_digest_get_digest(digest, (char *) mac_out, mac_len);
+  crypto_digest_free(digest);
+}
+
+/* Given a source length, return the new size including padding for the
+ * plaintext encryption. */
+static size_t
+compute_padded_plaintext_length(size_t plaintext_len)
+{
+  size_t plaintext_padded_len;
+
+  /* Make sure we won't overflow. */
+  tor_assert(plaintext_len <=
+             (SIZE_T_CEILING - HS_DESC_PLAINTEXT_PADDING_MULTIPLE));
+
+  /* Get the extra length we need to add. For example, if srclen is 234 bytes,
+   * this will expand to (2 * 128) == 256 thus an extra 22 bytes. */
+  plaintext_padded_len = CEIL_DIV(plaintext_len,
+                                  HS_DESC_PLAINTEXT_PADDING_MULTIPLE) *
+                         HS_DESC_PLAINTEXT_PADDING_MULTIPLE;
+  /* Can never be extra careful. Make sure we are _really_ padded. */
+  tor_assert(!(plaintext_padded_len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE));
+  return plaintext_padded_len;
+}
+
+/* Given a buffer, pad it up to the encrypted section padding requirement. Set
+ * the newly allocated string in padded_out and return the length of the
+ * padded buffer. */
+static size_t
+build_plaintext_padding(const char *plaintext, size_t plaintext_len,
+                        uint8_t **padded_out)
+{
+  size_t padded_len;
+  uint8_t *padded;
+
+  tor_assert(plaintext);
+  tor_assert(padded_out);
+
+  /* Allocate the final length including padding. */
+  padded_len = compute_padded_plaintext_length(plaintext_len);
+  tor_assert(padded_len >= plaintext_len);
+  padded = tor_malloc_zero(padded_len);
+
+  memcpy(padded, plaintext, plaintext_len);
+  *padded_out = padded;
+  return padded_len;
+}
+
+/* Using a key, IV and plaintext data of length plaintext_len, create the
+ * encrypted section by encrypting it and setting encrypted_out with the
+ * data. Return size of the encrypted data buffer. */
+static size_t
+build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext,
+                size_t plaintext_len, uint8_t **encrypted_out)
+{
+  size_t encrypted_len;
+  uint8_t *padded_plaintext, *encrypted;
+  crypto_cipher_t *cipher;
+
+  tor_assert(key);
+  tor_assert(iv);
+  tor_assert(plaintext);
+  tor_assert(encrypted_out);
+
+  /* This creates a cipher for AES128. It can't fail. */
+  cipher = crypto_cipher_new_with_iv((const char *) key, (const char *) iv);
+  /* This can't fail. */
+  encrypted_len = build_plaintext_padding(plaintext, plaintext_len,
+                                          &padded_plaintext);
+  /* Extra precautions that we have a valie padding length. */
+  tor_assert(encrypted_len <= HS_DESC_PADDED_PLAINTEXT_MAX_LEN);
+  tor_assert(!(encrypted_len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE));
+  /* We use a stream cipher so the encrypted length will be the same as the
+   * plaintext padded length. */
+  encrypted = tor_malloc_zero(encrypted_len);
+  /* This can't fail. */
+  crypto_cipher_encrypt(cipher, (char *) encrypted,
+                        (const char *) padded_plaintext, encrypted_len);
+  *encrypted_out = encrypted;
+  /* Cleanup. */
+  crypto_cipher_free(cipher);
+  tor_free(padded_plaintext);
+  return encrypted_len;
+}
+
+/* Encrypt the given plaintext buffer and using the descriptor to get the
+ * keys. Set encrypted_out with the encrypted data and return the length of
+ * it. */
+static size_t
+encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext,
+             char **encrypted_out)
+{
+  char *final_blob;
+  size_t encrypted_len, final_blob_len, offset = 0;
+  uint8_t *encrypted;
+  uint8_t salt[HS_DESC_ENCRYPTED_SALT_LEN];
+  uint8_t secret_key[CIPHER_KEY_LEN], secret_iv[CIPHER_IV_LEN];
+  uint8_t mac_key[DIGEST256_LEN], mac[DIGEST256_LEN];
+
+  tor_assert(desc);
+  tor_assert(plaintext);
+  tor_assert(encrypted_out);
+
+  /* Get our salt. The returned bytes are already hashed. */
+  crypto_strongest_rand(salt, sizeof(salt));
+
+  /* KDF construction resulting in a key from which the secret key, IV and MAC
+   * key are extracted which is what we need for the encryption. */
+  build_secret_key_iv_mac(desc, salt, sizeof(salt),
+                          secret_key, sizeof(secret_key),
+                          secret_iv, sizeof(secret_iv),
+                          mac_key, sizeof(mac_key));
+
+  /* Build the encrypted part that is do the actual encryption. */
+  encrypted_len = build_encrypted(secret_key, secret_iv, plaintext,
+                                  strlen(plaintext), &encrypted);
+  memwipe(secret_key, 0, sizeof(secret_key));
+  memwipe(secret_iv, 0, sizeof(secret_iv));
+  /* This construction is specified in section 2.5 of proposal 224. */
+  final_blob_len = sizeof(salt) + encrypted_len + DIGEST256_LEN;
+  final_blob = tor_malloc_zero(final_blob_len);
+
+  /* Build the MAC. */
+  build_mac(mac_key, sizeof(mac_key), salt, sizeof(salt),
+            encrypted, encrypted_len, mac, sizeof(mac));
+  memwipe(mac_key, 0, sizeof(mac_key));
+
+  /* The salt is the first value. */
+  memcpy(final_blob, salt, sizeof(salt));
+  offset = sizeof(salt);
+  /* Second value is the encrypted data. */
+  memcpy(final_blob + offset, encrypted, encrypted_len);
+  offset += encrypted_len;
+  /* Third value is the MAC. */
+  memcpy(final_blob + offset, mac, sizeof(mac));
+  offset += sizeof(mac);
+  /* Cleanup the buffers. */
+  memwipe(salt, 0, sizeof(salt));
+  memwipe(encrypted, 0, encrypted_len);
+  tor_free(encrypted);
+  /* Extra precaution. */
+  tor_assert(offset == final_blob_len);
+
+  *encrypted_out = final_blob;
+  return final_blob_len;
+}
+
+/* Take care of encoding the encrypted data section and then encrypting it
+ * with the descriptor's key. A newly allocated NUL terminated string pointer
+ * containing the encrypted encoded blob is put in encrypted_blob_out. Return
+ * 0 on success else a negative value. */
+static int
+encode_encrypted_data(const hs_descriptor_t *desc,
+                      char **encrypted_blob_out)
+{
+  int ret = -1;
+  char *encoded_str, *encrypted_blob;
+  smartlist_t *lines = smartlist_new();
+
+  tor_assert(desc);
+  tor_assert(encrypted_blob_out);
+
+  /* Build the start of the section prior to the introduction points. */
+  {
+    if (!desc->encrypted_data.create2_ntor) {
+      log_err(LD_BUG, "HS desc doesn't have recognized handshake type.");
+      goto err;
+    }
+    smartlist_add_asprintf(lines, "%s %d\n", str_create2_formats,
+                           ONION_HANDSHAKE_TYPE_NTOR);
+
+    if (desc->encrypted_data.auth_types &&
+        smartlist_len(desc->encrypted_data.auth_types)) {
+      /* Put the authentication-required line. */
+      char *buf = smartlist_join_strings(desc->encrypted_data.auth_types, " ",
+                                         0, NULL);
+      smartlist_add_asprintf(lines, "%s %s\n", str_auth_required, buf);
+      tor_free(buf);
+    }
+  }
+
+  /* Build the introduction point(s) section. */
+  SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+                          const hs_desc_intro_point_t *, ip) {
+    char *encoded_ip = encode_intro_point(&desc->plaintext_data.signing_kp,
+                                          ip);
+    if (encoded_ip == NULL) {
+      log_err(LD_BUG, "HS desc intro point is malformed.");
+      goto err;
+    }
+    smartlist_add(lines, encoded_ip);
+  } SMARTLIST_FOREACH_END(ip);
+
+  /* Build the entire encrypted data section into one encoded plaintext and
+   * then encrypt it. */
+  encoded_str = smartlist_join_strings(lines, "", 0, NULL);
+
+  /* Encrypt the section into an encrypted blob that we'll base64 encode
+   * before returning it. */
+  {
+    char *enc_b64;
+    ssize_t enc_b64_len, ret_len, enc_len;
+
+    enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob);
+    tor_free(encoded_str);
+    /* Get the encoded size plus a NUL terminating byte. */
+    enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1;
+    enc_b64 = tor_malloc_zero(enc_b64_len);
+    /* Base64 the encrypted blob before returning it. */
+    ret_len = base64_encode(enc_b64, enc_b64_len, encrypted_blob, enc_len,
+                            BASE64_ENCODE_MULTILINE);
+    /* Return length doesn't count the NUL byte. */
+    tor_assert(ret_len == (enc_b64_len - 1));
+    tor_free(encrypted_blob);
+    *encrypted_blob_out = enc_b64;
+  }
+  /* Success! */
+  ret = 0;
+
+ err:
+  SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+  smartlist_free(lines);
+  return ret;
+}
+
+/* Encode a v3 HS descriptor. Return 0 on success and set encoded_out to the
+ * newly allocated string of the encoded descriptor. On error, -1 is returned
+ * and encoded_out is untouched. */
+static int
+desc_encode_v3(const hs_descriptor_t *desc, char **encoded_out)
+{
+  int ret = -1;
+  char *encoded_str = NULL;
+  size_t encoded_len;
+  smartlist_t *lines = smartlist_new();
+
+  tor_assert(desc);
+  tor_assert(encoded_out);
+  tor_assert(desc->plaintext_data.version == 3);
+
+  /* Build the non-encrypted values. */
+  {
+    char *encoded_cert;
+    /* Encode certificate then create the first line of the descriptor. */
+    if (desc->plaintext_data.signing_key_cert->cert_type
+        != CERT_TYPE_HS_DESC_SIGN) {
+      log_err(LD_BUG, "HS descriptor signing key has an unexpected cert type "
+              "(%d)", (int) desc->plaintext_data.signing_key_cert->cert_type);
+      goto err;
+    }
+    if (encode_cert(desc->plaintext_data.signing_key_cert,
+                    &encoded_cert) < 0) {
+      /* The function will print error logs. */
+      goto err;
+    }
+    /* Create the hs descriptor line. */
+    smartlist_add_asprintf(lines, "%s %" PRIu32, str_hs_desc,
+                           desc->plaintext_data.version);
+    /* Add the descriptor lifetime line (in minutes). */
+    smartlist_add_asprintf(lines, "%s %" PRIu32, str_lifetime,
+                           desc->plaintext_data.lifetime_sec / 60);
+    /* Create the descriptor certificate line. */
+    smartlist_add_asprintf(lines, "%s\n%s", str_desc_cert, encoded_cert);
+    tor_free(encoded_cert);
+    /* Create the revision counter line. */
+    smartlist_add_asprintf(lines, "%s %" PRIu64, str_rev_counter,
+                           desc->plaintext_data.revision_counter);
+  }
+
+  /* Build the encrypted data section. */
+  {
+    char *enc_b64_blob;
+    if (encode_encrypted_data(desc, &enc_b64_blob) < 0) {
+      goto err;
+    }
+    smartlist_add_asprintf(lines,
+                           "%s\n"
+                           "-----BEGIN MESSAGE-----\n"
+                           "%s"
+                           "-----END MESSAGE-----",
+                           str_encrypted, enc_b64_blob);
+    tor_free(enc_b64_blob);
+  }
+
+  /* Join all lines in one string so we can generate a signature and append
+   * it to the descriptor. */
+  encoded_str = smartlist_join_strings(lines, "\n", 1, &encoded_len);
+
+  /* Sign all fields of the descriptor with our short term signing key. */
+  {
+    /* XXX: Add signature prefix. */
+    ed25519_signature_t sig;
+    char ed_sig_b64[ED25519_SIG_BASE64_LEN + 1];
+    if (ed25519_sign(&sig, (const uint8_t *) encoded_str, encoded_len,
+                     &desc->plaintext_data.signing_kp) < 0) {
+      log_warn(LD_BUG, "Can't sign encoded HS descriptor!");
+      tor_free(encoded_str);
+      goto err;
+    }
+    if (ed25519_signature_to_base64(ed_sig_b64, &sig) < 0) {
+      log_warn(LD_BUG, "Can't base64 encode descriptor signature!");
+      tor_free(encoded_str);
+      goto err;
+    }
+    /* Create the signature line. */
+    smartlist_add_asprintf(lines, "%s %s", str_signature, ed_sig_b64);
+  }
+  /* Free previous string that we used so compute the signature. */
+  tor_free(encoded_str);
+  encoded_str = smartlist_join_strings(lines, "\n", 1, NULL);
+  *encoded_out = encoded_str;
+
+  /* XXX: Decode the generated descriptor as an extra validation. */
+
+  /* XXX: Trigger a control port event. */
+
+  /* Success! */
+  ret = 0;
+
+ err:
+  SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+  smartlist_free(lines);
+  return ret;
+}
+
+/* Table of encode function version specific. The function are indexed by the
+ * version number so v3 callback is at index 3 in the array. */
+static int
+  (*encode_handlers[])(
+      const hs_descriptor_t *desc,
+      char **encoded_out) =
+{
+  /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+  desc_encode_v3,
+};
+
+/* Encode the given descriptor desc. On success, encoded_out points to a newly
+ * allocated NUL terminated string that contains the encoded descriptor as a
+ * string.
+ *
+ * Return 0 on success and encoded_out is a valid pointer. On error, -1 is
+ * returned and encoded_out is untouched. */
+int
+hs_desc_encode_descriptor(const hs_descriptor_t *desc, char **encoded_out)
+{
+  int ret = -1;
+
+  tor_assert(desc);
+  tor_assert(encoded_out);
+
+  /* Make sure we support the version of the descriptor format. */
+  if (!hs_desc_is_supported_version(desc->plaintext_data.version)) {
+    goto err;
+  }
+  /* Extra precaution. Having no handler for the supported version should
+   * never happened else we forgot to add it but we bumped the version. */
+  tor_assert(ARRAY_LENGTH(encode_handlers) >= desc->plaintext_data.version);
+  tor_assert(encode_handlers[desc->plaintext_data.version]);
+
+  ret = encode_handlers[desc->plaintext_data.version](desc, encoded_out);
+  if (ret < 0) {
+    goto err;
+  }
+
+ err:
+  return ret;
+}
diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h
new file mode 100644
index 0000000..98dbb22
--- /dev/null
+++ b/src/or/hs_descriptor.h
@@ -0,0 +1,169 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_descriptor.h
+ * \brief Header file for hs_descriptor.c
+ **/
+
+#ifndef TOR_HS_DESCRIPTOR_H
+#define TOR_HS_DESCRIPTOR_H
+
+#include <stdint.h>
+
+#include "address.h"
+#include "container.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+#include "torcert.h"
+
+/* The earliest descriptor format version we support. */
+#define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3
+/* The latest descriptor format version we support. */
+#define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3
+
+/* Lifetime of certificate in the descriptor. This defines the lifetime of the
+ * descriptor signing key and the cross certification cert of that key. */
+#define HS_DESC_CERT_LIFETIME (24 * 60 * 60)
+/* Length of the salt needed for the encrypted section of a descriptor. */
+#define HS_DESC_ENCRYPTED_SALT_LEN 16
+/* Length of the secret input needed for the KDF construction which derives
+ * the encryption key for the encrypted data section of the descriptor. This
+ * adds up to 68 bytes being the blinded key, hashed subcredential and
+ * revision counter. */
+#define HS_DESC_ENCRYPTED_SECRET_INPUT_LEN \
+  ED25519_PUBKEY_LEN + DIGEST256_LEN + sizeof(uint64_t)
+/* Length of the KDF output value which is the length of the secret key,
+ * the secret IV and MAC key length which is the length of H() output. */
+#define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \
+  CIPHER_KEY_LEN + CIPHER_IV_LEN + DIGEST256_LEN
+/* We need to pad the plaintext version of the encrypted data section before
+ * encryption and it has to be a multiple of this value. */
+#define HS_DESC_PLAINTEXT_PADDING_MULTIPLE 128
+/* XXX: Let's make sure this makes sense as an upper limit for the padded
+ * plaintext section. Then we should enforce it as now only an assert will be
+ * triggered if we are above it. */
+/* Once padded, this is the maximum length in bytes for the plaintext. */
+#define HS_DESC_PADDED_PLAINTEXT_MAX_LEN 8192
+
+/* Type of encryption key in the descriptor. */
+typedef enum {
+  HS_DESC_KEY_TYPE_LEGACY     = 1,
+  HS_DESC_KEY_TYPE_CURVE25519 = 2,
+} hs_desc_key_type_t;
+
+/* Link specifier object that contains information on how to extend to the
+ * relay that is the address, port and handshake type. */
+typedef struct hs_desc_link_specifier_t {
+  /* Indicate the type of link specifier. See trunnel ed25519_cert
+   * specification. */
+  uint8_t type;
+
+  /* It's either an address/port or a legacy identity fingerprint. */
+  union {
+    /* IP address and port of the relay use to extend. */
+    tor_addr_port_t ap;
+    /* Legacy identity. A 20-byte SHA1 identity fingerprint. */
+    uint8_t legacy_id[DIGEST_LEN];
+  } u;
+} hs_desc_link_specifier_t;
+
+/* Introduction point information located in a descriptor. */
+typedef struct hs_desc_intro_point_t {
+  /* Link specifier(s) which details how to extend to the relay. This list
+   * contains hs_desc_link_specifier_t object. It MUST have at least one. */
+  smartlist_t *link_specifiers;
+
+  /* Authentication key used to establish the introduction point circuit and
+   * cross-certifies the blinded public key for the replica thus signed by
+   * the blinded key and in turn signs it. */
+  tor_cert_t *auth_key_cert;
+
+  /* Encryption key type so we know which one to use in the union below. */
+  hs_desc_key_type_t enc_key_type;
+
+  /* Keys are mutually exclusive thus the union. */
+  union {
+    /* Encryption key used to encrypt request to hidden service. */
+    curve25519_keypair_t curve25519;
+
+    /* Backward compat: RSA 1024 encryption key for legacy purposes.
+     * Mutually exclusive with enc_key. */
+    crypto_pk_t *legacy;
+  } enc_key;
+} hs_desc_intro_point_t;
+
+/* The encrypted data section of a descriptor. Obviously the data in this is
+ * in plaintext but encrypted once encoded. */
+typedef struct hs_desc_encrypted_data_t {
+  /* Bitfield of CREATE2 cell supported formats. The only currently supported
+   * format is ntor. */
+  unsigned int create2_ntor : 1;
+
+  /* A list of authentication types that a client must at least support one
+   * in order to contact the service. Contains NULL terminated strings. */
+  smartlist_t *auth_types;
+
+  /* A list of intro points. Contains hs_desc_intro_point_t objects. */
+  smartlist_t *intro_points;
+} hs_desc_encrypted_data_t;
+
+/* Plaintext data that is unencrypted information of the descriptor. */
+typedef struct hs_desc_plaintext_data_t {
+  /* Version of the descriptor format. Spec specifies this field as a
+   * positive integer. */
+  uint32_t version;
+
+  /* The lifetime of the descriptor in seconds. */
+  uint32_t lifetime_sec;
+
+  /* Certificate with the short-term ed22519 descriptor signing key for the
+   * replica which is signed by the blinded public key for that replica. */
+  tor_cert_t *signing_key_cert;
+
+  /* Signing keypair which is used to sign the descriptor. Same public key
+   * as in the signing key certificate. */
+  ed25519_keypair_t signing_kp;
+
+  /* Blinded keypair used for this descriptor derived from the master
+   * identity key and generated for a specific replica number. */
+  ed25519_keypair_t blinded_kp;
+
+  /* Revision counter is incremented at each upload, regardless of whether
+   * the descriptor has changed. This avoids leaking whether the descriptor
+   * has changed. Spec specifies this as a 8 bytes positive integer. */
+  uint64_t revision_counter;
+} hs_desc_plaintext_data_t;
+
+/* Service descriptor in its decoded form. */
+typedef struct hs_descriptor_t {
+  /* Contains the plaintext part of the descriptor. */
+  hs_desc_plaintext_data_t plaintext_data;
+
+  /* The following contains what's in the encrypted part of the descriptor.
+   * It's only encrypted in the encoded version of the descriptor thus the
+   * data contained in that object is in plaintext. */
+  hs_desc_encrypted_data_t encrypted_data;
+
+  /* Subcredentials of a service, used by the client and service to decrypt
+   * the encrypted data. */
+  uint8_t subcredential[DIGEST256_LEN];
+} hs_descriptor_t;
+
+/* Return true iff the given descriptor format version is supported. */
+static inline int
+hs_desc_is_supported_version(uint32_t version)
+{
+  if (version < HS_DESC_SUPPORTED_FORMAT_VERSION_MIN ||
+      version > HS_DESC_SUPPORTED_FORMAT_VERSION_MAX) {
+    return 0;
+  }
+  return 1;
+}
+
+/* Public API. */
+
+int hs_desc_encode_descriptor(const hs_descriptor_t *desc,
+                              char **encoded_out);
+
+#endif /* TOR_HS_DESCRIPTOR_H */
diff --git a/src/or/include.am b/src/or/include.am
index 3ae45cb..e28bda4 100644
--- a/src/or/include.am
+++ b/src/or/include.am
@@ -49,6 +49,7 @@ LIBTOR_A_SOURCES = \
 	src/or/ext_orport.c				\
 	src/or/hibernate.c				\
 	src/or/hs_common.c				\
+	src/or/hs_descriptor.c				\
 	src/or/keypin.c					\
 	src/or/main.c					\
 	src/or/microdesc.c				\
@@ -159,6 +160,7 @@ ORHEADERS = \
 	src/or/entrynodes.h				\
 	src/or/hibernate.h				\
 	src/or/hs_common.h				\
+	src/or/hs_descriptor.h				\
 	src/or/keypin.h					\
 	src/or/main.h					\
 	src/or/microdesc.h				\
diff --git a/src/or/torcert.h b/src/or/torcert.h
index 4665e67..29bc0fc 100644
--- a/src/or/torcert.h
+++ b/src/or/torcert.h
@@ -11,6 +11,8 @@
 #define CERT_TYPE_ID_SIGNING    0x04
 #define CERT_TYPE_SIGNING_LINK  0x05
 #define CERT_TYPE_SIGNING_AUTH  0x06
+#define CERT_TYPE_HS_DESC_SIGN  0x08
+#define CERT_TYPE_HS_IP_AUTH    0x09
 #define CERT_TYPE_ONION_ID      0x0A
 #define CERT_TYPE_HS_IP_ENC     0x0B
 





More information about the tor-commits mailing list