diff --git a/ChangeLog b/ChangeLog index 4b9d80d6d..b7af1db6b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,7 @@ * fix uTP edge case where udp socket buffer fills up * fix nagle implementation in uTP + * fix support for storing arbitrary data in the DHT * fixed bug in uTP packet circle buffer * fix potential crash when using torrent_handle::add_piece * added missing add_torrent_alert to python binding diff --git a/docs/dht_store.html b/docs/dht_store.html index a6c3f97b6..88eed896a 100644 --- a/docs/dht_store.html +++ b/docs/dht_store.html @@ -98,14 +98,19 @@ i.e. the node ID of the node sending the message, to maintain the structure of t network.

The token field also has the same semantics as the standard DHT message get_peers and announce_peer, when requesting an item and to write an item respectively.

+

The k field is the PKCS#1 encoded 2048 bit RSA public key, which the signature +can be authenticated with. When looking up a mutable item, the target field +MUST be the SHA-1 hash of this key.

The distinction between storing mutable and immutable items is the inclusion -of a public key, a sequence number and signature (k, seq and sig). -The distinction betwewn retrieving a mutable and immutable item is the inclusion of -the public key spill-over (k) in the get request.

-

The v key is the value to be stored. It is allowed to be any bencoded type (list, +of a public key, a sequence number and signature (k, seq and sig).

+

get requests for mutable items and immutable items cannot be distinguished from +eachother. An implementation can either store mutable and immutable items in the same +hash table internally, or in separate ones and potentially do two lookups for get +requests.

+

The v field is the value to be stored. It is allowed to be any bencoded type (list, dict, string or integer). When it's being hashed (for verifying its signature or to calculate its key), its flattened, bencoded, form is used.

-

Storing nodes are SHOULD reject put requests where the bencoded form of v is longer +

Storing nodes SHOULD reject put requests where the bencoded form of v is longer than 767 bytes.

@@ -172,7 +177,8 @@ there is no need to authenticate the origin of them. This makes immutable items

mutable items

Mutable items can be updated, without changing their DHT keys. To authenticate that only the original publisher can update an item, it is signed by a private key -generated by the original publisher.

+generated by the original publisher. The target ID mutable items are stored under +is the SHA-1 hash of the public key (as it appears in the put message).

In order to avoid a malicious node to overwrite the list head with an old version, the sequence number seq must be monotonically increasing for each update, and a node hosting the list node MUST not downgrade a list head from a higher sequence @@ -187,7 +193,7 @@ number and v key. e.g. something like this:: < "a": { "id": <20 byte id of sending node (string)>, - "k": <RSA-2048 public key (268 bytes string)>, + "k": <RSA-2048 public key (PKCS#1 encoded)>, "seq": <monotonically increasing sequence number (integer)>, "sig": <RSA-2048 signature (256 bytes string)>, "token": <write-token (string)>, @@ -217,8 +223,7 @@ stored on the node, MUST reject the request.

"a": { "id": <20 byte id of sending node (string)>, - "target:" <first 20 bytes of public key (string)>, - "k": <remaining 248 bytes of public key (string)> + "target:" <20 byte SHA-1 hash of public key (string)> }, "t": <transaction-id (string)>, "y": "q", @@ -259,6 +264,11 @@ verification failure if a bencoding serializer alters the order of entries in th
  • hash the concatenated string with SHA-1
  • sign or verify the hash digest.
  • +

    On the storage node, the signature MUST be verified before accepting the store command. The data +MUST be stored under the SHA-1 hash of the public key (as it appears in the bencoded dict).

    +

    On the subscribing nodes, the key they get back from a get request MUST be verified to hash +to the target ID the lookup was made for, as well as verifying the signature. If any of these fail, +the response SHOULD be considered invalid.

    expiration

    diff --git a/docs/dht_store.rst b/docs/dht_store.rst index 0d8a017a8..0386b6a87 100644 --- a/docs/dht_store.rst +++ b/docs/dht_store.rst @@ -44,16 +44,23 @@ network. The ``token`` field also has the same semantics as the standard DHT message ``get_peers`` and ``announce_peer``, when requesting an item and to write an item respectively. +The ``k`` field is the PKCS#1 encoded 2048 bit RSA public key, which the signature +can be authenticated with. When looking up a mutable item, the ``target`` field +MUST be the SHA-1 hash of this key. + The distinction between storing mutable and immutable items is the inclusion of a public key, a sequence number and signature (``k``, ``seq`` and ``sig``). -The distinction betwewn retrieving a mutable and immutable item is the inclusion of -the public key spill-over (``k``) in the ``get`` request. -The ``v`` key is the *value* to be stored. It is allowed to be any bencoded type (list, +``get`` requests for mutable items and immutable items cannot be distinguished from +eachother. An implementation can either store mutable and immutable items in the same +hash table internally, or in separate ones and potentially do two lookups for ``get`` +requests. + +The ``v`` field is the *value* to be stored. It is allowed to be any bencoded type (list, dict, string or integer). When it's being hashed (for verifying its signature or to calculate its key), its flattened, bencoded, form is used. -Storing nodes are SHOULD reject ``put`` requests where the bencoded form of ``v`` is longer +Storing nodes SHOULD reject ``put`` requests where the bencoded form of ``v`` is longer than 767 bytes. immutable items @@ -131,7 +138,8 @@ mutable items Mutable items can be updated, without changing their DHT keys. To authenticate that only the original publisher can update an item, it is signed by a private key -generated by the original publisher. +generated by the original publisher. The target ID mutable items are stored under +is the SHA-1 hash of the public key (as it appears in the ``put`` message). In order to avoid a malicious node to overwrite the list head with an old version, the sequence number ``seq`` must be monotonically increasing for each update, @@ -152,7 +160,7 @@ Request: "a": { "id": *<20 byte id of sending node (string)>*, - "k": **, + "k": **, "seq": **, "sig": **, "token": **, @@ -187,8 +195,7 @@ Request: "a": { "id": *<20 byte id of sending node (string)>*, - "target:" **, - "k": ** + "target:" *<20 byte SHA-1 hash of public key (string)>* }, "t": **, "y": "q", @@ -230,6 +237,13 @@ value and sequence number should be done as follows: 3. hash the concatenated string with SHA-1 4. sign or verify the hash digest. +On the storage node, the signature MUST be verified before accepting the store command. The data +MUST be stored under the SHA-1 hash of the public key (as it appears in the bencoded dict). + +On the subscribing nodes, the key they get back from a ``get`` request MUST be verified to hash +to the target ID the lookup was made for, as well as verifying the signature. If any of these fail, +the response SHOULD be considered invalid. + expiration ---------- diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp index ee82d4981..67a3b9f33 100644 --- a/include/libtorrent/kademlia/node.hpp +++ b/include/libtorrent/kademlia/node.hpp @@ -131,14 +131,15 @@ struct dht_immutable_item int size; }; +struct rsa_key { char bytes[268]; }; + struct dht_mutable_item : dht_immutable_item { char sig[256]; int seq; + rsa_key key; }; -struct rsa_key { char bytes[268]; }; - inline bool operator<(rsa_key const& lhs, rsa_key const& rhs) { return memcmp(lhs.bytes, rhs.bytes, sizeof(lhs.bytes)) < 0; @@ -184,7 +185,7 @@ class TORRENT_EXTRA_EXPORT node_impl : boost::noncopyable { typedef std::map table_t; typedef std::map dht_immutable_table_t; -typedef std::map dht_mutable_table_t; +typedef std::map dht_mutable_table_t; public: node_impl(alert_dispatcher* alert_disp, udp_socket_interface* sock diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp index a6c2dc269..a9f3d973b 100644 --- a/src/kademlia/node.cpp +++ b/src/kademlia/node.cpp @@ -891,8 +891,7 @@ void node_impl::incoming_request(msg const& m, entry& e) return; #endif - rsa_key target; - memcpy(target.bytes, msg_keys[3]->string_ptr(), sizeof(target.bytes)); + 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()) { @@ -916,6 +915,7 @@ void node_impl::incoming_request(msg const& m, entry& e) memcpy(to_add.sig, msg_keys[4]->string_ptr(), 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)); boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert( std::make_pair(target, to_add)); @@ -967,14 +967,13 @@ void node_impl::incoming_request(msg const& m, entry& e) { key_desc_t msg_desc[] = { {"target", lazy_entry::string_t, 20, 0}, - {"k", lazy_entry::string_t, 268-20, key_desc_t::optional}, }; // k is not used for now // attempt to parse the message - lazy_entry const* msg_keys[2]; - if (!verify_message(arg_ent, msg_desc, msg_keys, 2, error_string, sizeof(error_string))) + lazy_entry const* msg_keys[1]; + if (!verify_message(arg_ent, msg_desc, msg_keys, 1, error_string, sizeof(error_string))) { incoming_error(e, error_string); return; @@ -993,27 +992,22 @@ void node_impl::incoming_request(msg const& m, entry& e) m_table.find_node(target, n, 0); write_nodes_entry(reply, n); - if (msg_keys[1]) + dht_immutable_table_t::iterator i = m_immutable_table.find(target); + if (i != m_immutable_table.end()) { - rsa_key key; - memcpy(key.bytes, msg_keys[0]->string_ptr(), 20); - memcpy(key.bytes + 20, msg_keys[1]->string_ptr(), 268-20); - dht_mutable_table_t::iterator i = m_mutable_table.find(key); + dht_immutable_item const& f = i->second; + reply["v"] = bdecode(f.value, f.value + f.size); + } + else + { + dht_mutable_table_t::iterator i = m_mutable_table.find(target); if (i != m_mutable_table.end()) { dht_mutable_item const& f = i->second; reply["v"] = bdecode(f.value, f.value + f.size); reply["seq"] = f.seq; reply["sig"] = std::string(f.sig, f.sig + 256); - } - } - else - { - dht_immutable_table_t::iterator i = m_immutable_table.find(target); - if (i != m_immutable_table.end()) - { - dht_immutable_item const& f = i->second; - reply["v"] = bdecode(f.value, f.value + f.size); + reply["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes)); } } }