Permalink
Fetching contributors…
Cannot retrieve contributors at this time
233 lines (188 sloc) 9.49 KB
// Copyright (c) 2016 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#pragma once
// This file implements TLS (aka SSL) encrypted networking. It is actually a wrapper, currently
// around OpenSSL / BoringSSL / LibreSSL, but the interface is intended to remain
// implementation-agnostic.
//
// Unlike OpenSSL's API, the API defined in this file is intended to be hard to use wrong. Good
// ciphers and settings are used by default. Certificates validation is performed automatically
// and cannot be bypassed.
#include <kj/async-io.h>
namespace kj {
class TlsPrivateKey;
class TlsCertificate;
struct TlsKeypair;
class TlsSniCallback;
enum class TlsVersion {
SSL_3, // avoid; cryptographically broken
TLS_1_0,
TLS_1_1,
TLS_1_2
};
class TlsContext {
// TLS system. Allocate one of these, configure it with the proper keys and certificates (or
// use the defaults), and then use it to wrap the standard KJ network interfaces in
// implementations that transparently use TLS.
public:
struct Options {
Options();
// Initializes all values to reasonable defaults.
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.
TlsVersion minVersion;
// Minimum version. Defaults to minimum version that hasn't been cryptographically broken.
// If you override this, consider doing:
//
// options.minVersion = kj::max(myVersion, options.minVersion);
kj::StringPtr cipherList;
// OpenSSL cipher list string. The default is a curated list designed to be compatible with
// almost all software in curent use (specifically, based on Mozilla's "intermediate"
// recommendations). The defaults will change in future versions of this library to account
// for the latest cryptanalysis.
//
// Generally you should only specify your own `cipherList` if:
// - You have extreme backwards-compatibility needs and wish to enable obsolete and/or broken
// algorithms.
// - You need quickly to disable an algorithm recently discovered to be broken.
kj::Maybe<const TlsKeypair&> defaultKeypair;
// Default keypair to use for all connections. Required for servers; optional for clients.
kj::Maybe<TlsSniCallback&> sniCallback;
// Callback that can be used to choose a different key/certificate based on the specific
// hostname requested by the client.
};
TlsContext(Options options = Options());
~TlsContext() noexcept(false);
KJ_DISALLOW_COPY(TlsContext);
kj::Promise<kj::Own<kj::AsyncIoStream>> wrapServer(kj::Own<kj::AsyncIoStream> stream);
// Upgrade a regular network stream to TLS and begin the initial handshake as the server. The
// returned promise resolves when the handshake has completed successfully.
kj::Promise<kj::Own<kj::AsyncIoStream>> wrapClient(
kj::Own<kj::AsyncIoStream> stream, kj::StringPtr expectedServerHostname);
// Upgrade a regular network stream to TLS and begin the initial handshake as a client. The
// returned promise resolves when the handshake has completed successfully, including validating
// the server's certificate.
//
// You must specify the server's hostname. This is used for two purposes:
// 1. It is sent to the server in the initial handshake via the TLS SNI extension, so that a
// server serving multiple hosts knows which certificate to use.
// 2. The server's certificate is validated against this hostname. If validation fails, the
// promise returned by wrapClient() will be broken; you'll never get a stream.
kj::Own<kj::ConnectionReceiver> wrapPort(kj::Own<kj::ConnectionReceiver> port);
// Upgrade a ConnectionReceiver to one that automatically upgrades all accepted connections to
// TLS (acting as the server).
kj::Own<kj::Network> wrapNetwork(kj::Network& network);
// Upgrade a Network to one that automatically upgrades all connections to TLS. The network will
// only accept addresses of the form "hostname" and "hostname:port" (it does not accept raw IP
// addresses). It will automatically use SNI and verify certificates based on these hostnames.
private:
void* ctx; // actually type SSL_CTX, but we don't want to #include the OpenSSL headers here
struct SniCallback;
};
class TlsPrivateKey {
// A private key suitable for use in a TLS server.
public:
TlsPrivateKey(kj::ArrayPtr<const byte> asn1);
// Parse a single binary (ASN1) private key. Supports PKCS8 keys as well as "traditional format"
// RSA and DSA keys. Does not accept encrypted keys; it is the caller's responsibility to
// decrypt.
TlsPrivateKey(kj::StringPtr pem, kj::Maybe<kj::StringPtr> password = nullptr);
// Parse a single PEM-encoded private key. Supports PKCS8 keys as well as "traditional format"
// RSA and DSA keys. A password may optionally be provided and will be used if the key is
// encrypted.
~TlsPrivateKey() noexcept(false);
TlsPrivateKey(const TlsPrivateKey& other);
TlsPrivateKey& operator=(const TlsPrivateKey& other);
// Copy-by-refcount.
inline TlsPrivateKey(TlsPrivateKey&& other): pkey(other.pkey) { other.pkey = nullptr; }
inline TlsPrivateKey& operator=(TlsPrivateKey&& other) {
pkey = other.pkey; other.pkey = nullptr;
return *this;
}
private:
void* pkey; // actually type EVP_PKEY*
friend class TlsContext;
static int passwordCallback(char* buf, int size, int rwflag, void* u);
};
class TlsCertificate {
// A TLS certificate, possibly with chained intermediate certificates.
public:
TlsCertificate(kj::ArrayPtr<const byte> asn1);
// Parse a single binary (ASN1) X509 certificate.
TlsCertificate(kj::ArrayPtr<const kj::ArrayPtr<const byte>> asn1);
// Parse a chain of binary (ASN1) X509 certificates.
TlsCertificate(kj::StringPtr pem);
// Parse a PEM-encode X509 certificate or certificate chain. A chain can be constructed by
// concatenating multiple PEM-encoded certificates, starting with the leaf certificate.
~TlsCertificate() noexcept(false);
TlsCertificate(const TlsCertificate& other);
TlsCertificate& operator=(const TlsCertificate& other);
// Copy-by-refcount.
inline TlsCertificate(TlsCertificate&& other) {
memcpy(chain, other.chain, sizeof(chain));
memset(other.chain, 0, sizeof(chain));
}
inline TlsCertificate& operator=(TlsCertificate&& other) {
memcpy(chain, other.chain, sizeof(chain));
memset(other.chain, 0, sizeof(chain));
return *this;
}
private:
void* chain[10];
// Actually type X509*[10].
//
// Note that OpenSSL has a default maximum cert chain length of 10. Although configurable at
// runtime, you'd actually have to convince the _peer_ to reconfigure, which is unlikely except
// in specific use cases. So to avoid excess allocations we just assume a max of 10 certs.
//
// If this proves to be a problem, we should maybe use STACK_OF(X509) here, but stacks are not
// refcounted -- the X509_chain_up_ref() function actually allocates a new stack and uprefs all
// the certs.
friend class TlsContext;
};
struct TlsKeypair {
// A pair of a private key and a certificate, for use by a server.
TlsPrivateKey privateKey;
TlsCertificate certificate;
};
class TlsSniCallback {
// Callback object to implement Server Name Indication, in which the server is able to decide
// what key and certificate to use based on the hostname that the client is requesting.
//
// TODO(someday): Currently this callback is synchronous, because the OpenSSL API seems to be
// synchronous. Other people (e.g. Node) have figured out how to do it asynchronously, but
// it's unclear to me if and how this is possible while using the OpenSSL APIs. It looks like
// Node may be manually parsing the ClientHello message rather than relying on OpenSSL. We
// could do that but it's too much work for today.
public:
virtual kj::Maybe<TlsKeypair> getKey(kj::StringPtr hostname) = 0;
// Get the key to use for `hostname`. Null return means use the default from
// TlsContext::Options::defaultKeypair.
};
} // namespace kj