diff --git a/docs/dht_store.html b/docs/dht_store.html new file mode 100644 index 000000000..0638f4a05 --- /dev/null +++ b/docs/dht_store.html @@ -0,0 +1,287 @@ + + + + + + + +BitTorrent extension for arbitrary DHT store + + + + + + + +
+
+
+ +
+ +
+

BitTorrent extension for arbitrary DHT store

+ +++ + + + + + +
Author:Arvid Norberg, arvid@rasterbar.com
Version:Draft
+ +

This is a proposal for an extension to the BitTorrent DHT to allow +storing and retrieving of arbitrary data.

+

It supports both storing immutable items, where the key is +the SHA-1 hash of the data itself, and mutable items, where +the key is the public key of the key pair used to sign the data.

+

There are two new proposed messages, put and get.

+
+

terminology

+

In this document, a storage node refers to the node in the DHT to which +an item is being announced and stored on. A subscribing node refers to +a node which makes look-ups in the DHT to find the storage nodes, to +request items from them, and possibly re-announce those items to keep them +alive.

+
+
+

messages

+

The proposed new messages get and put are similar to the existing get_peers +and announce_peer.

+

Responses to get should always include nodes and nodes6 has the same +semantics as in its get_peers response. It should also include a write token, +token, with the same semantics as get_peers.

+

The id field in these messages has the same semantics as the standard DHT messages, +i.e. the node ID of the node sending the message, to maintain the structure of the DHT +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 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, +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 +than 767 bytes.

+
+
+

immutable items

+

Immutable items are stored under their SHA-1 hash, and since they cannot be modified, +there is no need to authenticate the origin of them. This makes immutable items simple.

+
+

put message

+

Request:

+
+{
+        "a":
+        {
+                "id": <20 byte id of sending node (string)>,
+                "v": <any bencoded type, whose encoded size < 768>
+        },
+        "t": <transaction-id (string)>,
+        "y": "q",
+        "q": "put"
+}
+
+

Response:

+
+{
+        "r": { "id": <20 byte id of sending node (string)> },
+        "t": <transaction-id (string)>,
+        "y": "r",
+}
+
+
+
+

get message

+

Request:

+
+{
+        "a":
+        {
+                "id": <20 byte id of sending node (string)>,
+                "target": <SHA-1 hash of item (string)>,
+        },
+        "t": <transaction-id (string)>,
+        "y": "q",
+        "q": "get"
+}
+
+

Response:

+
+{
+   "r":
+        {
+                "id": <20 byte id of sending node (string)>,
+                "token": <write token (string)>,
+                "v": <any bencoded type whose SHA-1 hash matches 'target'>,
+                "nodes": <IPv4 nodes close to 'target'>
+                "nodes6": <IPv6 nodes close to 'target'>
+        },
+        "t": <transaction-id>,
+        "y": "r",
+}
+
+
+
+
+

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.

+

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 +number to a lower one, only upgrade.

+

The signature is a 2048 bit RSA signature of the SHA-1 hash of the bencoded sequence +number and v key. e.g. something like this:: 3:seqi4e1:v12:Hello world!.

+
+

put message

+

Request:

+
+{
+        "a":
+        {
+                "id": <20 byte id of sending node (string)>,
+                "k": <RSA-2048 public key (268 bytes string)>,
+                "seq": <monotonically increasing sequence number (integer)>,
+                "sig": <RSA-2048 signature (256 bytes string)>,
+                "token": <write-token (string)>,
+                "v": <any bencoded type, whose encoded size < 768>
+        },
+        "t": <transaction-id (string)>,
+        "y": "q",
+        "q": "put"
+}
+
+

Storing nodes receiving a put request where seq is lower than what's already +stored on the node, MUST reject the request.

+

Response:

+
+{
+        "r": { "id": <20 byte id of sending node (string)> },
+        "t": <transaction-id (string)>,
+        "y": "r",
+}
+
+
+
+

get message

+

Request:

+
+{
+        "r":
+        {
+                "id": <20 byte id of sending node (string)>,
+                "target:" <first 20 bytes of public key (string)>,
+                "k": <remaining 248 bytes of public key (string)>
+        },
+        "t": <transaction-id (string)>,
+        "y": "r",
+        "q": "get"
+}
+
+

Response:

+
+{
+        "r":
+        {
+                "id": <20 byte id of sending node (string)>,
+                "k": <RSA-2048 public key (268 bytes string)>,
+                "seq": <monotonically increasing sequence number (integer)>,
+                "sig": <RSA-2048 signature (256 bytes string)>,
+                "token": <write-token (string)>,
+                "v": <any bencoded type, whose encoded size < 768>
+
+        },
+        "t": <transaction-id (string)>,
+        "y": "r",
+}
+
+
+
+
+

signature verification

+

In order to make it maximally difficult to attack the bencoding parser, signing and verification of the +value and sequence number should be done as follows:

+
    +
  1. encode value and sequence number separately
  2. +
  3. concatenate "3:seqi" seq "e1:v" and the encoded value. +sequence number 1 of value "Hello World!" would be converted to: 3:seqi1e1:v12:Hello World! +In this way it is not possible to convince a node that part of the length is actually part of the +sequence number even if the parser contains certain bugs. Furthermore it is not possible to have a +verification failure if a bencoding serializer alters the order of entries in the dictionary.
  4. +
  5. hash the concatenated string with SHA-1
  6. +
  7. sign or verify the hash digest.
  8. +
+
+
+

expiration

+

Without re-announcement, these items MAY expire in 2 hours. In order +to keep items alive, they SHOULD be re-announced once an hour.

+

Subscriber nodes MAY help out in announcing items the are interested in to the DHT, +to keep them alive.

+
+
+

test vectors

+
+
+ +
+ + +
+ + diff --git a/docs/dht_store.rst b/docs/dht_store.rst new file mode 100644 index 000000000..723ab2724 --- /dev/null +++ b/docs/dht_store.rst @@ -0,0 +1,244 @@ +============================================ +BitTorrent extension for arbitrary DHT store +============================================ + +:Author: Arvid Norberg, arvid@rasterbar.com +:Version: Draft + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +This is a proposal for an extension to the BitTorrent DHT to allow +storing and retrieving of arbitrary data. + +It supports both storing *immutable* items, where the key is +the SHA-1 hash of the data itself, and *mutable* items, where +the key is the public key of the key pair used to sign the data. + +There are two new proposed messages, ``put`` and ``get``. + +terminology +----------- + +In this document, a *storage node* refers to the node in the DHT to which +an item is being announced and stored on. A *subscribing node* refers to +a node which makes look-ups in the DHT to find the storage nodes, to +request items from them, and possibly re-announce those items to keep them +alive. + +messages +-------- + +The proposed new messages ``get`` and ``put`` are similar to the existing ``get_peers`` +and ``announce_peer``. + +Responses to ``get`` should always include ``nodes`` and ``nodes6`` has the same +semantics as in its ``get_peers`` response. It should also include a write token, +``token``, with the same semantics as ``get_peers``. + +The ``id`` field in these messages has the same semantics as the standard DHT messages, +i.e. the node ID of the node sending the message, to maintain the structure of the DHT +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 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, +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 +than 767 bytes. + +immutable items +--------------- + +Immutable items are stored under their SHA-1 hash, and since they cannot be modified, +there is no need to authenticate the origin of them. This makes immutable items simple. + +put message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "v": ** + }, + "t": **, + "y": "q", + "q": "put" + } + +Response: + +.. parsed-literal:: + + { + "r": { "id": *<20 byte id of sending node (string)>* }, + "t": **, + "y": "r", + } + +get message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "target": **, + }, + "t": **, + "y": "q", + "q": "get" + } + +Response: + +.. parsed-literal:: + + { + "r": + { + "id": *<20 byte id of sending node (string)>*, + "token": **, + "v": **, + "nodes": ** + "nodes6": ** + }, + "t": **, + "y": "r", + } + + +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. + +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 +number to a lower one, only upgrade. + +The signature is a 2048 bit RSA signature of the SHA-1 hash of the bencoded sequence +number and ``v`` key. e.g. something like this:: ``3:seqi4e1:v12:Hello world!``. + +put message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "k": **, + "seq": **, + "sig": **, + "token": **, + "v": ** + }, + "t": **, + "y": "q", + "q": "put" + } + +Storing nodes receiving a ``put`` request where ``seq`` is lower than what's already +stored on the node, MUST reject the request. + +Response: + +.. parsed-literal:: + + { + "r": { "id": *<20 byte id of sending node (string)>* }, + "t": **, + "y": "r", + } + +get message +........... + +Request: + +.. parsed-literal:: + + { + "r": + { + "id": *<20 byte id of sending node (string)>*, + "target:" **, + "k": ** + }, + "t": **, + "y": "r", + "q": "get" + } + +Response: + +.. parsed-literal:: + + { + "r": + { + "id": *<20 byte id of sending node (string)>*, + "k": **, + "seq": **, + "sig": **, + "token": **, + "v": ** + + }, + "t": **, + "y": "r", + } + +signature verification +---------------------- + +In order to make it maximally difficult to attack the bencoding parser, signing and verification of the +value and sequence number should be done as follows: + +1. encode value and sequence number separately +2. concatenate "3:seqi" ``seq`` "e1:v" and the encoded value. + sequence number 1 of value "Hello World!" would be converted to: 3:seqi1e1:v12:Hello World! + In this way it is not possible to convince a node that part of the length is actually part of the + sequence number even if the parser contains certain bugs. Furthermore it is not possible to have a + verification failure if a bencoding serializer alters the order of entries in the dictionary. +3. hash the concatenated string with SHA-1 +4. sign or verify the hash digest. + +expiration +---------- + +Without re-announcement, these items MAY expire in 2 hours. In order +to keep items alive, they SHOULD be re-announced once an hour. + +Subscriber nodes MAY help out in announcing items the are interested in to the DHT, +to keep them alive. + +test vectors +------------ + + diff --git a/docs/makefile b/docs/makefile index 27504512a..21c2ea39e 100644 --- a/docs/makefile +++ b/docs/makefile @@ -5,6 +5,7 @@ WEB_PATH = ~/Documents/rasterbar/web/products/libtorrent TARGETS = index \ udp_tracker_protocol \ dht_rss \ + dht_store \ client_test \ manual \ building \ diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp index f2b3c97d3..3d88e3288 100644 --- a/include/libtorrent/kademlia/node.hpp +++ b/include/libtorrent/kademlia/node.hpp @@ -130,6 +130,19 @@ struct dht_immutable_item int size; }; +struct dht_mutable_item : dht_immutable_item +{ + char sig[256]; + int seq; +}; + +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; +} + inline bool operator<(peer_entry const& lhs, peer_entry const& rhs) { return lhs.addr.address() == rhs.addr.address() @@ -165,6 +178,7 @@ class node_impl : boost::noncopyable { typedef std::map table_t; typedef std::map dht_immutable_table_t; +typedef std::map dht_mutable_table_t; public: typedef boost::function3 external_ip_fun; @@ -277,6 +291,7 @@ public: private: table_t m_map; dht_immutable_table_t m_immutable_table; + dht_mutable_table_t m_mutable_table; ptime m_last_tracker_tick; diff --git a/include/libtorrent/rsa.hpp b/include/libtorrent/rsa.hpp index 944e88d83..065b315b5 100644 --- a/include/libtorrent/rsa.hpp +++ b/include/libtorrent/rsa.hpp @@ -34,18 +34,19 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_SIGN_HPP_INCLUDED #include "libtorrent/config.hpp" +#include "libtorrent/hasher.hpp" namespace libtorrent { // both of these use SHA-1 as the message digest to be signed/verified // returns the size of the resulting signature - TORRENT_EXPORT int sign_rsa(char const* data, int data_len + TORRENT_EXPORT int sign_rsa(sha1_hash const& digest , char const* private_key, int private_len , char* signature, int sig_len); // returns true if the signature is valid - TORRENT_EXPORT bool verify_rsa(char const* data, int data_len + TORRENT_EXPORT bool verify_rsa(sha1_hash const& digest , char const* public_key, int public_len , char const* signature, int sig_len); diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp index 21b412195..d0be1225e 100644 --- a/src/kademlia/node.cpp +++ b/src/kademlia/node.cpp @@ -51,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/kademlia/refresh.hpp" #include "libtorrent/kademlia/find_data.hpp" +#include "libtorrent/rsa.hpp" namespace libtorrent { namespace dht { @@ -786,19 +787,31 @@ void node_impl::incoming_request(msg const& m, entry& e) } else if (strcmp(query, "put") == 0) { + // the first 2 entries are for both mutable and + // immutable puts const static key_desc_t msg_desc[] = { {"token", lazy_entry::string_t, 0, 0}, {"v", lazy_entry::none_t, 0, 0}, + {"seq", lazy_entry::int_t, 0, 0}, + // public key + {"k", lazy_entry::string_t, 268, 0}, + {"sig", lazy_entry::string_t, 256, 0}, }; // attempt to parse the message - lazy_entry const* msg_keys[2]; + lazy_entry const* msg_keys[5]; if (!verify_message(arg_ent, msg_desc, msg_keys, 2, error_string, sizeof(error_string))) { incoming_error(e, error_string); return; } + bool mutable_put = false; + + // is this a mutable put? + if (verify_message(arg_ent, msg_desc, msg_keys, 5, error_string, sizeof(error_string))) + mutable_put = true; + // pointer and length to the whole entry std::pair buf = msg_keys[1]->data_section(); if (buf.second > 767 || buf.second <= 0) @@ -807,7 +820,15 @@ void node_impl::incoming_request(msg const& m, entry& e) return; } - sha1_hash target = hasher(buf.first, buf.second).final(); + sha1_hash target; + if (!mutable_put) + target = hasher(buf.first, buf.second).final(); + else + target = sha1_hash(msg_keys[3]->string_ptr()); + +// fprintf(stderr, "%s PUT target: %s\n" +// , mutable_put ? "mutable":"immutable" +// , to_hex(target.to_string()).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 @@ -817,50 +838,134 @@ void node_impl::incoming_request(msg const& m, entry& e) return; } - dht_immutable_table_t::iterator i = m_immutable_table.find(target); - if (i == m_immutable_table.end()) - { - // make sure we don't add too many items - if (int(m_immutable_table.size()) >= m_settings.max_dht_items) - { - // delete the least important one (i.e. the one - // the fewest peers are announcing) - dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin() - , m_immutable_table.end() - , boost::bind(&dht_immutable_item::num_announcers - , boost::bind(&dht_immutable_table_t::value_type::second, _1))); - TORRENT_ASSERT(j != m_immutable_table.end()); - m_immutable_table.erase(j); - } - dht_immutable_item to_add; - to_add.value = (char*)malloc(buf.second); - to_add.size = buf.second; - memcpy(to_add.value, buf.first, buf.second); - - boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert( - std::make_pair(target, to_add)); - } + dht_immutable_item* f = 0; - dht_immutable_item& f = i->second; + if (!mutable_put) + { + dht_immutable_table_t::iterator i = m_immutable_table.find(target); + if (i == m_immutable_table.end()) + { + // make sure we don't add too many items + if (int(m_immutable_table.size()) >= m_settings.max_dht_items) + { + // delete the least important one (i.e. the one + // the fewest peers are announcing) + dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin() + , m_immutable_table.end() + , boost::bind(&dht_immutable_item::num_announcers + , boost::bind(&dht_immutable_table_t::value_type::second, _1))); + TORRENT_ASSERT(j != m_immutable_table.end()); + free(j->second.value); + m_immutable_table.erase(j); + } + dht_immutable_item to_add; + to_add.value = (char*)malloc(buf.second); + to_add.size = buf.second; + memcpy(to_add.value, buf.first, buf.second); + + boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert( + std::make_pair(target, to_add)); + } + +// fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size())); + + f = &i->second; + } + else + { + // mutable put, we must verify the signature + // generate the message digest by merging the sequence number and the + hasher digest; + char seq[20]; + int len = snprintf(seq, sizeof(seq), "3:seqi%"PRId64"e1:v", msg_keys[2]->int_value()); + digest.update(seq, len); + std::pair buf = msg_keys[1]->data_section(); + digest.update(buf.first, buf.second); + + if (!verify_rsa(digest.final(), msg_keys[3]->string_ptr(), msg_keys[3]->string_length() + , msg_keys[4]->string_ptr(), msg_keys[4]->string_length())) + { + incoming_error(e, "invalid signature"); + return; + } + + rsa_key target; + memcpy(target.bytes, msg_keys[3]->string_ptr(), sizeof(target.bytes)); + dht_mutable_table_t::iterator i = m_mutable_table.find(target); + if (i == m_mutable_table.end()) + { + // make sure we don't add too many items + if (int(m_mutable_table.size()) >= m_settings.max_dht_items) + { + // delete the least important one (i.e. the one + // the fewest peers are announcing) + dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin() + , m_mutable_table.end() + , boost::bind(&dht_immutable_item::num_announcers + , boost::bind(&dht_mutable_table_t::value_type::second, _1))); + TORRENT_ASSERT(j != m_mutable_table.end()); + free(j->second.value); + 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)); + TORRENT_ASSERT(sizeof(to_add.sig) == msg_keys[4]->string_length()); + memcpy(to_add.value, buf.first, buf.second); + + boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert( + std::make_pair(target, to_add)); + +// fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size())); + } + else + { + dht_mutable_item* item = &i->second; + + if (item->seq > msg_keys[2]->int_value()) + { + incoming_error(e, "old sequence number"); + return; + } + + if (item->seq < msg_keys[2]->int_value()) + { + if (item->size != buf.second) + { + free(item->value); + item->value = (char*)malloc(buf.second); + item->size = buf.second; + } + item->seq = msg_keys[2]->int_value(); + memcpy(item->sig, msg_keys[4]->string_ptr(), sizeof(item->sig)); + TORRENT_ASSERT(sizeof(item->sig) == msg_keys[4]->string_length()); + memcpy(item->value, buf.first, buf.second); + } + } + + f = &i->second; + } m_table.node_seen(id, m.addr); - f.last_seen = time_now(); + f->last_seen = time_now(); // maybe increase num_announcers if we haven't seen this IP before sha1_hash iphash; hash_address(m.addr.address(), iphash); - if (!f.ips.find(iphash)) + if (!f->ips.find(iphash)) { - f.ips.set(iphash); - ++f.num_announcers; + f->ips.set(iphash); + ++f->num_announcers; } } else if (strcmp(query, "get") == 0) { key_desc_t msg_desc[] = { {"target", lazy_entry::string_t, 20, 0}, - {"k", lazy_entry::string_t, 0, key_desc_t::optional}, + {"k", lazy_entry::string_t, 268-20, key_desc_t::optional}, }; // k is not used for now @@ -875,6 +980,10 @@ void node_impl::incoming_request(msg const& m, entry& e) sha1_hash target(msg_keys[0]->string_ptr()); +// fprintf(stderr, "%s GET target: %s\n" +// , msg_keys[1] ? "mutable":"immutable" +// , to_hex(target.to_string()).c_str()); + reply["token"] = generate_token(m.addr, msg_keys[0]->string_ptr()); nodes_t n; @@ -882,11 +991,28 @@ void node_impl::incoming_request(msg const& m, entry& e) m_table.find_node(target, n, 0); write_nodes_entry(reply, n); - dht_immutable_table_t::iterator i = m_immutable_table.find(target); - if (i != m_immutable_table.end()) + if (msg_keys[1]) { - dht_immutable_item const& f = i->second; - reply["v"] = bdecode(f.value, f.value + f.size); + 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); + 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); + } } } else diff --git a/src/rsa.cpp b/src/rsa.cpp index 5938ec213..45fdca393 100644 --- a/src/rsa.cpp +++ b/src/rsa.cpp @@ -45,7 +45,7 @@ namespace libtorrent { // returns the size of the resulting signature -int sign_rsa(char const* data, int data_len +int sign_rsa(sha1_hash const& digest , char const* private_key, int private_len , char* signature, int sig_len) { @@ -54,6 +54,7 @@ int sign_rsa(char const* data, int data_len RSA* priv = 0; unsigned char const* key = (unsigned char const*)private_key; priv = d2i_RSAPrivateKey(&priv, &key, private_len); + if (priv == 0) return -1; if (RSA_size(priv) > sig_len) { @@ -61,9 +62,6 @@ int sign_rsa(char const* data, int data_len return -1; } - // hash the data - sha1_hash digest = hasher(data, data_len).final(); - RSA_sign(NID_sha1, &digest[0], 20, (unsigned char*)signature, (unsigned int*)&sig_len, priv); RSA_free(priv); @@ -72,7 +70,7 @@ int sign_rsa(char const* data, int data_len } // returns true if the signature is valid -bool verify_rsa(char const* data, int data_len +bool verify_rsa(sha1_hash const& digest , char const* public_key, int public_len , char const* signature, int sig_len) { @@ -81,9 +79,7 @@ bool verify_rsa(char const* data, int data_len RSA* pub = 0; unsigned char const* key = (unsigned char const*)public_key; pub = d2i_RSAPublicKey(&pub, &key, public_len); - - // hash the data - sha1_hash digest = hasher(data, data_len).final(); + if (pub == 0) return false; int ret = RSA_verify(NID_sha1, &digest[0], 20, (unsigned char*)signature, sig_len, pub); diff --git a/test/test_dht.cpp b/test/test_dht.cpp index 230b68fa7..8cba71eda 100644 --- a/test/test_dht.cpp +++ b/test/test_dht.cpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/kademlia/node.hpp" // for verify_message #include "libtorrent/bencode.hpp" #include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/rsa.hpp" // for generate_rsa_keys and sign_rsa #include #include "test.hpp" @@ -75,9 +76,10 @@ static const std::string no; void send_dht_msg(node_impl& node, char const* msg, udp::endpoint const& ep , lazy_entry* reply, char const* t = "10", char const* info_hash = 0 , char const* name = 0, std::string const token = std::string(), int port = 0 - , std::string const target = std::string(), entry const* value = 0 - , std::string const id = std::string() - , bool scrape = false, bool seed = false) + , 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) { // we're about to clear out the backing buffer // for this lazy_entry, so we better clear it now @@ -87,15 +89,18 @@ void send_dht_msg(node_impl& node, char const* msg, udp::endpoint const& ep e["t"] = t; e["y"] = "q"; entry::dictionary_type& a = e["a"].dict(); - a["id"] = id.empty() ? generate_next().to_string() : id; - if (info_hash) a["info_hash"] = info_hash; + a["id"] = generate_next().to_string(); + if (info_hash) a["info_hash"] = std::string(info_hash, 20); if (name) a["n"] = name; if (!token.empty()) a["token"] = token; if (port) a["port"] = port; - if (!target.empty()) a["target"] = target; + if (target) a["target"] = std::string(target, 20); if (value) a["v"] = *value; + if (!sig.empty()) a["sig"] = sig; + if (!key.empty()) a["k"] = key; if (scrape) a["scrape"] = 1; if (seed) a["seed"] = 1; + if (seq >= 0) a["seq"] = seq; char msg_buf[1500]; int size = bencode(msg_buf, e); // std::cerr << "sending: " << e << "\n"; @@ -135,6 +140,7 @@ struct announce_item sha1_hash target; void gen() { + num_peers = (rand() % 5) + 1; ent["next"] = next.to_string(); ent["A"] = "a"; ent["B"] = "b"; @@ -147,10 +153,10 @@ struct announce_item } }; -void announce_items(node_impl& node, udp::endpoint const* eps - , node_id const* ids, announce_item const* items, int num_items) +void announce_immutable_items(node_impl& node, udp::endpoint const* eps + , announce_item const* items, int num_items) { - std::string tokens[1000]; + std::string token; for (int i = 0; i < 1000; ++i) { for (int j = 0; j < num_items; ++j) @@ -158,7 +164,7 @@ void announce_items(node_impl& node, udp::endpoint const* eps if ((i % items[j].num_peers) == 0) continue; lazy_entry response; send_dht_msg(node, "get", eps[i], &response, "10", 0 - , 0, no, 0, items[j].target.to_string()); + , 0, no, 0, (char const*)&items[j].target[0]); key_desc_t desc[] = { @@ -172,12 +178,12 @@ void announce_items(node_impl& node, udp::endpoint const* eps lazy_entry const* parsed[5]; char error_string[200]; - fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); +// fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); int ret = verify_message(&response, desc, parsed, 5, error_string, sizeof(error_string)); if (ret) { TEST_EQUAL(parsed[4]->string_value(), "r"); - tokens[i] = parsed[2]->string_value(); + token = parsed[2]->string_value(); } else { @@ -194,8 +200,7 @@ void announce_items(node_impl& node, udp::endpoint const* eps } send_dht_msg(node, "put", eps[i], &response, "10", 0 - , 0, tokens[i], 0, items[j].target.to_string(), &items[j].ent - , ids[i].to_string()); + , 0, token, 0, (char const*)&items[j].target[0], &items[j].ent); key_desc_t desc2[] = { @@ -220,35 +225,24 @@ void announce_items(node_impl& node, udp::endpoint const* eps for (int j = 0; j < num_items; ++j) { lazy_entry response; - send_dht_msg(node, "get", eps[0], &response, "10", 0 - , 0, no, 0, items[j].target.to_string(), 0 - , ids[0].to_string()); + send_dht_msg(node, "get", eps[j], &response, "10", 0 + , 0, no, 0, (char const*)&items[j].target[0]); key_desc_t desc[] = { { "r", lazy_entry::dict_t, 0, key_desc_t::parse_children }, - { "v", lazy_entry::dict_t, 0, key_desc_t::parse_children}, - { "A", lazy_entry::string_t, 1, 0}, - { "B", lazy_entry::string_t, 1, 0}, - { "num_peers", lazy_entry::int_t, 0, key_desc_t::last_child}, + { "v", lazy_entry::dict_t, 0, 0}, { "id", lazy_entry::string_t, 20, key_desc_t::last_child}, { "y", lazy_entry::string_t, 1, 0}, }; - lazy_entry const* parsed[7]; + lazy_entry const* parsed[4]; char error_string[200]; - int ret = verify_message(&response, desc, parsed, 7, error_string, sizeof(error_string)); + int ret = verify_message(&response, desc, parsed, 4, error_string, sizeof(error_string)); if (ret) { - TEST_EQUAL(parsed[6]->string_value(), "r"); - TEST_EQUAL(parsed[2]->string_value(), "a"); - TEST_EQUAL(parsed[3]->string_value(), "b"); - items_num.insert(items_num.begin(), parsed[4]->int_value()); - } - else - { - fprintf(stderr, "unexpected msg: %s\n", print_entry(response).c_str()); + items_num.insert(items_num.begin(), j); } } @@ -401,14 +395,14 @@ int test_main() } response.clear(); send_dht_msg(node, "announce_peer", source, &response, "10", "01010101010101010101" - , "test", token, 8080, std::string(), 0, std::string(), false, i >= 50); + , "test", token, 8080, 0, 0, false, i >= 50); response.clear(); } // ====== get_peers ====== send_dht_msg(node, "get_peers", source, &response, "10", "01010101010101010101" - , 0, std::string(), 0, std::string(), 0, std::string(), true); + , 0, no, 0, 0, 0, true); dht::key_desc_t peer2_desc[] = { {"y", lazy_entry::string_t, 1, 0}, @@ -463,6 +457,8 @@ int test_main() test.set(iphash); } + // these are test vectors from BEP 33 + // http://www.bittorrent.org/beps/bep_0033.html fprintf(stderr, "test.size: %f\n", test.size()); TEST_CHECK(fabs(test.size() - 1224.93f) < 0.001); fprintf(stderr, "%s\n", to_hex(test.to_string()).c_str()); @@ -473,13 +469,9 @@ int test_main() // ====== put ====== udp::endpoint eps[1000]; - node_id ids[1000]; for (int i = 0; i < 1000; ++i) - { eps[i] = udp::endpoint(rand_v4(), (rand() % 16534) + 1); - ids[i] = generate_next(); - } announce_item items[] = { @@ -496,7 +488,80 @@ int test_main() for (int i = 0; i < sizeof(items)/sizeof(items[0]); ++i) items[i].gen(); - announce_items(node, eps, ids, items, sizeof(items)/sizeof(items[0])); + announce_immutable_items(node, eps, items, sizeof(items)/sizeof(items[0])); + + // ==== get / put mutable items === + + char private_key[1192]; + int private_len = sizeof(private_key); + char public_key[268]; + int public_len = sizeof(public_key); + + fprintf(stderr, "generating RSA keys\n"); + ret = generate_rsa_keys(public_key, &public_len, private_key, &private_len, 2048); + fprintf(stderr, "pub: %d priv:%d\n", public_len, private_len); + + TEST_CHECK(ret); + + send_dht_msg(node, "get", source, &response, "10", 0 + , 0, no, 0, public_key, 0, false, false, std::string(public_key + 20, public_len-20)); + + 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(); + } + else + { + fprintf(stderr, " invalid get response: %s\n%s\n" + , error_string, print_entry(response).c_str()); + TEST_ERROR(error_string); + } + + char signature[256]; + int sig_len = sizeof(signature); + char buffer[1024]; + int seq = 4; + int pos = snprintf(buffer, sizeof(buffer), "3:seqi%de1:v", seq); + hasher h(buffer, pos); + char* ptr = buffer; + int len = bencode(ptr, items[0].ent); + h.update(buffer, len); + sign_rsa(h.final(), private_key, private_len, signature, sig_len); + + send_dht_msg(node, "put", source, &response, "10", 0 + , 0, token, 0, 0, &items[0].ent, false, false + , std::string(public_key, public_len) + , std::string(signature, 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); + } return 0; } diff --git a/test/test_primitives.cpp b/test/test_primitives.cpp index 3983d14c8..35231aff8 100644 --- a/test/test_primitives.cpp +++ b/test/test_primitives.cpp @@ -397,12 +397,13 @@ int test_main() #if defined TORRENT_USE_OPENSSL // test sign_rsa and verify_rsa - char private_key[256]; + char private_key[1192]; int private_len = sizeof(private_key); - char public_key[512]; + char public_key[268]; int public_len = sizeof(public_key); ret = generate_rsa_keys(public_key, &public_len, private_key, &private_len, 2048); + fprintf(stderr, "keysizes: pub: %d priv: %d\n", public_len, private_len); TEST_CHECK(ret); @@ -410,12 +411,13 @@ int test_main() std::generate(test_message, test_message + 1024, &std::rand); char signature[256]; - int sig_len = sign_rsa(test_message, sizeof(test_message) + int sig_len = sign_rsa(hasher(test_message, sizeof(test_message)).final() , private_key, private_len, signature, sizeof(signature)); TEST_CHECK(sig_len == 256); - ret = verify_rsa(test_message, sizeof(test_message), public_key, public_len, signature, sig_len); + ret = verify_rsa(hasher(test_message, sizeof(test_message)).final() + , public_key, public_len, signature, sig_len); TEST_CHECK(ret == 1); #endif