/* * This file Copyright (C) Mnemosyne LLC * * This file is licensed by the GPL version 2. Works owned by the * Transmission project are granted a special exemption to clause 2(b) * so that the bulk of its code can remain under the MIT license. * This exemption does not extend to derived works not owned by * the Transmission project. * * $Id$ */ #include #include #include #include /* UCHAR_MAX */ #include #include #include #include "transmission.h" #include "bencode.h" #include "clients.h" #include "crypto.h" #include "handshake.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, PEER_ID_LEN = 20, INCOMING_HANDSHAKE_LEN = 48, /* Encryption Constants */ PadA_MAXLEN = 512, PadB_MAXLEN = 512, PadC_MAXLEN = 512, PadD_MAXLEN = 512, VC_LENGTH = 8, KEY_LEN = 96, 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 { tr_bool haveReadAnythingFromPeer; tr_bool havePeerID; tr_bool haveSentBitTorrentHandshake; tr_peerIo * io; tr_crypto * crypto; tr_session * session; uint8_t mySecret[KEY_LEN]; 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, }; /** *** **/ #define dbgmsg( handshake, ... ) \ do { \ if( tr_deepLoggingIsActive( ) ) \ tr_deepLog( __FILE__, __LINE__, tr_peerIoGetAddrStr( handshake->io ), __VA_ARGS__ ); \ } while( 0 ) static const char* getStateName( const handshake_state_t state ) { const char * str = "f00!"; switch( state ) { case AWAITING_HANDSHAKE: str = "awaiting handshake"; break; case AWAITING_PEER_ID: str = "awaiting peer id"; break; case AWAITING_YA: str = "awaiting ya"; break; case AWAITING_PAD_A: str = "awaiting pad a"; break; case AWAITING_CRYPTO_PROVIDE: str = "awaiting crypto_provide"; break; case AWAITING_PAD_C: str = "awaiting pad c"; break; case AWAITING_IA: str = "awaiting ia"; break; case AWAITING_YB: str = "awaiting yb"; break; case AWAITING_VC: str = "awaiting vc"; break; case AWAITING_CRYPTO_SELECT: str = "awaiting crypto select"; break; case AWAITING_PAD_D: str = "awaiting pad d"; break; } return str; } 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 void buildHandshakeMessage( tr_handshake * handshake, uint8_t * buf ) { uint8_t * walk = buf; const uint8_t * torrentHash = tr_cryptoGetTorrentHash( handshake->crypto ); const tr_torrent * tor = tr_torrentFindFromHash( handshake->session, torrentHash ); const uint8_t * peer_id = tor && tor->peer_id ? tor->peer_id : tr_getPeerId( ); 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( strlen( ( const char* )peer_id ) == PEER_ID_LEN ); assert( walk - buf == HANDSHAKE_SIZE ); } static int tr_handshakeDone( tr_handshake * handshake, tr_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]; const tr_torrent * tor; const uint8_t * tor_peer_id; uint8_t peer_id[PEER_ID_LEN]; dbgmsg( handshake, "payload: need %d, got %zu", 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 ); tor_peer_id = tor && tor->peer_id ? tor->peer_id : tr_getPeerId( ); if( !memcmp( peer_id, tor_peer_id, PEER_ID_LEN ) ) { dbgmsg( handshake, "streuth! we've connected to ourselves." ); return HANDSHAKE_PEER_IS_SELF; } /** *** Extensions **/ tr_peerIoEnableLTEP( handshake->io, HANDSHAKE_HAS_LTEP( reserved ) ); tr_peerIoEnableFEXT( handshake->io, HANDSHAKE_HAS_FASTEXT( reserved ) ); tr_peerIoEnableDHT( handshake->io, HANDSHAKE_HAS_DHT( 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 ], *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_cryptoRandInt( PadA_MAXLEN ); tr_cryptoRandBuf( 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, 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 int readYb( tr_handshake * handshake, struct evbuffer * inbuf ) { int isEncrypted; const uint8_t * secret; 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 a %s handshake", ( isEncrypted ? "encrypted" : "plaintext" ) ); 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 ); secret = tr_cryptoComputeSecret( handshake->crypto, yb ); memcpy( handshake->mySecret, secret, KEY_LEN ); /* 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]; tr_sha1( req1, "req1", 4, secret, KEY_LEN, NULL ); 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 ); tr_sha1( req3, "req3", 4, secret, KEY_LEN, NULL ); 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]; buildHandshakeMessage( handshake, msg ); evbuffer_add_uint16 ( outbuf, sizeof( msg ) ); evbuffer_add ( outbuf, msg, sizeof( msg ) ); handshake->haveSentBitTorrentHandshake = 1; } /* 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 ) { const uint8_t key[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 }; const int key_len = VC_LENGTH; uint8_t tmp[VC_LENGTH]; /* 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 ) { uint32_t crypto_select; uint16_t pad_d_len; 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 provide" ); 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; uint8_t * tmp; dbgmsg( handshake, "pad d: need %zu, got %zu", needlen, evbuffer_get_length( inbuf ) ); if( evbuffer_get_length( inbuf ) < needlen ) return READ_LATER; tmp = tr_new( uint8_t, needlen ); tr_peerIoReadBytes( handshake->io, inbuf, tmp, needlen ); tr_free( tmp ); 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; uint8_t reserved[HANDSHAKE_FLAGS_LEN]; uint8_t hash[SHA_DIGEST_LENGTH]; dbgmsg( handshake, "payload: need %d, got %zu", 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) */ pstr = tr_new( uint8_t, pstrlen + 1 ); tr_peerIoReadBytes( handshake->io, inbuf, pstr, pstrlen ); pstr[pstrlen] = '\0'; if( strcmp( (char*)pstr, "BitTorrent protocol" ) ) { tr_free( pstr ); return tr_handshakeDone( handshake, FALSE ); } tr_free( pstr ); /* reserved bytes */ tr_peerIoReadBytes( handshake->io, inbuf, reserved, sizeof( reserved ) ); /** *** Extensions **/ tr_peerIoEnableLTEP( handshake->io, HANDSHAKE_HAS_LTEP( reserved ) ); tr_peerIoEnableFEXT( handshake->io, HANDSHAKE_HAS_FASTEXT( reserved ) ); tr_peerIoEnableDHT( handshake->io, HANDSHAKE_HAS_DHT( 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]; buildHandshakeMessage( handshake, msg ); tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), FALSE ); handshake->haveSentBitTorrentHandshake = 1; } setReadState( handshake, AWAITING_PEER_ID ); return READ_NOW; } static int readPeerId( tr_handshake * handshake, struct evbuffer * inbuf ) { tr_bool peerIsGood; char client[128]; tr_torrent * tor; const uint8_t * tor_peer_id; uint8_t peer_id[PEER_ID_LEN]; 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 ) ); tor_peer_id = tor && tor->peer_id ? tor->peer_id : tr_getPeerId( ); peerIsGood = memcmp( peer_id, tor_peer_id, PEER_ID_LEN ) != 0; dbgmsg( handshake, "isPeerGood == %d", (int)peerIsGood ); return tr_handshakeDone( handshake, peerIsGood ); } 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, *secret; int len; dbgmsg( handshake, "in readYa... need %d, have %zu", 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 ); secret = tr_cryptoComputeSecret( handshake->crypto, ya ); memcpy( handshake->mySecret, secret, KEY_LEN ); tr_sha1( handshake->myReq1, "req1", 4, secret, KEY_LEN, NULL ); dbgmsg( handshake, "sending B->A: Diffie Hellman Yb, PadB" ); /* send our public key to the peer */ walk = outbuf; myKey = tr_cryptoGetMyPublicKey( handshake->crypto, &len ); memcpy( walk, myKey, len ); walk += len; len = tr_cryptoRandInt( PadB_MAXLEN ); tr_cryptoRandBuf( 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; 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 ); tr_torrent * tor = NULL; 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 ); tr_sha1( req3, "req3", 4, handshake->mySecret, KEY_LEN, NULL ); for( i = 0; i < SHA_DIGEST_LENGTH; ++i ) obfuscatedTorrentHash[i] = req2[i] ^ req3[i]; if(( tor = tr_torrentFindFromObfuscatedHash( handshake->session, obfuscatedTorrentHash ))) { const tr_bool clientIsSeed = tr_torrentIsSeed( tor ); const tr_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 ) { uint16_t ia_len; const size_t needlen = handshake->pad_c_len + sizeof( uint16_t ); if( evbuffer_get_length( inbuf ) < needlen ) return READ_LATER; evbuffer_drain( inbuf, handshake->pad_c_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 %zu, need %zu", 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( ); dbgmsg( handshake, "sending vc" ); /* send VC */ { uint8_t vc[VC_LENGTH]; memset( vc, 0, VC_LENGTH ); evbuffer_add( outbuf, vc, VC_LENGTH ); } /* 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]; buildHandshakeMessage( handshake, msg ); evbuffer_add( outbuf, msg, sizeof( msg ) ); handshake->haveSentBitTorrentHandshake = 1; } /* 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 %zu, need %zu", 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 ) { tr_handshake * handshake = arg; struct evbuffer * inbuf = tr_peerIoGetReadBuffer( io ); ReadState ret; tr_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 tr_bool fireDoneFunc( tr_handshake * handshake, tr_bool isConnected ) { const uint8_t * peer_id = isConnected && handshake->havePeerID ? tr_peerIoGetPeersId( handshake->io ) : NULL; const tr_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, tr_bool isOK ) { tr_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 UNUSED, short what, void * vhandshake ) { tr_handshake * handshake = vhandshake; /* 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 = 1; 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( int 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 = 1; setReadState( handshake, AWAITING_HANDSHAKE ); tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), FALSE ); } return handshake; } struct tr_peerIo* tr_handshakeGetIO( tr_handshake * handshake ) { assert( handshake ); assert( handshake->io ); return handshake->io; } struct tr_peerIo* tr_handshakeStealIO( tr_handshake * handshake ) { struct tr_peerIo * io; assert( handshake ); assert( handshake->io ); io = handshake->io; handshake->io = NULL; return io; } const tr_address * tr_handshakeGetAddr( const struct tr_handshake * handshake, tr_port * port ) { assert( handshake ); assert( handshake->io ); return tr_peerIoGetAddress( handshake->io, port ); }