// 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.
|
|
#ifndef KJ_COMPAT_TLS_H_
|
#define KJ_COMPAT_TLS_H_
|
// 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.
|
|
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
|
|
static int sniCallback(void* ssl, int* ad, void* arg);
|
};
|
|
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);
|
// Parse a single PEM-encoded 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() 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;
|
};
|
|
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
|
|
#endif // KJ_COMPAT_TLS_H_
|