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
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
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
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 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 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
@ -282,6 +288,9 @@ some additional error codes.
+------------+-----------------------------+
| 206 | invalid signature |
+------------+-----------------------------+
| 207 | salt (i.e. ``salt`` field) |
| | too big. |
+------------+-----------------------------+
| 301 | the CAS hash mismatched, |
| | re-read value and try |
| | again. |
@ -323,7 +332,6 @@ Response:
"k": *<curve25519 public key (32 bytes string)>*,
"nodes": *<IPv4 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)>*,
"sig": *<curve25519 signature (64 bytes 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
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!
@ -385,11 +400,59 @@ private key::
e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
signature::
**target ID**::
4a533d47ec9c7d95b1ad75f576cffc641853b750
**signature**::
305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff
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
---------

View File

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

View File

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

View File

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

View File

@ -46,23 +46,21 @@ void get_item::got_data(lazy_entry const* v,
boost::uint64_t seq,
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 (hasher(pk, item_pk_len).final() != m_target)
return;
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;
}
}
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);
bool put_requested = m_data_callback(m_data);
if (put_requested)

View File

@ -45,8 +45,9 @@ namespace libtorrent { namespace dht
namespace
{
enum { canonical_length = 1100 };
int canonical_string(std::pair<char const*, int> v, boost::uint64_t seq, char out[canonical_length])
enum { canonical_length = 1200 };
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!
#ifdef TORRENT_DEBUG
@ -54,15 +55,48 @@ namespace
error_code ec;
TORRENT_ASSERT(lazy_bdecode(v.first, v.first + v.second, e, ec) == 0);
#endif
int len = snprintf(out, canonical_length, "3:seqi%" PRId64 "e1:v", seq);
memcpy(out + len, v.first, v.second);
len += v.second;
TORRENT_ASSERT(len <= canonical_length);
return len;
char* ptr = out;
int left = canonical_length - (ptr - out);
if (salt.second > 0)
{
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,
char const* pk,
char const* sig)
@ -74,7 +108,7 @@ bool verify_mutable_item(std::pair<char const*, int> v,
#endif
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,
(unsigned char const*)str,
@ -82,7 +116,9 @@ bool verify_mutable_item(std::pair<char const*, int> v,
(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,
char const* pk,
char const* sk,
@ -95,7 +131,7 @@ void sign_mutable_item(std::pair<char const*, int> v,
#endif
char str[canonical_length];
int len = canonical_string(v, seq, str);
int len = canonical_string(v, seq, salt, str);
ed25519_sign((unsigned char*)sig,
(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];
int len = canonical_string(v, seq, str);
int len = canonical_string(v, seq, salt, str);
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();
}
item::item(lazy_item const& i)
: m_seq(i.seq)
, 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)
void item::assign(entry const& v, std::pair<char const*, int> salt
, boost::uint64_t seq, char const* pk, char const* sk)
{
m_value = v;
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];
int bsize = bencode(buffer, v);
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);
m_seq = seq;
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;
}
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);
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;
memcpy(m_pk, pk, item_pk_len);
memcpy(m_sig, sig, item_sig_len);
if (salt.second > 0)
m_salt.assign(salt.first, salt.second);
m_seq = seq;
m_mutable = true;
}
@ -174,14 +211,8 @@ sha1_hash item::cas()
TORRENT_ASSERT(m_mutable);
char buffer[1000];
int bsize = bencode(buffer, m_value);
return mutable_item_cas(std::make_pair(buffer, bsize), 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();
return mutable_item_cas(std::make_pair(buffer, bsize)
, std::pair<char const*, int>(m_salt.c_str(), m_salt.size()), m_seq);
}
} } // 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},
{"sig", lazy_entry::string_t, item_sig_len, 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
lazy_entry const* msg_keys[6];
if (!verify_message(arg_ent, msg_desc, msg_keys, 6, error_string, sizeof(error_string)))
lazy_entry const* msg_keys[7];
if (!verify_message(arg_ent, msg_desc, msg_keys, 7, error_string, sizeof(error_string)))
{
incoming_error(e, error_string);
return;
@ -917,6 +918,14 @@ void node_impl::incoming_request(msg const& m, entry& e)
// is this a mutable put?
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
std::pair<char const*, int> buf = msg_keys[1]->data_section();
if (buf.second > 1000 || buf.second <= 0)
@ -925,15 +934,23 @@ void node_impl::incoming_request(msg const& m, entry& e)
return;
}
sha1_hash target;
if (!mutable_put)
target = hasher(buf.first, buf.second).final();
else
target = hasher(msg_keys[3]->string_ptr(), item_pk_len).final();
std::pair<char const*, int> salt(NULL, 0);
if (msg_keys[6])
salt = std::pair<char const*, int>(
msg_keys[6]->string_ptr(), msg_keys[6]->string_length());
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"
// , 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
// 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
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
// msg_keys[4] is the signature, msg_keys[3] is the public key
if (!verify_mutable_item(msg_keys[1]->data_section()
, msg_keys[2]->int_value()
, msg_keys[3]->string_ptr()
, msg_keys[4]->string_ptr()))
if (!verify_mutable_item(buf, salt
, msg_keys[2]->int_value(), pk, sig))
{
incoming_error(e, "invalid signature", 206);
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);
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)));
TORRENT_ASSERT(j != m_mutable_table.end());
free(j->second.value);
free(j->second.salt);
m_mutable_table.erase(j);
}
dht_mutable_item to_add;
to_add.value = (char*)malloc(buf.second);
to_add.size = buf.second;
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());
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(
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
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()))
{

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
, bool scrape = false, bool seed = false
, 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
// 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 (seq >= 0) a["seq"] = seq;
if (cas) a["cas"] = std::string(cas, 20);
if (put_salt) a["salt"] = put_salt;
char msg_buf[1500];
int size = bencode(msg_buf, e);
#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM
@ -747,214 +749,247 @@ int test_main()
announce_immutable_items(node, eps, items, sizeof(items)/sizeof(items[0]));
// ==== get / put mutable items ===
fprintf(stderr, "generating ed25519 keys\n");
unsigned char seed[32];
ed25519_create_seed(seed);
char private_key[item_sk_len];
char public_key[item_pk_len];
ed25519_create_keypair((unsigned char*)public_key, (unsigned char*)private_key, seed);
fprintf(stderr, "pub: %s priv: %s\n"
, to_hex(std::string(public_key, item_pk_len)).c_str()
, to_hex(std::string(private_key, item_sk_len)).c_str());
TEST_CHECK(ret);
send_dht_request(node, "get", source, &response, "10", 0
, 0, no, 0, (char*)&hasher(public_key, item_pk_len).final()[0]
, 0, false, false, std::string(), std::string(), 64);
key_desc_t desc[] =
{
{ "r", lazy_entry::dict_t, 0, key_desc_t::parse_children },
{ "id", lazy_entry::string_t, 20, 0},
{ "token", lazy_entry::string_t, 0, 0},
{ "ip", lazy_entry::string_t, 0, key_desc_t::optional | key_desc_t::last_child},
{ "y", lazy_entry::string_t, 1, 0},
};
ret = verify_message(&response, desc, parsed, 5, error_string, sizeof(error_string));
if (ret)
{
TEST_EQUAL(parsed[4]->string_value(), "r");
token = parsed[2]->string_value();
fprintf(stderr, "got token: %s\n", token.c_str());
}
else
{
fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
fprintf(stderr, " invalid get response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
char signature[item_sig_len];
char buffer[1200];
int seq = 4;
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
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
#endif
send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[0].ent, false, false
, std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq);
key_desc_t desc2[] =
{
{ "y", lazy_entry::string_t, 1, 0 }
};
ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string));
if (ret)
{
fprintf(stderr, "put response: %s\n"
, print_entry(response).c_str());
TEST_EQUAL(parsed[0]->string_value(), "r");
}
else
{
fprintf(stderr, " invalid put response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
send_dht_request(node, "get", source, &response, "10", 0
, 0, no, 0, (char*)&hasher(public_key, item_pk_len).final()[0]
, 0, false, false, std::string(), std::string(), 64);
key_desc_t desc3[] =
{
{ "r", lazy_entry::dict_t, 0, key_desc_t::parse_children },
{ "id", lazy_entry::string_t, 20, 0},
{ "v", lazy_entry::none_t, 0, 0},
{ "seq", lazy_entry::int_t, 0, 0},
{ "sig", lazy_entry::string_t, 0, 0},
{ "ip", lazy_entry::string_t, 0, key_desc_t::optional | key_desc_t::last_child},
{ "y", lazy_entry::string_t, 1, 0},
};
ret = verify_message(&response, desc3, parsed, 7, error_string, sizeof(error_string));
if (ret == 0)
{
fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
fprintf(stderr, " invalid get response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
else
{
char value[1020];
char* ptr = value;
int value_len = bencode(ptr, items[0].ent);
TEST_EQUAL(value_len, parsed[2]->data_section().second);
TEST_CHECK(memcmp(parsed[2]->data_section().first, value, value_len) == 0);
TEST_EQUAL(seq, parsed[3]->int_value());
}
// also test that invalid signatures fail!
itemv.second = 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), 1);
#ifdef TORRENT_USE_VALGRIND
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
#endif
// break the signature
signature[2] ^= 0xaa;
TEST_CHECK(verify_mutable_item(itemv, seq, public_key, signature) != 1);
send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[0].ent, false, false
, std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq);
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));
if (ret)
{
fprintf(stderr, "put response: %s\n", print_entry(response).c_str());
TEST_EQUAL(parsed[1]->string_value(), "e");
// 206 is the code for invalid signature
TEST_EQUAL(parsed[0]->list_int_value_at(0), 206);
}
else
{
fprintf(stderr, " invalid put response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
// ==== get / put mutable items ===
// === test CAS put ===
std::pair<const char*, int> itemv;
std::pair<char const*, int> empty_salt(NULL, 0);
char signature[item_sig_len];
char buffer[1200];
int seq = 4;
char private_key[item_sk_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);
// this is the hash that we expect to be there
sha1_hash cas = mutable_item_cas(itemv, seq);
// increment sequence number
++seq;
// put item 1
itemv.second = bencode(buffer, items[1].ent);
sign_mutable_item(itemv, seq, public_key, private_key, signature);
TEST_EQUAL(verify_mutable_item(itemv, seq, public_key, signature), 1);
ed25519_create_keypair((unsigned char*)public_key, (unsigned char*)private_key, seed);
fprintf(stderr, "pub: %s priv: %s\n"
, to_hex(std::string(public_key, item_pk_len)).c_str()
, to_hex(std::string(private_key, item_sk_len)).c_str());
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
, 0, no, 0, (char*)&target_id[0]
, 0, false, false, std::string(), std::string(), 64);
key_desc_t desc[] =
{
{ "r", lazy_entry::dict_t, 0, key_desc_t::parse_children },
{ "id", lazy_entry::string_t, 20, 0},
{ "token", lazy_entry::string_t, 0, 0},
{ "ip", lazy_entry::string_t, 0, key_desc_t::optional | key_desc_t::last_child},
{ "y", lazy_entry::string_t, 1, 0},
};
ret = verify_message(&response, desc, parsed, 5, error_string, sizeof(error_string));
if (ret)
{
TEST_EQUAL(parsed[4]->string_value(), "r");
token = parsed[2]->string_value();
fprintf(stderr, "get response: %s\n"
, print_entry(response).c_str());
fprintf(stderr, "got token: %s\n", to_hex(token).c_str());
}
else
{
fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
fprintf(stderr, " invalid get response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
itemv = std::pair<char const*, int>(buffer, bencode(buffer, items[0].ent));
sign_mutable_item(itemv, salt, seq, public_key, private_key, signature);
TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), true);
#ifdef TORRENT_USE_VALGRIND
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
#endif
send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[1].ent, false, false
, std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq
, (char const*)&cas[0]);
send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[0].ent, false, false
, std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq, NULL, NULL, salt.first);
ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string));
if (ret)
{
fprintf(stderr, "put response: %s\n"
, print_entry(response).c_str());
TEST_EQUAL(parsed[0]->string_value(), "r");
}
else
{
fprintf(stderr, " invalid put response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string));
if (ret)
{
fprintf(stderr, "put response: %s\n"
, print_entry(response).c_str());
TEST_EQUAL(parsed[0]->string_value(), "r");
}
else
{
fprintf(stderr, " invalid put response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
// put the same message again. This should fail because the
// CAS hash is outdated, it's not the hash of the value that's
// stored anymore
send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[1].ent, false, false
, std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq
, (char const*)&cas[0]);
send_dht_request(node, "get", source, &response, "10", 0
, 0, no, 0, (char*)&target_id[0]
, 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[] =
{
{ "r", lazy_entry::dict_t, 0, key_desc_t::parse_children },
{ "id", lazy_entry::string_t, 20, 0},
{ "v", lazy_entry::none_t, 0, 0},
{ "seq", lazy_entry::int_t, 0, 0},
{ "sig", lazy_entry::string_t, 0, 0},
{ "ip", lazy_entry::string_t, 0, key_desc_t::optional | key_desc_t::last_child},
{ "y", lazy_entry::string_t, 1, 0},
};
ret = verify_message(&response, desc3, parsed, 7, error_string, sizeof(error_string));
if (ret == 0)
{
fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
fprintf(stderr, " invalid get response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
else
{
fprintf(stderr, "get response: %s\n"
, print_entry(response).c_str());
char value[1020];
char* ptr = value;
int value_len = bencode(ptr, items[0].ent);
TEST_EQUAL(value_len, parsed[2]->data_section().second);
TEST_CHECK(memcmp(parsed[2]->data_section().first, value, value_len) == 0);
TEST_EQUAL(seq, parsed[3]->int_value());
}
// also test that invalid signatures fail!
itemv.second = bencode(buffer, items[0].ent);
sign_mutable_item(itemv, salt, seq, public_key, private_key, signature);
TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), 1);
#ifdef TORRENT_USE_VALGRIND
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
#endif
// break the signature
signature[2] ^= 0xaa;
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
, 0, token, 0, 0, &items[0].ent, false, false
, std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq, NULL, NULL, salt.first);
ret = verify_message(&response, desc_error, parsed, 2, error_string, sizeof(error_string));
if (ret)
{
fprintf(stderr, "put response: %s\n", print_entry(response).c_str());
TEST_EQUAL(parsed[1]->string_value(), "e");
// 206 is the code for invalid signature
TEST_EQUAL(parsed[0]->list_int_value_at(0), 206);
}
else
{
fprintf(stderr, " invalid put response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
// === test CAS put ===
// this is the hash that we expect to be there
sha1_hash cas = mutable_item_cas(itemv, salt, seq);
// increment sequence number
++seq;
// put item 1
itemv.second = bencode(buffer, items[1].ent);
sign_mutable_item(itemv, salt, seq, public_key, private_key, signature);
TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), 1);
#ifdef TORRENT_USE_VALGRIND
VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len);
#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
, 0, token, 0, 0, &items[1].ent, false, false
, std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq
, (char const*)&cas[0], NULL, salt.first);
ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string));
if (ret)
{
fprintf(stderr, "put response: %s\n"
, print_entry(response).c_str());
TEST_EQUAL(parsed[0]->string_value(), "r");
}
else
{
fprintf(stderr, " invalid put response: %s\n%s\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
fprintf(stderr, "PUT CAS 2\n");
// put the same message again. This should fail because the
// CAS hash is outdated, it's not the hash of the value that's
// stored anymore
send_dht_request(node, "put", source, &response, "10", 0
, 0, token, 0, 0, &items[1].ent, false, false
, std::string(public_key, item_pk_len)
, std::string(signature, item_sig_len), seq
, (char const*)&cas[0], NULL, salt.first);
ret = verify_message(&response, desc_error, parsed, 2, error_string, sizeof(error_string));
if (ret)
{
fprintf(stderr, "put response: %s\n"
, print_entry(response).c_str());
TEST_EQUAL(parsed[1]->string_value(), "e");
// 301 is the error code for CAS hash mismatch
TEST_EQUAL(parsed[0]->list_int_value_at(0), 301);
}
else
{
fprintf(stderr, " invalid put response: %s\n%s\nExpected failure 301 (CAS hash mismatch)\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
ret = verify_message(&response, desc_error, parsed, 2, error_string, sizeof(error_string));
if (ret)
{
fprintf(stderr, "put response: %s\n"
, print_entry(response).c_str());
TEST_EQUAL(parsed[1]->string_value(), "e");
// 301 is the error code for CAS hash mismatch
TEST_EQUAL(parsed[0]->list_int_value_at(0), 301);
}
else
{
fprintf(stderr, " invalid put response: %s\n%s\nExpected failure 301 (CAS hash mismatch)\n"
, error_string, print_entry(response).c_str());
TEST_ERROR(error_string);
}
// test routing table
@ -1589,7 +1624,7 @@ int test_main()
g_sent_packets.clear();
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>()
, 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]);
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);
node.get_item(target, get_item_cb);
@ -1794,18 +1829,44 @@ int test_main()
} 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("e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d"
"b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d", 128, private_key);
sign_mutable_item(std::pair<char const*, int>("12:Hello World!", 15), 1
, public_key, private_key, signature);
sign_mutable_item(test_content, empty_salt, 1, public_key
, private_key, signature);
TEST_EQUAL(to_hex(std::string(signature, 64))
, "305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff"
"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;
}