From 060b849dda3e4c7c1a2535e8e4a8a04b97dc6a13 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sat, 18 Jun 2011 16:58:36 +0000 Subject: [PATCH] factor encryption handler a bit and add proposal for an authentication extension --- docs/auth.rst | 192 ++++++++++++++++++++++ docs/makefile | 1 + include/libtorrent/bt_peer_connection.hpp | 12 +- include/libtorrent/pe_crypto.hpp | 57 +++++-- src/bt_peer_connection.cpp | 56 ++++--- test/test_pe_crypto.cpp | 16 +- 6 files changed, 281 insertions(+), 53 deletions(-) create mode 100644 docs/auth.rst diff --git a/docs/auth.rst b/docs/auth.rst new file mode 100644 index 000000000..eadc9543e --- /dev/null +++ b/docs/auth.rst @@ -0,0 +1,192 @@ +=================================== +BitTorrent authentication extension +=================================== + +:Author: Arvid Norberg, arvid@rasterbar.com +:Version: Draft + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +BitTorrent authentication extension +----------------------------------- + +This extension indends to cover any combination of the following use cases: + +1. Verifying that a torrent is published by a trusted source +2. Have a swarm be private and having peers authenticate peers they connect to +3. Allow peers, with prior knowledge about each other's public key, authenticate in order to set up trusted connections to known peers (i.e. "friends") + +These building blocks could be used for building a web of trust, private +swarms and trusted sources for content. + +torrent file extension +---------------------- + +A .torrent file may have the following new fields (not inside the info-hash): + +"publisher" + containing the RSA public key of the publisher of the torrent. Private counterpart + of this key that has the authority to allow new peers onto the swarm. + +"signature" + The RSA signature of the ``info`` dictionary (specifically, the encrypted SHA-1 + hash of the ``info`` dictionary). + +These fields serve the purpose of satisfying use case (1), allowing downloaders to +verify that the torrent has a trusted source. + +extension handshake +------------------- + +In order to satisfy use case (2), any peer supporting this extension MUST verify +that each peer on the swarm it connects to and receive an incoming connection from +is authenticated by the publisher's public key, if the torrent is *private*. + +A torrent is private if the ``info`` dictionary contains an integer key ``private`` +set to 1. + +The extension handshake dictionary ("m") SHOULD contain a new extension key "lt_auth". + +For private torrents, the extension handshake dictionary +MUST contain the certificate granting this peer access to the torrent. The +certificate is a dictionary ``cert`` containing the ``info-hash``, +``pubkey`` (the peer's public key), ``expiry`` (posix time of when cert expires). +The ``cert`` dictionary MAY be extended with more fields. + +Next to the ``cert`` entry is a string ``sig`` being the signature +of the SHA-1 hash of the bencoded representation of the ``cert`` dictionary. +The signature is required for private torrents, but not required for non-private +torrents. + +An example extension handshake for a private torrent could look like this:: + + { + "cert": { + "expiry": 1333242356, + "info-hash": f0fb0ed2d73a28aece3a9e4f91c91cf60e0ea0a6, + "pubkey": + 79eaeecbfc91b129c34880e3113fc9545e7169fed95a7950a335cfcef3dd2989c + c80c69d9e66206cdb4e41eeae8b19234804cd55b3eb6dbee15a5362e3fa6eb33b + a61297dfc63426832623efdf54816474189ddf1baad1d08cb614ed130f9744671 + a0c9bdf32747c284955ad9ae65db875f4f74f51e624710fe7c3db5ec8ce55f175 + b49131cace810d6a61a9fcd0fa4e41e466e09c2a18629f4bebb8dbf4746648122 + bfc8153e3e76ea16cae62e0e1608effa6bf2f1d1954d2e3ef2ca1db327f75a1aa + 711ccad6a564821824a2708c20a9184a221554d1228fdaee39ba3ce4134847fab + 91514d4ed720fabe8082e342dfce15ced93545bdc6 + }, + "m": { + "lt_auth": 8 + } + "sig": + 1767303857272cc9133253614712584a3c6be5f81cd777e9e6e99fbf23be27ca9 + d96c3ad596521f90dd4fb2291d6c9a6108625cf58ca836c3e7f15595306cd9311 + 5c4736276cb4ee08b7f85a5eb26759bfb46b28f36cecfd73e71fac5cad8f22bb6 + 32d07ab4e530587d4fc4d21ae1c52aacbefa0a0dbabb9e24d6c552aaa464a8b94 + 97cc776e3b1b4051dbc9ef26952ab0e74188cbf1ea54f37309ef781d37b59ded7 + e6ce09a9531cc9916ecbe1fb93369da47ab79457e9709576f737e3d89e3774731 + d97ac8b1283dc46a382e4ddd5a155b17d6ded6683f508f111c5e974098315c509 + 77d533ddb52f42fa5019342496a217483b622c3a + } + + +This certificate would expire at ``Sat Mar 31 18:05:56 PDT 2012``. The values of ``sig``, +``info-hash`` and ``pubkey`` are binary strings, they are printed as hex in this example. +The RSA key size SHOULD be 2048 bits (256 bytes). + +Whenconnecting to a peer, or accepting an incoming connection, for a private torrent, +the client MUST verify the validity of the incoming certificate. This is done by: + +1. Verifying that the certificate has not expired. +2. Verifying that the ``info-hash`` matches the torrent the peers are connected over +3. Verifying that the signature ``sig``, is a valid signature made by the private counterpart of the public key of the publisher of the torrent (i.e. the ``publisher`` key in the torrent file) + +If the certificate fails any one of those checks, the peer MUST be +disconnected without exchanging any more information. As an exception, before +disconnecting, the peer MAY send an ``lt_auto`` message saying the authentication +failed. + +An example extension handshake for a non-private torrent could look like this:: + + { + "cert": { + "pubkey": + 79eaeecbfc91b129c34880e3113fc9545e7169fed95a7950a335cfcef3dd2989c + c80c69d9e66206cdb4e41eeae8b19234804cd55b3eb6dbee15a5362e3fa6eb33b + a61297dfc63426832623efdf54816474189ddf1baad1d08cb614ed130f9744671 + a0c9bdf32747c284955ad9ae65db875f4f74f51e624710fe7c3db5ec8ce55f175 + b49131cace810d6a61a9fcd0fa4e41e466e09c2a18629f4bebb8dbf4746648122 + bfc8153e3e76ea16cae62e0e1608effa6bf2f1d1954d2e3ef2ca1db327f75a1aa + 711ccad6a564821824a2708c20a9184a221554d1228fdaee39ba3ce4134847fab + 91514d4ed720fabe8082e342dfce15ced93545bdc6 + }, + "m": { + "lt_auth": 8 + } + } + +Note that the only required field in the ``cert`` in this case is the ``pubkey``, +The assumption in this case is that the public key is recognized by the client +matched by a local directory of trusted peers. This is to support use case (3). + +extension message +----------------- + +The ``lt_auth`` extension message is used to protect against man-in-the-middle attacks, +and peers trying to assume someone else's identity. To do this, the entire connection +is encrypted with RC4 following this message. If the connection is already encrypted +with RC4, this overrides that encryption by resetting the encryption state. + +It has the following format: + + ============= ============= ====================================================== + size name description + ============= ============= ====================================================== + uint8_t type 0 = encrypt the connection with RC4 using the symmetric + key passed in this message. + 1 = signature verification failed + 2 = info-hash verification failed + 3 = certificate expired + ------------- ------------- ------------------------------------------------------ + uint8_t[256] rc4_key If type is not 0, this field is not included. + The symmetric key the sender of this message will use + to encrypt every message following this one. The key + is encrypted with the recipient's public key. The + length of this field may vary, but for 2048 RSA keys + it ends up being 256 bytes. + ============= ============= ====================================================== + +Note that this message is somewhat of a layer violation, since it reaches down and +starts encrypting the entire stream below itself. This is illustrated in the figure +below. + +:: + + +---------------------------+ + | BitTorrent messages | -----+ lt_auth reaches down + +---------------------------+ | to the layer below + | (RC4 protocol encryption) | <----+ + +-------------+-------------+ + | TCP | uTP | + +----------+--+-----+-------+ + +applications +------------ + +This is a low level building block and not a complete system for authenticated or +private swarms. It does allow for peers to verify that a torrent comes from a +trusted source (assuming their public key is known in advance). It also allows +for publishers to limit access to content. + +For privacy reasons, it is possible to apply this extension in a way that each peer +uses separate identities (key pairs) for every torrent it participates in. + +Issues not covered by this extension are: + +1. How the distribution of signed certificates are distributed to peers from + the signing authority (publisher), for use case (2). +2. How peers discover each other and and *pair* by recognizing each other's + public keys for future trusted connections, for use case (3). +3. How peers learn about the public keys of trusted publishers, for use case (1). + diff --git a/docs/makefile b/docs/makefile index 21c2ea39e..576256a01 100644 --- a/docs/makefile +++ b/docs/makefile @@ -6,6 +6,7 @@ TARGETS = index \ udp_tracker_protocol \ dht_rss \ dht_store \ + auth \ client_test \ manual \ building \ diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp index d2f2a4419..2bd6ab9de 100644 --- a/include/libtorrent/bt_peer_connection.hpp +++ b/include/libtorrent/bt_peer_connection.hpp @@ -283,8 +283,8 @@ namespace libtorrent // stream key (info hash of attached torrent) // secret is the DH shared secret - // initializes m_RC4_handler - void init_pe_RC4_handler(char const* secret, sha1_hash const& stream_key); + // initializes m_rc4_handler + void init_pe_rc4_handler(char const* secret, sha1_hash const& stream_key); public: @@ -298,7 +298,7 @@ public: { #ifndef TORRENT_DISABLE_ENCRYPTION if (m_rc4_encrypted) - m_RC4_handler->encrypt(buffer, size); + m_rc4_handler->encrypt(buffer, size); #endif peer_connection::append_send_buffer(buffer, size, destructor, true); } @@ -410,17 +410,17 @@ private: int m_sync_bytes_read; // initialized during write_pe1_2_dhkey, and destroyed on - // creation of m_RC4_handler. Cannot reinitialize once + // creation of m_rc4_handler. Cannot reinitialize once // initialized. boost::scoped_ptr m_dh_key_exchange; // if RC4 is negotiated, this is used for // encryption/decryption during the entire session. Destroyed // if plaintext is selected - boost::scoped_ptr m_RC4_handler; + boost::scoped_ptr m_rc4_handler; // (outgoing only) synchronize verification constant with - // remote peer, this will hold RC4_decrypt(vc). Destroyed + // remote peer, this will hold rc4_decrypt(vc). Destroyed // after the sync step. boost::scoped_array m_sync_vc; diff --git a/include/libtorrent/pe_crypto.hpp b/include/libtorrent/pe_crypto.hpp index 2d4f58c54..482c1a831 100644 --- a/include/libtorrent/pe_crypto.hpp +++ b/include/libtorrent/pe_crypto.hpp @@ -85,33 +85,55 @@ namespace libtorrent sha1_hash m_xor_mask; }; - class RC4_handler // Non copyable + class rc4_handler // Non copyable { public: // Input longkeys must be 20 bytes - RC4_handler(sha1_hash const& rc4_local_longkey, - sha1_hash const& rc4_remote_longkey) + rc4_handler() + : m_encrypt(false) + , m_decrypt(false) { #ifdef TORRENT_USE_GCRYPT gcry_cipher_open(&m_rc4_incoming, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0); gcry_cipher_open(&m_rc4_outgoing, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0); - gcry_cipher_setkey(m_rc4_incoming, &rc4_remote_longkey[0], 20); - gcry_cipher_setkey(m_rc4_outgoing, &rc4_local_longkey[0], 20); -#elif defined TORRENT_USE_OPENSSL - RC4_set_key(&m_local_key, 20, &rc4_local_longkey[0]); - RC4_set_key(&m_remote_key, 20, &rc4_remote_longkey[0]); -#else - rc4_init(&rc4_remote_longkey[0], 20, &m_rc4_incoming); - rc4_init(&rc4_local_longkey[0], 20, &m_rc4_outgoing); #endif + }; + void set_incoming_key(unsigned char const* key, int len) + { + m_decrypt = true; +#ifdef TORRENT_USE_GCRYPT + gcry_cipher_close(m_rc4_incoming); + gcry_cipher_open(&m_rc4_incoming, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0); + gcry_cipher_setkey(m_rc4_incoming, key, len); +#elif defined TORRENT_USE_OPENSSL + RC4_set_key(&m_remote_key, len, key); +#else + rc4_init(key, len, &m_rc4_incoming); +#endif + // Discard first 1024 bytes + char buf[1024]; + decrypt(buf, 1024); + } + + void set_outgoing_key(unsigned char const* key, int len) + { + m_encrypt = true; +#ifdef TORRENT_USE_GCRYPT + gcry_cipher_close(m_rc4_outgoing); + gcry_cipher_open(&m_rc4_outgoing, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0); + gcry_cipher_setkey(m_rc4_outgoing, key, len); +#elif defined TORRENT_USE_OPENSSL + RC4_set_key(&m_local_key, len, key); +#else + rc4_init(key, len, &m_rc4_outgoing); +#endif // Discard first 1024 bytes char buf[1024]; encrypt(buf, 1024); - decrypt(buf, 1024); - }; + } - ~RC4_handler() + ~rc4_handler() { #ifdef TORRENT_USE_GCRYPT gcry_cipher_close(m_rc4_incoming); @@ -121,6 +143,8 @@ namespace libtorrent void encrypt(char* pos, int len) { + if (!m_encrypt) return; + TORRENT_ASSERT(len >= 0); TORRENT_ASSERT(pos); @@ -135,6 +159,8 @@ namespace libtorrent void decrypt(char* pos, int len) { + if (!m_decrypt) return; + TORRENT_ASSERT(len >= 0); TORRENT_ASSERT(pos); @@ -158,6 +184,9 @@ namespace libtorrent rc4 m_rc4_incoming; rc4 m_rc4_outgoing; #endif + // determines whether or not encryption and decryption is enabled + bool m_encrypt; + bool m_decrypt; }; } // namespace libtorrent diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index 5f77fd584..1437734c7 100644 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -380,9 +380,9 @@ namespace libtorrent #ifndef TORRENT_DISABLE_ENCRYPTION if (m_encrypted) { - m_rc4_encrypted ? - p.flags |= peer_info::rc4_encrypted : - p.flags |= peer_info::plaintext_encrypted; + p.flags |= m_rc4_encrypted + ? peer_info::rc4_encrypted + : peer_info::plaintext_encrypted; } #endif @@ -493,7 +493,7 @@ namespace libtorrent ptr += 20; // Discard DH key exchange data, setup RC4 keys - init_pe_RC4_handler(secret, info_hash); + init_pe_rc4_handler(secret, info_hash); m_dh_key_exchange.reset(); // secret should be invalid at this point // write the verification constant and crypto field @@ -516,7 +516,7 @@ namespace libtorrent #endif write_pe_vc_cryptofield(ptr, encrypt_size, crypto_provide, pad_size); - m_RC4_handler->encrypt(ptr, encrypt_size); + m_rc4_handler->encrypt(ptr, encrypt_size); send_buffer(msg, sizeof(msg) - 512 + pad_size); } @@ -536,7 +536,7 @@ namespace libtorrent char msg[512 + 8 + 4 + 2]; write_pe_vc_cryptofield(msg, sizeof(msg), crypto_select, pad_size); - m_RC4_handler->encrypt(msg, buf_size); + m_rc4_handler->encrypt(msg, buf_size); send_buffer(msg, buf_size); // encryption method has been negotiated @@ -581,7 +581,7 @@ namespace libtorrent detail::write_uint16(handshake_len, write_buf); // len(IA) } - void bt_peer_connection::init_pe_RC4_handler(char const* secret, sha1_hash const& stream_key) + void bt_peer_connection::init_pe_rc4_handler(char const* secret, sha1_hash const& stream_key) { INVARIANT_CHECK; @@ -595,7 +595,7 @@ namespace libtorrent // outgoing connection : hash ('keyA',S,SKEY) // incoming connection : hash ('keyB',S,SKEY) - is_local() ? h.update(keyA, 4) : h.update(keyB, 4); + if (is_local()) h.update(keyA, 4); else h.update(keyB, 4); h.update(secret, dh_key_len); h.update((char const*)stream_key.begin(), 20); const sha1_hash local_key = h.final(); @@ -606,14 +606,16 @@ namespace libtorrent // outgoing connection : hash ('keyB',S,SKEY) // incoming connection : hash ('keyA',S,SKEY) - is_local() ? h.update(keyB, 4) : h.update(keyA, 4); + if (is_local()) h.update(keyB, 4); else h.update(keyA, 4); h.update(secret, dh_key_len); h.update((char const*)stream_key.begin(), 20); const sha1_hash remote_key = h.final(); - TORRENT_ASSERT(!m_RC4_handler.get()); - m_RC4_handler.reset(new (std::nothrow) RC4_handler(local_key, remote_key)); - if (!m_RC4_handler) + TORRENT_ASSERT(!m_rc4_handler.get()); + m_rc4_handler.reset(new (std::nothrow) rc4_handler); + m_rc4_handler->set_incoming_key(&remote_key[0], 20); + m_rc4_handler->set_outgoing_key(&local_key[0], 20); + if (!m_rc4_handler) { disconnect(errors::no_memory); return; @@ -644,7 +646,7 @@ namespace libtorrent void encrypt(char* buf, int len, void* userdata) { - RC4_handler* rc4 = (RC4_handler*)userdata; + rc4_handler* rc4 = (rc4_handler*)userdata; rc4->encrypt(buf, len); } @@ -659,7 +661,7 @@ namespace libtorrent if (m_encrypted && m_rc4_encrypted) { fun = encrypt; - userdata = m_RC4_handler.get(); + userdata = m_rc4_handler.get(); } #endif @@ -2322,8 +2324,8 @@ namespace libtorrent if (m_rc4_encrypted && m_encrypted) { std::pair wr_buf = wr_recv_buffers(bytes_transferred); - m_RC4_handler->decrypt(wr_buf.first.begin, wr_buf.first.left()); - if (wr_buf.second.left()) m_RC4_handler->decrypt(wr_buf.second.begin, wr_buf.second.left()); + m_rc4_handler->decrypt(wr_buf.first.begin, wr_buf.first.left()); + if (wr_buf.second.left()) m_rc4_handler->decrypt(wr_buf.second.begin, wr_buf.second.left()); } #endif @@ -2507,7 +2509,7 @@ namespace libtorrent TORRENT_ASSERT(t); } - init_pe_RC4_handler(m_dh_key_exchange->get_secret(), ti.info_hash()); + init_pe_rc4_handler(m_dh_key_exchange->get_secret(), ti.info_hash()); #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** stream key found, torrent located"); #endif @@ -2515,7 +2517,7 @@ namespace libtorrent } } - if (!m_RC4_handler.get()) + if (!m_rc4_handler.get()) { disconnect(errors::invalid_info_hash, 1); return; @@ -2523,7 +2525,7 @@ namespace libtorrent // verify constant buffer::interval wr_recv_buf = wr_recv_buffer(); - m_RC4_handler->decrypt(wr_recv_buf.begin + 20, 8); + m_rc4_handler->decrypt(wr_recv_buf.begin + 20, 8); wr_recv_buf.begin += 28; const char sh_vc[] = {0,0,0,0, 0,0,0,0}; @@ -2568,7 +2570,7 @@ namespace libtorrent return; } std::fill(m_sync_vc.get(), m_sync_vc.get() + 8, 0); - m_RC4_handler->decrypt(m_sync_vc.get(), 8); + m_rc4_handler->decrypt(m_sync_vc.get(), 8); } TORRENT_ASSERT(m_sync_vc.get()); @@ -2626,7 +2628,7 @@ namespace libtorrent if (!packet_finished()) return; buffer::interval wr_buf = wr_recv_buffer(); - m_RC4_handler->decrypt(wr_buf.begin, packet_size()); + m_rc4_handler->decrypt(wr_buf.begin, packet_size()); recv_buffer = receive_buffer(); @@ -2749,7 +2751,7 @@ namespace libtorrent int pad_size = is_local() ? packet_size() : packet_size() - 2; buffer::interval wr_buf = wr_recv_buffer(); - m_RC4_handler->decrypt(wr_buf.begin, packet_size()); + m_rc4_handler->decrypt(wr_buf.begin, packet_size()); recv_buffer = receive_buffer(); @@ -2798,7 +2800,7 @@ namespace libtorrent // ia is always rc4, so decrypt it buffer::interval wr_buf = wr_recv_buffer(); - m_RC4_handler->decrypt(wr_buf.begin, packet_size()); + m_rc4_handler->decrypt(wr_buf.begin, packet_size()); #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** decrypted ia : %d bytes", packet_size()); @@ -2806,7 +2808,7 @@ namespace libtorrent if (!m_rc4_encrypted) { - m_RC4_handler.reset(); + m_rc4_handler.reset(); #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** destroyed rc4 keys"); #endif @@ -2830,14 +2832,14 @@ namespace libtorrent { buffer::interval wr_buf = wr_recv_buffer(); wr_buf.begin += packet_size(); - m_RC4_handler->decrypt(wr_buf.begin, wr_buf.left()); + m_rc4_handler->decrypt(wr_buf.begin, wr_buf.left()); #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** decrypted remaining %d bytes", wr_buf.left()); #endif } else // !m_rc4_encrypted { - m_RC4_handler.reset(); + m_rc4_handler.reset(); #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** destroyed rc4 keys"); #endif @@ -3301,7 +3303,7 @@ namespace libtorrent TORRENT_ASSERT( (bool(m_state != read_pe_dhkey) || m_dh_key_exchange.get()) || !is_local()); - TORRENT_ASSERT(!m_rc4_encrypted || m_RC4_handler.get()); + TORRENT_ASSERT(!m_rc4_encrypted || m_rc4_handler.get()); #endif if (!in_handshake()) { diff --git a/test/test_pe_crypto.cpp b/test/test_pe_crypto.cpp index 42c3cae59..a80ffc075 100644 --- a/test/test_pe_crypto.cpp +++ b/test/test_pe_crypto.cpp @@ -156,8 +156,12 @@ int test_main() sha1_hash test1_key = hasher("test1_key",8).final(); sha1_hash test2_key = hasher("test2_key",8).final(); - RC4_handler RC41(test2_key, test1_key); - RC4_handler RC42(test1_key, test2_key); + rc4_handler rc41; + rc41.set_incoming_key(&test2_key[0], 20); + rc41.set_outgoing_key(&test1_key[0], 20); + rc4_handler rc42; + rc42.set_incoming_key(&test1_key[0], 20); + rc42.set_outgoing_key(&test2_key[0], 20); for (int rep = 0; rep < repcount; ++rep) { @@ -168,12 +172,12 @@ int test_main() std::fill(buf, buf + buf_len, 0); std::fill(zero_buf, zero_buf + buf_len, 0); - RC41.encrypt(buf, buf_len); - RC42.decrypt(buf, buf_len); + rc41.encrypt(buf, buf_len); + rc42.decrypt(buf, buf_len); TEST_CHECK(std::equal(buf, buf + buf_len, zero_buf)); - RC42.encrypt(buf, buf_len); - RC41.decrypt(buf, buf_len); + rc42.encrypt(buf, buf_len); + rc41.decrypt(buf, buf_len); TEST_CHECK(std::equal(buf, buf + buf_len, zero_buf)); delete[] buf;