• Kenton Varda's avatar
    Replace all include guards with #pragma once. · 677a52ab
    Kenton Varda authored
    @kloepper pointed out a while back that every compiler you've ever heard of supports this. Plus, it's more concise, it's not prone to copy-paste errors, and it looks nicer.
    
    At the time I wanted to remain consistent and I didn't feel like spending the time to update all my existing code. But, every time I've added a new header since I've cursed the include guard, so I finally broke down and changed it.
    677a52ab
tls.h 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