factor encryption handler a bit and add proposal for an authentication extension

This commit is contained in:
Arvid Norberg 2011-06-18 16:58:36 +00:00
parent fbf2eb6f88
commit 060b849dda
6 changed files with 281 additions and 53 deletions

192
docs/auth.rst Normal file
View File

@ -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).

View File

@ -6,6 +6,7 @@ TARGETS = index \
udp_tracker_protocol \
dht_rss \
dht_store \
auth \
client_test \
manual \
building \

View File

@ -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<dh_key_exchange> 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<RC4_handler> m_RC4_handler;
boost::scoped_ptr<rc4_handler> 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<char> m_sync_vc;

View File

@ -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

View File

@ -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<buffer::interval, buffer::interval> 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())
{

View File

@ -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;