mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-11 21:53:48 +01:00
6c8c973191
We need to keep these around for TAP and old-style hidden services, but they're obsolete, and we shouldn't encourage anyone to use them. So I've added "obsolete" to their names, and a comment explaining what the problem is. Closes ticket 23026.
245 lines
8.0 KiB
C
245 lines
8.0 KiB
C
/* Copyright (c) 2001 Matej Pfajfar.
|
|
* Copyright (c) 2001-2004, Roger Dingledine.
|
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
|
* Copyright (c) 2007-2017, The Tor Project, Inc. */
|
|
/* See LICENSE for licensing information */
|
|
|
|
/**
|
|
* \file onion_tap.c
|
|
* \brief Functions to implement the original Tor circuit extension handshake
|
|
* (a.k.a TAP).
|
|
*
|
|
* The "TAP" handshake is the first one that was widely used in Tor: It
|
|
* combines RSA1024-OAEP and AES128-CTR to perform a hybrid encryption over
|
|
* the first message DH1024 key exchange. (The RSA-encrypted part of the
|
|
* encryption is authenticated; the AES-encrypted part isn't. This was
|
|
* not a smart choice.)
|
|
*
|
|
* We didn't call it "TAP" ourselves -- Ian Goldberg named it in "On the
|
|
* Security of the Tor Authentication Protocol". (Spoiler: it's secure, but
|
|
* its security is kind of fragile and implementation dependent. Never modify
|
|
* this implementation without reading and understanding that paper at least.)
|
|
*
|
|
* We have deprecated TAP since the ntor handshake came into general use. It
|
|
* is still used for hidden service IP and RP connections, however.
|
|
*
|
|
* This handshake, like the other circuit-extension handshakes, is
|
|
* invoked from onion.c.
|
|
**/
|
|
|
|
#include "or.h"
|
|
#include "config.h"
|
|
#include "onion_tap.h"
|
|
#include "rephist.h"
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
/** Given a router's 128 byte public key,
|
|
* stores the following in onion_skin_out:
|
|
* - [42 bytes] OAEP padding
|
|
* - [16 bytes] Symmetric key for encrypting blob past RSA
|
|
* - [70 bytes] g^x part 1 (inside the RSA)
|
|
* - [58 bytes] g^x part 2 (symmetrically encrypted)
|
|
*
|
|
* Stores the DH private key into handshake_state_out for later completion
|
|
* of the handshake.
|
|
*
|
|
* The meeting point/cookies and auth are zeroed out for now.
|
|
*/
|
|
int
|
|
onion_skin_TAP_create(crypto_pk_t *dest_router_key,
|
|
crypto_dh_t **handshake_state_out,
|
|
char *onion_skin_out) /* TAP_ONIONSKIN_CHALLENGE_LEN bytes */
|
|
{
|
|
char challenge[DH_KEY_LEN];
|
|
crypto_dh_t *dh = NULL;
|
|
int dhbytes, pkbytes;
|
|
|
|
tor_assert(dest_router_key);
|
|
tor_assert(handshake_state_out);
|
|
tor_assert(onion_skin_out);
|
|
*handshake_state_out = NULL;
|
|
memset(onion_skin_out, 0, TAP_ONIONSKIN_CHALLENGE_LEN);
|
|
|
|
if (!(dh = crypto_dh_new(DH_TYPE_CIRCUIT)))
|
|
goto err;
|
|
|
|
dhbytes = crypto_dh_get_bytes(dh);
|
|
pkbytes = (int) crypto_pk_keysize(dest_router_key);
|
|
tor_assert(dhbytes == 128);
|
|
tor_assert(pkbytes == 128);
|
|
|
|
if (crypto_dh_get_public(dh, challenge, dhbytes))
|
|
goto err;
|
|
|
|
/* set meeting point, meeting cookie, etc here. Leave zero for now. */
|
|
if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out,
|
|
TAP_ONIONSKIN_CHALLENGE_LEN,
|
|
challenge, DH_KEY_LEN,
|
|
PK_PKCS1_OAEP_PADDING, 1)<0)
|
|
goto err;
|
|
|
|
memwipe(challenge, 0, sizeof(challenge));
|
|
*handshake_state_out = dh;
|
|
|
|
return 0;
|
|
err:
|
|
/* LCOV_EXCL_START
|
|
* We only get here if RSA encryption fails or DH keygen fails. Those
|
|
* shouldn't be possible. */
|
|
memwipe(challenge, 0, sizeof(challenge));
|
|
if (dh) crypto_dh_free(dh);
|
|
return -1;
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
|
|
/** Given an encrypted DH public key as generated by onion_skin_create,
|
|
* and the private key for this onion router, generate the reply (128-byte
|
|
* DH plus the first 20 bytes of shared key material), and store the
|
|
* next key_out_len bytes of key material in key_out.
|
|
*/
|
|
int
|
|
onion_skin_TAP_server_handshake(
|
|
/*TAP_ONIONSKIN_CHALLENGE_LEN*/
|
|
const char *onion_skin,
|
|
crypto_pk_t *private_key,
|
|
crypto_pk_t *prev_private_key,
|
|
/*TAP_ONIONSKIN_REPLY_LEN*/
|
|
char *handshake_reply_out,
|
|
char *key_out,
|
|
size_t key_out_len)
|
|
{
|
|
char challenge[TAP_ONIONSKIN_CHALLENGE_LEN];
|
|
crypto_dh_t *dh = NULL;
|
|
ssize_t len;
|
|
char *key_material=NULL;
|
|
size_t key_material_len=0;
|
|
int i;
|
|
crypto_pk_t *k;
|
|
|
|
len = -1;
|
|
for (i=0;i<2;++i) {
|
|
k = i==0?private_key:prev_private_key;
|
|
if (!k)
|
|
break;
|
|
len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge,
|
|
TAP_ONIONSKIN_CHALLENGE_LEN,
|
|
onion_skin,
|
|
TAP_ONIONSKIN_CHALLENGE_LEN,
|
|
PK_PKCS1_OAEP_PADDING,0);
|
|
if (len>0)
|
|
break;
|
|
}
|
|
if (len<0) {
|
|
log_info(LD_PROTOCOL,
|
|
"Couldn't decrypt onionskin: client may be using old onion key");
|
|
goto err;
|
|
} else if (len != DH_KEY_LEN) {
|
|
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
|
|
"Unexpected onionskin length after decryption: %ld",
|
|
(long)len);
|
|
goto err;
|
|
}
|
|
|
|
dh = crypto_dh_new(DH_TYPE_CIRCUIT);
|
|
if (!dh) {
|
|
/* LCOV_EXCL_START
|
|
* Failure to allocate a DH key should be impossible.
|
|
*/
|
|
log_warn(LD_BUG, "Couldn't allocate DH key");
|
|
goto err;
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
if (crypto_dh_get_public(dh, handshake_reply_out, DH_KEY_LEN)) {
|
|
/* LCOV_EXCL_START
|
|
* This can only fail if the length of the key we just allocated is too
|
|
* big. That should be impossible. */
|
|
log_info(LD_GENERAL, "crypto_dh_get_public failed.");
|
|
goto err;
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
|
|
key_material_len = DIGEST_LEN+key_out_len;
|
|
key_material = tor_malloc(key_material_len);
|
|
len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, challenge,
|
|
DH_KEY_LEN, key_material,
|
|
key_material_len);
|
|
if (len < 0) {
|
|
log_info(LD_GENERAL, "crypto_dh_compute_secret failed.");
|
|
goto err;
|
|
}
|
|
|
|
/* send back H(K|0) as proof that we learned K. */
|
|
memcpy(handshake_reply_out+DH_KEY_LEN, key_material, DIGEST_LEN);
|
|
|
|
/* use the rest of the key material for our shared keys, digests, etc */
|
|
memcpy(key_out, key_material+DIGEST_LEN, key_out_len);
|
|
|
|
memwipe(challenge, 0, sizeof(challenge));
|
|
memwipe(key_material, 0, key_material_len);
|
|
tor_free(key_material);
|
|
crypto_dh_free(dh);
|
|
return 0;
|
|
err:
|
|
memwipe(challenge, 0, sizeof(challenge));
|
|
if (key_material) {
|
|
memwipe(key_material, 0, key_material_len);
|
|
tor_free(key_material);
|
|
}
|
|
if (dh) crypto_dh_free(dh);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/** Finish the client side of the DH handshake.
|
|
* Given the 128 byte DH reply + 20 byte hash as generated by
|
|
* onion_skin_server_handshake and the handshake state generated by
|
|
* onion_skin_create, verify H(K) with the first 20 bytes of shared
|
|
* key material, then generate key_out_len more bytes of shared key
|
|
* material and store them in key_out.
|
|
*
|
|
* After the invocation, call crypto_dh_free on handshake_state.
|
|
*/
|
|
int
|
|
onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state,
|
|
const char *handshake_reply, /* TAP_ONIONSKIN_REPLY_LEN bytes */
|
|
char *key_out,
|
|
size_t key_out_len,
|
|
const char **msg_out)
|
|
{
|
|
ssize_t len;
|
|
char *key_material=NULL;
|
|
size_t key_material_len;
|
|
tor_assert(crypto_dh_get_bytes(handshake_state) == DH_KEY_LEN);
|
|
|
|
key_material_len = DIGEST_LEN + key_out_len;
|
|
key_material = tor_malloc(key_material_len);
|
|
len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, handshake_state,
|
|
handshake_reply, DH_KEY_LEN, key_material,
|
|
key_material_len);
|
|
if (len < 0) {
|
|
if (msg_out)
|
|
*msg_out = "DH computation failed.";
|
|
goto err;
|
|
}
|
|
|
|
if (tor_memneq(key_material, handshake_reply+DH_KEY_LEN, DIGEST_LEN)) {
|
|
/* H(K) does *not* match. Something fishy. */
|
|
if (msg_out)
|
|
*msg_out = "Digest DOES NOT MATCH on onion handshake. Bug or attack.";
|
|
goto err;
|
|
}
|
|
|
|
/* use the rest of the key material for our shared keys, digests, etc */
|
|
memcpy(key_out, key_material+DIGEST_LEN, key_out_len);
|
|
|
|
memwipe(key_material, 0, key_material_len);
|
|
tor_free(key_material);
|
|
return 0;
|
|
err:
|
|
memwipe(key_material, 0, key_material_len);
|
|
tor_free(key_material);
|
|
return -1;
|
|
}
|
|
|