diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 0480f63308..2a000f5002 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -79,7 +79,9 @@ #define str_intro_point "introduction-point" #define str_ip_auth_key "auth-key" #define str_ip_enc_key "enc-key" -#define str_ip_enc_key_cert "enc-key-certification" +#define str_ip_enc_key_cert "enc-key-cert" +#define str_ip_legacy_key "legacy-key" +#define str_ip_legacy_key_cert "legacy-key-cert" #define str_intro_point_start "\n" str_intro_point " " /* Constant string value for the construction to encrypt the encrypted data * section. */ @@ -134,9 +136,10 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = { static token_rule_t hs_desc_intro_point_v3_token_table[] = { T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ), T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ), - T1(str_ip_enc_key, R3_INTRO_ENC_KEY, ARGS, OBJ_OK), - T1_END(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERTIFICATION, - NO_ARGS, NEED_OBJ), + T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK), + T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK), + T01(str_ip_legacy_key, R3_INTRO_LEGACY_KEY, ARGS, NEED_KEY_1024), + T01(str_ip_legacy_key_cert, R3_INTRO_LEGACY_KEY_CERT, ARGS, OBJ_OK), END_OF_TABLE }; @@ -153,8 +156,12 @@ desc_intro_point_free(hs_desc_intro_point_t *ip) smartlist_free(ip->link_specifiers); } tor_cert_free(ip->auth_key_cert); - if (ip->enc_key_type == HS_DESC_KEY_TYPE_LEGACY) { - crypto_pk_free(ip->enc_key.legacy); + tor_cert_free(ip->enc_key_cert); + if (ip->legacy.key) { + crypto_pk_free(ip->legacy.key); + } + if (ip->legacy.cert.encoded) { + tor_free(ip->legacy.cert.encoded); } tor_free(ip); } @@ -406,101 +413,68 @@ encode_link_specifiers(const smartlist_t *specs) return encoded_b64; } -/* Encode an introduction point encryption key and return a newly allocated - * string with it. On failure, return NULL. */ +/* Encode an introduction point legacy key and certificate. Return a newly + * allocated string with it. On failure, return NULL. */ static char * -encode_enc_key(const ed25519_public_key_t *sig_key, - const hs_desc_intro_point_t *ip) +encode_legacy_key(const hs_desc_intro_point_t *ip) { - char *encoded = NULL; - time_t now = time(NULL); + char *key_str, b64_cert[256], *encoded = NULL; + size_t key_str_len; - 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 = NULL; - - /* Create cross certification cert. */ - cert_len = tor_make_rsa_ed25519_crosscert(sig_key, 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) { - tor_free(cert_data); - log_warn(LD_REND, "Unable to encode legacy crosscert."); - goto err; - } - tor_free(cert_data); - /* 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; + /* Encode cross cert. */ + if (base64_encode(b64_cert, sizeof(b64_cert), + (const char *) ip->legacy.cert.encoded, + ip->legacy.cert.len, BASE64_ENCODE_MULTILINE) < 0) { + log_warn(LD_REND, "Unable to encode legacy crosscert."); + goto done; } - case HS_DESC_KEY_TYPE_CURVE25519: - { - int signbit, ret; - 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_CROSS_HS_IP_KEYS, - sig_key, now, - HS_DESC_CERT_LIFETIME, - CERT_FLAG_INCLUDE_SIGNING_KEY); - memwipe(&curve_kp, 0, sizeof(curve_kp)); - if (!cross_cert) { - goto err; - } - ret = tor_cert_encode_ed22519(cross_cert, &encoded_cert); - tor_cert_free(cross_cert); - if (ret) { - 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); + /* Convert the encryption key to PEM format NUL terminated. */ + if (crypto_pk_write_public_key_to_string(ip->legacy.key, &key_str, + &key_str_len) < 0) { + log_warn(LD_REND, "Unable to encode legacy encryption key."); + goto done; } + tor_asprintf(&encoded, + "%s \n%s" /* Newline is added by the call above. */ + "%s\n" + "-----BEGIN CROSSCERT-----\n" + "%s" + "-----END CROSSCERT-----", + str_ip_legacy_key, key_str, + str_ip_legacy_key_cert, b64_cert); + tor_free(key_str); - err: + done: + return encoded; +} + +/* Encode an introduction point encryption key and certificate. Return a newly + * allocated string with it. On failure, return NULL. */ +static char * +encode_enc_key(const hs_desc_intro_point_t *ip) +{ + char *encoded = NULL, *encoded_cert; + char key_b64[CURVE25519_BASE64_PADDED_LEN + 1]; + + tor_assert(ip); + + /* Base64 encode the encryption key for the "enc-key" field. */ + if (curve25519_public_to_base64(key_b64, &ip->enc_key) < 0) { + goto done; + } + if (tor_cert_encode_ed22519(ip->enc_key_cert, &encoded_cert) < 0) { + goto done; + } + tor_asprintf(&encoded, + "%s ntor %s\n" + "%s\n%s", + str_ip_enc_key, key_b64, + str_ip_enc_key_cert, encoded_cert); + tor_free(encoded_cert); + + done: return encoded; } @@ -535,7 +509,7 @@ encode_intro_point(const ed25519_public_key_t *sig_key, /* Encryption key encoding. */ { - char *encoded_enc_key = encode_enc_key(sig_key, ip); + char *encoded_enc_key = encode_enc_key(ip); if (encoded_enc_key == NULL) { goto err; } @@ -543,6 +517,18 @@ encode_intro_point(const ed25519_public_key_t *sig_key, tor_free(encoded_enc_key); } + /* Legacy key if any. */ + if (ip->legacy.key != NULL) { + /* Strong requirement else the IP creation was badly done. */ + tor_assert(ip->legacy.cert.encoded); + char *encoded_legacy_key = encode_legacy_key(ip); + if (encoded_legacy_key == NULL) { + goto err; + } + smartlist_add_asprintf(lines, "%s", encoded_legacy_key); + tor_free(encoded_legacy_key); + } + /* Join them all in one blob of text. */ encoded_ip = smartlist_join_strings(lines, "\n", 1, NULL); @@ -1581,6 +1567,64 @@ desc_decrypt_all(const hs_descriptor_t *desc, char **decrypted_out) return decrypted_len; } +/* Given the token tok for an intro point legacy key, the list of tokens, the + * introduction point ip being decoded and the descriptor desc from which it + * comes from, decode the legacy key and set the intro point object. Return 0 + * on success else -1 on failure. */ +static int +decode_intro_legacy_key(const directory_token_t *tok, + smartlist_t *tokens, + hs_desc_intro_point_t *ip, + const hs_descriptor_t *desc) +{ + tor_assert(tok); + tor_assert(tokens); + tor_assert(ip); + tor_assert(desc); + + if (!crypto_pk_public_exponent_ok(tok->key)) { + log_warn(LD_REND, "Introduction point legacy key is invalid"); + goto err; + } + ip->legacy.key = crypto_pk_dup_key(tok->key); + /* Extract the legacy cross certification cert which MUST be present if we + * have a legacy key. */ + tok = find_opt_by_keyword(tokens, R3_INTRO_LEGACY_KEY_CERT); + if (!tok) { + log_warn(LD_REND, "Introduction point legacy key cert is missing"); + goto err; + } + tor_assert(tok->object_body); + if (strcmp(tok->object_type, "CROSSCERT")) { + /* Info level because this might be an unknown field that we should + * ignore. */ + log_info(LD_REND, "Introduction point legacy encryption key " + "cross-certification has an unknown format."); + goto err; + } + /* Keep a copy of the certificate. */ + ip->legacy.cert.encoded = tor_memdup(tok->object_body, tok->object_size); + ip->legacy.cert.len = tok->object_size; + /* The check on the expiration date is for the entire lifetime of a + * certificate which is 24 hours. However, a descriptor has a maximum + * lifetime of 12 hours meaning we have a 12h difference between the two + * which ultimately accomodate the clock skewed client. */ + if (rsa_ed25519_crosscert_check(ip->legacy.cert.encoded, + ip->legacy.cert.len, ip->legacy.key, + &desc->plaintext_data.signing_pubkey, + approx_time() - HS_DESC_CERT_LIFETIME)) { + log_warn(LD_REND, "Unable to check cross-certification on the " + "introduction point legacy encryption key."); + ip->cross_certified = 0; + goto err; + } + + /* Success. */ + return 0; + err: + return -1; +} + /* Given the start of a section and the end of it, decode a single * introduction point from that section. Return a newly allocated introduction * point object containing the decoded data. Return NULL if the section can't @@ -1591,7 +1635,6 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) hs_desc_intro_point_t *ip = NULL; memarea_t *area = NULL; smartlist_t *tokens = NULL; - tor_cert_t *cross_cert = NULL; const directory_token_t *tok; tor_assert(desc); @@ -1625,84 +1668,67 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) log_warn(LD_REND, "Unexpected object type for introduction auth key"); goto err; } - /* Parse cert and do some validation. */ if (cert_parse_and_validate(&ip->auth_key_cert, tok->object_body, tok->object_size, CERT_TYPE_AUTH_HS_IP_KEY, "introduction point auth-key") < 0) { goto err; } + /* Validate authentication certificate with descriptor signing key. */ + if (tor_cert_checksig(ip->auth_key_cert, + &desc->plaintext_data.signing_pubkey, 0) < 0) { + log_warn(LD_REND, "Invalid authentication key signature"); + goto err; + } - /* Exactly one "enc-key" ... */ + /* Exactly one "enc-key" SP "ntor" SP key NL */ tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY); if (!strcmp(tok->args[0], "ntor")) { - /* "enc-key" SP "ntor" SP key NL */ - if (tok->n_args != 2 || tok->object_body) { - log_warn(LD_REND, "Introduction point ntor encryption key is invalid"); - goto err; - } + /* This field is using GE(2) so for possible forward compatibility, we + * accept more fields but must be at least 2. */ + tor_assert(tok->n_args >= 2); - if (curve25519_public_from_base64(&ip->enc_key.curve25519.pubkey, - tok->args[1]) < 0) { - log_warn(LD_REND, "Introduction point ntor encryption key is invalid"); + if (curve25519_public_from_base64(&ip->enc_key, tok->args[1]) < 0) { + log_warn(LD_REND, "Introduction point ntor enc-key is invalid"); goto err; } - ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519; - } else if (!strcmp(tok->args[0], "legacy")) { - /* "enc-key" SP "legacy" NL key NL */ - if (!tok->key) { - log_warn(LD_REND, "Introduction point legacy encryption key is " - "invalid"); - goto err; - } - ip->enc_key.legacy = crypto_pk_dup_key(tok->key); - ip->enc_key_type = HS_DESC_KEY_TYPE_LEGACY; } else { /* Unknown key type so we can't use that introduction point. */ log_warn(LD_REND, "Introduction point encryption key is unrecognized."); goto err; } - /* "enc-key-certification" NL certificate NL */ - tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY_CERTIFICATION); + /* Exactly once "enc-key-cert" NL certificate NL */ + tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY_CERT); tor_assert(tok->object_body); /* Do the cross certification. */ - switch (ip->enc_key_type) { - case HS_DESC_KEY_TYPE_CURVE25519: - { - if (strcmp(tok->object_type, "ED25519 CERT")) { + if (strcmp(tok->object_type, "ED25519 CERT")) { log_warn(LD_REND, "Introduction point ntor encryption key " "cross-certification has an unknown format."); goto err; - } - if (cert_parse_and_validate(&cross_cert, tok->object_body, - tok->object_size, CERT_TYPE_CROSS_HS_IP_KEYS, - "introduction point enc-key-certification") < 0) { - goto err; - } - break; } - case HS_DESC_KEY_TYPE_LEGACY: - if (strcmp(tok->object_type, "CROSSCERT")) { - log_warn(LD_REND, "Introduction point legacy encryption key " - "cross-certification has an unknown format."); - goto err; - } - if (rsa_ed25519_crosscert_check((const uint8_t *) tok->object_body, - tok->object_size, ip->enc_key.legacy, - &desc->plaintext_data.signing_key_cert->signed_key, - approx_time()-86400)) { - log_warn(LD_REND, "Unable to check cross-certification on the " - "introduction point legacy encryption key."); - goto err; - } - break; - default: - tor_assert(0); - break; + if (cert_parse_and_validate(&ip->enc_key_cert, tok->object_body, + tok->object_size, CERT_TYPE_CROSS_HS_IP_KEYS, + "introduction point enc-key-cert") < 0) { + goto err; + } + if (tor_cert_checksig(ip->enc_key_cert, + &desc->plaintext_data.signing_pubkey, 0) < 0) { + log_warn(LD_REND, "Invalid encryption key signature"); + goto err; } /* It is successfully cross certified. Flag the object. */ ip->cross_certified = 1; + + /* Do we have a "legacy-key" SP key NL ?*/ + tok = find_opt_by_keyword(tokens, R3_INTRO_LEGACY_KEY); + if (tok) { + if (decode_intro_legacy_key(tok, tokens, ip, desc) < 0) { + goto err; + } + } + + /* Introduction point has been parsed successfully. */ goto done; err: @@ -1710,7 +1736,6 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) ip = NULL; done: - tor_cert_free(cross_cert); SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); smartlist_free(tokens); if (area) { diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index b7d512c06b..b8b94792de 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -58,12 +58,6 @@ typedef enum { HS_DESC_AUTH_ED25519 = 1 } hs_desc_auth_type_t; -/* 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 { @@ -91,18 +85,29 @@ typedef struct hs_desc_intro_point_t { * 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; + /* Encryption key for the "ntor" type. */ + curve25519_public_key_t enc_key; - /* Keys are mutually exclusive thus the union. */ - union { - /* Encryption key used to encrypt request to hidden service. */ - curve25519_keypair_t curve25519; + /* Certificate cross certifying the descriptor signing key by the encryption + * curve25519 key. This certificate contains the signing key and is of type + * CERT_TYPE_CROSS_HS_IP_KEYS [0B]. */ + tor_cert_t *enc_key_cert; - /* Backward compat: RSA 1024 encryption key for legacy purposes. - * Mutually exclusive with enc_key. */ - crypto_pk_t *legacy; - } enc_key; + /* (Optional): If this introduction point is a legacy one that is version <= + * 0.2.9.x (HSIntro=3), we use this extra key for the intro point to be able + * to relay the cells to the service correctly. */ + struct { + /* RSA public key. */ + crypto_pk_t *key; + + /* Cross certified cert with the descriptor signing key (RSA->Ed). Because + * of the cross certification API, we need to keep the certificate binary + * blob and its length in order to properly encode it after. */ + struct { + uint8_t *encoded; + size_t len; + } cert; + } legacy; /* True iff the introduction point has passed the cross certification. Upon * decoding an intro point, this must be true. */ diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h index f4974a9683..b9f1613457 100644 --- a/src/or/parsecommon.h +++ b/src/or/parsecommon.h @@ -162,7 +162,9 @@ typedef enum { R3_INTRODUCTION_POINT, R3_INTRO_AUTH_KEY, R3_INTRO_ENC_KEY, - R3_INTRO_ENC_KEY_CERTIFICATION, + R3_INTRO_ENC_KEY_CERT, + R3_INTRO_LEGACY_KEY, + R3_INTRO_LEGACY_KEY_CERT, R3_DESC_AUTH_TYPE, R3_DESC_AUTH_KEY, R3_DESC_AUTH_CLIENT, diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c index a17bf0f0a2..3f0d6a9413 100644 --- a/src/test/hs_test_helpers.c +++ b/src/test/hs_test_helpers.c @@ -51,15 +51,36 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now, tt_assert(ip->auth_key_cert); if (legacy) { - ip->enc_key.legacy = crypto_pk_new(); - ip->enc_key_type = HS_DESC_KEY_TYPE_LEGACY; - tt_assert(ip->enc_key.legacy); - ret = crypto_pk_generate_key(ip->enc_key.legacy); + ip->legacy.key = crypto_pk_new(); + tt_assert(ip->legacy.key); + ret = crypto_pk_generate_key(ip->legacy.key); tt_int_op(ret, ==, 0); - } else { - ret = curve25519_keypair_generate(&ip->enc_key.curve25519, 0); + ssize_t cert_len = tor_make_rsa_ed25519_crosscert( + &signing_kp->pubkey, ip->legacy.key, + now + HS_DESC_CERT_LIFETIME, + &ip->legacy.cert.encoded); + tt_assert(ip->legacy.cert.encoded); + tt_u64_op(cert_len, OP_GT, 0); + ip->legacy.cert.len = cert_len; + } + + /* Encryption key. */ + { + int signbit; + curve25519_keypair_t curve25519_kp; + ed25519_keypair_t ed25519_kp; + tor_cert_t *cross_cert; + + ret = curve25519_keypair_generate(&curve25519_kp, 0); tt_int_op(ret, ==, 0); - ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519; + ed25519_keypair_from_curve25519_keypair(&ed25519_kp, &signbit, + &curve25519_kp); + cross_cert = tor_cert_create(signing_kp, CERT_TYPE_CROSS_HS_IP_KEYS, + &ed25519_kp.pubkey, time(NULL), + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + tt_assert(cross_cert); + ip->enc_key_cert = cross_cert; } intro_point = ip; @@ -192,19 +213,11 @@ hs_helper_desc_equal(const hs_descriptor_t *desc1, *ip2 = smartlist_get(desc2->encrypted_data .intro_points, i); tt_assert(tor_cert_eq(ip1->auth_key_cert, ip2->auth_key_cert)); - tt_int_op(ip1->enc_key_type, OP_EQ, ip2->enc_key_type); - tt_assert(ip1->enc_key_type == HS_DESC_KEY_TYPE_LEGACY || - ip1->enc_key_type == HS_DESC_KEY_TYPE_CURVE25519); - switch (ip1->enc_key_type) { - case HS_DESC_KEY_TYPE_LEGACY: - tt_int_op(crypto_pk_cmp_keys(ip1->enc_key.legacy, - ip2->enc_key.legacy), OP_EQ, 0); - break; - case HS_DESC_KEY_TYPE_CURVE25519: - tt_mem_op(ip1->enc_key.curve25519.pubkey.public_key, OP_EQ, - ip2->enc_key.curve25519.pubkey.public_key, - CURVE25519_PUBKEY_LEN); - break; + if (ip1->legacy.key) { + tt_int_op(crypto_pk_cmp_keys(ip1->legacy.key, ip2->legacy.key), + OP_EQ, 0); + } else { + tt_mem_op(&ip1->enc_key, OP_EQ, &ip2->enc_key, CURVE25519_PUBKEY_LEN); } tt_int_op(smartlist_len(ip1->link_specifiers), ==, diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c index b4c58937c4..b1abe381d4 100644 --- a/src/test/test_hs_descriptor.c +++ b/src/test/test_hs_descriptor.c @@ -410,7 +410,7 @@ test_decode_invalid_intro_point(void *arg) const char *enc_key = "enc-key ntor bpZKLsuhxP6woDQ3yVyjm5gUKSk7RjfAijT2qrzbQk0="; const char *enc_key_cert = - "enc-key-certification\n" + "enc-key-cert\n" "-----BEGIN ED25519 CERT-----\n" "AQsACOhZAUpNvCZ1aJaaR49lS6MCdsVkhVGVrRqoj0Y2T4SzroAtAQAgBABFOcGg\n" "lbTt1DF5nKTE/gU3Fr8ZtlCIOhu1A+F5LM7fqCUupfesg0KTHwyIZOYQbJuM5/he\n"