std1.0.0-beta-7 SafeC docs

Source tls/tls.h

View in the repository

// SafeC Standard Library — TLS: encrypted, authenticated streams over a
// connected socket (client and server), on the operating system's TLS
// implementation when there is one and SafeC's own otherwise.
//
//   struct TlsConfig cfg = std::TlsConfig::new();          // verify against the system roots
//   cfg.set_alpn("http/1.1");
//   struct Result<TlsStream, TlsError> r = std::TlsStream::connect(fd, "example.com", &cfg);
//   match (r.get_err()) { case some(e): { printf("%s\n", e.message); } case null: {} }
//   struct TlsStream t = r.value;
//   t.write(...); t.read(...);                             // io_traits.h Stream: BufReader<TlsStream> works
//   t.close();                                             // close_notify, then closes fd
//   cfg.free();
//
// Backends (the 'impl' of a stream; tls_impl_name() names them):
//   TLS_IMPL_OPENSSL         Linux/BSD: libssl/libcrypto 3.x or 1.1, loaded
//                            with dlopen() at run time — nothing to link,
//                            no headers; the system CA store through
//                            OpenSSL's default paths plus the usual bundles.
//   TLS_IMPL_SCHANNEL        Windows: SChannel through SSPI; peers verified
//                            against the Windows certificate store.
//   TLS_IMPL_SECURE_TRANSPORT macOS: Security.framework (SecureTransport,
//                            TLS 1.2 at most; verified with SecTrust against
//                            the system keychains).
//   TLS_IMPL_SAFEC           everywhere: std's own TLS 1.3 (std/crypto's
//                            X25519/P-256/P-384, AES-GCM, ChaCha20-Poly1305,
//                            RSA/ECDSA), verifying against the system's PEM
//                            bundle on disk (or the Windows store).
// Selection (TlsConfig::backend / TlsServerConfig::backend): TLS_BACKEND_AUTO
// takes the SAFEC_TLS environment variable ("os", "safec", "openssl",
// "schannel", "securetransport") when it is set, else the OS backend when it
// is available (and can meet min_version: SecureTransport can't do 1.3),
// else SafeC's. An OS backend that is present but fails is an error, never
// a silent fallback. TLS_BACKEND_OS / _SAFEC / a TLS_IMPL_* force one.
//
// Blocking and non-blocking use: connect()/TlsAcceptor::accept() run the
// whole handshake on a blocking socket. On a non-blocking socket, start with
// TlsStream::client()/TlsAcceptor::start() and call handshake() until it
// returns 0; it, read() and write() return TLS_WANT_READ / TLS_WANT_WRITE
// when the socket would block (wait for that readiness — std/async's
// IoReady, std::Reactor — and call again).
//
// Security notes: peers are verified by default (chain to a trusted root,
// validity dates, key usage, the host name); turn verification off only for
// tests. The SafeC backend is young code: its primitives are checked against
// RFC/NIST test vectors and its handshake against OpenSSL, but it hasn't had
// an external audit; AES there is table-based (ChaCha20-Poly1305, constant
// time, is preferred). See stdlib/tls.md.
#pragma once
#include <std/collections/string.h>
#include <std/collections/vec.h>
#include <std/crypto/x509.h>
#include <std/result.h>

#define TLS_VERSION_1_2 0x0303
#define TLS_VERSION_1_3 0x0304

#define TLS_BACKEND_AUTO   0
#define TLS_BACKEND_OS     1
#define TLS_BACKEND_SAFEC  2

#define TLS_IMPL_NONE              0
#define TLS_IMPL_OPENSSL           1
#define TLS_IMPL_SCHANNEL          2
#define TLS_IMPL_SECURE_TRANSPORT  3
#define TLS_IMPL_SAFEC             4
// TlsConfig::backend also accepts a TLS_IMPL_* (+ this offset) to force one.
#define TLS_BACKEND_IMPL_BASE      16

// read / write / handshake results besides counts and 0.
#define TLS_WANT_READ   -2
#define TLS_WANT_WRITE  -3

// TlsError codes.
#define TLS_OK                    0
#define TLS_ERR_UNAVAILABLE       1   // the backend isn't on this system (or not built for this target)
#define TLS_ERR_CONFIG            2   // bad configuration: unreadable PEM, unknown setting
#define TLS_ERR_IO                3   // the socket failed
#define TLS_ERR_CLOSED            4   // the peer closed the connection mid-handshake
#define TLS_ERR_TIMEOUT           5   // a blocking socket's timeout expired
#define TLS_ERR_PROTOCOL          6   // malformed or unexpected message
#define TLS_ERR_ALERT             7   // the peer sent a fatal alert (TlsError::alert)
#define TLS_ERR_HANDSHAKE         8   // no common version / cipher suite / group / signature scheme
#define TLS_ERR_CERT_UNTRUSTED    9   // no chain to a trusted root (self-signed, unknown CA)
#define TLS_ERR_CERT_HOSTNAME    10   // the certificate isn't for this host
#define TLS_ERR_CERT_EXPIRED     11   // outside its validity period
#define TLS_ERR_CERT_INVALID     12   // bad signature, malformed, wrong key usage, ...
#define TLS_ERR_CERT_REVOKED     13   // revoked (OS backends that check)
#define TLS_ERR_NO_CERT          14   // the peer sent no certificate where one is required
#define TLS_ERR_NO_MEMORY        15
#define TLS_ERR_STATE            16   // wrong call for the stream's state
#define TLS_ERR_INTERNAL         17

#define TLS_ERROR_MESSAGE_MAX 224

namespace std {

// A backend's private state (opaque; each backend keeps its own struct).
struct TlsImpl_ { unsigned char opaque_; };

struct TlsError {
    int       code;      // TLS_OK or TLS_ERR_*
    int       alert;     // the TLS alert (RFC 8446 §6) received or sent, -1 if none
    long long detail;    // the backend's own code (OpenSSL error, SECURITY_STATUS, OSStatus), 0 if none
    char      message[TLS_ERROR_MESSAGE_MAX];

    static struct TlsError none();
    static struct TlsError make(int code, const &char message);
    int ok() const;
};

// The fixed English name of a TLS_ERR_* code.
const &char tls_error_name(int code);

// Client settings. Owns its strings: free() when done (a stream doesn't
// keep a reference to its config).
struct TlsConfig {
    int           verify_peer;       // 1: check the chain and dates (default)
    int           verify_hostname;   // 1: check the name (default)
    int           use_system_roots;  // 1: trust the OS's roots (default)
    struct String ca_pem;            // more trusted roots, PEM (empty: none)
    struct String ca_file;           // a PEM bundle file of more trusted roots
    struct String server_name;       // SNI + the name to verify; empty: connect()'s host
    struct String alpn;              // protocols to offer, comma-separated ("h2,http/1.1")
    int           min_version;       // TLS_VERSION_1_2 (default) or _1_3
    int           max_version;       // TLS_VERSION_1_3 (default)
    int           backend;           // TLS_BACKEND_AUTO (default)
    struct String cert_pem;          // client certificate chain (PEM), if the server asks
    struct String key_pem;           // its private key (PEM: PKCS#8, RSA or EC)

    static struct TlsConfig new();
    void set_ca_pem(const &char pem);
    void set_ca_file(const &char path);
    void set_server_name(const &char name);
    void set_alpn(const &char protocols);
    void set_client_cert(const &char cert_pem, const &char key_pem);
    // Verification off (tests against self-signed servers only).
    void insecure_skip_verify();
    struct TlsConfig clone() const;
    void drop();
    void free() consume;   // = drop()
};

// Server settings: the certificate chain and key are required.
struct TlsServerConfig {
    struct String cert_pem;          // leaf first, then intermediates (PEM)
    struct String key_pem;           // private key (PEM: PKCS#8, RSA or EC)
    struct String alpn;              // protocols the server speaks, in preference order
    int           min_version;       // TLS_VERSION_1_2 (default)
    int           max_version;       // TLS_VERSION_1_3 (default)
    int           backend;           // TLS_BACKEND_AUTO (default)
    int           verify_client;     // 0 none (default), 1 request a certificate, 2 require one
    struct String client_ca_pem;     // roots for client certificates (verify_client != 0)

    static struct TlsServerConfig new();
    // Reads cert_pem / key_pem from files. 0 if either can't be read.
    int  load_files(const &char cert_path, const &char key_path);
    void set_alpn(const &char protocols);
    void drop();
    void free() consume;   // = drop()
};

// A TLS connection over a socket fd it owns once created.
struct TlsStream {
    int           fd;
    int           impl;           // TLS_IMPL_*
    int           state;          // internal: 0 handshaking, 1 open, 2 closed, 3 failed
    int           is_server;
    ?&heap TlsImpl_ impl_;        // internal: the backend's state
    struct TlsError err;          // the last failure
    // Negotiated, once the handshake is done:
    int           version_;       // TLS_VERSION_*
    char          cipher_[64];    // IANA suite name, e.g. "TLS_AES_128_GCM_SHA256"
    char          alpn_[64];      // the protocol agreed, "" if none
    struct Vec<unsigned char> peer_der_;   // the peer's leaf certificate (DER), empty if none

    // Client: handshake on a blocking, connected socket. 'host' names the
    // server (SNI and verification) unless cfg.server_name is set; null cfg
    // = TlsConfig::new()'s defaults. On failure the fd is left open (the
    // caller's to close).
    static struct Result<TlsStream, TlsError> connect(int fd, const &char host, const ?&TlsConfig cfg);
    // Client, non-blocking: set up only; drive with handshake().
    static struct Result<TlsStream, TlsError> client(int fd, const &char host, const ?&TlsConfig cfg);

    // Advances the handshake: 0 when complete, TLS_WANT_READ/WRITE, -1 on
    // failure (see last_error()).
    int handshake();
    // Up to n bytes of application data: the count, 0 once the peer closed
    // (close_notify or EOF), -1 on error, TLS_WANT_READ/WRITE.
    long long read([]unsigned char buf);
    // Up to n bytes (at most one record's worth may be accepted at a
    // time): the count, -1 on error, TLS_WANT_WRITE/READ.
    long long write([]const unsigned char buf);
    // Sends close_notify (once). 0 on success.
    int shutdown();
    // shutdown() if open, frees the TLS state and closes the socket.
    int close() consume;
    void drop();   // close(), ignoring the result
    // Frees the TLS state but leaves the fd open (after a failed handshake,
    // or to hand the socket on).
    void release() consume;
    void free_state_();             // internal: frees the backend state (not peer_der_)

    int is_open() const;
    int version() const;            // TLS_VERSION_*, 0 before the handshake
    const &char cipher() const;     // "" before the handshake
    const &char alpn() const;
    const &char backend_name() const;
    // The peer's leaf certificate summary (subject, issuer, validity,
    // SANs); 0 if there is none.
    int peer_certificate(&X509Cert out) const;
    const &TlsError last_error() const;
};

// A server context: the parsed certificate and key, reused for every
// connection.
struct TlsAcceptor {
    int    impl;     // TLS_IMPL_*
    ?&heap TlsImpl_ impl_;   // internal
    int    verify_client;

    static struct Result<TlsAcceptor, TlsError> new(const &TlsServerConfig cfg);
    // Handshake on a blocking accepted socket; the fd stays open on failure.
    struct Result<TlsStream, TlsError> accept(int fd);
    // Non-blocking: set up only; drive with handshake().
    struct Result<TlsStream, TlsError> start(int fd);
    void drop();
    void free() consume;   // = drop()
};

// Is the backend usable here (its library loads, the target has it)?
int tls_backend_available(int impl);
// What TLS_BACKEND_AUTO picks for a client (SAFEC_TLS included).
int tls_default_impl();
// "openssl", "schannel", "securetransport", "safec", "none".
const &char tls_impl_name(int impl);

} // namespace std