support salt feature in DHT put

This commit is contained in:
Arvid Norberg 2014-01-03 04:18:46 +00:00
parent f26df6cbfa
commit 54bbd3cae0
8 changed files with 483 additions and 291 deletions

View File

@ -170,7 +170,7 @@ concatenated with the ``v`` key. e.g. something like this::
If the ``salt`` key is present and non-empty, the salt string must be included If the ``salt`` key is present and non-empty, the salt string must be included
in what's signed. Note that if ``salt`` is specified and an empty string, it is in what's signed. Note that if ``salt`` is specified and an empty string, it is
as if it was not specified and nothing in addition to the sequence number and as if it was not specified and nothing in addition to the sequence number and
the data is signed. the data is signed. The salt string may not be longer than 64 bytes.
When a salt is included in what is signed, the key ``salt`` with the value of When a salt is included in what is signed, the key ``salt`` with the value of
the key is prepended in its bencoded form. For example, if ``salt`` is "foobar", the key is prepended in its bencoded form. For example, if ``salt`` is "foobar",
@ -230,6 +230,12 @@ publisher doesn't know ahead of time how many different items are to be
published. It can distribute a single public key for users to authenticate the published. It can distribute a single public key for users to authenticate the
published blobs. published blobs.
Note that the salt is not returned in the response to a ``get`` request. This
is intentional. When issuing a ``get`` request for an item is expected to
know what the salt is (because it is part of what the target ID that is being
looked up is derived from). There is no need to repeat it back for bystanders
to see.
The ``cas`` field is optional. If present it is interpreted as the sha-1 hash of The ``cas`` field is optional. If present it is interpreted as the sha-1 hash of
the sequence number, ``v`` field and possibly the ``salt`` field, that is the sequence number, ``v`` field and possibly the ``salt`` field, that is
expected to be replaced. The buffer to hash is the same as the one signed when expected to be replaced. The buffer to hash is the same as the one signed when
@ -282,6 +288,9 @@ some additional error codes.
+------------+-----------------------------+ +------------+-----------------------------+
| 206 | invalid signature | | 206 | invalid signature |
+------------+-----------------------------+ +------------+-----------------------------+
| 207 | salt (i.e. ``salt`` field) |
| | too big. |
+------------+-----------------------------+
| 301 | the CAS hash mismatched, | | 301 | the CAS hash mismatched, |
| | re-read value and try | | | re-read value and try |
| | again. | | | again. |
@ -323,7 +332,6 @@ Response:
"k": *<curve25519 public key (32 bytes string)>*, "k": *<curve25519 public key (32 bytes string)>*,
"nodes": *<IPv4 nodes close to 'target'>*, "nodes": *<IPv4 nodes close to 'target'>*,
"nodes6": *<IPv6 nodes close to 'target'>*, "nodes6": *<IPv6 nodes close to 'target'>*,
"salt": *<optional salt to be appended to "k" when hashing (string)>*
"seq": *<monotonically increasing sequence number (integer)>*, "seq": *<monotonically increasing sequence number (integer)>*,
"sig": *<curve25519 signature (64 bytes string)>*, "sig": *<curve25519 signature (64 bytes string)>*,
"token": *<write-token (string)>*, "token": *<write-token (string)>*,
@ -369,10 +377,17 @@ Any node that's interested in keeping a blob in the DHT alive may announce it.
It would simply repeat the signature for a mutable put without having the It would simply repeat the signature for a mutable put without having the
private key. private key.
test vector test vectors
----------- ------------
The buffer being signed:: test 1 (mutable)
................
value::
12:Hello World!
buffer being signed::
3:seqi1e1:v12:Hello World! 3:seqi1e1:v12:Hello World!
@ -385,11 +400,59 @@ private key::
e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
signature:: **target ID**::
4a533d47ec9c7d95b1ad75f576cffc641853b750
**signature**::
305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff 305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff
1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01 1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01
test 2 (mutable with salt)
..........................
value::
12:Hello World!
salt::
foobar
buffer being signed::
4:salt6:foobar3:seqi1e1:v12:Hello World!
public key::
77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548
private key::
e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
**target ID**::
411eba73b6f087ca51a3795d9c8c938d365e32c1
**signature**::
6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d
df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08
test 3 (immutable)
..................
value::
12:Hello World!
**target ID**::
e5f96f6f38320f0f33959cb4d3d656452117aadb
resources resources
--------- ---------

View File

@ -63,6 +63,7 @@ protected:
data_callback m_data_callback; data_callback m_data_callback;
item m_data; item m_data;
std::string m_salt;
}; };
class get_item_observer : public find_data_observer class get_item_observer : public find_data_observer

View File

@ -42,18 +42,30 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent { namespace dht namespace libtorrent { namespace dht
{ {
bool TORRENT_EXTRA_EXPORT verify_mutable_item(std::pair<char const*, int> v, sha1_hash TORRENT_EXTRA_EXPORT item_target_id(
std::pair<char const*, int> v
, std::pair<char const*, int> salt
, char const* pk);
bool TORRENT_EXTRA_EXPORT verify_mutable_item(
std::pair<char const*, int> v,
std::pair<char const*, int> salt,
boost::uint64_t seq, boost::uint64_t seq,
char const* pk, char const* pk,
char const* sig); char const* sig);
void TORRENT_EXTRA_EXPORT sign_mutable_item(std::pair<char const*, int> v, void TORRENT_EXTRA_EXPORT sign_mutable_item(
std::pair<char const*, int> v,
std::pair<char const*, int> salt,
boost::uint64_t seq, boost::uint64_t seq,
char const* pk, char const* pk,
char const* sk, char const* sk,
char* sig); char* sig);
sha1_hash TORRENT_EXTRA_EXPORT mutable_item_cas(std::pair<char const*, int> v, boost::uint64_t seq); sha1_hash TORRENT_EXTRA_EXPORT mutable_item_cas(
std::pair<char const*, int> v
, std::pair<char const*, int> salt
, boost::uint64_t seq);
struct TORRENT_EXTRA_EXPORT invalid_item : std::exception struct TORRENT_EXTRA_EXPORT invalid_item : std::exception
{ {
@ -67,22 +79,32 @@ enum
item_sig_len = 64 item_sig_len = 64
}; };
struct lazy_item;
class TORRENT_EXTRA_EXPORT item class TORRENT_EXTRA_EXPORT item
{ {
public: public:
item() : m_mutable(false) {} item() : m_mutable(false) {}
item(entry const& v) { assign(v); } item(entry const& v) { assign(v); }
item(entry const& v, boost::uint64_t seq, char const* pk, char const* sk); item(entry const& v
, std::pair<char const*, int> salt
, boost::uint64_t seq, char const* pk, char const* sk);
item(lazy_entry const* v) { assign(v); } item(lazy_entry const* v) { assign(v); }
item(lazy_entry const* v, boost::uint64_t seq, char const* pk, char const* sig); item(lazy_entry const* v, std::pair<char const*, int> salt
item(lazy_item const&); , boost::uint64_t seq, char const* pk, char const* sig);
void assign(entry const& v) { assign(v, 0, NULL, NULL); } void assign(entry const& v)
void assign(entry const& v, boost::uint64_t seq, char const* pk, char const* sk); {
void assign(lazy_entry const* v) { assign(v, 0, NULL, NULL); } assign(v, std::pair<char const*, int>(NULL, 0), 0, NULL, NULL);
bool assign(lazy_entry const* v, boost::uint64_t seq, char const* pk, char const* sig); }
void assign(entry const& v
, std::pair<char const*, int> salt
, boost::uint64_t seq, char const* pk, char const* sk);
void assign(lazy_entry const* v)
{
assign(v, std::pair<char const*, int>(NULL, 0), 0, NULL, NULL);
}
bool assign(lazy_entry const* v
, std::pair<char const*, int> salt
, boost::uint64_t seq, char const* pk, char const* sig);
void clear() { m_value = entry(); } void clear() { m_value = entry(); }
bool empty() const { return m_value.type() == entry::undefined_t; } bool empty() const { return m_value.type() == entry::undefined_t; }
@ -98,25 +120,13 @@ public:
private: private:
entry m_value; entry m_value;
std::string m_salt;
char m_pk[item_pk_len]; char m_pk[item_pk_len];
char m_sig[item_sig_len]; char m_sig[item_sig_len];
boost::uint64_t m_seq; boost::uint64_t m_seq;
bool m_mutable; bool m_mutable;
}; };
struct lazy_item
{
lazy_item(lazy_entry const* v) : value(v), pk(NULL), sig(NULL), seq(0) {}
lazy_item(lazy_entry const* v, char const* pk, char const* sig, boost::uint64_t seq);
bool is_mutable() const { return pk && sig; }
lazy_entry const* value;
char const* pk;
char const* sig;
boost::uint64_t const seq;
};
} } // namespace libtorrent::dht } } // namespace libtorrent::dht
#endif // LIBTORRENT_ITEM_HPP #endif // LIBTORRENT_ITEM_HPP

View File

@ -140,6 +140,8 @@ struct dht_mutable_item : dht_immutable_item
char sig[item_sig_len]; char sig[item_sig_len];
boost::uint64_t seq; boost::uint64_t seq;
ed25519_public_key key; ed25519_public_key key;
char* salt;
int salt_size;
}; };
// internal // internal

View File

@ -46,23 +46,21 @@ void get_item::got_data(lazy_entry const* v,
boost::uint64_t seq, boost::uint64_t seq,
char const* sig) char const* sig)
{ {
std::pair<char const*, int> salt(m_salt.c_str(), m_salt.size());
sha1_hash incoming_target = item_target_id(v->data_section(), salt, pk);
if (incoming_target != m_target) return;
if (pk && sig) if (pk && sig)
{ {
if (hasher(pk, item_pk_len).final() != m_target)
return;
if (m_data.empty() || m_data.seq() < seq) if (m_data.empty() || m_data.seq() < seq)
{ {
if (!m_data.assign(v, seq, pk, sig)) if (!m_data.assign(v, salt, seq, pk, sig))
return; return;
} }
} }
else if (m_data.empty()) else if (m_data.empty())
{ {
std::pair<char const*, int> buf = v->data_section();
if (hasher(buf.first, buf.second).final() != m_target)
return;
m_data.assign(v); m_data.assign(v);
bool put_requested = m_data_callback(m_data); bool put_requested = m_data_callback(m_data);
if (put_requested) if (put_requested)

View File

@ -45,8 +45,9 @@ namespace libtorrent { namespace dht
namespace namespace
{ {
enum { canonical_length = 1100 }; enum { canonical_length = 1200 };
int canonical_string(std::pair<char const*, int> v, boost::uint64_t seq, char out[canonical_length]) int canonical_string(std::pair<char const*, int> v, boost::uint64_t seq
, std::pair<char const*, int> salt, char out[canonical_length])
{ {
// v must be valid bencoding! // v must be valid bencoding!
#ifdef TORRENT_DEBUG #ifdef TORRENT_DEBUG
@ -54,15 +55,48 @@ namespace
error_code ec; error_code ec;
TORRENT_ASSERT(lazy_bdecode(v.first, v.first + v.second, e, ec) == 0); TORRENT_ASSERT(lazy_bdecode(v.first, v.first + v.second, e, ec) == 0);
#endif #endif
int len = snprintf(out, canonical_length, "3:seqi%" PRId64 "e1:v", seq); char* ptr = out;
memcpy(out + len, v.first, v.second);
len += v.second; int left = canonical_length - (ptr - out);
TORRENT_ASSERT(len <= canonical_length); if (salt.second > 0)
return len; {
ptr += snprintf(ptr, left, "4:salt%d:", salt.second);
left = canonical_length - (ptr - out);
memcpy(ptr, salt.first, (std::min)(salt.second, left));
ptr += (std::min)(salt.second, left);
left = canonical_length - (ptr - out);
}
ptr += snprintf(ptr, canonical_length - (ptr - out)
, "3:seqi%" PRId64 "e1:v", seq);
left = canonical_length - (ptr - out);
memcpy(ptr, v.first, (std::min)(v.second, left));
ptr += (std::min)(v.second, left);
TORRENT_ASSERT((ptr - out) <= canonical_length);
return ptr - out;
} }
} }
bool verify_mutable_item(std::pair<char const*, int> v, sha1_hash item_target_id(
std::pair<char const*, int> v
, std::pair<char const*, int> salt
, char const* pk)
{
hasher h;
if (pk)
{
h.update(pk, item_pk_len);
if (salt.second > 0) h.update(salt.first, salt.second);
}
else
{
h.update(v.first, v.second);
}
return h.final();
}
bool verify_mutable_item(
std::pair<char const*, int> v,
std::pair<char const*, int> salt,
boost::uint64_t seq, boost::uint64_t seq,
char const* pk, char const* pk,
char const* sig) char const* sig)
@ -74,7 +108,7 @@ bool verify_mutable_item(std::pair<char const*, int> v,
#endif #endif
char str[canonical_length]; char str[canonical_length];
int len = canonical_string(v, seq, str); int len = canonical_string(v, seq, salt, str);
return ed25519_verify((unsigned char const*)sig, return ed25519_verify((unsigned char const*)sig,
(unsigned char const*)str, (unsigned char const*)str,
@ -82,7 +116,9 @@ bool verify_mutable_item(std::pair<char const*, int> v,
(unsigned char const*)pk) == 1; (unsigned char const*)pk) == 1;
} }
void sign_mutable_item(std::pair<char const*, int> v, void sign_mutable_item(
std::pair<char const*, int> v,
std::pair<char const*, int> salt,
boost::uint64_t seq, boost::uint64_t seq,
char const* pk, char const* pk,
char const* sk, char const* sk,
@ -95,7 +131,7 @@ void sign_mutable_item(std::pair<char const*, int> v,
#endif #endif
char str[canonical_length]; char str[canonical_length];
int len = canonical_string(v, seq, str); int len = canonical_string(v, seq, salt, str);
ed25519_sign((unsigned char*)sig, ed25519_sign((unsigned char*)sig,
(unsigned char const*)str, (unsigned char const*)str,
@ -105,35 +141,31 @@ void sign_mutable_item(std::pair<char const*, int> v,
); );
} }
sha1_hash mutable_item_cas(std::pair<char const*, int> v, boost::uint64_t seq) sha1_hash mutable_item_cas(std::pair<char const*, int> v
, std::pair<char const*, int> salt
, boost::uint64_t seq)
{ {
char str[canonical_length]; char str[canonical_length];
int len = canonical_string(v, seq, str); int len = canonical_string(v, seq, salt, str);
return hasher(str, len).final(); return hasher(str, len).final();
} }
item::item(entry const& v, boost::uint64_t seq, char const* pk, char const* sk) item::item(entry const& v
, std::pair<char const*, int> salt
, boost::uint64_t seq, char const* pk, char const* sk)
{ {
assign(v, seq, pk, sk); assign(v, salt, seq, pk, sk);
} }
item::item(lazy_entry const* v, boost::uint64_t seq, char const* pk, char const* sig) item::item(lazy_entry const* v, std::pair<char const*, int> salt
, boost::uint64_t seq, char const* pk, char const* sig)
{ {
if (!assign(v, seq, pk, sig)) if (!assign(v, salt, seq, pk, sig))
throw invalid_item(); throw invalid_item();
} }
item::item(lazy_item const& i) void item::assign(entry const& v, std::pair<char const*, int> salt
: m_seq(i.seq) , boost::uint64_t seq, char const* pk, char const* sk)
, m_mutable(i.is_mutable())
{
m_value = *i.value;
// if this is a mutable item lazy_item will have already verified it
memcpy(m_pk, i.pk, item_pk_len);
memcpy(m_sig, i.sig, item_sig_len);
}
void item::assign(entry const& v, boost::uint64_t seq, char const* pk, char const* sk)
{ {
m_value = v; m_value = v;
if (pk && sk) if (pk && sk)
@ -141,7 +173,8 @@ void item::assign(entry const& v, boost::uint64_t seq, char const* pk, char cons
char buffer[1000]; char buffer[1000];
int bsize = bencode(buffer, v); int bsize = bencode(buffer, v);
TORRENT_ASSERT(bsize <= 1000); TORRENT_ASSERT(bsize <= 1000);
sign_mutable_item(std::make_pair(buffer, bsize), seq, pk, sk, m_sig); sign_mutable_item(std::make_pair(buffer, bsize)
, salt, seq, pk, sk, m_sig);
memcpy(m_pk, pk, item_pk_len); memcpy(m_pk, pk, item_pk_len);
m_seq = seq; m_seq = seq;
m_mutable = true; m_mutable = true;
@ -150,15 +183,19 @@ void item::assign(entry const& v, boost::uint64_t seq, char const* pk, char cons
m_mutable = false; m_mutable = false;
} }
bool item::assign(lazy_entry const* v, boost::uint64_t seq, char const* pk, char const* sig) bool item::assign(lazy_entry const* v
, std::pair<char const*, int> salt
, boost::uint64_t seq, char const* pk, char const* sig)
{ {
TORRENT_ASSERT(v->data_section().second <= 1000); TORRENT_ASSERT(v->data_section().second <= 1000);
if (pk && sig) if (pk && sig)
{ {
if (!verify_mutable_item(v->data_section(), seq, pk, sig)) if (!verify_mutable_item(v->data_section(), salt, seq, pk, sig))
return false; return false;
memcpy(m_pk, pk, item_pk_len); memcpy(m_pk, pk, item_pk_len);
memcpy(m_sig, sig, item_sig_len); memcpy(m_sig, sig, item_sig_len);
if (salt.second > 0)
m_salt.assign(salt.first, salt.second);
m_seq = seq; m_seq = seq;
m_mutable = true; m_mutable = true;
} }
@ -174,14 +211,8 @@ sha1_hash item::cas()
TORRENT_ASSERT(m_mutable); TORRENT_ASSERT(m_mutable);
char buffer[1000]; char buffer[1000];
int bsize = bencode(buffer, m_value); int bsize = bencode(buffer, m_value);
return mutable_item_cas(std::make_pair(buffer, bsize), m_seq); return mutable_item_cas(std::make_pair(buffer, bsize)
} , std::pair<char const*, int>(m_salt.c_str(), m_salt.size()), m_seq);
lazy_item::lazy_item(lazy_entry const* v, char const* pk, char const* sig, boost::uint64_t seq)
: value(v), pk(pk), sig(sig), seq(seq)
{
if (is_mutable() && !verify_mutable_item(v->data_section(), seq, pk, sig))
throw invalid_item();
} }
} } // namespace libtorrent::dht } } // namespace libtorrent::dht

View File

@ -904,11 +904,12 @@ void node_impl::incoming_request(msg const& m, entry& e)
{"k", lazy_entry::string_t, item_pk_len, key_desc_t::optional}, {"k", lazy_entry::string_t, item_pk_len, key_desc_t::optional},
{"sig", lazy_entry::string_t, item_sig_len, key_desc_t::optional}, {"sig", lazy_entry::string_t, item_sig_len, key_desc_t::optional},
{"cas", lazy_entry::string_t, 20, key_desc_t::optional}, {"cas", lazy_entry::string_t, 20, key_desc_t::optional},
{"salt", lazy_entry::string_t, 0, key_desc_t::optional},
}; };
// attempt to parse the message // attempt to parse the message
lazy_entry const* msg_keys[6]; lazy_entry const* msg_keys[7];
if (!verify_message(arg_ent, msg_desc, msg_keys, 6, error_string, sizeof(error_string))) if (!verify_message(arg_ent, msg_desc, msg_keys, 7, error_string, sizeof(error_string)))
{ {
incoming_error(e, error_string); incoming_error(e, error_string);
return; return;
@ -917,6 +918,14 @@ void node_impl::incoming_request(msg const& m, entry& e)
// is this a mutable put? // is this a mutable put?
bool mutable_put = (msg_keys[2] && msg_keys[3] && msg_keys[4]); bool mutable_put = (msg_keys[2] && msg_keys[3] && msg_keys[4]);
// public key (only set if it's a mutable put)
char const* pk = NULL;
if (msg_keys[3]) pk = msg_keys[3]->string_ptr();
// signature (only set if it's a mutable put)
char const* sig = NULL;
if (msg_keys[4]) sig = msg_keys[4]->string_ptr();
// pointer and length to the whole entry // pointer and length to the whole entry
std::pair<char const*, int> buf = msg_keys[1]->data_section(); std::pair<char const*, int> buf = msg_keys[1]->data_section();
if (buf.second > 1000 || buf.second <= 0) if (buf.second > 1000 || buf.second <= 0)
@ -925,15 +934,23 @@ void node_impl::incoming_request(msg const& m, entry& e)
return; return;
} }
sha1_hash target; std::pair<char const*, int> salt(NULL, 0);
if (!mutable_put) if (msg_keys[6])
target = hasher(buf.first, buf.second).final(); salt = std::pair<char const*, int>(
else msg_keys[6]->string_ptr(), msg_keys[6]->string_length());
target = hasher(msg_keys[3]->string_ptr(), item_pk_len).final(); if (salt.second > 64)
{
incoming_error(e, "salt too big", 207);
return;
}
// fprintf(stderr, "%s PUT target: %s\n" sha1_hash target = item_target_id(buf, salt, pk);
// fprintf(stderr, "%s PUT target: %s salt: %s key: %s\n"
// , mutable_put ? "mutable":"immutable" // , mutable_put ? "mutable":"immutable"
// , to_hex(target.to_string()).c_str()); // , to_hex(target.to_string()).c_str()
// , salt.second > 0 ? std::string(salt.first, salt.second).c_str() : ""
// , pk ? to_hex(std::string(pk, 32)).c_str() : "");
// verify the write-token. tokens are only valid to write to // verify the write-token. tokens are only valid to write to
// specific target hashes. it must match the one we got a "get" for // specific target hashes. it must match the one we got a "get" for
@ -983,19 +1000,16 @@ void node_impl::incoming_request(msg const& m, entry& e)
#ifdef TORRENT_USE_VALGRIND #ifdef TORRENT_USE_VALGRIND
VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4]->string_ptr(), item_sig_len); VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4]->string_ptr(), item_sig_len);
VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[3]->string_ptr(), item_pk_len); VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len);
#endif #endif
// msg_keys[4] is the signature, msg_keys[3] is the public key // msg_keys[4] is the signature, msg_keys[3] is the public key
if (!verify_mutable_item(msg_keys[1]->data_section() if (!verify_mutable_item(buf, salt
, msg_keys[2]->int_value() , msg_keys[2]->int_value(), pk, sig))
, msg_keys[3]->string_ptr()
, msg_keys[4]->string_ptr()))
{ {
incoming_error(e, "invalid signature", 206); incoming_error(e, "invalid signature", 206);
return; return;
} }
sha1_hash target = hasher(msg_keys[3]->string_ptr(), msg_keys[3]->string_length()).final();
dht_mutable_table_t::iterator i = m_mutable_table.find(target); dht_mutable_table_t::iterator i = m_mutable_table.find(target);
if (i == m_mutable_table.end()) if (i == m_mutable_table.end())
{ {
@ -1011,16 +1025,25 @@ void node_impl::incoming_request(msg const& m, entry& e)
, boost::bind(&dht_mutable_table_t::value_type::second, _1))); , boost::bind(&dht_mutable_table_t::value_type::second, _1)));
TORRENT_ASSERT(j != m_mutable_table.end()); TORRENT_ASSERT(j != m_mutable_table.end());
free(j->second.value); free(j->second.value);
free(j->second.salt);
m_mutable_table.erase(j); m_mutable_table.erase(j);
} }
dht_mutable_item to_add; dht_mutable_item to_add;
to_add.value = (char*)malloc(buf.second); to_add.value = (char*)malloc(buf.second);
to_add.size = buf.second; to_add.size = buf.second;
to_add.seq = msg_keys[2]->int_value(); to_add.seq = msg_keys[2]->int_value();
memcpy(to_add.sig, msg_keys[4]->string_ptr(), sizeof(to_add.sig)); to_add.salt = NULL;
to_add.salt_size = 0;
if (salt.second > 0)
{
to_add.salt = (char*)malloc(salt.second);
to_add.salt_size = salt.second;
memcpy(to_add.salt, salt.first, salt.second);
}
memcpy(to_add.sig, sig, sizeof(to_add.sig));
TORRENT_ASSERT(sizeof(to_add.sig) == msg_keys[4]->string_length()); TORRENT_ASSERT(sizeof(to_add.sig) == msg_keys[4]->string_length());
memcpy(to_add.value, buf.first, buf.second); memcpy(to_add.value, buf.first, buf.second);
memcpy(&to_add.key, msg_keys[3]->string_ptr(), sizeof(to_add.key)); memcpy(&to_add.key, pk, sizeof(to_add.key));
boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert( boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert(
std::make_pair(target, to_add)); std::make_pair(target, to_add));
@ -1037,7 +1060,10 @@ void node_impl::incoming_request(msg const& m, entry& e)
// matches the expected value before replacing it // matches the expected value before replacing it
if (msg_keys[5]) if (msg_keys[5])
{ {
sha1_hash h = mutable_item_cas(std::make_pair(item->value, item->size), item->seq); sha1_hash h = mutable_item_cas(
std::make_pair(item->value, item->size)
, std::make_pair(item->salt, item->salt_size)
, item->seq);
if (h != sha1_hash(msg_keys[5]->string_ptr())) if (h != sha1_hash(msg_keys[5]->string_ptr()))
{ {

View File

@ -147,7 +147,8 @@ void send_dht_request(node_impl& node, char const* msg, udp::endpoint const& ep
, char const* target = 0, entry const* value = 0 , char const* target = 0, entry const* value = 0
, bool scrape = false, bool seed = false , bool scrape = false, bool seed = false
, std::string const key = std::string(), std::string const sig = std::string() , std::string const key = std::string(), std::string const sig = std::string()
, int seq = -1, char const* cas = 0, sha1_hash const* nid = NULL) , int seq = -1, char const* cas = 0, sha1_hash const* nid = NULL
, char const* put_salt = NULL)
{ {
// we're about to clear out the backing buffer // we're about to clear out the backing buffer
// for this lazy_entry, so we better clear it now // for this lazy_entry, so we better clear it now
@ -171,6 +172,7 @@ void send_dht_request(node_impl& node, char const* msg, udp::endpoint const& ep
if (seed) a["seed"] = 1; if (seed) a["seed"] = 1;
if (seq >= 0) a["seq"] = seq; if (seq >= 0) a["seq"] = seq;
if (cas) a["cas"] = std::string(cas, 20); if (cas) a["cas"] = std::string(cas, 20);
if (put_salt) a["salt"] = put_salt;
char msg_buf[1500]; char msg_buf[1500];
int size = bencode(msg_buf, e); int size = bencode(msg_buf, e);
#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM #if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM
@ -747,13 +749,33 @@ int test_main()
announce_immutable_items(node, eps, items, sizeof(items)/sizeof(items[0])); announce_immutable_items(node, eps, items, sizeof(items)/sizeof(items[0]));
key_desc_t desc2[] =
{
{ "y", lazy_entry::string_t, 1, 0 }
};
key_desc_t desc_error[] =
{
{ "e", lazy_entry::list_t, 2, 0 },
{ "y", lazy_entry::string_t, 1, 0},
};
// ==== get / put mutable items === // ==== get / put mutable items ===
fprintf(stderr, "generating ed25519 keys\n"); std::pair<const char*, int> itemv;
unsigned char seed[32]; std::pair<char const*, int> empty_salt(NULL, 0);
ed25519_create_seed(seed); char signature[item_sig_len];
char buffer[1200];
int seq = 4;
char private_key[item_sk_len]; char private_key[item_sk_len];
char public_key[item_pk_len]; char public_key[item_pk_len];
for (int with_salt = 0; with_salt < 2; ++with_salt)
{
seq = 4;
fprintf(stderr, "\nTEST GET/PUT%s \ngenerating ed25519 keys\n\n"
, with_salt ? " with-salt" : " no-salt");
unsigned char seed[32];
ed25519_create_seed(seed);
ed25519_create_keypair((unsigned char*)public_key, (unsigned char*)private_key, seed); ed25519_create_keypair((unsigned char*)public_key, (unsigned char*)private_key, seed);
fprintf(stderr, "pub: %s priv: %s\n" fprintf(stderr, "pub: %s priv: %s\n"
@ -762,8 +784,19 @@ int test_main()
TEST_CHECK(ret); TEST_CHECK(ret);
std::pair<const char*, int> salt(NULL, 0);
if (with_salt)
salt = std::pair<char const*, int>("foobar", 6);
hasher h(public_key, 32);
if (with_salt) h.update(salt.first, salt.second);
sha1_hash target_id = h.final();
fprintf(stderr, "target_id: %s\n"
, to_hex(target_id.to_string()).c_str());
send_dht_request(node, "get", source, &response, "10", 0 send_dht_request(node, "get", source, &response, "10", 0
, 0, no, 0, (char*)&hasher(public_key, item_pk_len).final()[0] , 0, no, 0, (char*)&target_id[0]
, 0, false, false, std::string(), std::string(), 64); , 0, false, false, std::string(), std::string(), 64);
key_desc_t desc[] = key_desc_t desc[] =
@ -780,7 +813,9 @@ int test_main()
{ {
TEST_EQUAL(parsed[4]->string_value(), "r"); TEST_EQUAL(parsed[4]->string_value(), "r");
token = parsed[2]->string_value(); token = parsed[2]->string_value();
fprintf(stderr, "got token: %s\n", token.c_str()); fprintf(stderr, "get response: %s\n"
, print_entry(response).c_str());
fprintf(stderr, "got token: %s\n", to_hex(token).c_str());
} }
else else
{ {
@ -790,12 +825,9 @@ int test_main()
TEST_ERROR(error_string); TEST_ERROR(error_string);
} }
char signature[item_sig_len]; itemv = std::pair<char const*, int>(buffer, bencode(buffer, items[0].ent));
char buffer[1200]; sign_mutable_item(itemv, salt, seq, public_key, private_key, signature);
int seq = 4; TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), true);
std::pair<const char*, int> itemv(buffer, bencode(buffer, items[0].ent));
sign_mutable_item(itemv, seq, public_key, private_key, signature);
TEST_EQUAL(verify_mutable_item(itemv, seq, public_key, signature), true);
#ifdef TORRENT_USE_VALGRIND #ifdef TORRENT_USE_VALGRIND
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
#endif #endif
@ -803,12 +835,7 @@ int test_main()
send_dht_request(node, "put", source, &response, "10", 0 send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[0].ent, false, false , 0, token, 0, 0, &items[0].ent, false, false
, std::string(public_key, item_pk_len) , std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq); , std::string(signature, item_sig_len), seq, NULL, NULL, salt.first);
key_desc_t desc2[] =
{
{ "y", lazy_entry::string_t, 1, 0 }
};
ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string)); ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string));
if (ret) if (ret)
@ -825,9 +852,12 @@ int test_main()
} }
send_dht_request(node, "get", source, &response, "10", 0 send_dht_request(node, "get", source, &response, "10", 0
, 0, no, 0, (char*)&hasher(public_key, item_pk_len).final()[0] , 0, no, 0, (char*)&target_id[0]
, 0, false, false, std::string(), std::string(), 64); , 0, false, false, std::string(), std::string(), 64);
fprintf(stderr, "target_id: %s\n"
, to_hex(target_id.to_string()).c_str());
key_desc_t desc3[] = key_desc_t desc3[] =
{ {
{ "r", lazy_entry::dict_t, 0, key_desc_t::parse_children }, { "r", lazy_entry::dict_t, 0, key_desc_t::parse_children },
@ -849,6 +879,8 @@ int test_main()
} }
else else
{ {
fprintf(stderr, "get response: %s\n"
, print_entry(response).c_str());
char value[1020]; char value[1020];
char* ptr = value; char* ptr = value;
int value_len = bencode(ptr, items[0].ent); int value_len = bencode(ptr, items[0].ent);
@ -856,32 +888,27 @@ int test_main()
TEST_CHECK(memcmp(parsed[2]->data_section().first, value, value_len) == 0); TEST_CHECK(memcmp(parsed[2]->data_section().first, value, value_len) == 0);
TEST_EQUAL(seq, parsed[3]->int_value()); TEST_EQUAL(seq, parsed[3]->int_value());
} }
// also test that invalid signatures fail! // also test that invalid signatures fail!
itemv.second = bencode(buffer, items[0].ent); itemv.second = bencode(buffer, items[0].ent);
sign_mutable_item(itemv, seq, public_key, private_key, signature); sign_mutable_item(itemv, salt, seq, public_key, private_key, signature);
TEST_EQUAL(verify_mutable_item(itemv, seq, public_key, signature), 1); TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), 1);
#ifdef TORRENT_USE_VALGRIND #ifdef TORRENT_USE_VALGRIND
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
#endif #endif
// break the signature // break the signature
signature[2] ^= 0xaa; signature[2] ^= 0xaa;
TEST_CHECK(verify_mutable_item(itemv, seq, public_key, signature) != 1); fprintf(stderr, "PUT broken signature\n");
TEST_CHECK(verify_mutable_item(itemv, salt, seq, public_key, signature) != 1);
send_dht_request(node, "put", source, &response, "10", 0 send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[0].ent, false, false , 0, token, 0, 0, &items[0].ent, false, false
, std::string(public_key, item_pk_len) , std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq); , std::string(signature, item_sig_len), seq, NULL, NULL, salt.first);
key_desc_t desc_error[] =
{
{ "e", lazy_entry::list_t, 2, 0 },
{ "y", lazy_entry::string_t, 1, 0},
};
ret = verify_message(&response, desc_error, parsed, 2, error_string, sizeof(error_string)); ret = verify_message(&response, desc_error, parsed, 2, error_string, sizeof(error_string));
if (ret) if (ret)
@ -901,22 +928,26 @@ int test_main()
// === test CAS put === // === test CAS put ===
// this is the hash that we expect to be there // this is the hash that we expect to be there
sha1_hash cas = mutable_item_cas(itemv, seq); sha1_hash cas = mutable_item_cas(itemv, salt, seq);
// increment sequence number // increment sequence number
++seq; ++seq;
// put item 1 // put item 1
itemv.second = bencode(buffer, items[1].ent); itemv.second = bencode(buffer, items[1].ent);
sign_mutable_item(itemv, seq, public_key, private_key, signature); sign_mutable_item(itemv, salt, seq, public_key, private_key, signature);
TEST_EQUAL(verify_mutable_item(itemv, seq, public_key, signature), 1); TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), 1);
#ifdef TORRENT_USE_VALGRIND #ifdef TORRENT_USE_VALGRIND
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
#endif #endif
TEST_CHECK(item_target_id(itemv, salt, public_key) == target_id);
fprintf(stderr, "PUT CAS 1\n");
send_dht_request(node, "put", source, &response, "10", 0 send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[1].ent, false, false , 0, token, 0, 0, &items[1].ent, false, false
, std::string(public_key, item_pk_len) , std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq , std::string(signature, item_sig_len), seq
, (char const*)&cas[0]); , (char const*)&cas[0], NULL, salt.first);
ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string)); ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string));
if (ret) if (ret)
@ -932,6 +963,8 @@ int test_main()
TEST_ERROR(error_string); TEST_ERROR(error_string);
} }
fprintf(stderr, "PUT CAS 2\n");
// put the same message again. This should fail because the // put the same message again. This should fail because the
// CAS hash is outdated, it's not the hash of the value that's // CAS hash is outdated, it's not the hash of the value that's
// stored anymore // stored anymore
@ -939,7 +972,7 @@ int test_main()
, 0, token, 0, 0, &items[1].ent, false, false , 0, token, 0, 0, &items[1].ent, false, false
, std::string(public_key, item_pk_len) , std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq , std::string(signature, item_sig_len), seq
, (char const*)&cas[0]); , (char const*)&cas[0], NULL, salt.first);
ret = verify_message(&response, desc_error, parsed, 2, error_string, sizeof(error_string)); ret = verify_message(&response, desc_error, parsed, 2, error_string, sizeof(error_string));
if (ret) if (ret)
@ -957,6 +990,8 @@ int test_main()
TEST_ERROR(error_string); TEST_ERROR(error_string);
} }
}
// test routing table // test routing table
{ {
@ -1589,7 +1624,7 @@ int test_main()
g_sent_packets.clear(); g_sent_packets.clear();
itemv.second = bencode(buffer, items[0].ent); itemv.second = bencode(buffer, items[0].ent);
sign_mutable_item(itemv, seq, public_key, private_key, signature); sign_mutable_item(itemv, empty_salt, seq, public_key, private_key, signature);
send_dht_response(node, response, initial_node, nodes_t(), "10", 1234, std::set<tcp::endpoint>() send_dht_response(node, response, initial_node, nodes_t(), "10", 1234, std::set<tcp::endpoint>()
, NULL, &items[0].ent, std::string(public_key, item_pk_len), std::string(signature, item_sig_len), seq); , NULL, &items[0].ent, std::string(public_key, item_pk_len), std::string(signature, item_sig_len), seq);
@ -1723,7 +1758,7 @@ int test_main()
node.m_table.add_node(nodes[i]); node.m_table.add_node(nodes[i]);
sha1_hash target = hasher(public_key, item_pk_len).final(); sha1_hash target = hasher(public_key, item_pk_len).final();
g_put_item.assign(items[0].ent, seq, public_key, private_key); g_put_item.assign(items[0].ent, empty_salt, seq, public_key, private_key);
std::string sig(g_put_item.sig(), item_sig_len); std::string sig(g_put_item.sig(), item_sig_len);
node.get_item(target, get_item_cb); node.get_item(target, get_item_cb);
@ -1794,18 +1829,44 @@ int test_main()
} while (false); } while (false);
// test vector // test vector 1
// test content
std::pair<char const*, int> test_content("12:Hello World!", 15);
// test salt
std::pair<char const*, int> test_salt("foobar", 6);
from_hex("77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548", 64, public_key); from_hex("77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548", 64, public_key);
from_hex("e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d" from_hex("e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d"
"b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d", 128, private_key); "b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d", 128, private_key);
sign_mutable_item(std::pair<char const*, int>("12:Hello World!", 15), 1 sign_mutable_item(test_content, empty_salt, 1, public_key
, public_key, private_key, signature); , private_key, signature);
TEST_EQUAL(to_hex(std::string(signature, 64)) TEST_EQUAL(to_hex(std::string(signature, 64))
, "305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff" , "305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff"
"1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01"); "1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01");
sha1_hash target_id = item_target_id(test_content, empty_salt, public_key);
TEST_EQUAL(to_hex(target_id.to_string()), "4a533d47ec9c7d95b1ad75f576cffc641853b750");
// test vector 2 (the keypair is the same as test 1)
sign_mutable_item(test_content, test_salt, 1, public_key
, private_key, signature);
TEST_EQUAL(to_hex(std::string(signature, 64))
, "6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d"
"df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08");
target_id = item_target_id(test_content, test_salt, public_key);
TEST_EQUAL(to_hex(target_id.to_string()), "411eba73b6f087ca51a3795d9c8c938d365e32c1");
// test vector 3
target_id = item_target_id(test_content, empty_salt, NULL);
TEST_EQUAL(to_hex(target_id.to_string()), "e5f96f6f38320f0f33959cb4d3d656452117aadb");
return 0; return 0;
} }