1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 09:37:56 +00:00
transmission/libtransmission/handshake.c
Mike Gelfand 117ab3e8d2 #4400, #5462: Move DH helpers to crypto-utils
On a way to factoring out OpenSSL support to a standalone file to ease
addition of other crypto libraries support in the future, move helpers
providing DH key exchange to crypto-utils.{c,h}. OpenSSL-related
functionality (DH context management) is moved to crypto-utils-openssl.c.
Since we know in advance that DH secret key management code will be the
same for most of backends, implement common functionality in separate
crypto-utils-fallback.c.

Add new tr_dh_ctx_t and tr_dh_secret_t types and functions to be
implemented by crypto backends:
* tr_dh_new - allocate DH context,
* tr_dh_free - free the context,
* tr_dh_make_key - generate private/public keypair,
* tr_dh_agree - perform DH key exchange and generate secret key,
* tr_dh_secret_derive - calculate secret key hash,
* tr_dh_secret_free - free the secret key,
* tr_dh_align_key - align some DH key in the buffer allocated for it.

Make DH secret key not accessible in plain form outside the crypto
backend. This allows for implementations where the key is managed by
the underlying library and is not even exposed to our backend.
2014-12-04 19:18:08 +00:00

1259 lines
35 KiB
C

/*
* This file Copyright (C) 2007-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#include <assert.h>
#include <errno.h>
#include <string.h> /* strcmp (), strlen () */
#include <event2/buffer.h>
#include <event2/event.h>
#include "transmission.h"
#include "clients.h"
#include "crypto-utils.h"
#include "handshake.h"
#include "log.h"
#include "peer-io.h"
#include "peer-mgr.h"
#include "session.h"
#include "torrent.h"
#include "tr-dht.h"
#include "utils.h"
/* enable LibTransmission extension protocol */
#define ENABLE_LTEP * /
/* fast extensions */
#define ENABLE_FAST * /
/* DHT */
#define ENABLE_DHT * /
/***
****
***/
#define HANDSHAKE_NAME "\023BitTorrent protocol"
enum
{
/* BitTorrent Handshake Constants */
HANDSHAKE_NAME_LEN = 20,
HANDSHAKE_FLAGS_LEN = 8,
HANDSHAKE_SIZE = 68,
INCOMING_HANDSHAKE_LEN = 48,
/* Encryption Constants */
PadA_MAXLEN = 512,
PadB_MAXLEN = 512,
PadC_MAXLEN = 512,
PadD_MAXLEN = 512,
VC_LENGTH = 8,
CRYPTO_PROVIDE_PLAINTEXT = 1,
CRYPTO_PROVIDE_CRYPTO = 2,
/* how long to wait before giving up on a handshake */
HANDSHAKE_TIMEOUT_SEC = 30
};
#ifdef ENABLE_LTEP
#define HANDSHAKE_HAS_LTEP(bits)(((bits)[5] & 0x10) ? 1 : 0)
#define HANDSHAKE_SET_LTEP(bits)((bits)[5] |= 0x10)
#else
#define HANDSHAKE_HAS_LTEP(bits)(0)
#define HANDSHAKE_SET_LTEP(bits)((void)0)
#endif
#ifdef ENABLE_FAST
#define HANDSHAKE_HAS_FASTEXT(bits)(((bits)[7] & 0x04) ? 1 : 0)
#define HANDSHAKE_SET_FASTEXT(bits)((bits)[7] |= 0x04)
#else
#define HANDSHAKE_HAS_FASTEXT(bits)(0)
#define HANDSHAKE_SET_FASTEXT(bits)((void)0)
#endif
#ifdef ENABLE_DHT
#define HANDSHAKE_HAS_DHT(bits)(((bits)[7] & 0x01) ? 1 : 0)
#define HANDSHAKE_SET_DHT(bits)((bits)[7] |= 0x01)
#else
#define HANDSHAKE_HAS_DHT(bits)(0)
#define HANDSHAKE_SET_DHT(bits)((void)0)
#endif
/* http://www.azureuswiki.com/index.php/Extension_negotiation_protocol
these macros are to be used if both extended messaging and the
azureus protocol is supported, they indicate which protocol is preferred */
#define HANDSHAKE_GET_EXTPREF(reserved) ((reserved)[5] & 0x03)
#define HANDSHAKE_SET_EXTPREF(reserved, val)((reserved)[5] |= 0x03 & (val))
typedef uint8_t handshake_state_t;
struct tr_handshake
{
bool haveReadAnythingFromPeer;
bool havePeerID;
bool haveSentBitTorrentHandshake;
tr_peerIo * io;
tr_crypto * crypto;
tr_session * session;
handshake_state_t state;
tr_encryption_mode encryptionMode;
uint16_t pad_c_len;
uint16_t pad_d_len;
uint16_t ia_len;
uint32_t crypto_select;
uint32_t crypto_provide;
uint8_t myReq1[SHA_DIGEST_LENGTH];
handshakeDoneCB doneCB;
void * doneUserData;
struct event * timeout_timer;
};
/**
***
**/
enum
{
/* incoming */
AWAITING_HANDSHAKE,
AWAITING_PEER_ID,
AWAITING_YA,
AWAITING_PAD_A,
AWAITING_CRYPTO_PROVIDE,
AWAITING_PAD_C,
AWAITING_IA,
AWAITING_PAYLOAD_STREAM,
/* outgoing */
AWAITING_YB,
AWAITING_VC,
AWAITING_CRYPTO_SELECT,
AWAITING_PAD_D,
N_STATES
};
/**
***
**/
#define dbgmsg(handshake, ...) \
do { \
if (tr_logGetDeepEnabled ()) \
tr_logAddDeep (__FILE__, __LINE__, tr_peerIoGetAddrStr (handshake->io), __VA_ARGS__); \
} while (0)
static const char*
getStateName (const handshake_state_t state)
{
static const char * const state_strings[N_STATES] =
{
/* AWAITING_HANDSHAKE */ "awaiting handshake",
/* AWAITING_PEER_ID */ "awaiting peer id",
/* AWAITING_YA */ "awaiting ya",
/* AWAITING_PAD_A */ "awaiting pad a",
/* AWAITING_CRYPTO_PROVIDE */ "awaiting crypto_provide",
/* AWAITING_PAD_C */ "awaiting pad c",
/* AWAITING_IA */ "awaiting ia",
/* AWAITING_PAYLOAD_STREAM */ "awaiting payload stream",
/* AWAITING_YB */ "awaiting yb",
/* AWAITING_VC */ "awaiting vc",
/* AWAITING_CRYPTO_SELECT */ "awaiting crypto select",
/* AWAITING_PAD_D */ "awaiting pad d"
};
return state<N_STATES ? state_strings[state] : "unknown state";
}
static void
setState (tr_handshake * handshake, handshake_state_t state)
{
dbgmsg (handshake, "setting to state [%s]", getStateName (state));
handshake->state = state;
}
static void
setReadState (tr_handshake * handshake, handshake_state_t state)
{
setState (handshake, state);
}
static bool
buildHandshakeMessage (tr_handshake * handshake, uint8_t * buf)
{
const unsigned char * peer_id = NULL;
const uint8_t * torrentHash;
tr_torrent * tor;
bool success;
if ((torrentHash = tr_cryptoGetTorrentHash (handshake->crypto)))
if ((tor = tr_torrentFindFromHash (handshake->session, torrentHash)))
peer_id = tr_torrentGetPeerId (tor);
if (peer_id == NULL)
{
success = false;
}
else
{
uint8_t * walk = buf;
memcpy (walk, HANDSHAKE_NAME, HANDSHAKE_NAME_LEN);
walk += HANDSHAKE_NAME_LEN;
memset (walk, 0, HANDSHAKE_FLAGS_LEN);
HANDSHAKE_SET_LTEP (walk);
HANDSHAKE_SET_FASTEXT (walk);
/* Note that this doesn't depend on whether the torrent is private.
* We don't accept DHT peers for a private torrent,
* but we participate in the DHT regardless. */
if (tr_dhtEnabled (handshake->session))
HANDSHAKE_SET_DHT (walk);
walk += HANDSHAKE_FLAGS_LEN;
memcpy (walk, torrentHash, SHA_DIGEST_LENGTH);
walk += SHA_DIGEST_LENGTH;
memcpy (walk, peer_id, PEER_ID_LEN);
walk += PEER_ID_LEN;
assert (walk - buf == HANDSHAKE_SIZE);
success = true;
}
return success;
}
static int tr_handshakeDone (tr_handshake * handshake,
bool isConnected);
enum
{
HANDSHAKE_OK,
HANDSHAKE_ENCRYPTION_WRONG,
HANDSHAKE_BAD_TORRENT,
HANDSHAKE_PEER_IS_SELF,
};
static int
parseHandshake (tr_handshake * handshake,
struct evbuffer * inbuf)
{
uint8_t name[HANDSHAKE_NAME_LEN];
uint8_t reserved[HANDSHAKE_FLAGS_LEN];
uint8_t hash[SHA_DIGEST_LENGTH];
tr_torrent * tor;
uint8_t peer_id[PEER_ID_LEN];
dbgmsg (handshake, "payload: need %d, got %"TR_PRIuSIZE,
HANDSHAKE_SIZE, evbuffer_get_length (inbuf));
if (evbuffer_get_length (inbuf) < HANDSHAKE_SIZE)
return READ_LATER;
/* confirm the protocol */
tr_peerIoReadBytes (handshake->io, inbuf, name, HANDSHAKE_NAME_LEN);
if (memcmp (name, HANDSHAKE_NAME, HANDSHAKE_NAME_LEN))
return HANDSHAKE_ENCRYPTION_WRONG;
/* read the reserved bytes */
tr_peerIoReadBytes (handshake->io, inbuf, reserved, HANDSHAKE_FLAGS_LEN);
/* torrent hash */
tr_peerIoReadBytes (handshake->io, inbuf, hash, sizeof (hash));
assert (tr_peerIoHasTorrentHash (handshake->io));
if (!tr_torrentExists (handshake->session, hash)
|| memcmp (hash, tr_peerIoGetTorrentHash (handshake->io), SHA_DIGEST_LENGTH))
{
dbgmsg (handshake, "peer returned the wrong hash. wtf?");
return HANDSHAKE_BAD_TORRENT;
}
/* peer_id */
tr_peerIoReadBytes (handshake->io, inbuf, peer_id, sizeof (peer_id));
tr_peerIoSetPeersId (handshake->io, peer_id);
/* peer id */
handshake->havePeerID = true;
dbgmsg (handshake, "peer-id is [%*.*s]", PEER_ID_LEN, PEER_ID_LEN, peer_id);
tor = tr_torrentFindFromHash (handshake->session, hash);
if (!memcmp (peer_id, tr_torrentGetPeerId(tor), PEER_ID_LEN))
{
dbgmsg (handshake, "streuth! we've connected to ourselves.");
return HANDSHAKE_PEER_IS_SELF;
}
/**
*** Extensions
**/
tr_peerIoEnableDHT (handshake->io, HANDSHAKE_HAS_DHT (reserved));
tr_peerIoEnableLTEP (handshake->io, HANDSHAKE_HAS_LTEP (reserved));
tr_peerIoEnableFEXT (handshake->io, HANDSHAKE_HAS_FASTEXT (reserved));
return HANDSHAKE_OK;
}
/***
****
**** OUTGOING CONNECTIONS
****
***/
/* 1 A->B: Diffie Hellman Ya, PadA */
static void
sendYa (tr_handshake * handshake)
{
int len;
const uint8_t * public_key;
char outbuf[ KEY_LEN + PadA_MAXLEN ];
char *walk = outbuf;
/* add our public key (Ya) */
public_key = tr_cryptoGetMyPublicKey (handshake->crypto, &len);
assert (len == KEY_LEN);
assert (public_key);
memcpy (walk, public_key, len);
walk += len;
/* add some bullshit padding */
len = tr_rand_int (PadA_MAXLEN);
tr_rand_buffer (walk, len);
walk += len;
/* send it */
setReadState (handshake, AWAITING_YB);
tr_peerIoWriteBytes (handshake->io, outbuf, walk - outbuf, false);
}
static uint32_t
getCryptoProvide (const tr_handshake * handshake)
{
uint32_t provide = 0;
switch (handshake->encryptionMode)
{
case TR_ENCRYPTION_REQUIRED:
case TR_ENCRYPTION_PREFERRED:
provide |= CRYPTO_PROVIDE_CRYPTO;
break;
case TR_CLEAR_PREFERRED:
provide |= CRYPTO_PROVIDE_CRYPTO | CRYPTO_PROVIDE_PLAINTEXT;
break;
}
return provide;
}
static uint32_t
getCryptoSelect (const tr_handshake * handshake,
uint32_t crypto_provide)
{
uint32_t choices[2];
int i;
int nChoices = 0;
switch (handshake->encryptionMode)
{
case TR_ENCRYPTION_REQUIRED:
choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
break;
case TR_ENCRYPTION_PREFERRED:
choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
choices[nChoices++] = CRYPTO_PROVIDE_PLAINTEXT;
break;
case TR_CLEAR_PREFERRED:
choices[nChoices++] = CRYPTO_PROVIDE_PLAINTEXT;
choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
break;
}
for (i=0; i<nChoices; ++i)
if (crypto_provide & choices[i])
return choices[i];
return 0;
}
static void
computeRequestHash (const tr_handshake * handshake,
const char * name,
uint8_t * hash)
{
tr_cryptoSecretKeySha1 (handshake->crypto, name, 4, NULL, 0, hash);
}
static int
readYb (tr_handshake * handshake, struct evbuffer * inbuf)
{
int isEncrypted;
uint8_t yb[KEY_LEN];
struct evbuffer * outbuf;
size_t needlen = HANDSHAKE_NAME_LEN;
if (evbuffer_get_length (inbuf) < needlen)
return READ_LATER;
isEncrypted = memcmp (evbuffer_pullup (inbuf, HANDSHAKE_NAME_LEN), HANDSHAKE_NAME, HANDSHAKE_NAME_LEN);
if (isEncrypted)
{
needlen = KEY_LEN;
if (evbuffer_get_length (inbuf) < needlen)
return READ_LATER;
}
dbgmsg (handshake, "got an %s handshake", (isEncrypted ? "encrypted" : "plain"));
tr_peerIoSetEncryption (handshake->io, isEncrypted ? PEER_ENCRYPTION_RC4
: PEER_ENCRYPTION_NONE);
if (!isEncrypted)
{
setState (handshake, AWAITING_HANDSHAKE);
return READ_NOW;
}
handshake->haveReadAnythingFromPeer = true;
/* compute the secret */
evbuffer_remove (inbuf, yb, KEY_LEN);
tr_cryptoComputeSecret (handshake->crypto, yb);
/* now send these: HASH ('req1', S), HASH ('req2', SKEY) xor HASH ('req3', S),
* ENCRYPT (VC, crypto_provide, len (PadC), PadC, len (IA)), ENCRYPT (IA) */
outbuf = evbuffer_new ();
/* HASH ('req1', S) */
{
uint8_t req1[SHA_DIGEST_LENGTH];
computeRequestHash (handshake, "req1", req1);
evbuffer_add (outbuf, req1, SHA_DIGEST_LENGTH);
}
/* HASH ('req2', SKEY) xor HASH ('req3', S) */
{
int i;
uint8_t req2[SHA_DIGEST_LENGTH];
uint8_t req3[SHA_DIGEST_LENGTH];
uint8_t buf[SHA_DIGEST_LENGTH];
tr_sha1 (req2, "req2", 4, tr_cryptoGetTorrentHash (handshake->crypto), SHA_DIGEST_LENGTH, NULL);
computeRequestHash (handshake, "req3", req3);
for (i=0; i<SHA_DIGEST_LENGTH; ++i)
buf[i] = req2[i] ^ req3[i];
evbuffer_add (outbuf, buf, SHA_DIGEST_LENGTH);
}
/* ENCRYPT (VC, crypto_provide, len (PadC), PadC
* PadC is reserved for future extensions to the handshake...
* standard practice at this time is for it to be zero-length */
{
uint8_t vc[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 };
tr_peerIoWriteBuf (handshake->io, outbuf, false);
tr_cryptoEncryptInit (handshake->crypto);
tr_peerIoSetEncryption (handshake->io, PEER_ENCRYPTION_RC4);
evbuffer_add (outbuf, vc, VC_LENGTH);
evbuffer_add_uint32 (outbuf, getCryptoProvide (handshake));
evbuffer_add_uint16 (outbuf, 0);
}
/* ENCRYPT len (IA)), ENCRYPT (IA) */
{
uint8_t msg[HANDSHAKE_SIZE];
if (!buildHandshakeMessage (handshake, msg))
return tr_handshakeDone (handshake, false);
evbuffer_add_uint16 (outbuf, sizeof (msg));
evbuffer_add (outbuf, msg, sizeof (msg));
handshake->haveSentBitTorrentHandshake = true;
}
/* send it */
tr_cryptoDecryptInit (handshake->crypto);
setReadState (handshake, AWAITING_VC);
tr_peerIoWriteBuf (handshake->io, outbuf, false);
/* cleanup */
evbuffer_free (outbuf);
return READ_LATER;
}
static int
readVC (tr_handshake * handshake,
struct evbuffer * inbuf)
{
uint8_t tmp[VC_LENGTH];
const int key_len = VC_LENGTH;
const uint8_t key[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 };
/* note: this works w/o having to `unwind' the buffer if
* we read too much, but it is pretty brute-force.
* it would be nice to make this cleaner. */
for (;;)
{
if (evbuffer_get_length (inbuf) < VC_LENGTH)
{
dbgmsg (handshake, "not enough bytes... returning read_more");
return READ_LATER;
}
memcpy (tmp, evbuffer_pullup (inbuf, key_len), key_len);
tr_cryptoDecryptInit (handshake->crypto);
tr_cryptoDecrypt (handshake->crypto, key_len, tmp, tmp);
if (!memcmp (tmp, key, key_len))
break;
evbuffer_drain (inbuf, 1);
}
dbgmsg (handshake, "got it!");
evbuffer_drain (inbuf, key_len);
setState (handshake, AWAITING_CRYPTO_SELECT);
return READ_NOW;
}
static int
readCryptoSelect (tr_handshake * handshake,
struct evbuffer * inbuf)
{
uint16_t pad_d_len;
uint32_t crypto_select;
static const size_t needlen = sizeof (uint32_t) + sizeof (uint16_t);
if (evbuffer_get_length (inbuf) < needlen)
return READ_LATER;
tr_peerIoReadUint32 (handshake->io, inbuf, &crypto_select);
handshake->crypto_select = crypto_select;
dbgmsg (handshake, "crypto select is %d", (int)crypto_select);
if (!(crypto_select & getCryptoProvide (handshake)))
{
dbgmsg (handshake, "peer selected an encryption option we didn't offer");
return tr_handshakeDone (handshake, false);
}
tr_peerIoReadUint16 (handshake->io, inbuf, &pad_d_len);
dbgmsg (handshake, "pad_d_len is %d", (int)pad_d_len);
if (pad_d_len > 512)
{
dbgmsg (handshake, "encryption handshake: pad_d_len is too long");
return tr_handshakeDone (handshake, false);
}
handshake->pad_d_len = pad_d_len;
setState (handshake, AWAITING_PAD_D);
return READ_NOW;
}
static int
readPadD (tr_handshake * handshake,
struct evbuffer * inbuf)
{
const size_t needlen = handshake->pad_d_len;
dbgmsg (handshake, "pad d: need %"TR_PRIuSIZE", got %"TR_PRIuSIZE,
needlen, evbuffer_get_length (inbuf));
if (evbuffer_get_length (inbuf) < needlen)
return READ_LATER;
tr_peerIoDrain (handshake->io, inbuf, needlen);
tr_peerIoSetEncryption (handshake->io, handshake->crypto_select);
setState (handshake, AWAITING_HANDSHAKE);
return READ_NOW;
}
/***
****
**** INCOMING CONNECTIONS
****
***/
static int
readHandshake (tr_handshake * handshake,
struct evbuffer * inbuf)
{
uint8_t pstrlen;
uint8_t pstr[20];
uint8_t reserved[HANDSHAKE_FLAGS_LEN];
uint8_t hash[SHA_DIGEST_LENGTH];
dbgmsg (handshake, "payload: need %d, got %"TR_PRIuSIZE,
INCOMING_HANDSHAKE_LEN, evbuffer_get_length (inbuf));
if (evbuffer_get_length (inbuf) < INCOMING_HANDSHAKE_LEN)
return READ_LATER;
handshake->haveReadAnythingFromPeer = true;
pstrlen = evbuffer_pullup (inbuf, 1)[0]; /* peek, don't read. We may be
handing inbuf to AWAITING_YA */
if (pstrlen == 19) /* unencrypted */
{
tr_peerIoSetEncryption (handshake->io, PEER_ENCRYPTION_NONE);
if (handshake->encryptionMode == TR_ENCRYPTION_REQUIRED)
{
dbgmsg (handshake, "peer is unencrypted, and we're disallowing that");
return tr_handshakeDone (handshake, false);
}
}
else /* encrypted or corrupt */
{
tr_peerIoSetEncryption (handshake->io, PEER_ENCRYPTION_RC4);
if (tr_peerIoIsIncoming (handshake->io))
{
dbgmsg (handshake, "I think peer is sending us an encrypted handshake...");
setState (handshake, AWAITING_YA);
return READ_NOW;
}
tr_cryptoDecrypt (handshake->crypto, 1, &pstrlen, &pstrlen);
if (pstrlen != 19)
{
dbgmsg (handshake, "I think peer has sent us a corrupt handshake...");
return tr_handshakeDone (handshake, false);
}
}
evbuffer_drain (inbuf, 1);
/* pstr (BitTorrent) */
assert (pstrlen == 19);
tr_peerIoReadBytes (handshake->io, inbuf, pstr, pstrlen);
pstr[pstrlen] = '\0';
if (memcmp (pstr, "BitTorrent protocol", 19))
return tr_handshakeDone (handshake, false);
/* reserved bytes */
tr_peerIoReadBytes (handshake->io, inbuf, reserved, sizeof (reserved));
/**
*** Extensions
**/
tr_peerIoEnableDHT (handshake->io, HANDSHAKE_HAS_DHT (reserved));
tr_peerIoEnableLTEP (handshake->io, HANDSHAKE_HAS_LTEP (reserved));
tr_peerIoEnableFEXT (handshake->io, HANDSHAKE_HAS_FASTEXT (reserved));
/* torrent hash */
tr_peerIoReadBytes (handshake->io, inbuf, hash, sizeof (hash));
if (tr_peerIoIsIncoming (handshake->io))
{
if (!tr_torrentExists (handshake->session, hash))
{
dbgmsg (handshake, "peer is trying to connect to us for a torrent we don't have.");
return tr_handshakeDone (handshake, false);
}
else
{
assert (!tr_peerIoHasTorrentHash (handshake->io));
tr_peerIoSetTorrentHash (handshake->io, hash);
}
}
else /* outgoing */
{
assert (tr_peerIoHasTorrentHash (handshake->io));
if (memcmp (hash, tr_peerIoGetTorrentHash (handshake->io), SHA_DIGEST_LENGTH))
{
dbgmsg (handshake, "peer returned the wrong hash. wtf?");
return tr_handshakeDone (handshake, false);
}
}
/**
*** If it's an incoming message, we need to send a response handshake
**/
if (!handshake->haveSentBitTorrentHandshake)
{
uint8_t msg[HANDSHAKE_SIZE];
if (!buildHandshakeMessage (handshake, msg))
return tr_handshakeDone (handshake, false);
tr_peerIoWriteBytes (handshake->io, msg, sizeof (msg), false);
handshake->haveSentBitTorrentHandshake = true;
}
setReadState (handshake, AWAITING_PEER_ID);
return READ_NOW;
}
static int
readPeerId (tr_handshake * handshake,
struct evbuffer * inbuf)
{
bool connected_to_self;
char client[128];
uint8_t peer_id[PEER_ID_LEN];
tr_torrent * tor;
if (evbuffer_get_length (inbuf) < PEER_ID_LEN)
return READ_LATER;
/* peer id */
tr_peerIoReadBytes (handshake->io, inbuf, peer_id, PEER_ID_LEN);
tr_peerIoSetPeersId (handshake->io, peer_id);
handshake->havePeerID = true;
tr_clientForId (client, sizeof (client), peer_id);
dbgmsg (handshake, "peer-id is [%s] ... isIncoming is %d", client,
tr_peerIoIsIncoming (handshake->io));
/* if we've somehow connected to ourselves, don't keep the connection */
tor = tr_torrentFindFromHash (handshake->session, tr_peerIoGetTorrentHash (handshake->io));
connected_to_self = (tor != NULL) && !memcmp (peer_id, tr_torrentGetPeerId(tor), PEER_ID_LEN);
return tr_handshakeDone (handshake, !connected_to_self);
}
static int
readYa (tr_handshake * handshake,
struct evbuffer * inbuf)
{
uint8_t ya[KEY_LEN];
uint8_t * walk, outbuf[KEY_LEN + PadB_MAXLEN];
const uint8_t * myKey;
int len;
dbgmsg (handshake, "in readYa... need %d, have %"TR_PRIuSIZE,
KEY_LEN, evbuffer_get_length (inbuf));
if (evbuffer_get_length (inbuf) < KEY_LEN)
return READ_LATER;
/* read the incoming peer's public key */
evbuffer_remove (inbuf, ya, KEY_LEN);
tr_cryptoComputeSecret (handshake->crypto, ya);
computeRequestHash (handshake, "req1", handshake->myReq1);
/* send our public key to the peer */
dbgmsg (handshake, "sending B->A: Diffie Hellman Yb, PadB");
walk = outbuf;
myKey = tr_cryptoGetMyPublicKey (handshake->crypto, &len);
memcpy (walk, myKey, len);
walk += len;
len = tr_rand_int (PadB_MAXLEN);
tr_rand_buffer (walk, len);
walk += len;
setReadState (handshake, AWAITING_PAD_A);
tr_peerIoWriteBytes (handshake->io, outbuf, walk - outbuf, false);
return READ_NOW;
}
static int
readPadA (tr_handshake * handshake, struct evbuffer * inbuf)
{
/* resynchronizing on HASH ('req1',S) */
struct evbuffer_ptr ptr = evbuffer_search (inbuf, (const char*)handshake->myReq1, SHA_DIGEST_LENGTH, NULL);
if (ptr.pos != -1) /* match */
{
evbuffer_drain (inbuf, ptr.pos);
dbgmsg (handshake, "found it... looking setting to awaiting_crypto_provide");
setState (handshake, AWAITING_CRYPTO_PROVIDE);
return READ_NOW;
}
else
{
const size_t len = evbuffer_get_length (inbuf);
if (len > SHA_DIGEST_LENGTH)
evbuffer_drain (inbuf, len - SHA_DIGEST_LENGTH);
return READ_LATER;
}
}
static int
readCryptoProvide (tr_handshake * handshake,
struct evbuffer * inbuf)
{
/* HASH ('req2', SKEY) xor HASH ('req3', S), ENCRYPT (VC, crypto_provide, len (PadC)) */
int i;
uint8_t vc_in[VC_LENGTH];
uint8_t req2[SHA_DIGEST_LENGTH];
uint8_t req3[SHA_DIGEST_LENGTH];
uint8_t obfuscatedTorrentHash[SHA_DIGEST_LENGTH];
uint16_t padc_len = 0;
uint32_t crypto_provide = 0;
tr_torrent * tor;
const size_t needlen = SHA_DIGEST_LENGTH /* HASH ('req1',s) */
+ SHA_DIGEST_LENGTH /* HASH ('req2', SKEY) xor HASH ('req3', S) */
+ VC_LENGTH
+ sizeof (crypto_provide)
+ sizeof (padc_len);
if (evbuffer_get_length (inbuf) < needlen)
return READ_LATER;
/* TODO: confirm they sent HASH ('req1',S) here? */
evbuffer_drain (inbuf, SHA_DIGEST_LENGTH);
/* This next piece is HASH ('req2', SKEY) xor HASH ('req3', S) ...
* we can get the first half of that (the obufscatedTorrentHash)
* by building the latter and xor'ing it with what the peer sent us */
dbgmsg (handshake, "reading obfuscated torrent hash...");
evbuffer_remove (inbuf, req2, SHA_DIGEST_LENGTH);
computeRequestHash (handshake, "req3", req3);
for (i=0; i<SHA_DIGEST_LENGTH; ++i)
obfuscatedTorrentHash[i] = req2[i] ^ req3[i];
if ((tor = tr_torrentFindFromObfuscatedHash (handshake->session, obfuscatedTorrentHash)))
{
const bool clientIsSeed = tr_torrentIsSeed (tor);
const bool peerIsSeed = tr_peerMgrPeerIsSeed (tor, tr_peerIoGetAddress (handshake->io, NULL));
dbgmsg (handshake, "got INCOMING connection's encrypted handshake for torrent [%s]", tr_torrentName (tor));
tr_peerIoSetTorrentHash (handshake->io, tor->info.hash);
if (clientIsSeed && peerIsSeed)
{
dbgmsg (handshake, "another seed tried to reconnect to us!");
return tr_handshakeDone (handshake, false);
}
}
else
{
dbgmsg (handshake, "can't find that torrent...");
return tr_handshakeDone (handshake, false);
}
/* next part: ENCRYPT (VC, crypto_provide, len (PadC), */
tr_cryptoDecryptInit (handshake->crypto);
tr_peerIoReadBytes (handshake->io, inbuf, vc_in, VC_LENGTH);
tr_peerIoReadUint32 (handshake->io, inbuf, &crypto_provide);
handshake->crypto_provide = crypto_provide;
dbgmsg (handshake, "crypto_provide is %d", (int)crypto_provide);
tr_peerIoReadUint16 (handshake->io, inbuf, &padc_len);
dbgmsg (handshake, "padc is %d", (int)padc_len);
handshake->pad_c_len = padc_len;
setState (handshake, AWAITING_PAD_C);
return READ_NOW;
}
static int
readPadC (tr_handshake * handshake,
struct evbuffer * inbuf)
{
char * padc;
uint16_t ia_len;
const size_t needlen = handshake->pad_c_len + sizeof (uint16_t);
if (evbuffer_get_length (inbuf) < needlen)
return READ_LATER;
/* read the throwaway padc */
padc = tr_new (char, handshake->pad_c_len);
tr_peerIoReadBytes (handshake->io, inbuf, padc, handshake->pad_c_len);
tr_free (padc);
/* read ia_len */
tr_peerIoReadUint16 (handshake->io, inbuf, &ia_len);
dbgmsg (handshake, "ia_len is %d", (int)ia_len);
handshake->ia_len = ia_len;
setState (handshake, AWAITING_IA);
return READ_NOW;
}
static int
readIA (tr_handshake * handshake,
struct evbuffer * inbuf)
{
const size_t needlen = handshake->ia_len;
struct evbuffer * outbuf;
uint32_t crypto_select;
dbgmsg (handshake, "reading IA... have %"TR_PRIuSIZE", need %"TR_PRIuSIZE,
evbuffer_get_length (inbuf), needlen);
if (evbuffer_get_length (inbuf) < needlen)
return READ_LATER;
/**
*** B->A: ENCRYPT (VC, crypto_select, len (padD), padD), ENCRYPT2 (Payload Stream)
**/
tr_cryptoEncryptInit (handshake->crypto);
outbuf = evbuffer_new ();
{
/* send VC */
uint8_t vc[VC_LENGTH];
memset (vc, 0, VC_LENGTH);
evbuffer_add (outbuf, vc, VC_LENGTH);
dbgmsg (handshake, "sending vc");
}
/* send crypto_select */
crypto_select = getCryptoSelect (handshake, handshake->crypto_provide);
if (crypto_select)
{
dbgmsg (handshake, "selecting crypto mode '%d'", (int)crypto_select);
evbuffer_add_uint32 (outbuf, crypto_select);
}
else
{
dbgmsg (handshake, "peer didn't offer an encryption mode we like.");
evbuffer_free (outbuf);
return tr_handshakeDone (handshake, false);
}
dbgmsg (handshake, "sending pad d");
/* ENCRYPT (VC, crypto_provide, len (PadD), PadD
* PadD is reserved for future extensions to the handshake...
* standard practice at this time is for it to be zero-length */
{
const uint16_t len = 0;
evbuffer_add_uint16 (outbuf, len);
}
/* maybe de-encrypt our connection */
if (crypto_select == CRYPTO_PROVIDE_PLAINTEXT)
{
tr_peerIoWriteBuf (handshake->io, outbuf, false);
tr_peerIoSetEncryption (handshake->io, PEER_ENCRYPTION_NONE);
}
dbgmsg (handshake, "sending handshake");
/* send our handshake */
{
uint8_t msg[HANDSHAKE_SIZE];
if (!buildHandshakeMessage (handshake, msg))
return tr_handshakeDone (handshake, false);
evbuffer_add (outbuf, msg, sizeof (msg));
handshake->haveSentBitTorrentHandshake = true;
}
/* send it out */
tr_peerIoWriteBuf (handshake->io, outbuf, false);
evbuffer_free (outbuf);
/* now await the handshake */
setState (handshake, AWAITING_PAYLOAD_STREAM);
return READ_NOW;
}
static int
readPayloadStream (tr_handshake * handshake,
struct evbuffer * inbuf)
{
int i;
const size_t needlen = HANDSHAKE_SIZE;
dbgmsg (handshake, "reading payload stream... have %"TR_PRIuSIZE", need %"TR_PRIuSIZE,
evbuffer_get_length (inbuf), needlen);
if (evbuffer_get_length (inbuf) < needlen)
return READ_LATER;
/* parse the handshake ... */
i = parseHandshake (handshake, inbuf);
dbgmsg (handshake, "parseHandshake returned %d", i);
if (i != HANDSHAKE_OK)
return tr_handshakeDone (handshake, false);
/* we've completed the BT handshake... pass the work on to peer-msgs */
return tr_handshakeDone (handshake, true);
}
/***
****
****
****
***/
static ReadState
canRead (struct tr_peerIo * io, void * arg, size_t * piece)
{
ReadState ret;
tr_handshake * handshake = arg;
struct evbuffer * inbuf = tr_peerIoGetReadBuffer (io);
bool readyForMore = true;
assert (tr_isPeerIo (io));
/* no piece data in handshake */
*piece = 0;
dbgmsg (handshake, "handling canRead; state is [%s]",
getStateName (handshake->state));
while (readyForMore)
{
switch (handshake->state)
{
case AWAITING_HANDSHAKE:
ret = readHandshake (handshake, inbuf);
break;
case AWAITING_PEER_ID:
ret = readPeerId (handshake, inbuf);
break;
case AWAITING_YA:
ret = readYa (handshake, inbuf);
break;
case AWAITING_PAD_A:
ret = readPadA (handshake, inbuf);
break;
case AWAITING_CRYPTO_PROVIDE:
ret = readCryptoProvide (handshake, inbuf);
break;
case AWAITING_PAD_C:
ret = readPadC (handshake, inbuf);
break;
case AWAITING_IA:
ret = readIA (handshake, inbuf);
break;
case AWAITING_PAYLOAD_STREAM:
ret = readPayloadStream (handshake, inbuf);
break;
case AWAITING_YB:
ret = readYb (handshake, inbuf);
break;
case AWAITING_VC:
ret = readVC (handshake, inbuf);
break;
case AWAITING_CRYPTO_SELECT:
ret = readCryptoSelect (handshake, inbuf);
break;
case AWAITING_PAD_D:
ret = readPadD (handshake, inbuf);
break;
default:
assert (0);
}
if (ret != READ_NOW)
readyForMore = false;
else if (handshake->state == AWAITING_PAD_C)
readyForMore = evbuffer_get_length (inbuf) >= handshake->pad_c_len;
else if (handshake->state == AWAITING_PAD_D)
readyForMore = evbuffer_get_length (inbuf) >= handshake->pad_d_len;
else if (handshake->state == AWAITING_IA)
readyForMore = evbuffer_get_length (inbuf) >= handshake->ia_len;
}
return ret;
}
static bool
fireDoneFunc (tr_handshake * handshake, bool isConnected)
{
const uint8_t * peer_id = isConnected && handshake->havePeerID
? tr_peerIoGetPeersId (handshake->io)
: NULL;
const bool success = (*handshake->doneCB)(handshake,
handshake->io,
handshake->haveReadAnythingFromPeer,
isConnected,
peer_id,
handshake->doneUserData);
return success;
}
static void
tr_handshakeFree (tr_handshake * handshake)
{
if (handshake->io)
tr_peerIoUnref (handshake->io); /* balanced by the ref in tr_handshakeNew */
event_free (handshake->timeout_timer);
tr_free (handshake);
}
static int
tr_handshakeDone (tr_handshake * handshake, bool isOK)
{
bool success;
dbgmsg (handshake, "handshakeDone: %s", isOK ? "connected" : "aborting");
tr_peerIoSetIOFuncs (handshake->io, NULL, NULL, NULL, NULL);
success = fireDoneFunc (handshake, isOK);
tr_handshakeFree (handshake);
return success ? READ_LATER : READ_ERR;
}
void
tr_handshakeAbort (tr_handshake * handshake)
{
if (handshake != NULL)
tr_handshakeDone (handshake, false);
}
static void
gotError (tr_peerIo * io,
short what,
void * vhandshake)
{
int errcode = errno;
tr_handshake * handshake = vhandshake;
if (io->utp_socket && !io->isIncoming && handshake->state == AWAITING_YB)
{
/* This peer probably doesn't speak uTP. */
tr_torrent *tor;
if (tr_peerIoHasTorrentHash (io))
tor = tr_torrentFindFromHash (handshake->session, tr_peerIoGetTorrentHash (io));
else
tor = NULL;
/* Don't mark a peer as non-uTP unless it's really a connect failure. */
if ((errcode == ETIMEDOUT || errcode == ECONNREFUSED) && tr_isTorrent(tor))
tr_peerMgrSetUtpFailed (tor, tr_peerIoGetAddress (io, NULL), true);
if (!tr_peerIoReconnect (handshake->io))
{
uint8_t msg[HANDSHAKE_SIZE];
buildHandshakeMessage (handshake, msg);
handshake->haveSentBitTorrentHandshake = true;
setReadState (handshake, AWAITING_HANDSHAKE);
tr_peerIoWriteBytes (handshake->io, msg, sizeof (msg), false);
}
}
/* if the error happened while we were sending a public key, we might
* have encountered a peer that doesn't do encryption... reconnect and
* try a plaintext handshake */
if (((handshake->state == AWAITING_YB) || (handshake->state == AWAITING_VC))
&& (handshake->encryptionMode != TR_ENCRYPTION_REQUIRED)
&& (!tr_peerIoReconnect (handshake->io)))
{
uint8_t msg[HANDSHAKE_SIZE];
dbgmsg (handshake, "handshake failed, trying plaintext...");
buildHandshakeMessage (handshake, msg);
handshake->haveSentBitTorrentHandshake = true;
setReadState (handshake, AWAITING_HANDSHAKE);
tr_peerIoWriteBytes (handshake->io, msg, sizeof (msg), false);
}
else
{
dbgmsg (handshake, "libevent got an error what==%d, errno=%d (%s)",
(int)what, errno, tr_strerror (errno));
tr_handshakeDone (handshake, false);
}
}
/**
***
**/
static void
handshakeTimeout (evutil_socket_t foo UNUSED, short bar UNUSED, void * handshake)
{
tr_handshakeAbort (handshake);
}
tr_handshake*
tr_handshakeNew (tr_peerIo * io,
tr_encryption_mode encryptionMode,
handshakeDoneCB doneCB,
void * doneUserData)
{
tr_handshake * handshake;
tr_session * session = tr_peerIoGetSession (io);
handshake = tr_new0 (tr_handshake, 1);
handshake->io = io;
handshake->crypto = tr_peerIoGetCrypto (io);
handshake->encryptionMode = encryptionMode;
handshake->doneCB = doneCB;
handshake->doneUserData = doneUserData;
handshake->session = session;
handshake->timeout_timer = evtimer_new (session->event_base, handshakeTimeout, handshake);
tr_timerAdd (handshake->timeout_timer, HANDSHAKE_TIMEOUT_SEC, 0);
tr_peerIoRef (io); /* balanced by the unref in tr_handshakeFree */
tr_peerIoSetIOFuncs (handshake->io, canRead, NULL, gotError, handshake);
tr_peerIoSetEncryption (io, PEER_ENCRYPTION_NONE);
if (tr_peerIoIsIncoming (handshake->io))
{
setReadState (handshake, AWAITING_HANDSHAKE);
}
else if (encryptionMode != TR_CLEAR_PREFERRED)
{
sendYa (handshake);
}
else
{
uint8_t msg[HANDSHAKE_SIZE];
buildHandshakeMessage (handshake, msg);
handshake->haveSentBitTorrentHandshake = true;
setReadState (handshake, AWAITING_HANDSHAKE);
tr_peerIoWriteBytes (handshake->io, msg, sizeof (msg), false);
}
return handshake;
}
struct tr_peerIo*
tr_handshakeGetIO (tr_handshake * handshake)
{
assert (handshake != NULL);
assert (handshake->io != NULL);
return handshake->io;
}
struct tr_peerIo*
tr_handshakeStealIO (tr_handshake * handshake)
{
struct tr_peerIo * io;
assert (handshake != NULL);
assert (handshake->io != NULL);
io = handshake->io;
handshake->io = NULL;
return io;
}
const tr_address *
tr_handshakeGetAddr (const struct tr_handshake * handshake,
tr_port * port)
{
assert (handshake != NULL);
assert (handshake->io != NULL);
return tr_peerIoGetAddress (handshake->io, port);
}