initial BitTorrent over SSL support

This commit is contained in:
Arvid Norberg
2011-09-12 03:51:49 +00:00
parent 90372b6caf
commit 469414d486
15 changed files with 688 additions and 182 deletions

View File

@@ -561,5 +561,10 @@ namespace libtorrent {
return torrent_alert::message() + " removed";
}
std::string torrent_need_cert_alert::message() const
{
return torrent_alert::message() + " needs SSL certificate";
}
} // namespace libtorrent

View File

@@ -99,9 +99,10 @@ namespace libtorrent
, boost::weak_ptr<torrent> tor
, shared_ptr<socket_type> s
, tcp::endpoint const& remote
, policy::peer* peerinfo)
, policy::peer* peerinfo
, bool outgoing)
: peer_connection(ses, tor, s, remote
, peerinfo)
, peerinfo, outgoing)
, m_state(read_protocol_identifier)
#ifndef TORRENT_DISABLE_EXTENSIONS
, m_upload_only_id(0)
@@ -152,8 +153,9 @@ namespace libtorrent
session_impl& ses
, boost::shared_ptr<socket_type> s
, tcp::endpoint const& remote
, policy::peer* peerinfo)
: peer_connection(ses, s, remote, peerinfo)
, policy::peer* peerinfo
, bool outgoing)
: peer_connection(ses, s, remote, peerinfo, outgoing)
, m_state(read_protocol_identifier)
#ifndef TORRENT_DISABLE_EXTENSIONS
, m_upload_only_id(0)
@@ -225,6 +227,10 @@ namespace libtorrent
boost::shared_ptr<torrent> t = associated_torrent().lock();
std::string const key = t->torrent_file().encryption_key();
if (key.size() == 32) out_enc_policy = pe_settings::disabled;
// never try an encrypted connection when already using SSL
if (get_socket()->get<ssl_stream<stream_socket> >() || get_socket()->get<ssl_stream<utp_stream> >())
out_enc_policy = pe_settings::disabled;
#endif
#ifdef TORRENT_VERBOSE_LOGGING
char const* policy_name[] = {"forced", "enabled", "disabled"};
@@ -2937,6 +2943,18 @@ namespace libtorrent
peer_log("*** unrecognized protocol header");
#endif
#ifdef TORRENT_USE_OPENSSL
if (get_socket()->get<ssl_stream<stream_socket> >()
|| get_socket()->get<ssl_stream<utp_stream> >())
{
#ifdef TORRENT_VERBOSE_LOGGING
peer_log("*** SSL peers are not allowed to use any other encryption");
#endif
disconnect(errors::invalid_info_hash, 1);
return;
}
#endif // TORRENT_USE_OPENSSL
bool found_encrypted_torrent = false;
#ifdef TORRENT_USE_OPENSSL
if (!is_local())
@@ -3078,7 +3096,13 @@ namespace libtorrent
std::copy(recv_buffer.begin + 8, recv_buffer.begin + 28
, (char*)info_hash.begin());
attach_to_torrent(info_hash, m_encrypted && m_rc4_encrypted);
#ifndef TORRENT_DISABLE_ENCRYPTION
bool allow_encrypted = m_encrypted && m_rc4_encrypted;
#else
bool allow_encrypted = true;
#endif
attach_to_torrent(info_hash, allow_encrypted);
if (is_disconnecting()) return;
}
else

View File

@@ -87,6 +87,10 @@ http_connection::http_connection(io_service& ios, connection_queue& cc
, m_abort(false)
{
TORRENT_ASSERT(!m_handler.empty());
// TODO: if we were handed an SSL context, we should really
// verify the hostname of the web server as well. This is supported
// in boost starting with version 1.47.0. See ssl::rfc2818_verification
// and ssl::context::set_verify_callback
}
http_connection::~http_connection()

View File

@@ -79,7 +79,8 @@ namespace libtorrent
, boost::weak_ptr<torrent> tor
, shared_ptr<socket_type> s
, tcp::endpoint const& endp
, policy::peer* peerinfo)
, policy::peer* peerinfo
, bool outgoing)
:
#ifdef TORRENT_DEBUG
m_last_choke(time_now() - hours(1))
@@ -139,7 +140,7 @@ namespace libtorrent
, m_choke_rejects(0)
, m_read_recurse(0)
, m_fast_reconnect(false)
, m_active(true)
, m_active(outgoing)
, m_peer_interested(false)
, m_peer_choked(true)
, m_interesting(false)
@@ -149,8 +150,8 @@ namespace libtorrent
, m_ignore_unchoke_slots(false)
, m_have_all(false)
, m_disconnecting(false)
, m_connecting(true)
, m_queued(true)
, m_connecting(outgoing)
, m_queued(outgoing)
, m_request_large_blocks(false)
, m_share_mode(false)
, m_upload_only(false)
@@ -203,9 +204,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(">>> OUTGOING_CONNECTION [ ep: %s transport: %s seed: %d p: %p]"
peer_log(">>> %s [ ep: %s transport: %s seed: %d p: %p ]"
, outgoing ? "OUTGOING_CONNECTION" : "INCOMING CONNECTION"
, print_endpoint(m_remote).c_str()
, (m_socket->get<utp_stream>()) ? "uTP connection" : "TCP connection"
, m_socket->get<ssl_stream<stream_socket> >() ? "SSL/TCP"
: m_socket->get<ssl_stream<utp_stream> >() ? "SSL/uTP"
: m_socket->get<utp_stream>() ? "uTP" : "TCP"
, m_peer_info ? m_peer_info->seed : 0, m_peer_info);
#endif
#ifdef TORRENT_DEBUG
@@ -223,7 +227,8 @@ namespace libtorrent
session_impl& ses
, shared_ptr<socket_type> s
, tcp::endpoint const& endp
, policy::peer* peerinfo)
, policy::peer* peerinfo
, bool outgoing)
:
#ifdef TORRENT_DEBUG
m_last_choke(time_now() - hours(1))
@@ -282,7 +287,7 @@ namespace libtorrent
, m_choke_rejects(0)
, m_read_recurse(0)
, m_fast_reconnect(false)
, m_active(false)
, m_active(outgoing)
, m_peer_interested(false)
, m_peer_choked(true)
, m_interesting(false)
@@ -292,8 +297,8 @@ namespace libtorrent
, m_ignore_unchoke_slots(false)
, m_have_all(false)
, m_disconnecting(false)
, m_connecting(false)
, m_queued(false)
, m_connecting(outgoing)
, m_queued(outgoing)
, m_request_large_blocks(false)
, m_share_mode(false)
, m_upload_only(false)
@@ -347,7 +352,8 @@ 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("<<< INCOMING_CONNECTION [ ep: %s transport: %s ]"
peer_log("<<< %s [ ep: %s transport: %s ]"
, outgoing ? "OUTGOING_CONNECTION" : "INCOMING CONNECTION"
, print_endpoint(m_remote).c_str()
, (m_socket->get<utp_stream>()) ? "uTP connection" : "TCP connection");
#endif
@@ -536,7 +542,7 @@ namespace libtorrent
TORRENT_ASSERT(m_peer_info == 0 || m_peer_info->connection == this);
boost::shared_ptr<torrent> t = m_torrent.lock();
if (!t)
if (!m_active)
{
tcp::socket::non_blocking_io ioc(true);
error_code ec;
@@ -556,7 +562,8 @@ namespace libtorrent
if (m_remote.address().is_v4())
m_socket->set_option(type_of_service(m_ses.settings().peer_tos), ec);
}
else if (t->ready_for_connections())
if (t && t->ready_for_connections())
{
init();
}
@@ -1132,6 +1139,16 @@ namespace libtorrent
t.reset();
}
#ifdef TORRENT_USE_OPENSSL
if (t && t->is_ssl_torrent())
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING
peer_log("*** can't attach to an ssl torrent");
#endif
t.reset();
}
#endif
if (!t)
{
// we couldn't find the torrent!
@@ -3320,7 +3337,7 @@ namespace libtorrent
peer_log("CONNECTION FAILED: %s", print_endpoint(m_remote).c_str());
#endif
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
(*m_ses.m_logger) << "CONNECTION FAILED: " << print_endpoint(m_remote) << "\n";
(*m_ses.m_logger) << time_now_string() << " CONNECTION FAILED: " << print_endpoint(m_remote) << "\n";
#endif
#ifdef TORRENT_STATS
@@ -5234,7 +5251,7 @@ namespace libtorrent
return;
}
#if defined TORRENT_VERBOSE_LOGGING
peer_log(">>> ASYNC_CONENCT [ dst: %s ]", print_endpoint(m_remote).c_str());
peer_log(">>> ASYNC_CONNECT [ dst: %s ]", print_endpoint(m_remote).c_str());
#endif
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("peer_connection::on_connection_complete");
@@ -5279,7 +5296,11 @@ namespace libtorrent
if (m_disconnecting) return;
m_last_receive = time_now();
if (m_socket->get<utp_stream>() && m_peer_info)
if ((m_socket->get<utp_stream>()
#ifdef TORRENT_USE_OPENSSL
|| m_socket->get<ssl_stream<utp_stream> >()
#endif
) && m_peer_info)
{
m_peer_info->confirmed_supports_utp = true;
m_peer_info->supports_utp = false;

View File

@@ -1771,41 +1771,40 @@ namespace aux {
return m_ipv4_interface;
}
session_impl::listen_socket_t session_impl::setup_listener(tcp::endpoint ep
void session_impl::setup_listener(listen_socket_t* s, tcp::endpoint ep
, int retries, bool v6_only, int flags, error_code& ec)
{
listen_socket_t s;
s.sock.reset(new socket_acceptor(m_io_service));
s.sock->open(ep.protocol(), ec);
s->sock.reset(new socket_acceptor(m_io_service));
s->sock->open(ep.protocol(), ec);
if (ec)
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
(*m_logger) << "failed to open socket: " << print_endpoint(ep)
<< ": " << ec.message() << "\n" << "\n";
#endif
return listen_socket_t();
return;
}
if (flags & session::listen_reuse_address)
{
error_code err; // ignore errors here
s.sock->set_option(socket_acceptor::reuse_address(true), err);
s->sock->set_option(socket_acceptor::reuse_address(true), err);
}
#if TORRENT_USE_IPV6
if (ep.protocol() == tcp::v6())
{
error_code err; // ignore errors here
s.sock->set_option(v6only(v6_only), err);
s->sock->set_option(v6only(v6_only), err);
#ifdef TORRENT_WINDOWS
#ifndef PROTECTION_LEVEL_UNRESTRICTED
#define PROTECTION_LEVEL_UNRESTRICTED 10
#endif
// enable Teredo on windows
s.sock->set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err);
s->sock->set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err);
#endif
}
#endif
s.sock->bind(ep, ec);
s->sock->bind(ep, ec);
while (ec && retries > 0)
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
@@ -1818,7 +1817,7 @@ namespace aux {
TORRENT_ASSERT_VAL(!ec, ec);
--retries;
ep.port(ep.port() + 1);
s.sock->bind(ep, ec);
s->sock->bind(ep, ec);
}
if (ec && !(flags & session::listen_no_system_port))
{
@@ -1826,7 +1825,7 @@ namespace aux {
// let the OS pick a port
ep.port(0);
ec = error_code();
s.sock->bind(ep, ec);
s->sock->bind(ep, ec);
}
if (ec)
{
@@ -1839,10 +1838,10 @@ namespace aux {
, print_endpoint(ep).c_str(), ec.message().c_str());
(*m_logger) << time_now_string() << msg << "\n";
#endif
return listen_socket_t();
return;
}
s.external_port = s.sock->local_endpoint(ec).port();
if (!ec) s.sock->listen(m_settings.listen_queue_size, ec);
s->external_port = s->sock->local_endpoint(ec).port();
if (!ec) s->sock->listen(m_settings.listen_queue_size, ec);
if (ec)
{
if (m_alerts.should_post<listen_failed_alert>())
@@ -1853,22 +1852,21 @@ namespace aux {
, print_endpoint(ep).c_str(), ec.message().c_str());
(*m_logger) << time_now_string() << msg << "\n";
#endif
return listen_socket_t();
return;
}
// if we asked the system to listen on port 0, which
// socket did it end up choosing?
if (ep.port() == 0)
ep.port(s.sock->local_endpoint().port());
ep.port(s->sock->local_endpoint().port());
if (m_alerts.should_post<listen_succeeded_alert>())
m_alerts.post_alert(listen_succeeded_alert(ep));
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
(*m_logger) << time_now_string() << " listening on: " << ep
<< " external port: " << s.external_port << "\n";
<< " external port: " << s->external_port << "\n";
#endif
return s;
}
void session_impl::open_listen_port(int flags, error_code& ec)
@@ -1887,8 +1885,8 @@ namespace aux {
// this means we should open two listen sockets
// one for IPv4 and one for IPv6
listen_socket_t s = setup_listener(
tcp::endpoint(address_v4::any(), m_listen_interface.port())
listen_socket_t s;
setup_listener(&s, tcp::endpoint(address_v4::any(), m_listen_interface.port())
, m_listen_port_retries, false, flags, ec);
if (s.sock)
@@ -1906,8 +1904,7 @@ namespace aux {
// only try to open the IPv6 port if IPv6 is installed
if (supports_ipv6())
{
s = setup_listener(
tcp::endpoint(address_v6::any(), m_listen_interface.port())
setup_listener(&s, tcp::endpoint(address_v6::any(), m_listen_interface.port())
, m_listen_port_retries, true, flags, ec);
if (s.sock)
@@ -1936,8 +1933,8 @@ namespace aux {
// we should only open a single listen socket, that
// binds to the given interface
listen_socket_t s = setup_listener(
m_listen_interface, m_listen_port_retries, false, flags, ec);
listen_socket_t s;
setup_listener(&s, m_listen_interface, m_listen_port_retries, false, flags, ec);
if (s.sock)
{
@@ -4470,11 +4467,11 @@ namespace aux {
return m_listen_sockets.front().external_port;
}
void session_impl::announce_lsd(sha1_hash const& ih, bool broadcast)
void session_impl::announce_lsd(sha1_hash const& ih, int port, bool broadcast)
{
// use internal listen port for local peers
if (m_lsd.get())
m_lsd->announce(ih, m_listen_interface.port(), broadcast);
m_lsd->announce(ih, port, broadcast);
}
void session_impl::on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih)

View File

@@ -1264,15 +1264,290 @@ namespace libtorrent
#endif
/*
#ifdef TORRENT_USE_OPENSSL
/*
bool verify_function(bool preverified, boost::asio::ssl::verify_context& ctx)
{
return false;
}
#endif
*/
void torrent::init_ssl(std::string const& cert)
{
using boost::asio::ssl::context;
// 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);
TORRENT_ASSERT(RAND_status() == 1);
// 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<context> ctx(
new (std::nothrow) context(m_ses.m_io_service, context::sslv23));
if (!ctx)
{
set_error(asio::error::no_memory, "SSL context");
pause();
return;
}
ctx->set_options(context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
error_code ec;
ctx->set_verify_mode(context::verify_peer
| context::verify_fail_if_no_peer_cert
| context::verify_client_once, ec);
if (ec)
{
set_error(ec, "SSL verify mode");
pause();
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);
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();
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;
// tell the client we need a cert for this torrent
alerts().post_alert(torrent_need_cert_alert(get_handle()));
m_ssl_acceptor.reset(new listen_socket_t);
m_ses.setup_listener(m_ssl_acceptor.get()
, tcp::endpoint(address_v4::any(), m_ses.m_listen_interface.port())
, m_ses.m_listen_port_retries + 10, false, 0, ec);
if (!m_ssl_acceptor->sock)
{
set_error(ec, "ssl listen port");
pause();
return;
}
// TODO: issue UPnP and NAT-PMP for this socket
async_accept(m_ssl_acceptor->sock);
set_allow_peers(false);
}
void torrent::async_accept(boost::shared_ptr<socket_acceptor> const& listener)
{
boost::shared_ptr<socket_type> c(new socket_type(m_ses.m_io_service));
c->instantiate<ssl_stream<stream_socket> >(m_ses.m_io_service, m_ssl_ctx.get());
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("torrent::on_accept_ssl_connection");
#endif
listener->async_accept(c->get<ssl_stream<stream_socket> >()->next_layer()
, boost::bind(&torrent::on_accept_ssl_connection, shared_from_this(), c
, boost::weak_ptr<socket_acceptor>(listener), _1));
}
void torrent::on_accept_ssl_connection(boost::shared_ptr<socket_type> const& s
, boost::weak_ptr<socket_acceptor> listen_socket, error_code const& e)
{
#if defined TORRENT_ASIO_DEBUGGING
complete_async("torrent::on_accept_ssl_connection");
#endif
// TODO: there's some code duplication with session_impl::on_accept_connection
boost::shared_ptr<socket_acceptor> listener = listen_socket.lock();
if (!listener) return;
if (e == asio::error::operation_aborted) return;
if (m_abort) return;
error_code ec;
if (e)
{
tcp::endpoint ep = listener->local_endpoint(ec);
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
std::string msg = "error accepting connection on '"
+ print_endpoint(ep) + "' " + e.message();
(*m_ses.m_logger) << msg << "\n";
#endif
#ifdef TORRENT_WINDOWS
// Windows sometimes generates this error. It seems to be
// non-fatal and we have to do another async_accept.
if (e.value() == ERROR_SEM_TIMEOUT)
{
async_accept(listener);
return;
}
#endif
#ifdef TORRENT_BSD
// Leopard sometimes generates an "invalid argument" error. It seems to be
// non-fatal and we have to do another async_accept.
if (e.value() == EINVAL)
{
async_accept(listener);
return;
}
#endif
if (alerts().should_post<listen_failed_alert>())
alerts().post_alert(listen_failed_alert(ep, e));
return;
}
async_accept(listener);
if (is_paused()) return;
// we got a connection request!
tcp::endpoint endp = s->remote_endpoint(ec);
if (ec)
{
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_ses.m_logger) << endp << " <== INCOMING CONNECTION FAILED, could "
"not retrieve remote endpoint " << ec.message() << "\n";
#endif
return;
}
if (!settings().enable_incoming_tcp)
{
if (alerts().should_post<peer_blocked_alert>())
alerts().post_alert(peer_blocked_alert(torrent_handle(), endp.address()));
return;
}
if (m_apply_ip_filter
&& m_ses.m_ip_filter.access(endp.address()) & ip_filter::blocked)
{
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_ses.m_logger) << "filtered blocked ip\n";
#endif
if (alerts().should_post<peer_blocked_alert>())
alerts().post_alert(peer_blocked_alert(get_handle(), endp.address()));
return;
}
if (m_connections.size() >= m_max_connections)
{
if (alerts().should_post<peer_disconnected_alert>())
{
alerts().post_alert(
peer_disconnected_alert(get_handle(), endp, peer_id()
, error_code(errors::too_many_connections, get_libtorrent_category())));
}
return;
}
m_ses.setup_socket_buffers(*s);
s->get<ssl_stream<stream_socket> >()->async_accept_handshake(boost::bind(&torrent::ssl_handshake
, shared_from_this(), _1, s));
}
void torrent::ssl_handshake(error_code const& ec, boost::shared_ptr<socket_type> s)
{
error_code e;
tcp::endpoint endp = s->remote_endpoint(e);
if (e) return;
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_ses.m_logger) << time_now_string() << " *** peer SSL handshake done [ ip: "
<< endp << " ec: " << ec.message() << "]\n";
#endif
if (ec)
{
if (alerts().should_post<peer_error_alert>())
{
alerts().post_alert(peer_error_alert(get_handle(), endp
, peer_id(), ec));
}
return;
}
boost::intrusive_ptr<peer_connection> c(
new bt_peer_connection(m_ses, shared_from_this(), s, endp, 0, false));
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS
c->m_in_constructor = false;
#endif
if (c->is_disconnecting()) return;
if (!m_policy.new_connection(*c, m_ses.session_time()))
{
#if defined TORRENT_LOGGING
(*m_ses.m_logger) << time_now_string() << " CLOSING CONNECTION "
<< p->remote() << " policy::new_connection returned false (i.e. peer list full)\n";
#endif
c->disconnect(errors::too_many_connections);
return;
}
// add the newly connected peer to this torrent's peer list
m_connections.insert(boost::get_pointer(c));
m_ses.m_connections.insert(c);
c->start();
if (settings().default_peer_upload_rate)
c->set_upload_limit(settings().default_peer_upload_rate);
if (settings().default_peer_download_rate)
c->set_download_limit(settings().default_peer_download_rate);
}
#endif
// this may not be called from a constructor because of the call to
// shared_from_this()
void torrent::init()
@@ -1284,88 +1559,7 @@ namespace libtorrent
#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<context> ctx(
new (std::nothrow) context(m_ses.m_io_service, context::sslv23));
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
/*
#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);
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();
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;
}
if (!cert.empty()) init_ssl(cert);
#endif
#ifdef TORRENT_USE_OPENSSL
@@ -1968,8 +2162,14 @@ namespace libtorrent
if (is_paused()) return;
#ifdef TORRENT_USE_OPENSSL
int port = is_ssl_torrent() ? m_ssl_acceptor->external_port : m_ses.listen_port();
#else
int port = m_ses.listen_port();
#endif
// announce with the local discovery service
m_ses.announce_lsd(m_torrent_file->info_hash()
m_ses.announce_lsd(m_torrent_file->info_hash(), port
, m_ses.settings().broadcast_lsd && m_lsd_seq == 0);
++m_lsd_seq;
}
@@ -1984,9 +2184,15 @@ namespace libtorrent
TORRENT_ASSERT(m_allow_peers);
#ifdef TORRENT_USE_OPENSSL
int port = is_ssl_torrent() ? m_ssl_acceptor->external_port : m_ses.listen_port();
#else
int port = m_ses.listen_port();
#endif
boost::weak_ptr<torrent> self(shared_from_this());
m_ses.m_dht->announce(m_torrent_file->info_hash()
, m_ses.listen_port(), is_seed()
, port, is_seed()
, boost::bind(&torrent::on_dht_announce_response_disp, self, _1));
}
@@ -2073,6 +2279,14 @@ namespace libtorrent
req.num_want = (req.event == tracker_request::stopped)
?0:settings().num_want;
// SSL torrents use their own listen socket
#ifdef TORRENT_USE_OPENSSL
// TODO: this pattern is repeated in a few places. Factor this into
// a function and generalize the concept of a torrent having a
// dedicated listen port
if (is_ssl_torrent()) req.listen_port = m_ssl_acceptor->external_port;
else
#endif
req.listen_port = m_ses.listen_port();
req.key = m_ses.m_key;
@@ -3207,6 +3421,12 @@ namespace libtorrent
m_ses.m_encrypted_torrents.erase(shared_from_this());
m_in_encrypted_list = false;
}
if (m_ssl_acceptor && m_ssl_acceptor->sock)
{
error_code ec;
m_ssl_acceptor->sock->close(ec);
}
#endif
m_abort = true;
@@ -3973,22 +4193,51 @@ namespace libtorrent
}
#ifdef TORRENT_USE_OPENSSL
// certificate is a filename to a .pem file which is our
// certificate. root_cert is a filename to a certificate
// on disk which is the trusted certificate authority (CA)
// for this torrent. 'certificate' must be signed by the
// 'root_cert', and any peer we connect to or that connect
// to use must present a valid certificate signed by 'root_cert'
void torrent::set_ssl_cert(std::string const& certificate, error_code& ec)
std::string password_callback(int length, boost::asio::ssl::context::password_purpose p
, std::string pw)
{
ec.clear();
if (!m_ssl_ctx)
{
ec = asio::error::operation_not_supported;
return;
}
if (p != boost::asio::ssl::context::for_reading) return "";
return pw;
}
// certificate is a filename to a .pem file which is our
// certificate. The certificate must be signed by the root
// cert of the torrent file. any peer we connect to or that
// connect to use must present a valid certificate signed
// by the torrent root cert as well
void torrent::set_ssl_cert(std::string const& certificate
, std::string const& private_key
, std::string const& dh_params
, std::string const& passphrase)
{
if (!m_ssl_ctx) return;
using boost::asio::ssl::context;
error_code ec;
m_ssl_ctx->set_password_callback(boost::bind(&password_callback, _1, _2, passphrase), ec);
if (ec)
{
if (alerts().should_post<torrent_error_alert>())
alerts().post_alert(torrent_error_alert(get_handle(), ec));
}
m_ssl_ctx->use_certificate_file(certificate, context::pem, ec);
if (ec)
{
if (alerts().should_post<torrent_error_alert>())
alerts().post_alert(torrent_error_alert(get_handle(), ec));
}
m_ssl_ctx->use_private_key_file(private_key, context::pem, ec);
if (ec)
{
if (alerts().should_post<torrent_error_alert>())
alerts().post_alert(torrent_error_alert(get_handle(), ec));
}
m_ssl_ctx->use_tmp_dh_file(dh_params, ec);
if (ec)
{
if (alerts().should_post<torrent_error_alert>())
alerts().post_alert(torrent_error_alert(get_handle(), ec));
}
}
#endif
@@ -4022,7 +4271,7 @@ namespace libtorrent
if (m_picker.get())
{
bitfield const& pieces = p->get_bitfield();
TORRENT_ASSERT(pieces.count() < int(pieces.size()));
TORRENT_ASSERT(pieces.count() <= int(pieces.size()));
m_picker->dec_refcount(pieces);
}
}
@@ -5159,6 +5408,9 @@ namespace libtorrent
}
#endif
// extend connect timeout by this many seconds
int timeout_extend = 0;
TORRENT_ASSERT(want_more_peers() || ignore_limit);
TORRENT_ASSERT(m_ses.num_connections() < m_ses.settings().connections_limit || ignore_limit);
@@ -5178,6 +5430,8 @@ namespace libtorrent
s->get<i2p_stream>()->set_destination(static_cast<policy::i2p_peer*>(peerinfo)->destination);
s->get<i2p_stream>()->set_command(i2p_stream::cmd_connect);
s->get<i2p_stream>()->set_session_id(m_ses.m_i2p_conn.session_id());
// i2p setups are slow
timeout_extend = 20;
}
else
#endif
@@ -5196,7 +5450,20 @@ namespace libtorrent
// don't make a TCP connection if it's disabled
if (sm == 0 && !m_ses.m_settings.enable_outgoing_tcp) return false;
bool ret = instantiate_connection(m_ses.m_io_service, m_ses.proxy(), *s, 0, sm, true);
void* userdata = 0;
#ifdef TORRENT_USE_OPENSSL
if (is_ssl_torrent())
{
userdata = m_ssl_ctx.get();
// SSL handshakes are slow
timeout_extend = 10;
// we don't support SSL over uTP yet
sm = 0;
}
#endif
bool ret = instantiate_connection(m_ses.m_io_service, m_ses.proxy(), *s, userdata, sm, true);
(void)ret;
TORRENT_ASSERT(ret);
}
@@ -5239,6 +5506,7 @@ namespace libtorrent
int timeout = settings().peer_connect_timeout;
if (peerinfo) timeout += 3 * peerinfo->failcount;
timeout += timeout_extend;
TORRENT_TRY
{
@@ -5379,6 +5647,7 @@ namespace libtorrent
(*m_ses.m_logger) << time_now_string() << " CLOSING CONNECTION "
<< p->remote() << " policy::new_connection returned false (i.e. peer list full)\n";
#endif
p->disconnect(errors::too_many_connections);
return false;
}
}
@@ -7675,6 +7944,11 @@ namespace libtorrent
st->handle = get_handle();
st->info_hash = info_hash();
st->listen_port = 0;
#ifdef TORRENT_USE_OPENSSL
if (is_ssl_torrent() && m_ssl_acceptor) st->listen_port = m_ssl_acceptor->external_port;
#endif
st->has_incoming = m_has_incoming;
if (m_error) st->error = m_error.message() + ": " + m_error_file;
st->seed_mode = m_seed_mode;

View File

@@ -112,6 +112,12 @@ namespace libtorrent
session_impl& ses = t->session(); \
ses.m_io_service.post(boost::bind(&torrent:: x, t, a1, a2, a3))
#define TORRENT_ASYNC_CALL4(x, a1, a2, a3, a4) \
boost::shared_ptr<torrent> t = m_torrent.lock(); \
if (!t) return; \
session_impl& ses = t->session(); \
ses.m_io_service.post(boost::bind(&torrent:: x, t, a1, a2, a3, a4))
#define TORRENT_SYNC_CALL(x) \
boost::shared_ptr<torrent> t = m_torrent.lock(); \
if (!t) return; \
@@ -375,13 +381,14 @@ namespace libtorrent
TORRENT_ASYNC_CALL(flush_cache);
}
void torrent_handle::set_ssl_certificates(
std::string const& certificate, error_code& ec)
void torrent_handle::set_ssl_certificate(
std::string const& certificate
, std::string const& private_key
, std::string const& dh_params
, std::string const& passphrase)
{
#ifdef TORRENT_USE_OPENSSL
TORRENT_ASYNC_CALL2(set_ssl_cert, certificate, boost::ref(ec));
#else
ec = boost::asio::error::operation_not_supported;
TORRENT_ASYNC_CALL4(set_ssl_cert, certificate, private_key, dh_params, passphrase);
#endif
}