diff --git a/docs/manual.html b/docs/manual.html index b0fcd57bd..ce6e6e076 100644 --- a/docs/manual.html +++ b/docs/manual.html @@ -339,7 +339,10 @@
  • prefer whole pieces
  • -
  • SSL torrents
  • +
  • SSL torrents +
  • @@ -8684,7 +8687,7 @@ as much space as has been downloaded.

    full allocation

    -

    When a torrent is started in full allocation mode, the disk-io thread (see threads_) +

    When a torrent is started in full allocation mode, the disk-io thread (see threads_) will make sure that the entire storage is allocated, and fill any gaps with zeros. This will be skipped if the filesystem supports sparse files or automatic zero filling. It will of course still check for existing pieces and fast resume data. The main @@ -9078,6 +9081,14 @@ different port. It defaults to port 4433. This setting is only taken into accoun normal listen socket is opened (i.e. just changing this setting won't necessarily close and re-open the SSL socket). To not listen on an SSL socket at all, set ssl_listen to 0.

    This feature is only available if libtorrent is build with openssl support (TORRENT_USE_OPENSSL).

    +

    Peer certificates must have at least one SubjectAltName field of type dNSName. At least +one of the fields must exactly match the name of the torrent. This is a byte-by-byte comparison, +the UTF-8 encoding must be identical (i.e. there's no unicode normalization going on). This +the recommended way of verifying certificates for HTTPS servers according to RFC 2818. Note +the difference that for torrents only dNSName fields are taken into account (not IP address fields) +and that only SubjectAltNames are taken into account, not the Common Name fields.

    +
    +

    testing

    To test incoming SSL connections to an SSL torrent, one can use the following openssl command:

     openssl s_client -cert <peer-certificate>.pem -key <peer-private-key>.pem -CAfile <torrent-cert>.pem -debug -connect 127.0.0.1:4433 -tls1 -servername <info-hash>
    @@ -9097,10 +9108,11 @@ the pem file to include in the .torrent file.

    The peer's certificate is located in ./newcert.pem and the certificate's private key in ./newkey.pem.

    +

    Docutils System Messages

    -
    -

    System Message: ERROR/3 (manual.rst, line 8686); backlink

    +
    +

    System Message: ERROR/3 (manual.rst, line 8686); backlink

    Unknown target name: "threads".
    diff --git a/docs/manual.rst b/docs/manual.rst index ab9588f14..0dab501ed 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -9057,7 +9057,29 @@ different port. It defaults to port 4433. This setting is only taken into accoun normal listen socket is opened (i.e. just changing this setting won't necessarily close and re-open the SSL socket). To not listen on an SSL socket at all, set ``ssl_listen`` to 0. -This feature is only available if libtorrent is build with openssl support (``TORRENT_USE_OPENSSL``). +This feature is only available if libtorrent is build with openssl support (``TORRENT_USE_OPENSSL``) +and requires at least openSSL version 1.0, since it needs SNI support. + +Peer certificates must have at least one *SubjectAltName* field of type dNSName. At least +one of the fields must *exactly* match the name of the torrent. This is a byte-by-byte comparison, +the UTF-8 encoding must be identical (i.e. there's no unicode normalization going on). This is +the recommended way of verifying certificates for HTTPS servers according to `RFC 2818`_. Note +the difference that for torrents only *dNSName* fields are taken into account (not IP address fields). +The most specific (i.e. last) *Common Name* field is also taken into account if no *SubjectAltName* +did not match. + +If any of these fields contain a single asterisk ("*"), the certificate is considered covering +any torrent, allowing it to be reused for any torrent. + +The purpose of matching the torrent name with the fields in the peer certificate is to allow +a publisher to have a single root certificate for all torrents it distributes, and issue +separate peer certificates for each torrent. A peer receiving a certificate will not necessarily +be able to access all torrents published by this root certificate (only if it has a "star cert"). + +.. _`RFC 2818`: http://www.ietf.org/rfc/rfc2818.txt + +testing +------- To test incoming SSL connections to an SSL torrent, one can use the following *openssl* command:: diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 424d2b492..e4334c9e3 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -1098,6 +1098,7 @@ int main(int argc, char* argv[]) " -S limits the upload slots\n" " -A allowed pieces set size\n" " -H Don't start DHT\n" + " -X Don't start local peer discovery\n" " -n announce to trackers in all tiers\n" " -W Set the max number of peers to keep in the peer list\n" " -B sets the peer timeout\n" @@ -1159,6 +1160,7 @@ int main(int argc, char* argv[]) int refresh_delay = 1000; bool start_dht = true; bool start_upnp = true; + bool start_lsd = true; int loop_limit = 0; std::deque events; @@ -1364,6 +1366,7 @@ int main(int argc, char* argv[]) break; case 'I': outgoing_interface = arg; break; case 'N': start_upnp = false; --i; break; + case 'X': start_lsd = false; --i; break; case 'Y': settings.ignore_limits_on_local_network = false; --i; break; case 'v': settings.active_downloads = atoi(arg); settings.active_limit = (std::max)(atoi(arg) * 2, settings.active_limit); @@ -1383,7 +1386,9 @@ int main(int argc, char* argv[]) if (ec) fprintf(stderr, "failed to create resume file directory: %s\n", ec.message().c_str()); - ses.start_lsd(); + if (start_lsd) + ses.start_lsd(); + if (start_upnp) { ses.start_upnp(); diff --git a/include/libtorrent/socket_type.hpp b/include/libtorrent/socket_type.hpp index 4cd3aea6b..40f98426d 100644 --- a/include/libtorrent/socket_type.hpp +++ b/include/libtorrent/socket_type.hpp @@ -181,6 +181,8 @@ namespace libtorrent io_service& get_io_service() const; bool is_open() const; + char const* type_name() const; + #ifndef BOOST_NO_EXCEPTIONS void open(protocol_type const& p); void close(); @@ -196,7 +198,7 @@ namespace libtorrent endpoint_type remote_endpoint(error_code& ec) const; void bind(endpoint_type const& endpoint, error_code& ec); std::size_t available(error_code& ec) const; - int type(); + int type() const; template @@ -292,6 +294,9 @@ namespace libtorrent size_type m_data[(storage_size + sizeof(size_type) - 1) / sizeof(size_type)]; }; + + bool is_ssl(socket_type const& s); + } #endif diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index a563dec3b..560a203cf 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -947,6 +947,7 @@ namespace libtorrent #ifdef TORRENT_USE_OPENSSL boost::shared_ptr m_ssl_ctx; + bool verify_peer_cert(bool preverified, boost::asio::ssl::verify_context& ctx); void init_ssl(std::string const& cert); #endif diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index 408d4751d..ecf1e0d36 100644 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -229,7 +229,7 @@ namespace libtorrent if (key.size() == 32) out_enc_policy = pe_settings::disabled; // never try an encrypted connection when already using SSL - if (get_socket()->get >() || get_socket()->get >()) + if (is_ssl(*get_socket())) out_enc_policy = pe_settings::disabled; #endif #ifdef TORRENT_VERBOSE_LOGGING @@ -2946,8 +2946,7 @@ namespace libtorrent #endif #ifdef TORRENT_USE_OPENSSL - if (get_socket()->get >() - || get_socket()->get >()) + if (is_ssl(*get_socket())) { #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** SSL peers are not allowed to use any other encryption"); @@ -3030,9 +3029,10 @@ namespace libtorrent #ifndef TORRENT_DISABLE_ENCRYPTION TORRENT_ASSERT(m_state != read_pe_dhkey); - if (!is_local() && - (m_ses.get_pe_settings().in_enc_policy == pe_settings::forced) && - !m_encrypted) + if (!is_local() + && m_ses.get_pe_settings().in_enc_policy == pe_settings::forced + && !m_encrypted + && !is_ssl(*get_socket())) { disconnect(errors::no_incoming_regular); return; diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 251227752..c6a9fbdaf 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -235,16 +235,12 @@ namespace libtorrent error_code ec; m_logger = m_ses.create_log(m_remote.address().to_string(ec) + "_" + to_string(m_remote.port()).elems, m_ses.listen_port()); - peer_log(">>> %s [ ep: %s transport: %s seed: %d p: %p ]" - , outgoing ? "OUTGOING_CONNECTION" : "INCOMING CONNECTION" + peer_log("%s [ ep: %s type: %s seed: %d p: %p local: %s]" + , outgoing ? ">>> OUTGOING_CONNECTION" : "<<< INCOMING CONNECTION" , print_endpoint(m_remote).c_str() - , -#ifdef TORRENT_USE_OPENSSL - m_socket->get >() ? "SSL/TCP" : - m_socket->get >() ? "SSL/uTP" : -#endif - m_socket->get() ? "uTP" : "TCP" - , m_peer_info ? m_peer_info->seed : 0, m_peer_info); + , m_socket->type_name() + , m_peer_info ? m_peer_info->seed : 0, m_peer_info + , print_endpoint(m_socket->local_endpoint(ec)).c_str()); #endif #ifdef TORRENT_DEBUG piece_failed = false; @@ -387,10 +383,11 @@ namespace libtorrent TORRENT_ASSERT(m_socket->remote_endpoint(ec) == m_remote || ec); m_logger = m_ses.create_log(remote().address().to_string(ec) + "_" + to_string(remote().port()).elems, m_ses.listen_port()); - peer_log("<<< %s [ ep: %s transport: %s ]" - , outgoing ? "OUTGOING_CONNECTION" : "INCOMING CONNECTION" + peer_log("%s [ ep: %s type: %s local: %s]" + , outgoing ? ">>> OUTGOING_CONNECTION" : "<<< INCOMING CONNECTION" , print_endpoint(m_remote).c_str() - , (m_socket->get()) ? "uTP connection" : "TCP connection"); + , m_socket->type_name() + , print_endpoint(m_socket->local_endpoint(ec)).c_str()); #endif #ifndef TORRENT_DISABLE_GEO_IP @@ -5379,7 +5376,7 @@ namespace libtorrent #endif m_socket->async_connect(m_remote , boost::bind(&peer_connection::on_connection_complete, self(), _1)); - m_connect = time_now(); + m_connect = time_now_hires(); m_statistics.sent_syn(m_remote.address().is_v6()); if (t->alerts().should_post()) @@ -5387,6 +5384,9 @@ namespace libtorrent t->alerts().post_alert(peer_connect_alert( t->get_handle(), remote(), pid())); } +#if defined TORRENT_VERBOSE_LOGGING + peer_log("*** LOCAL ENDPOINT[ e: %s ]", print_endpoint(m_socket->local_endpoint(ec)).c_str()); +#endif } void peer_connection::on_connection_complete(error_code const& e) @@ -5394,13 +5394,20 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("peer_connection::on_connection_complete"); #endif - ptime completed = time_now(); + ptime completed = time_now_hires(); TORRENT_ASSERT(m_ses.is_network_thread()); INVARIANT_CHECK; m_rtt = total_milliseconds(completed - m_connect); + +#ifdef TORRENT_USE_OPENSSL + // add this RTT to the PRNG seed, to add more unpredictability + boost::uint64_t now = total_microseconds(completed - m_connect); + // assume 12 bits of entropy (i.e. about 8 milliseconds) + RAND_add(&now, 8, 1.5); +#endif if (m_disconnecting) return; diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 950136b61..9cd4ba6a7 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -2538,7 +2538,7 @@ namespace aux { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " *** peer SSL handshake done [ ip: " - << endp << " ec: " << ec.message() << "]\n"; + << endp << " ec: " << ec.message() << " socket: " << s->type_name() << "]\n"; #endif if (ec) @@ -2560,6 +2560,13 @@ namespace aux { { TORRENT_ASSERT(is_network_thread()); +#ifdef TORRENT_USE_OPENSSL + // add the current time to the PRNG, to add more unpredictability + boost::uint64_t now = total_microseconds(time_now_hires() - min_time()); + // assume 12 bits of entropy (i.e. about 8 milliseconds) + RAND_add(&now, 8, 1.5); +#endif + if (m_paused) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -2584,7 +2591,8 @@ namespace aux { TORRENT_ASSERT(endp.address() != address_v4::any()); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " <== INCOMING CONNECTION " << endp << "\n"; + (*m_logger) << time_now_string() << " <== INCOMING CONNECTION " << endp + << " type: " << s->type_name() << "\n"; #endif if (m_alerts.should_post()) diff --git a/src/socket_type.cpp b/src/socket_type.cpp index 4fbd34147..7bfdf0a6a 100644 --- a/src/socket_type.cpp +++ b/src/socket_type.cpp @@ -39,6 +39,25 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + bool is_ssl(socket_type const& s) + { +#ifdef TORRENT_USE_OPENSSL +#define CASE(t) case socket_type_int_impl >::value: + switch (s.type()) + { + CASE(stream_socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + return true; + default: return false; + }; +#undef CASE +#else + return false; +#endif + } + void socket_type::destruct() { switch (m_type) @@ -131,6 +150,32 @@ namespace libtorrent m_type = type; } + char const* socket_type::type_name() const + { + static char const* const names[] = + { + "uninitialized", + "TCP", + "Socks5", + "HTTP", + "uTP", +#if TORRENT_USE_I2P + "I2P", +#else + "", +#endif +#ifdef TORRENT_USE_OPENSSL + "SSL/TCP", + "SSL/Socks5", + "SSL/HTTP", + "SSL/uTP" +#else + "","","","" +#endif + }; + return names[m_type]; + } + io_service& socket_type::get_io_service() const { return m_io_service; } @@ -164,7 +209,7 @@ namespace libtorrent std::size_t socket_type::available(error_code& ec) const { TORRENT_SOCKTYPE_FORWARD_RET(available(ec), 0) } - int socket_type::type() { return m_type; } + int socket_type::type() const { return m_type; } #ifndef BOOST_NO_EXCEPTIONS void socket_type::open(protocol_type const& p) diff --git a/src/torrent.cpp b/src/torrent.cpp index 74b46966c..bbcd042b2 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -1285,12 +1285,92 @@ namespace libtorrent #endif #ifdef TORRENT_USE_OPENSSL -/* - bool verify_function(bool preverified, boost::asio::ssl::verify_context& ctx) + + bool torrent::verify_peer_cert(bool preverified, boost::asio::ssl::verify_context& ctx) { - return true; + // if the cert wasn't signed by the correct CA, fail the verification + if (!preverified) return false; + + // we're only interested in checking the certificate at the end of the chain. + int depth = X509_STORE_CTX_get_error_depth(ctx.native_handle()); + if (depth > 0) return true; + + X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + + // Go through the alternate names in the certificate looking for matching DNS entries + GENERAL_NAMES* gens = static_cast( + X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0)); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::string names; + bool match = false; +#endif + for (int i = 0; i < sk_GENERAL_NAME_num(gens); ++i) + { + GENERAL_NAME* gen = sk_GENERAL_NAME_value(gens, i); + if (gen->type != GEN_DNS) continue; + ASN1_IA5STRING* domain = gen->d.dNSName; + if (domain->type != V_ASN1_IA5STRING || !domain->data || !domain->length) continue; + const char* torrent_name = reinterpret_cast(domain->data); + std::size_t name_length = domain->length; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (i > 1) names += " | n: "; + names.append(torrent_name, name_length); +#endif + if (strncmp(torrent_name, "*", name_length) == 0 + || strncmp(torrent_name, m_torrent_file->name().c_str(), name_length) == 0) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + match = true; + // if we're logging, keep looping over all names, + // for completeness of the log + continue; +#endif + return true; + } + } + + // no match in the alternate names, so try the common names. We should only + // use the "most specific" common name, which is the last one in the list. + X509_NAME* name = X509_get_subject_name(cert); + int i = -1; + ASN1_STRING* common_name = 0; + while ((i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0) + { + X509_NAME_ENTRY* name_entry = X509_NAME_get_entry(name, i); + common_name = X509_NAME_ENTRY_get_data(name_entry); + } + if (common_name && common_name->data && common_name->length) + { + const char* torrent_name = reinterpret_cast(common_name->data); + std::size_t name_length = common_name->length; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (!names.empty()) names += " | n: "; + names.append(torrent_name, name_length); +#endif + + if (strncmp(torrent_name, "*", name_length) == 0 + || strncmp(torrent_name, m_torrent_file->name().c_str(), name_length) == 0) + { +#if !defined(TORRENT_VERBOSE_LOGGING) && !defined(TORRENT_LOGGING) + return true; +#else + match = true; +#endif + } + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << time_now_string() << " <== INCOMING SSL CONNECTION [ torrent: " + << m_torrent_file->name() << " | n: " << names << " | match: " << (match?"yes":"no") + << " ]\n"; + return match; +#endif + + return false; } -*/ void torrent::init_ssl(std::string const& cert) { @@ -1299,8 +1379,12 @@ namespace libtorrent // this is needed for openssl < 1.0 to decrypt keys created by openssl 1.0+ OpenSSL_add_all_algorithms(); - // TODO: come up with something better - RAND_seed(&info_hash()[0], 20); + boost::uint64_t now = total_microseconds(time_now_hires() - min_time()); + // assume 9 bits of entropy (i.e. about 1 millisecond) + RAND_add(&now, 8, 1.125); + RAND_add(&info_hash()[0], 20, 3); + // entropy is also added on incoming and completed connection attempts + TORRENT_ASSERT(RAND_status() == 1); // create the SSL context for this torrent. We need to @@ -1331,17 +1415,17 @@ namespace libtorrent return; } - // this is used for debugging - /* -#error there's a bug where the async_handshake on the ssl_stream always succeeds, regardless of the certificate failing. It's not a trivial bug in asio, that's been tested with a small repro program. - ctx->set_verify_callback(verify_function, ec); + // the verification function verifies the distinguished name + // of a peer certificate to make sure it matches the info-hash + // of the torrent, or that it's a "star-cert" + ctx->set_verify_callback(boost::bind(&torrent::verify_peer_cert, this, _1, _2), ec); if (ec) { set_error(ec, "SSL verify callback"); pause(); return; } - */ + SSL_CTX* ssl_ctx = ctx->impl(); // create a new x.509 certificate store X509_STORE* cert_store = X509_STORE_new();