merged SSL unit tests from RC_0_16

This commit is contained in:
Arvid Norberg 2013-12-15 21:10:45 +00:00
parent 93d7d89ced
commit 1dfc93aad8
3 changed files with 354 additions and 1 deletions

View File

@ -0,0 +1,60 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 15441482899666218084 (0xd64b2ae27fdfac64)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=test CA
Validity
Not Before: Dec 15 20:42:32 2013 GMT
Not After : Dec 15 20:42:32 2014 GMT
Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=*
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (1024 bit)
Modulus:
00:df:4a:a7:c3:0b:23:3a:16:e3:aa:66:5d:f4:57:
54:f5:94:92:55:4e:38:f3:32:a9:1d:7a:cf:f4:35:
4e:25:28:7f:3a:3d:f1:c9:9f:99:f7:42:fc:8c:8c:
ac:f3:74:91:0e:80:5c:1e:8c:ff:26:94:1f:7e:2d:
d2:55:33:e9:69:e3:74:03:a0:78:43:45:25:5f:bb:
7d:59:e4:8c:46:ff:7a:4a:7f:51:89:71:f1:6b:07:
24:9b:0b:37:a5:53:c4:ed:57:05:cf:08:2c:64:43:
95:a3:93:6d:bd:98:f5:57:4b:7b:7a:c9:3f:47:a5:
7c:20:fa:91:55:58:df:24:2d
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
EA:FC:F9:50:A2:50:BB:33:DC:FC:15:17:CE:68:D2:3C:03:FD:5B:FF
X509v3 Authority Key Identifier:
keyid:36:A1:4C:8C:07:84:35:8E:76:DF:22:90:AD:63:21:F5:4A:BD:34:E4
Signature Algorithm: sha1WithRSAEncryption
2c:69:84:c4:cd:0d:43:20:6c:7a:60:9d:9d:ed:9c:e0:22:10:
8c:d1:9e:08:78:bd:ad:d5:03:9b:b2:4f:93:a1:6f:5b:38:47:
ed:30:9a:2e:e8:ea:64:e7:6c:dd:df:fd:40:29:4d:0f:2e:e3:
1f:5c:b8:ac:f4:37:28:05:22:35:70:3a:04:63:f8:88:d6:cb:
33:f2:bc:05:b4:d8:4f:0d:eb:4c:e1:a6:55:69:3b:c3:90:7a:
d5:ef:2b:0d:91:17:67:8e:08:79:bb:7d:50:f9:1f:36:91:c8:
22:0d:d6:f9:f2:5e:c5:66:bf:27:bc:b2:32:4d:2a:cd:7c:a4:
2a:ee
-----BEGIN CERTIFICATE-----
MIICoTCCAgqgAwIBAgIJANZLKuJ/36xkMA0GCSqGSIb3DQEBBQUAMFcxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQxEDAOBgNVBAMMB3Rlc3QgQ0EwHhcNMTMxMjE1MjA0MjMy
WhcNMTQxMjE1MjA0MjMyWjBRMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1T
dGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQowCAYDVQQD
DAEqMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfSqfDCyM6FuOqZl30V1T1
lJJVTjjzMqkdes/0NU4lKH86PfHJn5n3QvyMjKzzdJEOgFwejP8mlB9+LdJVM+lp
43QDoHhDRSVfu31Z5IxG/3pKf1GJcfFrBySbCzelU8TtVwXPCCxkQ5Wjk229mPVX
S3t6yT9HpXwg+pFVWN8kLQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIB
DQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU6vz5
UKJQuzPc/BUXzmjSPAP9W/8wHwYDVR0jBBgwFoAUNqFMjAeENY523yKQrWMh9Uq9
NOQwDQYJKoZIhvcNAQEFBQADgYEALGmExM0NQyBsemCdne2c4CIQjNGeCHi9rdUD
m7JPk6FvWzhH7TCaLujqZOds3d/9QClNDy7jH1y4rPQ3KAUiNXA6BGP4iNbLM/K8
BbTYTw3rTOGmVWk7w5B61e8rDZEXZ44Iebt9UPkfNpHIIg3W+fJexWa/J7yyMk0q
zXykKu4=
-----END CERTIFICATE-----

View File

@ -0,0 +1,17 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIsLJkydWZi7ACAggA
MBQGCCqGSIb3DQMHBAicrqPGKfsAVQSCAoBNO9Cjpr1Zuw5jKLyVkGFfTLLtvT4Z
b+zAYNiqhD3idw8wSkPKgs4JXbkCJtoOMUyUXPNSczsifjbIDewZbYBOxZXHFgtc
JS1nKCpwy5vktWzhl+PHhtJDOdAehK2igwkv5TwwXF4jJYc4pUNMjwebygJImqeG
9LhCd2LjBb3tk1qy5lqGRL4X+FQslSp0IGGNiBehQDskrAXE1rf8NoscJFO4UJfb
M0Ad59MVB620xgQ3rUxQ40RR2ZYZQ+o0/Bhe2hvSZyoeB+M1DnKbCvgi45qWYy25
NzT7wohQb1OAJ3EEYhRgnW3E4NtuUuXxUyRAK9M94dxlOR//QE2W32V7evV9Elgd
H16BcdObKqBycuqdLHkJ7jGcwJXbCD4eK+T9iLccH9G3/FO1n98yLQ67AnSJ0IaV
wBOENhJ14qIqr3dwFZIcEo/TzQqKApKef6GULlHQ7Ime7spJvVr9uNcnLa6yq8y0
2EjeAKitEtgFpLvt0V5xd+NPzyznsKybbT0va39/C1KA94eqniOG3rc8dDGsjd6k
ZFyNXR/aiT/FLmP84qukcSFsap+6PSyFJv5VVMcB4V2RtxPU5sD0HirYOpY/Wuj9
qG2ONflvh2HrGEn5snmD7Bm7k0eidQBOWCiYn8rNHVCjBV1aN+LxYtDAFHhzLKUB
BXD6wZoHzHZof5jdLbr6S58KHxQYnd0vFlC3n42HNtQrbWNgaFs33z1PrnGsgKma
bCJHBk4+e35ee8aGCpv9zEDGc1l5l8zpc4+SSOOkChKdfihdyj1+HaWx9q2StEPz
49G76d3X82SDpRYxchwl2S30eH3b+X2vLsQg3Z2gRi+LYOBFXD0tUuXF
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -41,9 +41,11 @@ POSSIBILITY OF SUCH DAMAGE.
#include "setup_transfer.hpp"
#include <fstream>
#include <iostream>
#include <boost/asio/connect.hpp>
#ifdef TORRENT_USE_OPENSSL
#include <boost/asio/ssl/error.hpp> // for asio::error::get_ssl_category()
#include <boost/asio/ssl.hpp>
#endif
using namespace libtorrent;
@ -154,6 +156,7 @@ void test_ssl(int test_idx, bool use_utp)
file.close();
add_torrent_params addp;
addp.save_path = ".";
addp.flags &= ~add_torrent_params::flag_paused;
addp.flags &= ~add_torrent_params::flag_auto_managed;
@ -165,7 +168,7 @@ void test_ssl(int test_idx, bool use_utp)
peer_errors = 0;
boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0
, true, false, true, "_ssl", 16 * 1024, &t, false, NULL, true, test.use_ssl_ports);
, true, false, true, "_ssl", 16 * 1024, &t, false, &addp, true, test.use_ssl_ports);
if (test.seed_has_cert)
{
@ -253,10 +256,283 @@ void test_ssl(int test_idx, bool use_utp)
p2 = ses2.abort();
}
std::string password_callback(int length, boost::asio::ssl::context::password_purpose p
, std::string pw)
{
if (p != boost::asio::ssl::context::for_reading) return "";
return pw;
}
struct attack_t
{
// flags controlling the connection attempt
boost::uint32_t flags;
// whether or not we expect to be able to connect
bool expect;
};
enum attack_flags_t
{
valid_certificate = 1,
invalid_certificate = 2,
valid_sni_hash = 4,
invalid_sni_hash = 8,
valid_bittorrent_hash = 16,
};
attack_t attacks[] =
{
// positive test
{ valid_certificate | valid_sni_hash | valid_bittorrent_hash, true},
// SNI
{ valid_certificate | invalid_sni_hash | valid_bittorrent_hash, false},
{ valid_certificate | valid_bittorrent_hash, false},
// certificate
{ valid_sni_hash | valid_bittorrent_hash, false},
{ invalid_certificate | valid_sni_hash | valid_bittorrent_hash, false},
// bittorrent hash
{ valid_certificate | valid_sni_hash, false},
};
const int num_attacks = sizeof(attacks)/sizeof(attacks[0]);
bool try_connect(session& ses1, int port
, boost::intrusive_ptr<torrent_info> const& t, boost::uint32_t flags)
{
using boost::asio::ssl::context;
fprintf(stderr, "\nMALICIOUS PEER TEST: ");
if (flags & invalid_certificate) fprintf(stderr, "invalid-certificate ");
else if (flags & valid_certificate) fprintf(stderr, "valid-certificate ");
else fprintf(stderr, "no-certificate ");
if (flags & invalid_sni_hash) fprintf(stderr, "invalid-SNI-hash ");
else if (flags & valid_sni_hash) fprintf(stderr, "valid-SNI-hash ");
else fprintf(stderr, "no-SNI-hash ");
if (flags & valid_bittorrent_hash) fprintf(stderr, "valid-bittorrent-hash ");
else fprintf(stderr, "invalid-bittorrent-hash ");
fprintf(stderr, "\n");
error_code ec;
boost::asio::io_service ios;
// create the SSL context for this torrent. We need to
// inject the root certificate, and no other, to
// verify other peers against
context ctx(ios, context::sslv23);
ctx.set_options(context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
// we're a malicious peer, we don't have any interest
// in verifying peers
ctx.set_verify_mode(context::verify_none, ec);
if (ec)
{
fprintf(stderr, "Failed to set SSL verify mode: %s\n"
, ec.message().c_str());
TEST_CHECK(!ec);
return false;
}
std::string certificate = combine_path("ssl", "peer_certificate.pem");
std::string private_key = combine_path("ssl", "peer_private_key.pem");
std::string dh_params = combine_path("ssl", "dhparams.pem");
if (flags & invalid_certificate)
{
certificate = combine_path("ssl", "invalid_peer_certificate.pem");
private_key = combine_path("ssl", "invalid_peer_private_key.pem");
}
// TODO: test using a signed certificate with the wrong info-hash in DN
if (flags & (valid_certificate | invalid_certificate))
{
ctx.set_password_callback(boost::bind(&password_callback, _1, _2, "test"), ec);
if (ec)
{
fprintf(stderr, "Failed to set certificate password callback: %s\n"
, ec.message().c_str());
TEST_CHECK(!ec);
return false;
}
ctx.use_certificate_file(certificate, context::pem, ec);
if (ec)
{
fprintf(stderr, "Failed to set certificate file: %s\n"
, ec.message().c_str());
TEST_CHECK(!ec);
return false;
}
ctx.use_private_key_file(private_key, context::pem, ec);
if (ec)
{
fprintf(stderr, "Failed to set private key: %s\n"
, ec.message().c_str());
TEST_CHECK(!ec);
return false;
}
ctx.use_tmp_dh_file(dh_params, ec);
if (ec)
{
fprintf(stderr, "Failed to set DH params: %s\n"
, ec.message().c_str());
TEST_CHECK(!ec);
return false;
}
}
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_sock(ios, ctx);
ssl_sock.lowest_layer().connect(tcp::endpoint(
address_v4::from_string("127.0.0.1"), port), ec);
print_alerts(ses1, "ses1", true, true, true, &on_alert);
if (ec)
{
fprintf(stderr, "Failed to connect: %s\n"
, ec.message().c_str());
TEST_CHECK(!ec);
return false;
}
if (flags & valid_sni_hash)
{
std::string name = to_hex(t->info_hash().to_string());
fprintf(stderr, "SNI: %s\n", name.c_str());
SSL_set_tlsext_host_name(ssl_sock.native_handle(), name.c_str());
}
else if (flags & invalid_sni_hash)
{
char const hex_alphabet[] = "0123456789abcdef";
std::string name;
name.reserve(40);
for (int i = 0; i < 40; ++i)
name += hex_alphabet[rand() % 16];
fprintf(stderr, "SNI: %s\n", name.c_str());
SSL_set_tlsext_host_name(ssl_sock.native_handle(), name.c_str());
}
ssl_sock.handshake(asio::ssl::stream_base::client, ec);
print_alerts(ses1, "ses1", true, true, true, &on_alert);
if (ec)
{
fprintf(stderr, "Failed SSL handshake: %s\n"
, ec.message().c_str());
return false;
}
char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04"
" " // space for info-hash
"aaaaaaaaaaaaaaaaaaaa" // peer-id
"\0\0\0\x01\x02"; // interested
// fill in the info-hash
if (flags & valid_bittorrent_hash)
{
std::memcpy(handshake + 28, &t->info_hash()[0], 20);
}
else
{
// TODO: also test using a hash that refers to a valid torrent
// but that differs from the SNI hash
std::generate(handshake + 28, handshake + 48, &rand);
}
// fill in the peer-id
std::generate(handshake + 48, handshake + 68, &rand);
boost::asio::write(ssl_sock, libtorrent::asio::buffer(handshake, (sizeof(handshake) - 1)), ec);
if (ec)
{
fprintf(stderr, "failed to write bittorrent handshake: %s\n"
, ec.message().c_str());
return false;
}
char buf[68];
boost::asio::read(ssl_sock, libtorrent::asio::buffer(buf, sizeof(buf)), ec);
if (ec)
{
fprintf(stderr, "failed to read bittorrent handshake: %s\n"
, ec.message().c_str());
return false;
}
if (memcmp(buf, "\x13" "BitTorrent protocol", 20) != 0)
{
fprintf(stderr, "invalid bittorrent handshake\n");
return false;
}
if (memcmp(buf + 28, &t->info_hash()[0], 20) != 0)
{
fprintf(stderr, "invalid info-hash in bittorrent handshake\n");
return false;
}
fprintf(stderr, "successfully connected over SSL and shook hand over bittorrent\n");
return true;
}
void test_malicious_peer()
{
error_code ec;
remove_all("tmp3_ssl", ec);
// set up session
session ses1(fingerprint("LT", 0, 1, 0, 0)
, std::make_pair(48075, 49000), "0.0.0.0", 0, alert_mask);
wait_for_listen(ses1, "ses1");
session_settings sett;
sett.ssl_listen = 1024 + rand() % 50000;
ses1.set_settings(sett);
// create torrent
create_directory("tmp3_ssl", ec);
std::ofstream file("tmp3_ssl/temporary");
boost::intrusive_ptr<torrent_info> t = ::create_torrent(&file
, 16 * 1024, 13, false, "ssl/root_ca_cert.pem");
file.close();
add_torrent_params addp;
addp.save_path = ".";
addp.flags &= ~add_torrent_params::flag_paused;
addp.flags &= ~add_torrent_params::flag_auto_managed;
addp.ti = t;
torrent_handle tor1 = ses1.add_torrent(addp, ec);
tor1.set_ssl_certificate(
combine_path("ssl", "peer_certificate.pem")
, combine_path("ssl", "peer_private_key.pem")
, combine_path("ssl", "dhparams.pem")
, "test");
wait_for_listen(ses1, "ses1");
for (int i = 0; i < num_attacks; ++i)
{
bool success = try_connect(ses1, sett.ssl_listen, t, attacks[i].flags);
TEST_EQUAL(attacks[i].expect, success);
}
}
int test_main()
{
using namespace libtorrent;
test_malicious_peer();
// No support for SSL/uTP yet, so always pass in false
for (int i = 0; i < sizeof(test_config)/sizeof(test_config[0]); ++i)
test_ssl(i, false);