diff --git a/examples/make_torrent.cpp b/examples/make_torrent.cpp index d6a43c676..ff1bda9fa 100644 --- a/examples/make_torrent.cpp +++ b/examples/make_torrent.cpp @@ -85,6 +85,8 @@ void print_usage() " If this is not specified, the torrent file is\n" " printed to the standard out, except on windows\n" " where the filename defaults to a.torrent\n" + "-c add root certificate to the torrent, to make\n" + " it an SSL torrent\n" , stderr); } @@ -109,6 +111,7 @@ int main(int argc, char* argv[]) int pad_file_limit = -1; int piece_size = 0; int flags = 0; + std::string root_cert; std::string outfile; std::string merklefile; @@ -160,6 +163,10 @@ int main(int argc, char* argv[]) case 'l': flags |= create_torrent::symlinks; break; + case 'c': + ++i; + root_cert = argv[i]; + break; default: print_usage(); return 1; @@ -198,6 +205,20 @@ int main(int argc, char* argv[]) fprintf(stderr, "\n"); t.set_creator(creator_str); + if (!root_cert.empty()) + { + FILE* cert = fopen(root_cert.c_str(), "rb"); + if (cert) + { + std::string pem; + pem.resize(5000); + int s = fread(&pem[0], 1, pem.size(), cert); + pem.resize(s); + t.set_root_cert(pem); + fclose(cert); + } + } + // create the torrent and print it to stdout std::vector torrent; bencode(back_inserter(torrent), t.generate()); diff --git a/include/libtorrent/create_torrent.hpp b/include/libtorrent/create_torrent.hpp index 14e020dc9..a7412665c 100644 --- a/include/libtorrent/create_torrent.hpp +++ b/include/libtorrent/create_torrent.hpp @@ -89,6 +89,7 @@ namespace libtorrent void add_http_seed(std::string const& url); void add_node(std::pair const& node); void add_tracker(std::string const& url, int tier = 0); + void set_root_cert(std::string const& pem); void set_priv(bool p) { m_private = p; } int num_pieces() const { return m_files.num_pieces(); } @@ -145,6 +146,9 @@ namespace libtorrent // to create the torrent file std::string m_created_by; + // this is the root cert for SSL torrents + std::string m_root_cert; + // this is used when creating a torrent. If there's // only one file there are cases where it's impossible // to know if it should be written as a multifile torrent diff --git a/include/libtorrent/http_connection.hpp b/include/libtorrent/http_connection.hpp index dfbd5ad47..3c3b4cd57 100644 --- a/include/libtorrent/http_connection.hpp +++ b/include/libtorrent/http_connection.hpp @@ -78,40 +78,13 @@ struct TORRENT_EXPORT http_connection : boost::enable_shared_from_this m_endpoints; #ifdef TORRENT_USE_OPENSSL - asio::ssl::context m_ssl_ctx; + asio::ssl::context* m_ssl_ctx; + bool m_own_ssl_context; #endif // the current download limit, in bytes per second diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 277c4f21e..f4f947b1d 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -921,6 +921,10 @@ namespace libtorrent // the object. piece_manager* m_storage; +#ifdef TORRENT_USE_OPENSSL + boost::shared_ptr m_ssl_ctx; +#endif + #ifdef TORRENT_DEBUG public: #endif diff --git a/include/libtorrent/torrent_info.hpp b/include/libtorrent/torrent_info.hpp index 8e556c26d..4dc222f0e 100644 --- a/include/libtorrent/torrent_info.hpp +++ b/include/libtorrent/torrent_info.hpp @@ -333,6 +333,8 @@ namespace libtorrent // ------- end deprecation ------- #endif + std::string const& ssl_cert() const { return m_ssl_root_cert; } + bool is_valid() const { return m_files.is_valid(); } bool priv() const { return m_private; } @@ -456,6 +458,11 @@ namespace libtorrent // to create the torrent file std::string m_created_by; + // for ssl-torrens, this contains the root + // certificate, in .pem format (i.e. ascii + // base64 encoded with head and tails) + std::string m_ssl_root_cert; + // the info section parsed. points into m_info_section // parsed lazily mutable lazy_entry m_info_dict; diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp index 3a0621fe4..8252cd50e 100644 --- a/include/libtorrent/tracker_manager.hpp +++ b/include/libtorrent/tracker_manager.hpp @@ -63,6 +63,9 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/intrusive_ptr_base.hpp" #include "libtorrent/size_type.hpp" #include "libtorrent/union_endpoint.hpp" +#ifdef TORRENT_USE_OPENSSL +#include +#endif namespace libtorrent { @@ -90,6 +93,9 @@ namespace libtorrent , num_want(0) , send_stats(true) , apply_ip_filter(true) +#ifdef TORRENT_USE_OPENSSL + , ssl_ctx(0) +#endif {} enum @@ -125,6 +131,9 @@ namespace libtorrent address bind_ip; bool send_stats; bool apply_ip_filter; +#ifdef TORRENT_USE_OPENSSL + boost::asio::ssl::context* ssl_ctx; +#endif }; struct TORRENT_EXPORT request_callback diff --git a/src/create_torrent.cpp b/src/create_torrent.cpp index bb6f51a71..43c53be96 100644 --- a/src/create_torrent.cpp +++ b/src/create_torrent.cpp @@ -298,6 +298,9 @@ namespace libtorrent info["name"] = m_files.name(); + if (!m_root_cert.empty()) + info["ssl-cert"] = m_root_cert; + if (m_private) info["private"] = 1; if (!m_multifile) @@ -446,6 +449,11 @@ namespace libtorrent , boost::bind(&announce_entry::second, _1) < boost::bind(&announce_entry::second, _2)); } + void create_torrent::set_root_cert(std::string const& cert) + { + m_root_cert = cert; + } + void create_torrent::set_hash(int index, sha1_hash const& h) { TORRENT_ASSERT(index >= 0); diff --git a/src/http_connection.cpp b/src/http_connection.cpp index 1595c9386..1d829b243 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -50,6 +50,52 @@ namespace libtorrent { enum { max_bottled_buffer = 1024 * 1024 }; +http_connection::http_connection(io_service& ios, connection_queue& cc + , http_handler const& handler, bool bottled + , http_connect_handler const& ch + , http_filter_handler const& fh +#ifdef TORRENT_USE_OPENSSL + , boost::asio::ssl::context* ssl_ctx +#endif + ) + : m_sock(ios) +#if TORRENT_USE_I2P + , m_i2p_conn(0) +#endif + , m_read_pos(0) + , m_resolver(ios) + , m_handler(handler) + , m_connect_handler(ch) + , m_filter_handler(fh) + , m_timer(ios) + , m_last_receive(time_now()) + , m_bottled(bottled) + , m_called(false) +#ifdef TORRENT_USE_OPENSSL + , m_ssl_ctx(ssl_ctx) + , m_own_ssl_context(false) +#endif + , m_rate_limit(0) + , m_download_quota(0) + , m_limiter_timer_active(false) + , m_limiter_timer(ios) + , m_redirects(5) + , m_connection_ticket(-1) + , m_cc(cc) + , m_ssl(false) + , m_priority(0) + , m_abort(false) +{ + TORRENT_ASSERT(!m_handler.empty()); +} + +http_connection::~http_connection() +{ +#ifdef TORRENT_USE_OPENSSL + if (m_own_ssl_context) delete m_ssl_ctx; +#endif +} + void http_connection::get(std::string const& url, time_duration timeout, int prio , proxy_settings const* ps, int handle_redirects, std::string const& user_agent , address const& bind_addr @@ -250,7 +296,20 @@ void http_connection::start(std::string const& hostname, std::string const& port void* userdata = 0; #ifdef TORRENT_USE_OPENSSL - if (m_ssl) userdata = &m_ssl_ctx; + if (m_ssl) + { + if (m_ssl_ctx == 0) + { + m_ssl_ctx = new boost::asio::ssl::context(m_resolver.get_io_service(), asio::ssl::context::sslv23_client); + if (m_ssl_ctx) + { + m_own_ssl_context = true; + error_code ec; + m_ssl_ctx->set_verify_mode(asio::ssl::context::verify_none, ec); + } + } + userdata = m_ssl_ctx; + } #endif instantiate_connection(m_resolver.get_io_service() , proxy ? *proxy : null_proxy, m_sock, userdata); diff --git a/src/http_tracker_connection.cpp b/src/http_tracker_connection.cpp index a6fe5189e..102acafc9 100644 --- a/src/http_tracker_connection.cpp +++ b/src/http_tracker_connection.cpp @@ -216,7 +216,11 @@ namespace libtorrent , boost::bind(&http_tracker_connection::on_response, self(), _1, _2, _3, _4) , true , boost::bind(&http_tracker_connection::on_connect, self(), _1) - , boost::bind(&http_tracker_connection::on_filter, self(), _1, _2))); + , boost::bind(&http_tracker_connection::on_filter, self(), _1, _2) +#ifdef TORRENT_USE_OPENSSL + , tracker_req().ssl_ctx +#endif + )); int timeout = tracker_req().event==tracker_request::stopped ?settings.stop_tracker_timeout diff --git a/src/torrent.cpp b/src/torrent.cpp index b1bc2ebf2..2127cf528 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -84,6 +84,8 @@ POSSIBILITY OF SUCH DAMAGE. #ifdef TORRENT_USE_OPENSSL #include "libtorrent/ssl_stream.hpp" +#include +#include #endif #if TORRENT_USE_IOSTREAM @@ -1261,6 +1263,15 @@ namespace libtorrent #endif +/* +#ifdef TORRENT_USE_OPENSSL + bool verify_function(bool preverified, boost::asio::ssl::verify_context& ctx) + { + return false; + } +#endif +*/ + // this may not be called from a constructor because of the call to // shared_from_this() void torrent::init() @@ -1270,6 +1281,98 @@ namespace libtorrent TORRENT_ASSERT(m_torrent_file->num_files() > 0); TORRENT_ASSERT(m_torrent_file->total_size() >= 0); +#ifdef TORRENT_USE_OPENSSL + std::string cert = m_torrent_file->ssl_cert(); + if (!cert.empty()) + { + using boost::asio::ssl::context; + + // create the SSL context for this torrent. We need to + // inject the root certificate, and no other, to + // verify other peers against + boost::shared_ptr ctx( + new (std::nothrow) context(m_ses.m_io_service, context::tlsv1)); + + if (!ctx) + { + set_error(asio::error::no_memory, "SSL context"); + pause(); + return; + } + + error_code ec; + ctx->set_verify_mode(context::verify_peer + | context::verify_fail_if_no_peer_cert, ec); + if (ec) + { + set_error(ec, "SSL context"); + pause(); + return; + } + +// this is used for debugging +/* + ctx->set_verify_callback(verify_function, ec); + if (ec) + { + set_error(ec, "SSL verify callback"); + pause(); + return; + } +*/ + SSL_CTX* ssl_ctx = ctx->impl(); + + // we don't want regular peers to be able to invite others + // by in turn signing new certificates. So, break the verification + // chain at depth 2. This is just a precaution in case the + // issuer of the peer certificates made a mistake and issued them + // as CA certs. + SSL_CTX_set_verify_depth(ssl_ctx, 0); + + // create a new x.509 certificate store + X509_STORE* cert_store = X509_STORE_new(); + if (!cert_store) + { + set_error(asio::error::no_memory, "x.509 certificate store"); + pause(); + return; + } + + // wrap the PEM certificate in a BIO, for openssl to read + BIO* bp = BIO_new_mem_buf((void*)cert.c_str(), cert.size()); + + // parse the certificate into OpenSSL's internal + // representation + X509* certificate = PEM_read_bio_X509_AUX(bp, 0, 0, 0); + + BIO_free(bp); + + if (!certificate) + { + X509_STORE_free(cert_store); + set_error(asio::error::no_memory, "x.509 certificate"); + pause(); + return; + } + + // add cert to cert_store + X509_STORE_add_cert(cert_store, certificate); + + // and lastly, replace the default cert store with ours + SSL_CTX_set_cert_store(ssl_ctx, cert_store); +#if 0 + char filename[100]; + snprintf(filename, sizeof(filename), "/tmp/%d.pem", rand()); + FILE* f = fopen(filename, "w+"); + fwrite(cert.c_str(), cert.size(), 1, f); + fclose(f); + ctx->load_verify_file(filename); +#endif + // if all went well, set the torrent ssl context to this one + m_ssl_ctx = ctx; + } +#endif + m_file_priority.resize(m_torrent_file->num_files(), 1); m_file_progress.resize(m_torrent_file->num_files(), 0); @@ -1941,6 +2044,11 @@ namespace libtorrent req.corrupt = m_total_failed_bytes; req.left = bytes_left(); if (req.left == -1) req.left = 16*1024; +#ifdef TORRENT_USE_OPENSSL + // if this torrent contains an SSL certificate, make sure + // any SSL tracker presents a certificate signed by it + req.ssl_ctx = m_ssl_ctx.get(); +#endif // exclude redundant bytes if we should if (!settings().report_true_downloaded) diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp index 178753cef..2e69bfa22 100644 --- a/src/torrent_info.cpp +++ b/src/torrent_info.cpp @@ -932,6 +932,9 @@ namespace libtorrent } m_private = info.dict_find_int_value("private", 0); + + m_ssl_root_cert = info.dict_find_string_value("ssl-cert"); + return true; }