Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
277 lines (223 sloc) 11.5 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;
class TlsConnection;
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::Promise<kj::AuthenticatedStream> wrapServer(kj::AuthenticatedStream stream);
kj::Promise<kj::AuthenticatedStream> wrapClient(
kj::AuthenticatedStream stream, kj::StringPtr expectedServerHostname);
// Like wrapServer() and wrapClient(), but also produces information about the peer's
// certificate (if any). The returned `peerIdentity` will be a `TlsPeerIdentity`.
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.
};
class TlsPeerIdentity final: public kj::PeerIdentity {
public:
KJ_DISALLOW_COPY(TlsPeerIdentity);
~TlsPeerIdentity() noexcept(false);
kj::String toString() override;
kj::PeerIdentity& getNetworkIdentity() { return *inner; }
// Gets the PeerIdentity of the underlying network connection.
bool hasCertificate() { return cert != nullptr; }
// Did the peer even present a (trusted) certificate? Servers must always present certificates.
// Clients need only present certificates when the `verifyClients` option is enabled.
//
// Methods of this class that read details of the certificate will throw exceptions when no
// certificate was presented. We don't have them return `Maybe`s because most applications know
// in advance whether or not a certificate should be present, so it would lead to lots of
// `KJ_ASSERT_NONNULL`...
kj::String getCommonName();
// Get the authenticated common name from the certificate.
bool matchesHostname(kj::StringPtr hostname);
// Check if the certificate authenticates the given hostname, considering wildcards and SAN
// extensions. If no certificate was provided, always returns false.
// TODO(someday): Methods for other things. Match hostnames (i.e. evaluate wildcards and SAN)?
// Key fingerprint? Other certificate fields?
private:
void* cert; // actually type X509*, but we don't want to #include the OpenSSL headers here.
kj::Own<kj::PeerIdentity> inner;
public: // (not really public, only TlsConnection can call this)
TlsPeerIdentity(void* cert, kj::Own<kj::PeerIdentity> inner, kj::Badge<TlsConnection>)
: cert(cert), inner(kj::mv(inner)) {}
};
} // namespace kj