Commit 3a72273d authored by Kenton Varda's avatar Kenton Varda

Implement TLS option to verify client certificates.

Use case for me is Cloudflare's "Authenticated Origin Pulls", in which Cloudflare authenticates itself to the origin server using client certificates. Thus the origin server can ensure that it only accepts traffic through Cloudflare, without resorting to IP whitelists.
parent 2e066e16
......@@ -411,6 +411,70 @@ KJ_TEST("TLS certificate validation") {
"self signed certificate");
}
KJ_TEST("TLS client certificate verification") {
TlsContext::Options serverOptions = TlsTest::defaultServer();
TlsContext::Options clientOptions = TlsTest::defaultClient();
serverOptions.verifyClients = true;
serverOptions.trustedCertificates = clientOptions.trustedCertificates;
// No certificate loaded in the client: fail
{
TlsTest test(clientOptions, serverOptions);
auto pipe = test.io.provider->newTwoWayPipe();
auto clientPromise = test.tlsClient.wrapClient(kj::mv(pipe.ends[0]), "example.com");
auto serverPromise = test.tlsServer.wrapServer(kj::mv(pipe.ends[1]));
KJ_EXPECT_THROW_MESSAGE("peer did not return a certificate",
serverPromise.wait(test.io.waitScope));
KJ_EXPECT_THROW_MESSAGE("alert handshake failure",
clientPromise.wait(test.io.waitScope));
}
// Self-signed certificate loaded in the client: fail
{
TlsKeypair selfSignedKeypair = { TlsPrivateKey(HOST_KEY), TlsCertificate(SELF_SIGNED_CERT) };
clientOptions.defaultKeypair = selfSignedKeypair;
TlsTest test(clientOptions, serverOptions);
auto pipe = test.io.provider->newTwoWayPipe();
auto clientPromise = test.tlsClient.wrapClient(kj::mv(pipe.ends[0]), "example.com");
auto serverPromise = test.tlsServer.wrapServer(kj::mv(pipe.ends[1]));
KJ_EXPECT_THROW_MESSAGE("certificate verify failed",
serverPromise.wait(test.io.waitScope));
KJ_EXPECT_THROW_MESSAGE("alert unknown ca",
clientPromise.wait(test.io.waitScope));
}
// Trusted certificate loaded in the client: success.
{
clientOptions.defaultKeypair = serverOptions.defaultKeypair;
TlsTest test(clientOptions, serverOptions);
ErrorNexus e;
auto pipe = test.io.provider->newTwoWayPipe();
auto clientPromise = e.wrap(test.tlsClient.wrapClient(kj::mv(pipe.ends[0]), "example.com"));
auto serverPromise = e.wrap(test.tlsServer.wrapServer(kj::mv(pipe.ends[1])));
auto client = clientPromise.wait(test.io.waitScope);
auto server = serverPromise.wait(test.io.waitScope);
auto writePromise = client->write("foo", 3);
char buf[4];
server->read(&buf, 3).wait(test.io.waitScope);
buf[3] = '\0';
KJ_ASSERT(kj::StringPtr(buf) == "foo");
}
}
#ifdef KJ_EXTERNAL_TESTS
KJ_TEST("TLS to capnproto.org") {
kj::AsyncIoContext io = setupAsyncIo();
......
......@@ -473,6 +473,7 @@ private:
TlsContext::Options::Options()
: useSystemTrustStore(true),
verifyClients(false),
minVersion(TlsVersion::TLS_1_0),
cipherList("ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS") {}
// Cipher list is Mozilla's "intermediate" list, except with classic DH removed since we don't
......@@ -516,6 +517,10 @@ TlsContext::TlsContext(Options options) {
}
}
if (options.verifyClients) {
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
}
// honor options.minVersion
long optionFlags = 0;
if (options.minVersion > TlsVersion::SSL_3) {
......
......@@ -58,6 +58,13 @@ public:
bool useSystemTrustStore;
// Whether or not to trust the system's default trust store. Default: true.
bool verifyClients;
// If true, when acting as a server, require the client to present a certificate. The
// certificate must be signed by one of the trusted CAs, otherwise the client will be rejected.
// (Typically you should set `useSystemTrustStore` false when using this flag, and specify
// your specific trusted CAs in `trustedCertificates`.)
// Default: false
kj::ArrayPtr<const TlsCertificate> trustedCertificates;
// Additional certificates which should be trusted. Default: none.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment