tempesta-tech / tempesta

All-in-one solution for high performance web content delivery and advanced protection against DDoS and web attacks
https://tempesta-tech.com/
GNU General Public License v2.0
617 stars 103 forks source link

Load certificates with Linux asymmetric keys API #1332

Open krizhanovsky opened 5 years ago

krizhanovsky commented 5 years ago

The current asymmetric keys API

The TLS handshakes code still relies on the mbed TLS code to load x509 certificates and private keys, which significantly duplicates the Linux asymmetric keys API. Following files in linux/net/tls/ are implementing the logic:

asn1.c oid.[ch] pem.[ch] pkparse.c x509.c x509_crt.c

and in linux/include/net/tls:

x509_crl.h x509_crt.h x509.h

linux/net/tls/x509_crl.c and linux/include/net/tls/x509_crl.h are responsible for CRL, which is not implemented by the Linux kernel, but required for client side TLS handshakes. The code, after some modifications, should be moved to linuxcrypto/asymmetric_keys/, but this isn't for this task.

At the moment a certificate and a private key are populated to the TLS handshake code by the separate module (Tempesta FW) in the following way:

  1. read a file with the certificate or a private key (e.g. in tfw_cfg_read_file() called from tfw_tls_set_cert_key()) using kernel_read().
  2. ttls_x509_crt_parse() or ttls_pk_parse_key() are called to parse the certificate or private key correspondingly. The parsing functions can handle DER or PEM formats.
  3. As the results of the parsing a chain of TlsX509Crt certificates (we'll convert all data structure names to lower case to satisfy the Linux kernel coding style) or TlsPkCtx.

The parsed certificate and the private key are associated with a server name (SNI or 'vhost' in terms of Tempesta FW). tfw_tls_sni() looks up the appropriate server configuration by a SNI shipped in ClientHello message and sets TlsPeerCfg for the current TLS connection handler TlsCtx.

Each server name, and TlsPeerCfg correspondingly, may have multiple pairs of a certificate and a private key.

X509 certificates are parsed into and handled by pages. The pages are scattered (sg_set_buf()) int sk_buff fragments to be sent to a client without copies, we only track the pages reference counter.

High level requirements

We need a generic service, which can listen for example on 443 TCP port and accept ingress TLS connections for many server names (SNIs). The pseudo-configuration for the system might look like:

listen :443;

host0.com {
    tls_certificate /root/cert_rsa0.pem;
    tls_certificate_key /root/privkey_rsa0.pem;

    tls_certificate /root/cert_ec0.pem;
    tls_certificate_key /root/privkey_ec0.pem;
}
....
....
host9999.com {
    tls_certificate /root/cert_ec9999.pem;
    tls_certificate_key /root/privkey_ec9999.pem;
}

In this case we have 10 thousand server names and all of them are served on the same TCP port. The required keys/keyrings lookup must be fast enough on such a number of SNIs. Each of the SNIs may have several pairs of x509 certificates and private keys to handle different types of TLS handshake ciphersuites.

There could be several different services (e.g. an NFS and HTTPS servers) operating with different SNIs and different sets of certificates and private keys.

QUESTION: Should we use ACLs for this? Otherwise it seems we need to require the services to be run with different users.

API example

The TLS implementation must register 2 new key_types: tls_cert for TLS certificates and tls_priv for private keys. A keyring handles the set of pairs of a certificate and corresponding private key (e.g. EC and RSA private key and a public key within a certificate) for a particular SNI.

To create a new keyring for SNI host0.com:

key_serial_t my_sni_kr = add_key("keyring", "host0.com", NULL, 0, 0);

QUESTION: Do we need to distinguish keyrings for example.com for an HTTPS service from an NFS service?

Load the certificate and the private key for the SNI:

char *rsa_cert = read_the_rsa_certificate_file();
char *rsa_key = read_the_rsa_private_key_file();

add_key("tls_cert", "tlscrt:", rsa_data, cert_len, my_sni_kr);
add_key("tls_priv", "tlspk:", rsa_key, pk_len, my_sni_kr);

QUESTION: is it a good idea to load different certificate and private key to the same keyring? E.g.

char *ec_cert = read_the_rsa_certificate_file();
char *ec_key = read_the_rsa_private_key_file();

add_key("tls_cert", "tlscrt:", ec_data, cert_len, my_sni_kr);
add_key("tls_priv", "tlspk:", ec_key, pk_len, my_sni_kr);

A(?) It seems it's doable with composing the key description that we want to search for, should be solved also in rxrpc.

Link the keys with TLS listening socket for all the managed server names (SNIs), see also #1433 :

setsockopt(sd, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));

struct tls12_crypto_info_aes_gcm_128 ci0 = {
    .versions = TLS_1_2_VERSION | TLS_1_3_VERSION,
    .tls_12_cipher_suites = ECDHE_ECDSA_AES128_GCM_SHA256 | ECDHE_ECDSA_AES256_GCM_SHA384,
    .tls_13_ciphersuites = TLS_AES_128_GCM_SHA256 | TLS_AES_256_GCM_SHA384,
    .ecurves = secp256r1 | rsa,
    .tls_keyring = "host0.com",
};
setsockopt(sd, SOL_TLS, TLS_HS, &ci0, sizeof(ci0));
....
struct tls12_crypto_info_aes_gcm_128 ci9999 = {
    .....
    .tls_keyring = "host9999.com",
};
setsockopt(sd, SOL_TLS, TLS_HS, &ci9999, sizeof(ci9999));

Moving to the Linux asymmetric keys API

General requirements

Linux provides add_key(2) system call with asymmetric-key(7) API which allows to load X509 certificates including ECDSA keys. This API should be used instead of current mbedTLS-inherited X509 infrastructure.

TLS listening sockets might be requested by processes with different UIDs and GIDs and/or containers. Different users/groups and containers must be able to update/revoke their certificates and keys, but don't have access to certificates and keys of other users/groups and containers working on the same host. The asymmetric-key(7) API takes care about all the security concerns (see key_task_permission()) and TLS application of the interface should use the same permission control mechanism.

QUESTION: The keys are stored in the associative array (see e.g. search_nested_keyrings()), but can it efficiently handle thousands of keys? A (by David Howells):

The keyring is a wrapper around an RCU-searchable radix tree of
keys.  You also need a good hash function.  The tree splits up to 16 ways at
each node, taking a nibble from the {hash, description} each time.  With an
ideal hash function, 10000 keys, say, would take log16(10000) = 4 steps to
find the node, and then it would need to iterate through the keys in the
terminal node (up to 16).

Loading certificates and private keys

The new TLS handshakes kernel service must:

  1. register the new key types tls_cert and tls_priv and appropriate instantiation and parsing code
  2. the keys instantiation and parsing code must use the Linux x509_cert_parse() and pkcs8_key_preparse() routines instead of current ttls_x509_crt_parse() or ttls_pk_parse_key(). (Probably we should leave RFC 5915 and PEM parsing for the user space.)
  3. tls_setsockopt() must be extended to accept the extended optval and call request_key() for a server name
  4. struct tls_context should be extened by a pointer to handshake specific handler (TlsCtx in the current code), which must be linked with the keyring

Keys storage

Since request_key()/request_key_rcu() search the keys in the current process context, we can not use the interface to process TLS handshakes in softirq (#1433). Thus, the setsockopt() must extract the keys and place them in alternate storage for the listening socket. The storage must be keyed by SNI string.

CAP_NET_ADMIN capability is required for a process working with the private keys and certificates.

References

Kernel key management Kernel Key Retention Service

Testing

krizhanovsky commented 3 weeks ago

Move priorities from upstream to the product release, so 1.0 for now