Internal ngtcp2 crypto API support library

This commit adds code that implements an interface between ngtcp2 and
a crypto library like OpenSSL/LibreSSL/QuicTLS.

The code mostly follows the structure of the original ngtcp2 crypto
library and shares significant amount of code with it, in particular
the portable its part and the part related to QuicTLS. That is done to
make it easier to update the code from time to time when there are
significant updates to either QUIC or TLS specifications. However,
there are many differences:

1. The code works not only on QuicTLS or LibreSSL, but also on OpenSSL
which, unlike the former two, does not (and, likely, will not) provide
native BoringSSL-style QUIC integration API;

2. The public interface of the library (API) has been significantly
simplified and reduced without sacrificing any functionality;

3. The code avoids using any "magic" constants as most of them can be
derived in runtime. That makes the code *much* easier to follow;

4. A lot of code was significantly simplified as we have some
middleware code that makes a lot of things much easier to do in a
secure way (like 'isc_buffer_t');

5. The code is better hardened and contains more security-related
checks;

6. The code is FIPS compliant. The original code is not suitable for
use on FIPS-certified systems and would fail to initialise on them.

That provides a foundation to implement QUIC networking code across
the whole range of platforms we support and in a unified way without
depending on OpenSSL-specific (or, rather any crypto-library specific)
support for QUIC. Any versions of the crypto-libraries we support
should be sufficient for that provided that they have TLSv1.3 support.
This commit is contained in:
Artem Boldariev
2024-09-11 21:37:19 +03:00
parent dbc2d6076b
commit d6a34be81a
2 changed files with 2804 additions and 0 deletions

View File

@@ -0,0 +1,323 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#pragma once
#include <ngtcp2/ngtcp2.h>
#include <isc/tls.h>
#define ISC_NGTCP2_CRYPTO_TOKEN_RAND_DATA_LEN (16)
/*%<
* The length of random data added to a token used for a QUIC token
* generated by 'ngtcp2_crypto_generate_retry_token()' or
* 'ngtcp2_crypto_generate_regular_token()'.
*/
#define ISC_NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY (0xb6)
/*%<
* Retry QUIC token "magic" value.
*/
#define ISC_NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR (0x36)
/*%<
* Regular QUIC token "magic" value.
*/
/*
* MAX(EVP_GCM_TLS_TAG_LEN, EVP_CCM_TLS_TAG_LEN, EVP_CHACHAPOLY_TLS_TAG_LEN) =
* 16
*/
#define ISC__NGTCP2_CRYPTO_MAX_AEAD_TAG_LEN (16)
#define ISC_NGTCP2_CRYPTO_MAX_RETRY_TOKEN_LEN \
(/* magic = */ sizeof(uint8_t) + /* cid_len = */ sizeof(uint8_t) + \
NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp) + \
ISC__NGTCP2_CRYPTO_MAX_AEAD_TAG_LEN + \
ISC_NGTCP2_CRYPTO_TOKEN_RAND_DATA_LEN)
/*%<
* Max length of a QUIC retry token.
*/
#define ISC_NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_LEN \
(/* magic = */ sizeof(uint8_t) + sizeof(ngtcp2_tstamp) + \
ISC__NGTCP2_CRYPTO_MAX_AEAD_TAG_LEN + \
ISC_NGTCP2_CRYPTO_TOKEN_RAND_DATA_LEN)
/*%<
* Max length of a QUIC regular token.
*/
#define ISC_NGTCP2_CRYPTO_STATIC_SECRET_LEN (16)
/*%<
* QUIC static secret length. In particular, used for tokens
* generation and verification. Intended to be used on a per-listener
* level.
*/
void
isc_ngtcp2_crypto_set_crypto_callbacks(ngtcp2_callbacks *callbacks);
/*%<
* Set the cryptography related callbacks within the the given
* 'callbacks' object. It never overwrites already set callbacks.
*
* Requires:
*\li 'callbacks' != NULL.
*/
const ngtcp2_callbacks *
isc_ngtcp2_crypto_get_default_crypto_callbacks(void);
/*%<
* Returns a global "ngtcp2_callbacks" object pointer with all
* default ngtcp2 crypto callbacks set. Might be useful if you want
* wrap one of the default callbacks with custom code for some
* reason. Use with care: you unlikely want to replace a default callback
* completely, but rather call it somewhere in your code, as the default
* callbacks are heavily intertwined.
*/
isc_result_t
isc_ngtcp2_crypto_bind_conn_tls(ngtcp2_conn *conn, isc_tls_t *tls);
/*%<
* Associate the given connection object 'conn' and a TLS object
* 'tls' with each other.
*
* It is expected that the connection object was created with the
* cryptography callbacks that are set with
* 'isc_ngtcp2_crypto_set_crypto_callbacks()'.
*
* NOTE: internally it calls 'isc_tls_set_quic_method()',
* 'ngtcp2_conn_set_tls_native_handle()',
* 'isc_tls_quic_set_app_data()'. Thus, it occupies the associated
* properties within these objects.
*
* Requires:
*\li 'conn' != NULL;
*\li 'tls' != NULL.
*/
isc_result_t
isc_ngtcp2_crypto_generate_stateless_reset_token(uint8_t *token_buf,
const size_t token_buflen,
const uint8_t *secret,
const size_t secretlen,
const ngtcp2_cid *cid);
/*%<
* Generate a stateless reset token via HKDF using the given secret
* ('secret') and the connection identifier ('cid') as input.
*
* Requires:
*\li 'token_buf' != NULL;
*\li 'token_buflen' >= NGTCP2_STATELESS_RESET_TOKENLEN;
*\li 'secret' != NULL;
*\li 'secretlen' > 0;
*\li 'cid' != NULL && 'cid->data' != NULL && 'cid->datalen' > 0.
*
* Returns 'ISC_R_SUCCESS' on success.
*/
size_t
isc_ngtcp2_crypto_generate_retry_token(
uint8_t *token_buf, const size_t token_buflen, const uint8_t *secret,
const size_t secretlen, const uint32_t version,
const ngtcp2_sockaddr *remote_addr, const ngtcp2_socklen remote_addrlen,
const ngtcp2_cid *retry_scid, const ngtcp2_cid *orig_dcid,
const ngtcp2_tstamp ts);
/*%<
* Generate a "Retry" packet token in the buffer pointed to by
* 'token' using the given secret ('secret"), QUIC version
* ('version'), remote client address ('remote_addr'), source
* connection ID chosen by server ('retry_scid'), original
* destination connection ID sent by the client in the "Initial"
* packet ('orig_dcid'). The timestamp ('ts') is supposed to be the
* token creation (current) timestamp.
*
* The successfully generated token starts with a byte equal to
* 'ISC_NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY'.
*
* Requires:
*\li 'token_buf' != NULL;
*\li 'token_buflen' >= ISC_NGTCP2_CRYPTO_MAX_RETRY_TOKEN_LEN;
*\li 'secret' != NULL;
*\li 'secretlen' > 0;
*\li 'remote_addr' != NULL;
*\li 'remote_addrlen' <= sizeof(ngtcp2_sockaddr_union);
*\li 'retry_scid' != NULL && 'retry_scid->data' != NULL &&
* 'retry_scid->datalen' > 0;
*\li 'orig_dcid' != NULL && 'orig_dcid->data' != NULL &&
* 'orig_dcid->datalen' > 0.
*
* Returns the newly generated token size on success, or 0 on failure.
*/
isc_result_t
isc_ngtcp2_crypto_verify_retry_token(
ngtcp2_cid *orig_dcid, const uint8_t *token_buf,
const size_t token_buflen, const uint8_t *secret,
const size_t secretlen, const uint32_t version,
const ngtcp2_sockaddr *remote_addr, const ngtcp2_socklen remote_addrlen,
const ngtcp2_cid *dcid, const ngtcp2_duration timeout,
const ngtcp2_tstamp ts);
/*%<
* Verify a "Retry" token stored in the buffer pointed to by
* 'tokenbuf' using the given secret ('secret"), QUIC version
* ('version'), remote client address ('remote_addr'), the destination
* Connection ID in "Initial" packet sent by the client ('dcid').
* When verification succeeds, the extracted original destination
* connection ID from the "Initial" packet sent by the client that
* triggered the "Retry" packet is saved into buffer pointed to by
* 'orig_dcid'. The timestamp ('ts') is supposed to be the
* current timestamp.
*
* Requires:
*\li 'orig_dcid' != NULL && 'orig_dcid->data' != NULL.
*\li 'token_buf' != NULL;
*\li 'token_buflen' >= ISC_NGTCP2_CRYPTO_MAX_RETRY_TOKEN_LEN;
*\li 'secret' != NULL;
*\li 'secretlen' > 0;
*\li 'remote_addr' != NULL;
*\li 'remote_addrlen' <= sizeof(ngtcp2_sockaddr_union);
*\li 'dcid' != NULL && 'dcid->data' != NULL &&
* 'dcid->datalen' > 0.
*
* Returns:
*\li #ISC_R_SUCCESS - on success;
*\li #ISC_R_UNEXPECTED - on unexpected retry token magic;
*\li #ISC_R_TIMEDOUT - on timeout;
*\li #ISC_R_FAILURE - on generic failure.
*/
size_t
isc_ngtcp2_crypto_generate_regular_token(
uint8_t *token_buf, const size_t token_buflen, const uint8_t *secret,
const size_t secretlen, const ngtcp2_sockaddr *remote_addr,
const ngtcp2_socklen remote_addrlen, const ngtcp2_tstamp ts);
/*%<
* Generate a token in the buffer pointed by 'token_buf' that is to
* be sent within a "NEW_TOKEN" frame. The secret ('secret') is used
* for generating keys and protecting data with AEAD. The client
* address is specified with 'remote_addr'. The timestamp ('ts') is
* supposed to be the token creation (current) timestamp.
*
* The successfully generated token starts with a byte equal to
* 'ISC_NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR'.
*
* Requires:
*\li 'token_buf' != NULL;
*\li 'token_buflen' >= ISC_NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_LEN;
*\li 'secret' != NULL;
*\li 'secretlen' > 0;
*\li 'remote_addr' != NULL;
*\li 'remote_addrlen' <= sizeof(ngtcp2_sockaddr_union).
*
* Returns the newly generated token size on success, or 0 on failure.
*/
isc_result_t
isc_ngtcp2_crypto_verify_regular_token(const uint8_t *token,
const size_t tokenlen,
const uint8_t *secret, size_t secretlen,
const ngtcp2_sockaddr *remote_addr,
const ngtcp2_socklen remote_addrlen,
const ngtcp2_duration timeout,
const ngtcp2_tstamp ts);
/*%<
* Verify the token in the buffer pointed by 'token_buf' that was
* received within a "NEW_TOKEN" frame. The secret ('secret') is used
* for verifying the data protected with AEAD. The client address is
* specified with 'remote_addr'. The timestamp ('ts') is supposed to be the
* current timestamp.
*
* The successfully verified token start with a byte that contains the
* 'ISC_NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR' value.
*
* Requires:
*\li 'token_buf' != NULL;
*\li 'token_buflen' >= ISC_NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_LEN;
*\li 'secret' != NULL;
*\li 'secretlen' > 0;
*\li 'remote_addr' != NULL;
*\li 'remote_addrlen' <= sizeof(ngtcp2_sockaddr_union).
*
* Returns:
*\li #ISC_R_SUCCESS - on success;
*\li #ISC_R_UNEXPECTED - on unexpected regular token magic;
*\li #ISC_R_TIMEDOUT - on timeout;
*\li #ISC_R_FAILURE - on generic failure.
*/
ssize_t
isc_ngtcp2_crypto_write_connection_close(uint8_t *dest, const size_t destlen,
const uint32_t version,
const ngtcp2_cid *dcid,
const ngtcp2_cid *scid,
const uint64_t error_code,
const uint8_t *reason,
const size_t reasonlen);
/*%<
* Write an "Initial" packet containing "CONNECTION_CLOSE" with the
* given 'error_code' and an optional 'reason' to the buffer pointed
* by 'dest'. The 'dcid' must be the source connection ID in
* "Initial" packet from the client. The 'scid' must be the
* destination connection ID in the "Initial" packet from the client.
*
* The function is supposed to be used to close a connection without
* altering the server's internal state (e.g. in the case when a retry
* token verification fails): that is in the "hot path." It must not
* be used on a client side.
*
* Internally, the function calls 'ngtcp2_pkt_write_connection_close()'.
*
* Requires:
*\li 'dest' != NULL;
*\li 'destlen' > 0;
*\li 'dcid' != NULL && 'dcid->datalen' > 0 && 'dcid->data' != NULL;
*\li 'scid' != NULL && 'scid->datalen' > 0 && 'scid->data' != NULL;
*\li 'reason' != NULL;
*\li 'reasonlen' > 0.
*
* Returns the newly generated token size on success, or <=0 on failure.
*/
ssize_t
isc_ngtcp2_crypto_write_retry(uint8_t *dest, const size_t destlen,
const uint32_t version, const ngtcp2_cid *dcid,
const ngtcp2_cid *scid,
const ngtcp2_cid *orig_dcid,
const uint8_t *token_buf,
const size_t token_buflen);
/*%<
* Write a "Retry" packet to the buffer pointed by "dest".
*
* 'dcid' is the connection ID in a packet that appeared as a
* source connection ID sent by the client.
*
* 'scid' is the server chosen source connection ID.
*
* 'orig_dcid' specifies the original destination connection ID
* which appeared in the packet as the destination connection ID sent
* by the client.
*
* 'token' specifies the pointer to the retry token data.
*
* Internally, the function calls 'ngtcp2_pkt_write_retry()'.
*
* Requires:
*\li 'dest' != NULL;
*\li 'destlen' > 0;
*\li 'dcid' != NULL && 'dcid->datalen' > 0 && 'dcid->data' != NULL;
*\li 'scid' != NULL && 'scid->datalen' > 0 && 'scid->data' != NULL;
*\li 'token_buf' != NULL;
*\li 'token_buflen' > 0.
*
* Returns the newly generated packet size on success, or <=0 on failure.
*/

2481
lib/isc/quic/ngtcp2_crypto.c Normal file

File diff suppressed because it is too large Load Diff