Compare commits

...

27 Commits

Author SHA1 Message Date
Artem Boldariev
0813f9dd91 fixup! WIP: QUIC session implementation 2025-03-23 13:31:28 +02:00
Artem Boldariev
bf75c33368 fixup! WIP: QUIC state machine unit test 2025-03-21 16:38:33 +02:00
Artem Boldariev
7a0b78634c fixup! fixup! WIP: QUIC session implementation 2025-03-21 16:38:25 +02:00
Artem Boldariev
f887792c97 fixup! fixup! WIP: QUIC session implementation 2025-03-21 13:56:38 +02:00
Artem Boldariev
40bf2f894b fixup! WIP: QUIC session implementation 2025-03-21 12:05:35 +02:00
Artem Boldariev
8e80d9d8f5 WIP: integrate QUIC session test into the codebase 2025-03-21 11:35:22 +02:00
Artem Boldariev
681dd90ee6 WIP: QUIC state machine unit test 2025-03-21 11:35:22 +02:00
Artem Boldariev
417098cecb WIP: integrate QUIC session into the codebase 2025-03-21 11:35:22 +02:00
Artem Boldariev
667989a2a4 WIP: QUIC session implementation
EXP: explicit timer updates
2025-03-21 11:35:22 +02:00
Artem Boldariev
cb0adfce3e Integrate generic ngtcp2 integration utilities
This commit adds the ngtcp2 integration utilities into the build
system.
2025-03-21 11:35:22 +02:00
Artem Boldariev
71969ee5d9 ngtcp2 integration test
This commits adds a test that helps to ensure that ngtcp2 is working
well on top of the custom internal crypto API implementation.
2025-03-21 11:35:22 +02:00
Artem Boldariev
cda2eb06b8 Integrate generic ngtcp2 integration utilities
This commit adds the ngtcp2 integration utilities into the build
system.
2025-03-21 11:35:22 +02:00
Artem Boldariev
a1557cc4ca Generic ngtcp2 integration utilities
This commit adds a set of functions which are aimed at simplification
of ngtcp2 integration in the BIND's code-base. As such, it complements
the ngtcp2 crypto API integration library.
2025-03-21 11:35:22 +02:00
Artem Boldariev
3516743b6a Integrate internal ngtcp2 crypto API support library
This commit integrates the internal ngtcp2 crypto API library
implementation into the build system.
2025-03-14 09:26:54 +02:00
Artem Boldariev
d6a34be81a 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.
2025-03-14 09:26:54 +02:00
Artem Boldariev
dbc2d6076b Integrate the QUIC integration API test into the build system
This commit integrates the QUIC integration API unit test into the
build system.
2025-03-14 09:26:54 +02:00
Artem Boldariev
c92b29163b QUIC integration API test
This commit adds a unit test specifically intended to verify that a
QUIC client and server could complete a handshake using the provided
wrapping code. It only simulates data exchanges via networking stack,
no networking or multi-threading are used, making a it helpful for
debugging QUIC-related handshake code.

Moreover, there are check that ensure that TLS session resumption
works over TLS too.

When QuicTLS is used, we ensure the portable OpenSSL QUIC
compatibility code (which is compatible with QuicTLS) interoperability
against a native QUIC API provided by the library. That ensure
compatibility with this widely deployed library without a need to
write networking code at all.
2025-03-14 09:26:54 +02:00
Artem Boldariev
71106440a1 Integrate the portable QUIC integration API into the build system
The commit integrates the QUIC integration API into the projects's
build system.
2025-03-04 00:48:10 +02:00
Artem Boldariev
ca3c776730 Portable implementation of QUIC integration API
This commit adds a QUIC integration interface implementation that is
implemented in such a way that allows it to work on OpenSSL and its
highly compatible forks that may not have native BoringSSL-style QUIC
integration API.

The code is know to not work on LibreSSL (due to deliberately broken
functionality within the library), but works on QuicTLS and
OpenSSL (1.1.1+).
2025-03-04 00:48:10 +02:00
Artem Boldariev
e857a5ffe7 Integrate QUIC/TLS crypto utilities into the build system
This commit integrates the QUIC/TLS crypto utilities into the build
system.
2025-03-04 00:48:10 +02:00
Artem Boldariev
ba7f408fee Add QUIC/TLS crypto utilities
This commit adds a set of QUIC/TLS related cryptography functions
which is intended to be used by both OpenSSL QUIC compatibility code
and ngtcp2 crypto API implementation.

These can be considered a lowest level QUIC cryptography building
blocks.
2025-03-04 00:48:09 +02:00
Artem Boldariev
c092387582 Integrate native implementation of QUIC integration API
This commit adds the native implementation of the QUIC integration API
into the build system.
2025-03-04 00:48:09 +02:00
Artem Boldariev
64d08b7f1a Native implementation of QUIC integration API
This commit adds a QUIC integration interface implementation that uses
the native QUIC integration API provided by LibreSSL and QuicTLS.
2025-03-04 00:48:09 +02:00
Artem Boldariev
9faa6d97a5 Integrate QUIC/TLS interaction interface into the build system
This commit integrates the QUIC/TLS interaction interface into the
projects build system.
2025-03-04 00:48:09 +02:00
Artem Boldariev
bcc519081f QUIC/TLS interaction interface and utilities
This commit provides an interface for interacting with a QUIC
integration TLS functionality. The interface is designed in such a way
that allows multiple QUIC integration APIs co-exist in one
codebase. It is modelled after the QUIC integration API found in
BoringSSL/LibreSSL/QuicTLS and thus makes it easy to make an
implementation of the interface for these libraries.

Two implementations are planned:

1. A "native" one that uses the QUIC integration API provided by
crypto libraries (LibreSSL/QuicTLS).
2. A portable compatibility layer.

An API that allows switching between the two at runtime is meant to
aid in unit and interoperability testing.
2025-03-04 00:48:09 +02:00
Artem Boldariev
ee19edc257 Make SSLKEYLOGFILE code reusable
The commit makes $SSLKEYLOGFILE-related code reusable so that we can
use it in QUIC-related code where the callback needs to be overridden.
2025-03-04 00:48:08 +02:00
Artem Boldariev
dfde7bc95f QUIC related autoconf changes
This commit adds QUIC related configuration options related to
libngtcp2 and support for QUIC provided by OpenSSL or it's forks.
2025-03-03 16:20:13 +02:00
24 changed files with 14248 additions and 3 deletions

View File

@@ -649,6 +649,7 @@ LIBS="$OPENSSL_LIBS $LIBS"
#
# Check for functions added in OpenSSL or LibreSSL
#
AC_CHECK_FUNCS([CRYPTO_free_ex_index])
AC_MSG_CHECKING([for Ed448 support])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([[#include <openssl/evp.h>]],
@@ -690,6 +691,74 @@ AX_RESTORE_FLAGS([openssl])
AC_SUBST([OPENSSL_CFLAGS])
AC_SUBST([OPENSSL_LIBS])
# [pairwise: --enable-quic --with-libngtcp2=auto, --enable-quic --with-libngtcp2=yes, --disable-quic]
AC_ARG_ENABLE([quic],
[AS_HELP_STRING([--disable-quic], [disable QUIC, removes dependency on libngtcp2 (default is --enable-quic)])],
[], [enable_quic=yes])
# [pairwise: skip]
AC_ARG_WITH([libngtcp2],
[AS_HELP_STRING([--with-libngtcp2],
[build with libngtcp2 library [yes|no|auto] (default is auto)])],
[], [with_libngtcp2="auto"])
AS_IF([test "$enable_quic" = "yes"],
[AS_CASE([$with_libngtcp2],
[no],[AC_MSG_ERROR([Use '--disable-quic' to disable QUIC support])],
[auto|yes],[PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 1.0.0],
[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <openssl/ssl.h>]],
[#if (!defined SSL_OP_NO_TLSv1_3) && (!defined TLS1_3_VERSION)
#error TLSv1.3 support is required for QUIC
#endif
])],
[AC_DEFINE([HAVE_LIBNGTCP2], [1], [Build with QUIC support])],
[AC_MSG_ERROR(m4_normalize([TLS version 1.3 support is required for QUIC but it is not provided by the OpenSSL/LibreSSL library in use. Either use an OpenSSL/LibreSSL library version with TLSv1.3 support or use --disable-quic to disable QUIC support.]))])],
[AC_MSG_ERROR(m4_normalize([QUIC support requested, but libnghttp2 not found.
Either install libngtcp2 or use --disable-quic.]))])],
[AC_MSG_ERROR([Specifying libngtcp2 installation path is not supported, adjust PKG_CONFIG_PATH instead])])])
AM_CONDITIONAL([HAVE_LIBNGTCP2], [test -n "$LIBNGTCP2_LIBS"])
AS_IF([test "$enable_quic" = "yes"],
AC_MSG_CHECKING([whether the OpenSSL library provides native TLS integration for QUIC])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([[#include <openssl/ssl.h>]],
[[SSL_QUIC_METHOD quic_method = { 0 };]])],
[quic_native="yes"]
[AC_DEFINE([HAVE_NATIVE_BORINGSSL_QUIC_API], [1], [define if the OpenSSL library provides native TLS integration for QUIC])
AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])]))
AM_CONDITIONAL([HAVE_NATIVE_BORINGSSL_QUIC_API], [test "$quic_native" = "yes"])
AS_IF([test "$quic_native" = "yes"],
AC_MSG_CHECKING([whether the SSL_QUIC_METHOD has both set_read_secret() and set_write_secret()])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([[#include <openssl/ssl.h>]],
[[SSL_QUIC_METHOD quic_method = { 0 };
void *set_read_secret = (void *)quic_method.set_read_secret;
void *set_write_secret = (void *)quic_method.set_write_secret;]])],
[quic_has_set_read_write_secret="yes"]
[AC_DEFINE([HAVE_QUIC_METHOD_SET_READ_WRITE_SECRET], [1], [define if the SSL_QUIC_METHOD has both set_read_secret() and set_write_secret()])
AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])]));
AM_CONDITIONAL([HAVE_QUIC_METHOD_SET_READ_WRITE_SECRET], [test "$quic_has_set_read_write_secret" = "yes"])
AC_MSG_CHECKING([whether LibreSSL is used])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([[#include <openssl/opensslv.h>]],
[[#ifndef LIBRESSL_VERSION_NUMBER
#error LibreSSL is not used
#endif
]])],
[have_libressl="yes"]
[AC_DEFINE([HAVE_LIBRESSL], [1], [define if LibreSSL is used])
AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])])
AM_CONDITIONAL([HAVE_LIBRESSL], [test "$have_libressl" = "yes"])
AC_CHECK_FUNCS([clock_gettime])
# [pairwise: --with-gssapi=yes, --with-gssapi=auto, --without-gssapi]

View File

@@ -256,6 +256,44 @@ libisc_la_LIBADD += \
$(LIBXML2_LIBS)
endif HAVE_LIBXML2
if HAVE_LIBNGTCP2
libisc_la_HEADERS += \
include/isc/ngtcp2_crypto.h \
include/isc/ngtcp2_utils.h \
include/isc/quic.h
libisc_la_SOURCES += \
quic/ngtcp2_crypto.c \
quic/ngtcp2_utils.c \
quic/quic-int.h \
quic/quic_cid.c \
quic/quic_crypto.c \
quic/quic_crypto.h \
quic/quic_session.c \
quic/quic_session.h \
quic/tls_quic.c
if HAVE_NATIVE_BORINGSSL_QUIC_API
libisc_la_SOURCES += \
quic/quic_interface_native.c
endif
# The QUIC compatibility layer will not work on LibreSSL
# due to purposefully broken key logging functionality
if !HAVE_LIBRESSL
libisc_la_SOURCES += \
quic/quic_interface_compat.c \
quic/tls_keylog_parser.c
endif
libisc_la_CPPFLAGS += \
$(LIBNGTCP2_CFLAGS)
libisc_la_LIBADD += \
$(LIBNGTCP2_LIBS)
endif
if !HAVE_SYSTEMTAP
DTRACE_DEPS = libisc_la-rwlock.lo libisc_la-job.lo
DTRACE_OBJS = .libs/libisc_la-rwlock.$(OBJEXT) .libs/libisc_la-job.$(OBJEXT)

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.
*/

View File

@@ -0,0 +1,249 @@
/*
* 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/region.h>
#include <isc/sockaddr.h>
#define ISC_NGTCP2_PROTO_VER_RESERVED ((uint32_t)0x1a2a3a4au)
/*%<
* The versions in form of 0x?a?a?a?a are a reserved to test version
* negotiation.
*/
#define ISC_NGTCP2_MAX_POSSIBLE_CID_LENGTH (255)
/*%<
* Maximum theoretical size of a CID that is possible and supported
* by 'ngtcp2_pkt_decode_version_cid()'. All currently used versions
* of QUIC have smaller limit defined as 'NGTCP2_MAX_CIDLEN', but
* future versions of QUIC might use larger CIDs.
*/
void
isc_ngtcp2_gen_cid(ngtcp2_cid *restrict cid, const size_t size);
/*%<
* Generate a new connection ID data.
*
* Requires:
*\li 'cid' != NULL;
*\li 'size' >= NGTCP2_MIN_CIDLEN && 'size' <=
* NGTCP2_MAX_CIDLEN.
*/
void
isc_ngtcp2_copy_cid(ngtcp2_cid *restrict dst, const ngtcp2_cid *restrict src);
/*%<
* Copy a connection ID data. 'dst->data' must point to a buffer
* that is large enough to hold the copied identifier.
*
* Requires:
*\li 'dst' != NULL;
*\li 'src' != NULL && 'src->datalen' > 0.
*/
static inline void
isc_ngtcp2_cid_region(ngtcp2_cid *restrict cid, isc_region_t *restrict region) {
REQUIRE(cid != NULL);
REQUIRE(region != NULL);
region->base = &cid->data[0];
region->length = cid->datalen;
}
/*%<
* Initialize the given 'isc_region_t' object to point to data held
* by the given 'ngtcp2_cid' object.
*
* Requires:
*\li 'cid' != NULL;
*\li 'region' != NULL.
*/
void
isc_ngtcp2_addr_init(ngtcp2_addr *restrict ngaddr,
const isc_sockaddr_t *restrict addr);
/*%<
* Initialize the given 'ngtcp2_addr' object according the data from
* the given 'isc_sockaddr_t' object.
*
* NOTE: Please keep in mind that no data is copied, only pointers are
* set and they are valid for as long as the given isc_sockaddr_t'
* object is valid.
*
* Requires:
*\li 'ngaddr' != NULL;
*\li 'addr' != NULL.
*/
void
isc_ngtcp2_path_init(ngtcp2_path *restrict path,
const isc_sockaddr_t *restrict local,
const isc_sockaddr_t *restrict peer);
/*%<
* Initialize the given 'ngtcp2_path' according the data from the
* given 'isc_sockaddr_t' objects.
*
* NOTE: Please keep in mind that no data is copied, only pointers are
* set and they are valid for as long as the given isc_sockaddr_t'
* objects are valid.
*
* Requires:
*\li 'path' != NULL;
*\li 'local' != NULL;
*\li 'peer' != NULL.
*/
void
isc_ngtcp2_path_storage_init(ngtcp2_path_storage *restrict path_storage,
const isc_sockaddr_t *restrict local,
const isc_sockaddr_t *restrict peer);
/*%<
* Initialize the given 'ngtcp2_path_storage' according the data
* from the given 'isc_sockaddr_t' objects. The data from the provided
* addresses is copied inside the path storage object.
*
* Requires:
*\li 'path_storage' != NULL;
*\li 'local' != NULL;
*\li 'peer' != NULL.
*/
void
isc_ngtcp2_path_getaddrs(const ngtcp2_path *restrict path,
isc_sockaddr_t *restrict local,
isc_sockaddr_t *restrict peer);
/*%<
* Return the individual components of the given QUIC path object,
* if pointers are specified.
*
* Requires:
*\li 'path' != NULL.
*/
static inline ngtcp2_duration
isc_ngtcp2_make_duration(const uint32_t seconds, const uint32_t millis) {
const ngtcp2_duration duration =
((NGTCP2_SECONDS * seconds) + (NGTCP2_MILLISECONDS * millis));
/*
* UINT64_MAX is an invalid value in ngtcp2. Often used as the no-value
* marker.
*/
INSIST(duration <= UINT64_MAX);
return duration;
}
/*%<
* An utility to generate a duration/timestamp with nanosecond
* accuracy that is suitable to use in ngtcp2.
*/
void
isc_ngtcp2_mem_init(ngtcp2_mem *restrict mem, isc_mem_t *mctx);
/*%<
* Initialize an 'ngtcp2_mem' object so that it can be used to route
* memory allocation operations to the given memory context.
*
* Requires:
*\li 'mem' != NULL;
*\li 'mctx' != NULL.
*/
bool
isc_ngtcp2_is_version_available(const uint32_t version,
const uint32_t *versions,
const size_t versions_len);
/*%<
* Returns 'true' if the given QUIC version is available in the given
* set of versions.
*
* Requires:
*\li 'versions' != NULL.
*/
uint32_t
isc_ngtcp2_select_version(const uint32_t client_original_chosen_version,
const uint32_t *client_preferred_versions,
const size_t client_preferred_versions_len,
const uint32_t *server_preferred_versions,
const size_t server_preferred_versions_len);
/*%<
*
* Get a negotiated QUIC version following the rules described in
* RFC8999 and, especially, RFC9368.
*
* NOTE: Similar to 'ngtcp2_select_version()' but a bit more strict
* according to the RFC9368.
*
* Requires:
*\li 'client_preferred_versions' != NULL;
*\li 'server_preferred_versions' != NULL.
*/
static inline bool
isc_ngtcp2_pkt_header_is_long(const uint8_t *pkt, const size_t pktlen) {
REQUIRE(pkt != NULL);
REQUIRE(pktlen >= 5);
if (pkt[0] & 0x80) {
return true;
}
return false;
}
/*%<
* Check if the QUIC packet uses a long form. The function is
* expected to be used after a successful call to
* 'ngtcp2_pkt_decode_version_cid()' which does some initial sanity
* checks on a packet.
*
* See RFC8999 for more details about this and other version-agnostic
* characteristics of QUIC.
*
* Requires:
*\li 'pkt' != NULL;
*\li 'pktlen' >= 5.
*/
isc_result_t
isc_ngtcp2_decode_pkt_header(const isc_region_t *pkt,
const size_t short_pkt_dcidlen, bool *pkt_long,
isc_region_t *pkt_scid, isc_region_t *pkt_dcid,
uint32_t *pkt_version);
/*%<
* Get the basic information about the given QUIC packet header. You
* can pass 'NULL' to the argument you are not interested in. NOTE:
* It is a specialized thin wrapper on top of
* ngtcp2_pkt_decode_version_cid() intended to be used as a first step
* in processing an incoming QUIC packet/UDP-datagram.
*
* Requires:
*\li 'pkt' != NULL && 'pkt->base' != NULL && 'pkt->length' > 0.
*/
isc_result_t
isc_ngtcp2_decode_pkt_header_data(const uint8_t *pkt_data, const size_t pkt_len,
const size_t short_pkt_dcidlen,
bool *pkt_long, isc_region_t *pkt_scid,
isc_region_t *pkt_dcid,
uint32_t *pkt_version);
/*%<
*
* Mostly same as above, but accepts data directly rather than via a
* pointer to `isc_region_t`.
*
* Requires:
*\li 'pkt_data' != NULL && 'pkt_len' > 0.
*/

193
lib/isc/include/isc/quic.h Normal file
View File

@@ -0,0 +1,193 @@
/*
* 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 <stdbool.h>
#include <isc/buffer.h>
#include <isc/tls.h>
#define ISC_QUIC_SERVER_SCID_LEN (18)
/*
* QUIC session.
*/
typedef struct isc_quic_session isc_quic_session_t;
/*
* QUIC CIDs implementation.
*/
typedef struct isc_quic_cid isc_quic_cid_t;
typedef uint64_t (*isc_quic_get_current_ts_cb_t)(void *restrict cbarg);
typedef void (*isc_quic_timer_update_cb_t)(isc_quic_session_t *restrict session,
const uint32_t timeout_ms,
void *cbarg);
typedef bool (*isc_quic_gen_unique_cid_cb_t)(
isc_quic_session_t *restrict session, const size_t cidlen,
const bool source, void *restrict cbarg,
isc_quic_cid_t **restrict pcid);
typedef bool (*isc_quic_assoc_conn_cid_cb_t)(
isc_quic_session_t *restrict session, isc_region_t *restrict cid_data,
const bool source, void *cbarg, isc_quic_cid_t **restrict pcid);
typedef void (*isc_quic_deassoc_conn_cid_cb_t)(
isc_quic_session_t *restrict session, void *cbarg,
isc_quic_cid_t **restrict pcid);
typedef bool (*isc_quic_on_handshake_cb_t)(isc_quic_session_t *session,
void *cbarg);
typedef bool (*isc_quic_on_remote_stream_open_cb_t)(isc_quic_session_t *session,
const int64_t streamd_id,
void *cbarg);
typedef bool (*isc_quic_on_stream_close_cb_t)(isc_quic_session_t *session,
const int64_t streamd_id,
const bool app_error_set,
const uint64_t app_error_code,
void *cbarg,
void *stream_user_data);
typedef bool (*isc_quic_on_recv_stream_data_cb_t)(
isc_quic_session_t *session, const int64_t streamd_id, const bool fin,
const uint64_t offset, const isc_region_t *restrict data, void *cbarg,
void *stream_user_data);
typedef void (*isc_quic_on_conn_close_cb_t)(isc_quic_session_t *session,
const uint32_t closing_timeout_ms,
void *cbarg);
typedef struct isc_quic_session_interface {
/*
* Low-level system interaction callbacks. These provide the
* foundation for the the session object itself
*/
isc_quic_get_current_ts_cb_t get_current_ts;
isc_quic_timer_update_cb_t timer_update;
isc_quic_gen_unique_cid_cb_t gen_unique_cid;
isc_quic_assoc_conn_cid_cb_t assoc_conn_cid;
isc_quic_deassoc_conn_cid_cb_t deassoc_conn_cid;
/* High-level callbacks. These provide the services for the higher
* level code. */
isc_quic_on_handshake_cb_t on_handshake;
isc_quic_on_remote_stream_open_cb_t on_remote_stream_open;
isc_quic_on_stream_close_cb_t on_stream_close;
isc_quic_on_recv_stream_data_cb_t on_recv_stream_data;
isc_quic_on_conn_close_cb_t on_conn_close;
} isc_quic_session_interface_t;
typedef void (*isc_quic_send_cb_t)(isc_quic_session_t *restrict session,
const int64_t stream_id,
const isc_result_t result, void *cbarg,
void *stream_user_data);
void
isc_quic_session_create(isc_mem_t *mctx, isc_tlsctx_t *tlsctx,
const isc_quic_session_interface_t *restrict cbs,
void *cbarg, const isc_sockaddr_t *restrict local,
const isc_sockaddr_t *restrict peer,
const uint32_t handshake_timeout_ms,
const uint32_t idle_timeout_ms,
const size_t max_uni_streams,
const size_t max_bidi_streams,
const uint32_t client_chosen_version,
const uint32_t *availalbe_versions,
const size_t available_versions_len,
const uint8_t *secret, const size_t secret_len,
const bool is_server, isc_quic_session_t **sessionp);
void
isc_quic_session_attach(isc_quic_session_t *restrict source,
isc_quic_session_t **targetp);
void
isc_quic_session_detach(isc_quic_session_t **sessionp);
isc_result_t
isc_quic_session_connect(isc_quic_session_t *restrict session,
uint8_t *restrict out_pkt_buf,
const size_t out_pkt_buf_len,
ssize_t *restrict ppkt_size);
isc_result_t
isc_quic_session_update_local_address(isc_quic_session_t *restrict session,
const isc_sockaddr_t *restrict local);
isc_result_t
isc_quic_session_send_data(isc_quic_session_t *restrict session,
const int64_t stream_id,
const isc_region_t *restrict data, const bool fin,
isc_quic_send_cb_t cb, void *cbarg);
isc_result_t
isc_quic_session_write_pkt(isc_quic_session_t *restrict session,
uint8_t *restrict out_pkt_buf,
const size_t out_pkt_buf_len,
ssize_t *restrict ppkt_size);
isc_result_t
isc_quic_session_read_pkt(isc_quic_session_t *restrict session,
const uint32_t version,
const isc_region_t *restrict pkt_dcid,
const isc_region_t *restrict pkt_scid,
const isc_region_t *restrict pkt_data,
uint8_t *restrict out_pkt_buf,
const size_t out_pkt_buf_len,
ssize_t *restrict ppkt_size);
isc_result_t
isc_quic_on_expiry_timer(isc_quic_session_t *restrict session,
uint8_t *restrict out_pkt_buf,
const size_t out_pkt_buf_len,
ssize_t *restrict ppkt_size);
isc_result_t
isc_quic_session_open_stream(isc_quic_session_t *restrict session,
const bool bidi, void *stream_user_data,
int64_t *restrict pstream_id);
isc_result_t
isc_quic_session_set_stream_user_data(isc_quic_session_t *restrict session,
const int64_t stream_id,
void *stream_user_data);
void *
isc_quic_session_get_stream_user_data(isc_quic_session_t *restrict session,
const int64_t stream_id);
isc_result_t
isc_quic_session_shutdown_stream(isc_quic_session_t *restrict session,
const int64_t stream_id, bool abrupt);
isc_result_t
isc_quic_session_shutdown(isc_quic_session_t *restrict session,
uint8_t *restrict out_pkt_buf,
const size_t out_pkt_buf_len,
ssize_t *restrict ppkt_size);
void
isc_quic_cid_create(isc_mem_t *mctx, const isc_region_t *restrict cid_data,
const bool source, isc_quic_cid_t **cidp);
void
isc_quic_cid_attach(isc_quic_cid_t *restrict source, isc_quic_cid_t **targetp);
void
isc_quic_cid_detach(isc_quic_cid_t **cidp);

View File

@@ -608,6 +608,314 @@ isc_tlsctx_set_random_session_id_context(isc_tlsctx_t *ctx);
*\li 'ctx' - a valid non-NULL pointer;
*/
void
isc_tls_sslkeylogfile_append(const char *line);
/*%<
* Appends the provided line to the dedicated SSL keys log file
* provided via "SSLKEYLOGFILE" environmental variable (iff the variable is
* set).
*/
/* QUIC related functionality (mostly see quic/tls_quic.c) */
#ifdef HAVE_LIBNGTCP2
typedef struct ssl_cipher_st isc_tls_cipher_t;
/*%<
* See 'SSL_CIPHER'.
*/
typedef void (*isc_tls_keylog_cb_t)(const isc_tls_t *tls, const char *line);
/*%<
* Keylog callback. See 'SSL_CTX_set_keylog_callback()'.
*/
typedef enum isc_quic_encryption_level {
ISC_QUIC_ENCRYPTION_INITIAL = 0,
ISC_QUIC_ENCRYPTION_EARLY_DATA,
ISC_QUIC_ENCRYPTION_HANDSHAKE,
ISC_QUIC_ENCRYPTION_APPLICATION
} isc_quic_encryption_level_t;
/*%<
* QUIC TLS encryption level (similar to 'ssl_encryption_level_t').
*/
typedef struct isc_tls_quic_method {
bool (*set_read_secret)(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const isc_tls_cipher_t *cipher,
const uint8_t *secret, const size_t secret_len);
bool (*set_write_secret)(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const isc_tls_cipher_t *cipher,
const uint8_t *secret,
const size_t secret_len);
bool (*add_handshake_data)(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const uint8_t *data, const size_t len);
/* The flush_flight() callback is used for optimisation means and
* cannot be implemented manually without access to the internals
* of the crypto libraries (TLS state machine). We need to skip
* it. */
/*bool (*flush_flight)(isc_tls_t *tls);*/
bool (*send_alert)(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const uint8_t alert);
} isc_tls_quic_method_t;
/*%<
* As set of functions (hooks) for interaction between TLS and QUIC. Closely
* resembles the LibreSSL/BoringSSL/QuicTLS's
* 'SSL_QUIC_METHOD/ssl_quic_method_st' type.
*/
typedef struct isc_tls_quic_interface isc_tls_quic_interface_t;
/*%<
* An implementation of QUIC and TLS interaction interface.
*/
const char *
isc_tls_quic_encryption_level_text(const isc_quic_encryption_level_t level);
/*%<
* Get textual description of a given QUIC encryption level. The
* function is intended for log messages.
*/
const isc_tls_quic_interface_t *
isc_tls_get_default_quic_interface(void);
/*%<
* Returns a set of hooks to interact wit the default implementation
* of QUIC integration API. It might be either a native QUIC
* integration API or a compatibility layer depending on BIND
* configuration.
*/
void
isc_tlsctx_quic_configure(isc_tlsctx_t *tlsctx,
const isc_tls_quic_interface_t *quic_interface);
/*%<
* Configures the given TLS context object to be used for QUIC.
* It is meant to be used as one of the last steps of a TLS
* context configuration.
*
* Requires:
*\li 'tlsctx' is a valid pointer to a TLS context object;
*\li 'quic_interface' - a valid pointer to a QUIC interface object.
*/
isc_tls_t *
isc_tls_create_quic(isc_tlsctx_t *ctx);
/*%<
* Create a new TLS structure to hold data for a new QUIC connection.
*
* Requires:
*\li 'ctx' != NULL.
*/
void
isc_tls_quic_set_app_data(isc_tls_t *tls, void *app_data);
/*%<
* Sets QUIC application data (user data) for the given TLS object
* configured for QUIC. Works similar to 'SSL_set_app_data()' which
* cannot be used for TLS objects configured for QUIC as this
* functionality is used internally.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
void *
isc_tls_quic_get_app_data(isc_tls_t *tls);
/*%<
* Returns QUIC application data (user data) for the given TLS
* object configured for QUIC. Works similar to 'SSL_get_app_data()'
* which cannot be used for TLS objects configured for QUIC as this
* functionality is used internally.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
void
isc_tls_quic_set_keylog_callback(isc_tls_t *tls, isc_tls_keylog_cb_t cb);
/*%<
* Sets QUIC keylog callback. See 'SSL_CTX_set_keylog_callback()' for more
* details. NOTE: No-op on LibreSSL.
*
* Additional information:
* https://datatracker.ietf.org/doc/draft-ietf-tls-keylogfile/
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
isc_result_t
isc_tls_set_quic_method(isc_tls_t *tls, const isc_tls_quic_method_t *method);
/*%<
* Configures the QUIC related hooks on a TLS object previously configured for
* QUIC. The 'method' must remain valid for the lifetime of 'tls'.
*
* NOTE: See 'SSL_set_quic_method()'.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object;
*\li 'method' is a valid pointer to a QUIC method object.
*
* Returns:
*\li #ISC_R_SUCCESS - on success;
*\li #ISC_R_FAILURE - on failure.
*/
isc_result_t
isc_tls_provide_quic_data(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const uint8_t *data, const size_t len);
/*%<
* Provides data from QUIC at a particular encryption level 'level' to a TLS
* object previously configured for QUIC.
*
* NOTE: See 'SSL_provide_quic_data()'.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object;
*\li 'len' == 0 || 'data' != NULL;
*
* Returns:
*\li #ISC_R_SUCCESS - on success;
*\li #ISC_R_FAILURE - on failure.
*/
int
isc_tls_process_quic_post_handshake(isc_tls_t *tls);
/*%<
* Processes any data that QUIC has provided that left after TLS handshake
* completion in a TLS object previously configured for QUIC.
*
* NOTE: See 'SSL_process_quic_post_handshake()'. You can pass the
* return value to 'SSL_get_error()' for better error processing.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*
* Returns:
*\li '1' - on success;
*\li '0' - on failure.
*/
int
isc_tls_do_quic_handshake(isc_tls_t *tls);
/*%<
* Processes any handshake data that QUIC has provided.
*
* NOTE: See 'SSL_do_handshake()'. The function shares return values
* with this function and can be considered a thin wrapper on top of
* it. The intention behind the wrapper to hide some differences in
* behaviour between native BoringSSL-style QUIC API and our OpenSSL
* compatibility layer. You can pass the return value to
* 'SSL_get_error()' for better error processing.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
isc_result_t
isc_tls_set_quic_transport_params(isc_tls_t *tls, const uint8_t *params,
const size_t params_len);
/*%<
* Sets the 'quic_transport_parameters' extension value in either the
*'ClientHello' or 'EncryptedExtensions' handshake message for a TLS object
* previously configured for QUIC. The pointers need to be valid only for the
* time of the call.
*
* NOTE: See 'SSL_set_quic_transport_params()'.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object;
*\li 'params' is a valid pointer;
*\li 'params_len' is more than zero.
*
* Returns:
*\li #ISC_R_SUCCESS - on success;
*\li #ISC_R_FAILURE - on failure.
*/
void
isc_tls_get_peer_quic_transport_params(isc_tls_t *tls,
const uint8_t **out_params,
size_t *out_params_len);
/*%<
* Returns the 'quic_transport_parameters' extension value and its length for
* either the 'ClientHello' or 'EncryptedExtensions' handshake message sent by
* the peer for a TLS object previously configured for QUIC. The data buffer is
* valid for the for until the call to 'isc_tls_quic_uninit()'.
*
* NOTE: See 'SSL_get_peer_quic_transport_params()'.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object;
*\li 'out_params' is a valid pointer to a nullified pointer;
*\li 'out_params_len' is a valid pointer to a nullified variable.
*
* Returns:
*\li #ISC_R_SUCCESS - on success;
*\li #ISC_R_FAILURE - on failure.
*/
isc_quic_encryption_level_t
isc_tls_quic_read_level(const isc_tls_t *tls);
/*%<
* Returns the current read encryption level. MUST NOT be called from a
* 'isc_tls_quic_method_t' callback (due to inconsistent behaviour in different
* crypto libraries in this situation).
*
* NOTE: See 'SSL_quic_read_level()'.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
isc_quic_encryption_level_t
isc_tls_quic_write_level(const isc_tls_t *tls);
/*%<
* Returns the current write encryption level. MUST NOT be called from a
* 'isc_tls_quic_method_t' callback (due to inconsistent behaviour in different
* crypto libraries in this situation).
*
* NOTE: See 'SSL_quic_write_level()'.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
void
isc_tls_quic_crypto_initialize(void);
/*%<
* Initiliazes the internal QUIC crypto library. It is supposed to
* be used right after loading all required OpenSSL crypto providers.
*
* Internally it mostly pre-fetches the crypto algorithms according to
* OpenSSL 3.X recommendations.
*
* https://docs.openssl.org/3.0/man7/crypto/#explicit-fetching
*/
void
isc_tls_quic_crypto_shutdown(void);
/*%<
* Uninitiliazes the internal QUIC crypto library. It is supposed to
* be used right before unloading the used OpenSSL crypto providers.
*/
void
isc__tls_quic_initialize(void);
void
isc__tls_quic_shutdown(void);
#endif /* HAVE_LIBNGTCP2 */
#define isc_tlserr2result(category, module, funcname, fallback) \
isc__tlserr2result(category, module, funcname, fallback, __FILE__, \
__LINE__)

View File

@@ -55,6 +55,9 @@ isc__lib_initialize(void) {
isc__mem_initialize();
isc__log_initialize();
isc__crypto_initialize();
#ifdef HAVE_LIBNGTCP2
isc__tls_quic_initialize();
#endif /* HAVE_LIBNGTCP2 */
isc__uv_initialize();
isc__xml_initialize();
isc__hash_initialize();
@@ -71,6 +74,9 @@ isc__lib_shutdown(void) {
isc__iterated_hash_shutdown();
isc__xml_shutdown();
isc__uv_shutdown();
#ifdef HAVE_LIBNGTCP2
isc__tls_quic_shutdown();
#endif /* HAVE_LIBNGTCP2 */
isc__crypto_shutdown();
isc__log_shutdown();
isc__mem_shutdown();

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

File diff suppressed because it is too large Load Diff

257
lib/isc/quic/ngtcp2_utils.c Normal file
View File

@@ -0,0 +1,257 @@
/*
* 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.
*/
#include <string.h>
#include <isc/mem.h>
#include <isc/ngtcp2_utils.h>
#include <isc/random.h>
void
isc_ngtcp2_gen_cid(ngtcp2_cid *restrict cid, const size_t size) {
REQUIRE(cid != NULL);
REQUIRE(size >= NGTCP2_MIN_CIDLEN && size <= NGTCP2_MAX_CIDLEN);
cid->datalen = size;
isc_random_buf(cid->data, cid->datalen);
}
void
isc_ngtcp2_copy_cid(ngtcp2_cid *restrict dst, const ngtcp2_cid *restrict src) {
REQUIRE(dst != NULL);
REQUIRE(src != NULL && src->datalen > 0);
memmove(dst->data, src->data, src->datalen);
dst->datalen = src->datalen;
}
void
isc_ngtcp2_addr_init(ngtcp2_addr *restrict ngaddr,
const isc_sockaddr_t *restrict addr) {
REQUIRE(ngaddr != NULL);
REQUIRE(addr != NULL);
*ngaddr = (ngtcp2_addr){ 0 };
ngaddr->addr = (ngtcp2_sockaddr *)&addr->type.sa;
ngaddr->addrlen = (ngtcp2_socklen)addr->length;
}
void
isc_ngtcp2_path_init(ngtcp2_path *restrict path,
const isc_sockaddr_t *restrict local,
const isc_sockaddr_t *restrict peer) {
REQUIRE(path != NULL);
REQUIRE(local != NULL);
REQUIRE(peer != NULL);
*path = (ngtcp2_path){ 0 };
isc_ngtcp2_addr_init(&path->local, local);
isc_ngtcp2_addr_init(&path->remote, peer);
}
void
isc_ngtcp2_path_storage_init(ngtcp2_path_storage *restrict path_storage,
const isc_sockaddr_t *restrict local,
const isc_sockaddr_t *restrict peer) {
REQUIRE(path_storage != NULL);
REQUIRE(local != NULL);
REQUIRE(peer != NULL);
*path_storage = (ngtcp2_path_storage){ 0 };
INSIST(local->length <= sizeof(path_storage->local_addrbuf));
INSIST(peer->length <= sizeof(path_storage->remote_addrbuf));
ngtcp2_path_storage_init(
path_storage, (ngtcp2_sockaddr *)&local->type.sa, local->length,
(ngtcp2_sockaddr *)&peer->type.sa, peer->length, NULL);
}
void
isc_ngtcp2_path_getaddrs(const ngtcp2_path *restrict path,
isc_sockaddr_t *restrict local,
isc_sockaddr_t *restrict peer) {
REQUIRE(path != NULL);
if (local != NULL) {
isc_sockaddr_fromsockaddr(local, path->local.addr);
}
if (peer != NULL) {
isc_sockaddr_fromsockaddr(peer, path->remote.addr);
}
}
static void *
isc__ngtcp2_malloc(size_t sz, isc_mem_t *mctx) {
return isc_mem_allocate(mctx, sz);
}
static void *
isc__ngtcp2_calloc(size_t n, size_t sz, isc_mem_t *mctx) {
return isc_mem_callocate(mctx, n, sz);
}
static void *
isc__ngtcp2_realloc(void *p, size_t newsz, isc_mem_t *mctx) {
return isc_mem_reallocate(mctx, p, newsz);
}
static void
isc__ngtcp2_free(void *p, isc_mem_t *mctx) {
if (p == NULL) { /* as standard free() behaves */
return;
}
isc_mem_free(mctx, p);
}
void
isc_ngtcp2_mem_init(ngtcp2_mem *restrict mem, isc_mem_t *mctx) {
REQUIRE(mem != NULL);
REQUIRE(mctx != NULL);
*mem = (ngtcp2_mem){ .malloc = (ngtcp2_malloc)isc__ngtcp2_malloc,
.calloc = (ngtcp2_calloc)isc__ngtcp2_calloc,
.realloc = (ngtcp2_realloc)isc__ngtcp2_realloc,
.free = (ngtcp2_free)isc__ngtcp2_free,
.user_data = (void *)mctx };
}
bool
isc_ngtcp2_is_version_available(const uint32_t version,
const uint32_t *versions,
const size_t versions_len) {
REQUIRE(versions != NULL);
if (version == 0) {
return false;
}
for (size_t i = 0; i < versions_len; i++) {
if (versions[i] == version &&
ngtcp2_is_supported_version(version))
{
return true;
}
}
return false;
}
uint32_t
isc_ngtcp2_select_version(const uint32_t client_original_chosen_version,
const uint32_t *client_preferred_versions,
const size_t client_preferred_versions_len,
const uint32_t *server_preferred_versions,
const size_t server_preferred_versions_len) {
size_t i, k;
REQUIRE(client_preferred_versions != NULL);
REQUIRE(server_preferred_versions != NULL);
/*
* RFC RFC9368, Section 4. Version Downgrade Prevention:
* Clients MUST ignore any received Version Negotiation packets
* that contain the Original Version.
* ...
* If an endpoint receives a Chosen Version equal to zero, or any
* Available Version equal to zero, it MUST treat it as a parsing
* failure.
*/
for (i = 0; i < server_preferred_versions_len; i++) {
if (server_preferred_versions[i] ==
client_original_chosen_version ||
server_preferred_versions[i] == 0)
{
return 0;
}
}
/* Choose a protocol version prioritising client's preferences. */
for (i = 0; i < client_preferred_versions_len; i++) {
const uint32_t client_version = client_preferred_versions[i];
for (k = 0; k < server_preferred_versions_len; k++) {
const uint32_t server_version =
server_preferred_versions[k];
if (client_version == server_version &&
ngtcp2_is_supported_version(client_version) &&
ngtcp2_is_supported_version(server_version))
{
return client_version;
}
}
}
return 0;
}
isc_result_t
isc_ngtcp2_decode_pkt_header(const isc_region_t *pkt,
const size_t short_pkt_dcidlen, bool *pkt_long,
isc_region_t *pkt_scid, isc_region_t *pkt_dcid,
uint32_t *pkt_version) {
ngtcp2_version_cid vc = { 0 };
REQUIRE(pkt != NULL && pkt->base != NULL && pkt->length > 0);
int ret = ngtcp2_pkt_decode_version_cid(&vc, pkt->base, pkt->length,
short_pkt_dcidlen);
/*
* We treat version negotiation not as an error, because our code
* is expected to handle it on its own.
*/
if (ret != NGTCP2_VERSION_NEGOTIATION_ERROR && ret != 0) {
return ISC_R_UNEXPECTED;
}
if (pkt_long != NULL) {
*pkt_long = isc_ngtcp2_pkt_header_is_long(pkt->base,
pkt->length);
}
if (pkt_scid != NULL) {
*pkt_scid =
(isc_region_t){ .base = (uint8_t *)vc.scid,
.length = (unsigned int)vc.scidlen };
}
if (pkt_dcid != NULL) {
*pkt_dcid =
(isc_region_t){ .base = (uint8_t *)vc.dcid,
.length = (unsigned int)vc.dcidlen };
}
if (pkt_version != NULL) {
*pkt_version = vc.version;
}
return ISC_R_SUCCESS;
}
isc_result_t
isc_ngtcp2_decode_pkt_header_data(const uint8_t *pkt_data, const size_t pkt_len,
const size_t short_pkt_dcidlen,
bool *pkt_long, isc_region_t *pkt_scid,
isc_region_t *pkt_dcid,
uint32_t *pkt_version) {
REQUIRE(pkt_data != NULL && pkt_len > 0);
isc_region_t pkt = { .base = (uint8_t *)pkt_data,
.length = (unsigned int)pkt_len };
return isc_ngtcp2_decode_pkt_header(&pkt, short_pkt_dcidlen, pkt_long,
pkt_scid, pkt_dcid, pkt_version);
}

176
lib/isc/quic/quic-int.h Normal file
View File

@@ -0,0 +1,176 @@
/*
* 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 <isc/tls.h>
#include <isc/types.h>
struct isc_tls_quic_interface {
void (*tlsctx_configure)(isc_tlsctx_t *tlsctx);
/* NOTE: the keylog callback will never be called on LibreSSL. */
void (*tlsctx_keylog_callback)(const isc_tls_t *tls, const char *line);
void (*tls_init)(isc_tls_t *tls, isc_mem_t *mctx);
void (*tls_uninit)(isc_tls_t *tls);
bool (*tls_calling_method_cb)(const isc_tls_t *tls);
/* See SSL_set_quic_method() */
int (*tls_set_quic_method)(isc_tls_t *tls,
const isc_tls_quic_method_t *method);
/* See SSL_provide_quic_data() */
int (*tls_provide_quic_data)(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const uint8_t *data, const size_t len);
/* See SSL_do_handshake(). Internally it is a thin wrapper on top
* of it. It exists mostly to properly handle failed callbacks in
* a compatibility layer.
*/
int (*tls_do_quic_handshake)(isc_tls_t *tls);
/*
* See SSL_process_quic_post_handshake(). In the worst case can
* be a dummy implementation (return 1) or a wrapper on top of
* SSL_read() in a compatibility layer.
*/
int (*tls_process_quic_post_handshake)(isc_tls_t *tls);
/* See SSL_set_quic_transport_params() */
int (*tls_set_quic_transport_params)(isc_tls_t *tls,
const uint8_t *params,
const size_t params_len);
/* See SSL_get_peer_quic_transport_params() */
void (*tls_get_peer_quic_transport_params)(isc_tls_t *tls,
const uint8_t **out_params,
size_t *out_params_len);
/* See SSL_quic_read_level() */
isc_quic_encryption_level_t (*tls_quic_read_level)(const isc_tls_t *tls);
/* See SSL_quic_read_level() */
isc_quic_encryption_level_t (*tls_quic_write_level)(
const isc_tls_t *tls);
};
/*%<
* An interface used to implement functionality to access
* QUIC-related TLS interfacing functionality of the used crypto
* library (or a compatibility interface). It is based on the
* interface used by LibreSSL/BoringSSL/QuicTLS so that it can be
* easily implemented for these libraries.
*/
void *
isc__tls_get_quic_data(const isc_tls_t *tls);
/*%<
* Returns opaque pointer referring to QUIC specific data associated with the
* 'tls' object.
*/
void
isc__tls_set_quic_data(isc_tls_t *tls, void *data);
/*%<
* Associates an opaque pointer referring to QUIC specific data with 'tls'
* object.
*/
void
isc__tls_quic_init(isc_tls_t *tls);
/*%<
* Initializes and configures the given TLS object to be used for QUIC.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
void
isc__tls_quic_uninit(isc_tls_t *tls);
/*%<
* Deinitializes the given TLS object to be used for QUIC.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
bool
isc__tls_is_quic(isc_tls_t *tls);
/*%<
* Returns 'true' if the given object is configured for QUIC.
*
* Requires:
*\li 'tls' is a valid pointer to a TLS object.
*/
#ifdef HAVE_NATIVE_BORINGSSL_QUIC_API
const isc_tls_quic_interface_t *
isc__tls_get_native_quic_interface(void);
/*%<
* Returns a set of hooks to interact wit the native (provided by the crypto
* library) implementation of QUIC integration API.
*
* NOTE: this function is primarily exposed for testing and debugging purposes.
* Please consider using 'isc_tls_get_default_quic_interface()' instead.
*/
#endif /* HAVE_NATIVE_BORINGSSL_QUIC_API */
#ifndef HAVE_LIBRESSL
#include <isc/buffer.h>
const isc_tls_quic_interface_t *
isc__tls_get_compat_quic_interface(void);
/*%<
* Returns a set of hooks to interact wit the compatibility layer-based
* implementation of QUIC integration API.
*
* NOTE: this function is primarily exposed for testing and debugging purposes.
* Please consider using 'isc_tls_get_default_quic_interface()' instead.
*/
typedef enum isc__tls_keylog_label {
ISC__TLS_KL_ILLEGAL = 0,
/* TLSv1.3 */
ISC__TLS_KL_CLIENT_EARLY_TRAFFIC_SECRET,
ISC__TLS_KL_EARLY_EXPORTER_MASTER_SECRET,
ISC__TLS_KL_CLIENT_HANDSHAKE_TRAFFIC_SECRET,
ISC__TLS_KL_SERVER_HANDSHAKE_TRAFFIC_SECRET,
ISC__TLS_KL_CLIENT_TRAFFIC_SECRET_0,
ISC__TLS_KL_SERVER_TRAFFIC_SECRET_0,
ISC__TLS_KL_EXPORTER_SECRET,
/* TLSv1.2 */
ISC__TLS_KL_CLIENT_RANDOM
} isc__tls_keylog_label_t;
/*%<
* Definitions corresponding to TLS SSLKEYLOGFILE format labels.
*
* See this IETF Draft:
* https://datatracker.ietf.org/doc/draft-ietf-tls-keylogfile/
*/
isc_result_t
isc__tls_parse_keylog_entry(const char *restrict line,
isc__tls_keylog_label_t *restrict out_label,
isc_buffer_t *restrict out_client_random,
isc_buffer_t *restrict out_secret);
/*%<
* Parse SSLKEYLOGFILE entry. If you are not interested in certain parts of the
* entry, use 'NULL' for the corresponding argument.
*
* NOTE: might alter the passed arguments even when fails.
*
* Requires:
*\li 'line' is a valid, non-NULL pointer.
*/
#endif /* HAVE_LIBRESSL */

59
lib/isc/quic/quic_cid.c Normal file
View File

@@ -0,0 +1,59 @@
#include "quic_session.h"
void
isc_quic_cid_create(isc_mem_t *memctx, const isc_region_t *restrict cid_data,
const bool source, isc_quic_cid_t **cidp) {
isc_quic_cid_t *cid = NULL;
REQUIRE(cid_data != NULL && cid_data->base != NULL &&
cid_data->length > 0);
REQUIRE(cidp != NULL && *cidp == NULL);
cid = isc_mem_get(memctx, sizeof(*cid));
*cid = (isc_quic_cid_t){
.source = source,
.global_link = ISC_LINK_INITIALIZER,
.local_link = ISC_LINK_INITIALIZER,
};
isc_refcount_init(&cid->references, 1);
ngtcp2_cid_init(&cid->cid, cid_data->base, cid_data->length);
isc_mem_attach(memctx, &cid->mctx);
/* We need to acquire a memory barrier here */
(void)isc_refcount_current(&cid->references);
cid->magic = QUIC_CID_MAGIC;
*cidp = cid;
}
void
isc_quic_cid_attach(isc_quic_cid_t *restrict source, isc_quic_cid_t **targetp) {
REQUIRE(VALID_QUIC_CID(source));
REQUIRE(targetp != NULL && *targetp == NULL);
isc_refcount_increment(&source->references);
*targetp = source;
}
void
isc_quic_cid_detach(isc_quic_cid_t **cidp) {
isc_quic_cid_t *restrict cid = NULL;
cid = *cidp;
*cidp = NULL;
REQUIRE(VALID_QUIC_CID(cid));
if (isc_refcount_decrement(&cid->references) > 1) {
return;
}
/* We need to acquire a memory barrier here */
(void)isc_refcount_current(&cid->references);
cid->magic = 0;
isc_mem_putanddetach(&cid->mctx, cid, sizeof(*cid));
}

962
lib/isc/quic/quic_crypto.c Normal file
View File

@@ -0,0 +1,962 @@
/*
* 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.
*/
/*
* This file contains QUIC-flavoured TLSv1.3-related cryptography
* functions. The code mostly inspired by QuicTLS/LibreSSL-specific
* code found in ngtcp2's native crypto library but with some peeks at
* similar code found in NGINX and HAProxy.
*
* The functions this code provides mostly fall into the following categories:
*
* 1. TLS cipher ('isc_tls_cipher_t' aka SSL_CIPHER) information retrieval:
* getting associated message digest, AEAD-scheme, etc;
* 2. HKDF-interface implementation (HKDF(), HKDF-Extract(), HKDF-Expand(),
* HKDF-ExpandLabel());
* 3. AEAD-interface (context creation, authenticated encryption/decryption);
* 4. Header Protection mask calculation;
*
* The code is intended to be used in both the custom ngtcp2 crypto
* library and OpenSSL QUIC compatibility code.
*/
#include <openssl/evp.h>
#include <openssl/kdf.h>
#include <openssl/opensslv.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
#define OPENSSL_3_API_OR_NEWER 1
#include <openssl/core_names.h>
#endif
#include <isc/buffer.h>
#include <isc/crypto.h>
#include "quic_crypto.h"
/* See RFC9001, Section 6.6 (Limits on AEAD Usage ) for more details */
/* Maximum key usage (encryption) limits */
#define QUIC_CRYPTO_MAX_ENCRYPTION_AES_GCM (1ULL << 23)
#define QUIC_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305 (1ULL << 62)
#define QUIC_CRYPTO_MAX_ENCRYPTION_AES_CCM (2965820ULL)
/*
* Maximum authentication failure (decryption) limits during the
* lifetime of a connection.
*/
#define QUIC_CRYPTO_MAX_DECRYPTION_FAILURES_AES_GCM (1ULL << 52)
#define QUIC_CRYPTO_MAX_DECRYPTION_FAILURES_CHACHA20_POLY1305 (1ULL << 36)
#define QUIC_CRYPTO_MAX_DECRYPTION_FAILURES_AES_CCM (2965820ULL)
static void
quic_crypto_prefetch(void);
static void
quic_crypto_prefetch_clear(void);
static bool quic_crypto_initialized = false;
static bool fips_mode_used = false;
static const EVP_CIPHER *crypto_aead_aes_128_gcm = NULL;
static const EVP_CIPHER *crypto_aead_aes_256_gcm = NULL;
static const EVP_CIPHER *crypto_aead_aes_128_ccm = NULL;
static const EVP_CIPHER *crypto_aead_chacha20_poly1305 = NULL;
static const EVP_CIPHER *crypto_cipher_chacha20 = NULL;
static const EVP_CIPHER *crypto_cipher_aes_128_ctr = NULL;
static const EVP_CIPHER *crypto_cipher_aes_256_ctr = NULL;
static const EVP_MD *crypto_md_sha256 = NULL;
static const EVP_MD *crypto_md_sha384 = NULL;
#ifdef OPENSSL_3_API_OR_NEWER
static EVP_KDF *crypto_kdf_hkdf = NULL;
#endif /* OPENSSL_3_API_OR_NEWER */
#ifdef OPENSSL_3_API_OR_NEWER
/*
* Explicitly prefetch certain objects to avoid performance penalty on
* OpenSSL >= 3.0.
*
* See here for more details:
* https://www.openssl.org/docs/man3.0/man7/crypto.html#Performance
* https://www.openssl.org/docs/man3.0/man7/crypto.html#Explicit-fetching
* https://www.openssl.org/docs/man3.0/man7/crypto.html#Implicit-fetching
*/
static void
quic_crypto_prefetch(void) {
crypto_aead_aes_128_gcm = EVP_CIPHER_fetch(NULL, "AES-128-GCM", NULL);
RUNTIME_CHECK(crypto_aead_aes_128_gcm != NULL);
crypto_aead_aes_256_gcm = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL);
RUNTIME_CHECK(crypto_aead_aes_256_gcm != NULL);
crypto_aead_aes_128_ccm = EVP_CIPHER_fetch(NULL, "AES-128-CCM", NULL);
RUNTIME_CHECK(crypto_aead_aes_128_ccm != NULL);
if (!fips_mode_used) {
crypto_aead_chacha20_poly1305 =
EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL);
RUNTIME_CHECK(crypto_aead_chacha20_poly1305 != NULL);
crypto_cipher_chacha20 = EVP_CIPHER_fetch(NULL, "ChaCha20",
NULL);
RUNTIME_CHECK(crypto_cipher_chacha20 != NULL);
}
crypto_cipher_aes_128_ctr = EVP_CIPHER_fetch(NULL, "AES-128-CTR", NULL);
RUNTIME_CHECK(crypto_cipher_aes_128_ctr != NULL);
crypto_cipher_aes_256_ctr = EVP_CIPHER_fetch(NULL, "AES-256-CTR", NULL);
RUNTIME_CHECK(crypto_cipher_aes_256_ctr != NULL);
crypto_md_sha256 = EVP_MD_fetch(NULL, "sha256", NULL);
RUNTIME_CHECK(crypto_md_sha256 != NULL);
crypto_md_sha384 = EVP_MD_fetch(NULL, "sha384", NULL);
RUNTIME_CHECK(crypto_md_sha384 != NULL);
crypto_kdf_hkdf = EVP_KDF_fetch(NULL, "hkdf", NULL);
RUNTIME_CHECK(crypto_kdf_hkdf != NULL);
}
#else /* !(OPENSSL_3_API_OR_NEWER) */
static void
quic_crypto_prefetch(void) {
crypto_aead_aes_128_gcm = EVP_aes_128_gcm();
RUNTIME_CHECK(crypto_aead_aes_128_gcm != NULL);
crypto_aead_aes_256_gcm = EVP_aes_256_gcm();
RUNTIME_CHECK(crypto_aead_aes_256_gcm != NULL);
crypto_aead_aes_128_ccm = EVP_aes_128_ccm();
RUNTIME_CHECK(crypto_aead_aes_128_ccm != NULL);
if (!fips_mode_used) {
crypto_aead_chacha20_poly1305 = EVP_chacha20_poly1305();
RUNTIME_CHECK(crypto_aead_chacha20_poly1305 != NULL);
crypto_cipher_chacha20 = EVP_chacha20();
RUNTIME_CHECK(crypto_cipher_chacha20 != NULL);
}
crypto_cipher_aes_128_ctr = EVP_aes_128_ctr();
RUNTIME_CHECK(crypto_cipher_aes_128_ctr != NULL);
crypto_cipher_aes_256_ctr = EVP_aes_256_ctr();
RUNTIME_CHECK(crypto_cipher_aes_256_ctr != NULL);
crypto_md_sha256 = EVP_sha256();
RUNTIME_CHECK(crypto_md_sha256 != NULL);
crypto_md_sha384 = EVP_sha384();
RUNTIME_CHECK(crypto_md_sha384 != NULL);
}
#endif /* OPENSSL_3_API_OR_NEWER */
static void
quic_crypto_prefetch_clear(void) {
RUNTIME_CHECK(crypto_aead_aes_128_gcm != NULL);
crypto_aead_aes_128_gcm = NULL;
RUNTIME_CHECK(crypto_aead_aes_256_gcm != NULL);
crypto_aead_aes_256_gcm = NULL;
RUNTIME_CHECK(crypto_aead_aes_128_ccm != NULL);
crypto_aead_aes_128_ccm = NULL;
if (!fips_mode_used) {
RUNTIME_CHECK(crypto_aead_chacha20_poly1305 != NULL);
crypto_aead_chacha20_poly1305 = NULL;
RUNTIME_CHECK(crypto_cipher_chacha20 != NULL);
crypto_cipher_chacha20 = NULL;
}
RUNTIME_CHECK(crypto_cipher_aes_128_ctr != NULL);
crypto_cipher_aes_128_ctr = NULL;
RUNTIME_CHECK(crypto_cipher_aes_256_ctr != NULL);
crypto_cipher_aes_256_ctr = NULL;
RUNTIME_CHECK(crypto_md_sha256 != NULL);
crypto_md_sha256 = NULL;
RUNTIME_CHECK(crypto_md_sha384 != NULL);
crypto_md_sha384 = NULL;
#ifdef OPENSSL_3_API_OR_NEWER
RUNTIME_CHECK(crypto_kdf_hkdf != NULL);
EVP_KDF_free(crypto_kdf_hkdf);
crypto_kdf_hkdf = NULL;
#endif /* OPENSSL_3_API_OR_NEWER */
}
void
isc__quic_crypto_initialize(void) {
if (quic_crypto_initialized) {
return;
}
quic_crypto_initialized = true;
fips_mode_used = isc_crypto_fips_mode();
quic_crypto_prefetch();
}
void
isc__quic_crypto_shutdown(void) {
if (!quic_crypto_initialized) {
return;
}
quic_crypto_prefetch_clear();
quic_crypto_initialized = false;
fips_mode_used = false;
}
bool
isc__quic_crypto_tls_cipher_supported(const isc_tls_cipher_t *tls_cipher) {
uint32_t tls_cipher_id;
REQUIRE(tls_cipher != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
tls_cipher_id = SSL_CIPHER_get_id(tls_cipher);
switch (tls_cipher_id) {
case TLS1_3_CK_AES_128_GCM_SHA256:
return true;
case TLS1_3_CK_AES_256_GCM_SHA384:
return true;
case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
return !fips_mode_used;
case TLS1_3_CK_AES_128_CCM_SHA256:
return true;
}
return false;
}
const EVP_CIPHER *
isc__quic_crypto_tls_cipher_aead(const isc_tls_cipher_t *tls_cipher) {
uint32_t tls_cipher_id;
REQUIRE(tls_cipher != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
tls_cipher_id = SSL_CIPHER_get_id(tls_cipher);
switch (tls_cipher_id) {
case TLS1_3_CK_AES_128_GCM_SHA256:
return crypto_aead_aes_128_gcm;
case TLS1_3_CK_AES_256_GCM_SHA384:
return crypto_aead_aes_256_gcm;
case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
return crypto_aead_chacha20_poly1305;
case TLS1_3_CK_AES_128_CCM_SHA256:
return crypto_aead_aes_128_ccm;
}
return NULL;
}
size_t
isc__quic_crypto_aead_taglen(const EVP_CIPHER *aead) {
REQUIRE(aead != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
switch (EVP_CIPHER_nid(aead)) {
case NID_aes_128_gcm:
case NID_aes_256_gcm:
return EVP_GCM_TLS_TAG_LEN;
case NID_chacha20_poly1305:
return EVP_CHACHAPOLY_TLS_TAG_LEN;
case NID_aes_128_ccm:
return EVP_CCM_TLS_TAG_LEN;
}
UNREACHABLE();
}
size_t
isc__quic_crypto_aead_keylen(const EVP_CIPHER *aead) {
REQUIRE(aead != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
return (size_t)EVP_CIPHER_key_length(aead);
}
size_t
isc__quic_crypto_aead_ivlen(const EVP_CIPHER *aead) {
REQUIRE(aead != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
return (size_t)EVP_CIPHER_iv_length(aead);
}
size_t
isc__quic_crypto_aead_packet_protection_ivlen(const EVP_CIPHER *aead) {
/* By RFC9001, Section 5.1 it is at least 8 bytes long */
return ISC_MAX(8, isc__quic_crypto_aead_ivlen(aead));
}
uint64_t
isc__quic_crypto_tls_cipher_aead_max_encryption(
const isc_tls_cipher_t *tls_cipher) {
uint32_t tls_cipher_id;
REQUIRE(tls_cipher != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
tls_cipher_id = SSL_CIPHER_get_id(tls_cipher);
switch (tls_cipher_id) {
case TLS1_3_CK_AES_128_GCM_SHA256:
case TLS1_3_CK_AES_256_GCM_SHA384:
return QUIC_CRYPTO_MAX_ENCRYPTION_AES_GCM;
case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
return QUIC_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305;
case TLS1_3_CK_AES_128_CCM_SHA256:
return QUIC_CRYPTO_MAX_ENCRYPTION_AES_CCM;
}
UNREACHABLE();
}
uint64_t
isc__quic_crypto_tls_cipher_aead_max_decyption_failures(
const isc_tls_cipher_t *tls_cipher) {
uint32_t tls_cipher_id;
REQUIRE(tls_cipher != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
tls_cipher_id = SSL_CIPHER_get_id(tls_cipher);
switch (tls_cipher_id) {
case TLS1_3_CK_AES_128_GCM_SHA256:
case TLS1_3_CK_AES_256_GCM_SHA384:
return QUIC_CRYPTO_MAX_DECRYPTION_FAILURES_AES_GCM;
case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
return QUIC_CRYPTO_MAX_DECRYPTION_FAILURES_CHACHA20_POLY1305;
case TLS1_3_CK_AES_128_CCM_SHA256:
return QUIC_CRYPTO_MAX_DECRYPTION_FAILURES_AES_CCM;
}
UNREACHABLE();
}
const EVP_MD *
isc__quic_crypto_tls_cipher_md(const isc_tls_cipher_t *tls_cipher) {
uint32_t tls_cipher_id;
REQUIRE(tls_cipher != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
tls_cipher_id = SSL_CIPHER_get_id(tls_cipher);
switch (tls_cipher_id) {
case TLS1_3_CK_AES_128_GCM_SHA256:
case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
case TLS1_3_CK_AES_128_CCM_SHA256:
return crypto_md_sha256;
case TLS1_3_CK_AES_256_GCM_SHA384:
return crypto_md_sha384;
}
return NULL;
}
size_t
isc__quic_crypto_md_hashlen(const EVP_MD *md) {
REQUIRE(md != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
return (size_t)EVP_MD_size(md);
}
const EVP_CIPHER *
isc__quic_crypto_tls_cipher_hp(const isc_tls_cipher_t *tls_cipher) {
uint32_t tls_cipher_id;
REQUIRE(tls_cipher != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
tls_cipher_id = SSL_CIPHER_get_id(tls_cipher);
switch (tls_cipher_id) {
case TLS1_3_CK_AES_128_GCM_SHA256:
case TLS1_3_CK_AES_128_CCM_SHA256:
return crypto_cipher_aes_128_ctr;
case TLS1_3_CK_AES_256_GCM_SHA384:
return crypto_cipher_aes_256_ctr;
case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
return crypto_cipher_chacha20;
break;
}
return NULL;
}
const EVP_MD *
isc__quic_crypto_md_sha256(void) {
RUNTIME_CHECK(quic_crypto_initialized);
return crypto_md_sha256;
}
const EVP_CIPHER *
isc__quic_crypto_aead_aes_128_gcm(void) {
RUNTIME_CHECK(quic_crypto_initialized);
return crypto_aead_aes_128_gcm;
}
const EVP_CIPHER *
isc__quic_crypto_cipher_aes_128_ctr(void) {
RUNTIME_CHECK(quic_crypto_initialized);
return crypto_cipher_aes_128_ctr;
}
bool
isc__quic_crypto_hkdf_extract(uint8_t *dest, const EVP_MD *md,
const uint8_t *secret, const size_t secretlen,
const uint8_t *salt, const size_t saltlen) {
REQUIRE(dest != NULL);
REQUIRE(md != NULL);
REQUIRE(secret != NULL);
REQUIRE(secretlen > 0);
RUNTIME_CHECK(quic_crypto_initialized);
#ifdef OPENSSL_3_API_OR_NEWER
EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(crypto_kdf_hkdf);
int mode = EVP_KDF_HKDF_MODE_EXTRACT_ONLY;
OSSL_PARAM params[] = {
OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode),
OSSL_PARAM_construct_utf8_string(
OSSL_KDF_PARAM_DIGEST, (char *)EVP_MD_get0_name(md), 0),
OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
(void *)secret, secretlen),
OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT,
(void *)salt, saltlen),
OSSL_PARAM_construct_end(),
};
bool ret = true;
if (EVP_KDF_derive(kctx, dest, (size_t)EVP_MD_size(md), params) <= 0) {
ret = false;
}
EVP_KDF_CTX_free(kctx);
return ret;
#else /* !( OPENSSL_3_API_OR_NEWER) */
bool ret = true;
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
size_t destlen = (size_t)EVP_MD_size(md);
if (pctx == NULL) {
return false;
}
if (EVP_PKEY_derive_init(pctx) != 1 ||
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) !=
1 ||
EVP_PKEY_CTX_set_hkdf_md(pctx, md) != 1 ||
EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1 ||
EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1 ||
EVP_PKEY_derive(pctx, dest, &destlen) != 1)
{
ret = false;
}
EVP_PKEY_CTX_free(pctx);
return ret;
#endif /* OPENSSL_3_API_OR_NEWER */
}
bool
isc__quic_crypto_hkdf_expand(uint8_t *dest, size_t destlen, const EVP_MD *md,
const uint8_t *secret, const size_t secretlen,
const uint8_t *info, const size_t infolen) {
REQUIRE(dest != NULL);
REQUIRE(destlen > 0);
REQUIRE(md != NULL);
REQUIRE(secret != NULL);
REQUIRE(secretlen > 0);
REQUIRE(info != NULL);
REQUIRE(infolen > 0);
RUNTIME_CHECK(quic_crypto_initialized);
#ifdef OPENSSL_3_API_OR_NEWER
EVP_KDF_CTX *kdf_ctx = EVP_KDF_CTX_new(crypto_kdf_hkdf);
int mode = EVP_KDF_HKDF_MODE_EXPAND_ONLY;
OSSL_PARAM params[] = {
OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode),
OSSL_PARAM_construct_utf8_string(
OSSL_KDF_PARAM_DIGEST, (char *)EVP_MD_get0_name(md), 0),
OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
(void *)secret, secretlen),
OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO,
(void *)info, infolen),
OSSL_PARAM_construct_end(),
};
bool ret = true;
if (EVP_KDF_derive(kdf_ctx, dest, destlen, params) <= 0) {
ret = false;
}
EVP_KDF_CTX_free(kdf_ctx);
return ret;
#else /* !(OPENSSL_3_API_OR_NEWER) */
bool ret = true;
EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if (pkey_ctx == NULL) {
return false;
}
if (EVP_PKEY_derive_init(pkey_ctx) != 1 ||
EVP_PKEY_CTX_hkdf_mode(pkey_ctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) !=
1 ||
EVP_PKEY_CTX_set_hkdf_md(pkey_ctx, md) != 1 ||
EVP_PKEY_CTX_set1_hkdf_salt(pkey_ctx, (const unsigned char *)"",
0) != 1 ||
EVP_PKEY_CTX_set1_hkdf_key(pkey_ctx, secret, (int)secretlen) != 1 ||
EVP_PKEY_CTX_add1_hkdf_info(pkey_ctx, info, (int)infolen) != 1 ||
EVP_PKEY_derive(pkey_ctx, dest, &destlen) != 1)
{
ret = false;
}
EVP_PKEY_CTX_free(pkey_ctx);
return ret;
#endif /* OPENSSL_3_API_OR_NEWER */
}
/*
* See RFC8446, Section 7.1:
* https://www.rfc-editor.org/rfc/rfc8446#section-7.1
*
*
* struct {
* uint16 length = Length;
* opaque label<7..255> = "tls13 " + Label;
* opaque context<0..255> = Context;
* } HkdfLabel;
*/
bool
isc__quic_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen,
const EVP_MD *md, const uint8_t *secret,
const size_t secretlen, const uint8_t *label,
const size_t labellen) {
uint8_t label_start[] = "tls13 ";
uint8_t hkdf_buf[UINT8_MAX];
isc_buffer_t hkdf_label = { 0 };
isc_region_t hkdf_data = { 0 };
RUNTIME_CHECK(quic_crypto_initialized);
isc_buffer_init(&hkdf_label, hkdf_buf, sizeof(hkdf_buf));
isc_buffer_putuint16(&hkdf_label, destlen);
isc_buffer_putuint8(&hkdf_label,
(uint8_t)(sizeof(label_start) - 1 + labellen));
isc_buffer_putmem(&hkdf_label, label_start, sizeof(label_start) - 1);
isc_buffer_putmem(&hkdf_label, label, labellen);
/* Zero-sized "Context" */
isc_buffer_putuint8(&hkdf_label, 0);
isc_buffer_usedregion(&hkdf_label, &hkdf_data);
return isc__quic_crypto_hkdf_expand(dest, destlen, md, secret,
secretlen, hkdf_data.base,
hkdf_data.length);
}
bool
isc__quic_crypto_hkdf(uint8_t *dest, size_t destlen, const EVP_MD *md,
const uint8_t *secret, size_t secretlen,
const uint8_t *salt, size_t saltlen, const uint8_t *info,
size_t infolen) {
REQUIRE(dest != NULL);
REQUIRE(destlen > 0);
REQUIRE(md != NULL);
REQUIRE(secret != NULL);
REQUIRE(secretlen > 0);
REQUIRE(info != NULL);
REQUIRE(infolen > 0);
RUNTIME_CHECK(quic_crypto_initialized);
#ifdef OPENSSL_3_API_OR_NEWER
EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(crypto_kdf_hkdf);
OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(
OSSL_KDF_PARAM_DIGEST, (char *)EVP_MD_get0_name(md), 0),
OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
(void *)secret, secretlen),
OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT,
(void *)salt, saltlen),
OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO,
(void *)info, infolen),
OSSL_PARAM_construct_end(),
};
bool ret = true;
if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) {
ret = false;
}
EVP_KDF_CTX_free(kctx);
return ret;
#else /* !(OPENSSL_3_API_OR_NEWER) */
bool ret = true;
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if (pctx == NULL) {
return false;
}
if (EVP_PKEY_derive_init(pctx) != 1 ||
EVP_PKEY_CTX_hkdf_mode(
pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) != 1 ||
EVP_PKEY_CTX_set_hkdf_md(pctx, md) != 1 ||
EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1 ||
EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1 ||
EVP_PKEY_CTX_add1_hkdf_info(pctx, info, (int)infolen) != 1 ||
EVP_PKEY_derive(pctx, dest, &destlen) != 1)
{
ret = false;
}
EVP_PKEY_CTX_free(pctx);
return ret;
#endif /* OPENSSL_3_API_OR_NEWER */
}
bool
isc__quic_crypto_aead_ctx_encrypt_create(EVP_CIPHER_CTX **out_aead_ctx,
const EVP_CIPHER *aead,
const uint8_t *key, size_t noncelen) {
REQUIRE(out_aead_ctx != NULL && *out_aead_ctx == NULL);
REQUIRE(aead != NULL);
REQUIRE(key != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
int cipher_nid = EVP_CIPHER_nid(aead);
EVP_CIPHER_CTX *aead_ctx = NULL;
size_t taglen = 0;
#ifdef OPENSSL_3_API_OR_NEWER
OSSL_PARAM params[3];
#endif /* OPENSSL_3_API_OR_NEWER */
taglen = isc__quic_crypto_aead_taglen(aead);
aead_ctx = EVP_CIPHER_CTX_new();
if (aead_ctx == NULL) {
return false;
}
#ifdef OPENSSL_3_API_OR_NEWER
params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_IVLEN,
&noncelen);
if (cipher_nid == NID_aes_128_ccm) {
params[1] = OSSL_PARAM_construct_octet_string(
OSSL_CIPHER_PARAM_AEAD_TAG, NULL, taglen);
params[2] = OSSL_PARAM_construct_end();
} else {
params[1] = OSSL_PARAM_construct_end();
}
#endif /* OPENSSL_3_API_OR_NEWER */
if (!EVP_EncryptInit_ex(aead_ctx, aead, NULL, NULL, NULL) ||
#ifdef OPENSSL_3_API_OR_NEWER
!EVP_CIPHER_CTX_set_params(aead_ctx, params) ||
#else /* !(OPENSSL_3_API_OR_NEWER) */
!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN,
(int)noncelen, NULL) ||
(cipher_nid == NID_aes_128_ccm &&
!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_TAG, (int)taglen,
NULL)) ||
#endif /* OPENSSL_3_API_OR_NEWER */
!EVP_EncryptInit_ex(aead_ctx, NULL, NULL, key, NULL))
{
EVP_CIPHER_CTX_free(aead_ctx);
return false;
}
*out_aead_ctx = aead_ctx;
return true;
}
bool
isc__quic_crypto_aead_ctx_decrypt_create(EVP_CIPHER_CTX **out_aead_ctx,
const EVP_CIPHER *aead,
const uint8_t *key, size_t noncelen) {
REQUIRE(out_aead_ctx != NULL && *out_aead_ctx == NULL);
REQUIRE(aead != NULL);
REQUIRE(key != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
EVP_CIPHER_CTX *actx = NULL;
int cipher_nid = EVP_CIPHER_nid(aead);
size_t taglen = isc__quic_crypto_aead_taglen(aead);
#ifdef OPENSSL_3_API_OR_NEWER
OSSL_PARAM params[3];
#endif /* OPENSSL_3_API_OR_NEWER */
actx = EVP_CIPHER_CTX_new();
if (actx == NULL) {
return -1;
}
#ifdef OPENSSL_3_API_OR_NEWER
params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_IVLEN,
&noncelen);
if (cipher_nid == NID_aes_128_ccm) {
params[1] = OSSL_PARAM_construct_octet_string(
OSSL_CIPHER_PARAM_AEAD_TAG, NULL, taglen);
params[2] = OSSL_PARAM_construct_end();
} else {
params[1] = OSSL_PARAM_construct_end();
}
#endif /*OPENSSL_3_API_OR_NEWER */
if (!EVP_DecryptInit_ex(actx, aead, NULL, NULL, NULL) ||
#ifdef OPENSSL_3_API_OR_NEWER
!EVP_CIPHER_CTX_set_params(actx, params) ||
#else /* !(OPENSSL_3_API_OR_NEWER) */
!EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen,
NULL) ||
(cipher_nid == NID_aes_128_ccm &&
!EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen,
NULL)) ||
#endif /* !(OPENSSL_3_API_OR_NEWER) */
!EVP_DecryptInit_ex(actx, NULL, NULL, key, NULL))
{
EVP_CIPHER_CTX_free(actx);
return false;
}
*out_aead_ctx = actx;
return true;
}
bool
isc__quic_crypto_aead_encrypt(uint8_t *dest, const EVP_CIPHER *aead,
EVP_CIPHER_CTX *aead_ctx, const uint8_t *nonce,
const uint8_t *plaintext,
const size_t plaintextlen, const uint8_t *aad,
const size_t aadlen) {
REQUIRE(dest != NULL);
REQUIRE(aead != NULL);
REQUIRE(aead_ctx != NULL);
REQUIRE(nonce != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
size_t taglen = isc__quic_crypto_aead_taglen(aead);
int cipher_nid = EVP_CIPHER_nid(aead);
int len = 0;
#ifdef OPENSSL_3_API_OR_NEWER
OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
dest + plaintextlen, taglen),
OSSL_PARAM_construct_end(),
};
#endif /* OPENSSL_3_API_OR_NEWER */
if (!EVP_EncryptInit_ex(aead_ctx, NULL, NULL, NULL, nonce) ||
(cipher_nid == NID_aes_128_ccm &&
!EVP_EncryptUpdate(aead_ctx, NULL, &len, NULL,
(int)plaintextlen)) ||
!EVP_EncryptUpdate(aead_ctx, NULL, &len, aad, (int)aadlen) ||
!EVP_EncryptUpdate(aead_ctx, dest, &len, plaintext,
(int)plaintextlen) ||
!EVP_EncryptFinal_ex(aead_ctx, dest + len, &len) ||
#ifdef OPENSSL_3_API_OR_NEWER
!EVP_CIPHER_CTX_get_params(aead_ctx, params)
#else /* !(OPENSSL_3_API_OR_NEWER) */
!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_GET_TAG, (int)taglen,
dest + plaintextlen)
#endif /* OPENSSL_3_API_OR_NEWER */
)
{
return false;
}
return true;
}
bool
isc__quic_crypto_aead_decrypt(uint8_t *dest, const EVP_CIPHER *aead,
EVP_CIPHER_CTX *aead_ctx, const uint8_t *nonce,
const uint8_t *ciphertext, size_t ciphertextlen,
const uint8_t *aad, const size_t aadlen) {
REQUIRE(dest != NULL);
REQUIRE(aead != NULL);
REQUIRE(aead_ctx != NULL);
REQUIRE(nonce != NULL);
REQUIRE(ciphertext != NULL);
REQUIRE(ciphertextlen > 0);
size_t taglen = isc__quic_crypto_aead_taglen(aead);
int cipher_nid = EVP_CIPHER_nid(aead);
int len = 0;
const uint8_t *tag = NULL;
#ifdef OPENSSL_3_API_OR_NEWER
OSSL_PARAM params[2];
#endif /* OPENSSL_3_API_OR_NEWER */
if (taglen > ciphertextlen) {
return false;
}
ciphertextlen -= taglen;
tag = ciphertext + ciphertextlen;
#ifdef OPENSSL_3_API_OR_NEWER
params[0] = OSSL_PARAM_construct_octet_string(
OSSL_CIPHER_PARAM_AEAD_TAG, (void *)tag, taglen);
params[1] = OSSL_PARAM_construct_end();
#endif /* OPENSSL_3_API_OR_NEWER */
if (!EVP_DecryptInit_ex(aead_ctx, NULL, NULL, NULL, nonce) ||
#ifdef OPENSSL_3_API_OR_NEWER
!EVP_CIPHER_CTX_set_params(aead_ctx, params) ||
#else /* !(OPENSSL_3_API_OR_NEWER) */
!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_TAG, (int)taglen,
(uint8_t *)tag) ||
#endif /* !(OPENSSL_3_API_OR_NEWER) */
(cipher_nid == NID_aes_128_ccm &&
!EVP_DecryptUpdate(aead_ctx, NULL, &len, NULL,
(int)ciphertextlen)) ||
!EVP_DecryptUpdate(aead_ctx, NULL, &len, aad, (int)aadlen) ||
!EVP_DecryptUpdate(aead_ctx, dest, &len, ciphertext,
(int)ciphertextlen) ||
(cipher_nid != NID_aes_128_ccm &&
!EVP_DecryptFinal_ex(aead_ctx, dest + ciphertextlen, &len)))
{
return false;
}
return true;
}
bool
isc__quic_crypto_hp_cipher_ctx_encrypt_create(EVP_CIPHER_CTX **out_hp_cipher_ctx,
const EVP_CIPHER *hp_cipher,
const uint8_t *key) {
EVP_CIPHER_CTX *cipher_ctx = NULL;
REQUIRE(out_hp_cipher_ctx != NULL && *out_hp_cipher_ctx == NULL);
REQUIRE(hp_cipher != NULL);
REQUIRE(key != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
cipher_ctx = EVP_CIPHER_CTX_new();
if (cipher_ctx == NULL) {
return false;
}
if (!EVP_EncryptInit_ex(cipher_ctx, hp_cipher, NULL, key, NULL)) {
EVP_CIPHER_CTX_free(cipher_ctx);
return false;
}
*out_hp_cipher_ctx = cipher_ctx;
return true;
}
/*
* See RFC9001, sections 5.4.1-5.4.4 on QUIC header protection
* details. OpenSSL's EVP API (aka "EnVeloPe") hides gory details of
* different algorithms here.
*/
bool
isc__quic_crypto_hp_mask(uint8_t *dest, EVP_CIPHER_CTX *hp_ctx,
const uint8_t *sample) {
static const uint8_t mask[ISC__QUIC_CRYPTO_HP_MASK_LEN] = { 0 };
int len = 0;
REQUIRE(dest != NULL);
REQUIRE(hp_ctx != NULL);
REQUIRE(sample != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
if (!EVP_EncryptInit_ex(hp_ctx, NULL, NULL, NULL, sample) ||
!EVP_EncryptUpdate(hp_ctx, dest, &len, mask, sizeof(mask)) ||
!EVP_EncryptFinal_ex(hp_ctx, dest + sizeof(mask), &len))
{
return false;
}
return true;
}
void
isc__quic_crypto_cipher_ctx_free(EVP_CIPHER_CTX **pcipher_ctx) {
REQUIRE(pcipher_ctx != NULL && *pcipher_ctx != NULL);
RUNTIME_CHECK(quic_crypto_initialized);
EVP_CIPHER_CTX_free(*pcipher_ctx);
*pcipher_ctx = NULL;
}
bool
isc__quic_crypto_random(uint8_t *data, const size_t datalen) {
int ret = 0;
REQUIRE(data != NULL);
REQUIRE(datalen > 0);
ret = RAND_bytes(data, (int)datalen);
if (ret != 1) {
return false;
}
return true;
}

437
lib/isc/quic/quic_crypto.h Normal file
View File

@@ -0,0 +1,437 @@
/*
* 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 <openssl/evp.h>
#include <openssl/kdf.h>
#include <openssl/ssl.h>
#include <isc/tls.h>
#include <isc/types.h>
/*
* The following constants are either taken or derived from RFC9001, RFC9369
* (QUICv2).
*/
#define ISC__QUIC_CRYPTO_HP_MASK_LEN (5)
/* Client initial key label */
#define ISC__QUIC_CRYPTO_CLIENT_IN_LABEL "client in"
#define ISC__QUIC_CRYPTO_CLIENT_IN_LABEL_LEN \
(sizeof(ISC__QUIC_CRYPTO_CLIENT_IN_LABEL) - 1)
/* Server initial key label */
#define ISC__QUIC_CRYPTO_SERVER_IN_LABEL "server in"
#define ISC__QUIC_CRYPTO_SERVER_IN_LABEL_LEN \
(sizeof(ISC__QUIC_CRYPTO_SERVER_IN_LABEL) - 1)
/* Salt to derive initial secret for QUIC */
#define ISC__QUIC_CRYPTO_INITIAL_SALT_V1 \
"\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17\x9a\xe6\xa4\xc8\x0c\xad\xcc" \
"\xbb\x7f\x0a"
#define ISC__QUIC_CRYPTO_INITIAL_SALT_V1_LEN \
(sizeof(ISC__QUIC_CRYPTO_INITIAL_SALT_V1) - 1)
/* QUIC key label */
#define ISC__QUIC_CRYPTO_QUIC_KEY_LABEL_V1 "quic key"
#define ISC__QUIC_CRYPTO_QUIC_KEY_LABEL_V1_LEN \
(sizeof(ISC__QUIC_CRYPTO_QUIC_KEY_LABEL_V1) - 1)
/* QUIC IV label */
#define ISC__QUIC_CRYPTO_QUIC_IV_LABEL_V1 "quic iv"
#define ISC__QUIC_CRYPTO_QUIC_IV_LABEL_V1_LEN \
((sizeof(ISC__QUIC_CRYPTO_QUIC_IV_LABEL_V1) - 1))
/* QUIC header protection label */
#define ISC__QUIC_CRYPTO_QUIC_HP_LABEL_V1 "quic hp"
#define ISC__QUIC_CRYPTO_QUIC_HP_LABEL_V1_LEN \
((sizeof(ISC__QUIC_CRYPTO_QUIC_HP_LABEL_V1) - 1))
/* QUIC key update label */
#define ISC__QUIC_CRYPTO_QUIC_KU_LABEL_V1 "quic ku"
#define ISC__QUIC_CRYPTO_QUIC_KU_LABEL_V1_LEN \
((sizeof(ISC__QUIC_CRYPTO_QUIC_KU_LABEL_V1) - 1))
/* Salt to derive initial secret for QUICv2 */
#define ISC__QUIC_CRYPTO_INITIAL_SALT_V2 \
"\x0d\xed\xe3\xde\xf7\x00\xa6\xdb\x81\x93\x81\xbe\x6e\x26\x9d\xcb\xf9" \
"\xbd\x2e\xd9"
#define ISC__QUIC_CRYPTO_INITIAL_SALT_V2_LEN \
(sizeof(ISC__QUIC_CRYPTO_INITIAL_SALT_V2) - 1)
/* QUICv2 key label */
#define ISC__QUIC_CRYPTO_QUIC_KEY_LABEL_V2 "quicv2 key"
#define ISC__QUIC_CRYPTO_QUIC_KEY_LABEL_V2_LEN \
(sizeof(ISC__QUIC_CRYPTO_QUIC_KEY_LABEL_V2) - 1)
/* QUICv2 IV label */
#define ISC__QUIC_CRYPTO_QUIC_IV_LABEL_V2 "quicv2 iv"
#define ISC__QUIC_CRYPTO_QUIC_IV_LABEL_V2_LEN \
((sizeof(ISC__QUIC_CRYPTO_QUIC_IV_LABEL_V2) - 1))
/* QUICv2 header protection label */
#define ISC__QUIC_CRYPTO_QUIC_HP_LABEL_V2 "quicv2 hp"
#define ISC__QUIC_CRYPTO_QUIC_HP_LABEL_V2_LEN \
((sizeof(ISC__QUIC_CRYPTO_QUIC_HP_LABEL_V2) - 1))
/* QUICv2 key update label */
#define ISC__QUIC_CRYPTO_QUIC_KU_LABEL_V2 "quicv2 ku"
#define ISC__QUIC_CRYPTO_QUIC_KU_LABEL_V2_LEN \
((sizeof(ISC__QUIC_CRYPTO_QUIC_KU_LABEL_V2) - 1))
/* These are defined in RFC8446 */
#define ISC__QUIC_CRYPTO_KEY_LABEL "key"
#define ISC__QUIC_CRYPTO_KEY_LABEL_LEN (sizeof(ISC__QUIC_CRYPTO_KEY_LABEL) - 1)
#define ISC__QUIC_CRYPTO_IV_LABEL "iv"
#define ISC__QUIC_CRYPTO_IV_LABEL_LEN ((sizeof(ISC__QUIC_CRYPTO_IV_LABEL) - 1))
bool
isc__quic_crypto_tls_cipher_supported(const isc_tls_cipher_t *tls_cipher);
/*%<
* Check if the given TLS cipher can be used for QUIC.
*
* Requires:
*\li 'tls_cipher' != NULL.
*/
const EVP_CIPHER *
isc__quic_crypto_tls_cipher_aead(const isc_tls_cipher_t *tls_cipher);
/*%<
* Return the AEAD-scheme associated with the given TLS cipher.
*
* Requires:
*\li 'tls_cipher' != NULL.
*/
size_t
isc__quic_crypto_aead_taglen(const EVP_CIPHER *aead);
/*%<
* Return the tag length (overhead) for the given AEAD-scheme.
*
* Requires:
*\li 'aead' != NULL.
*/
size_t
isc__quic_crypto_aead_keylen(const EVP_CIPHER *aead);
/*%<
* Return the tag length for the given AEAD-scheme.
*
* Requires:
*\li 'aead' != NULL.
*/
size_t
isc__quic_crypto_aead_ivlen(const EVP_CIPHER *aead);
/*%<
* Return the IV (initialization vector) length for the given AEAD-scheme.
*
* Requires:
*\li 'aead' != NULL.
*/
size_t
isc__quic_crypto_aead_packet_protection_ivlen(const EVP_CIPHER *aead);
/*%<
* Return the packet protection IV (initialization vector) length for the given
* AEAD-scheme.
*
* Requires:
*\li 'aead' != NULL.
*/
uint64_t
isc__quic_crypto_tls_cipher_aead_max_encryption(
const isc_tls_cipher_t *tls_cipher);
/*%<
* Return the max encryption limit for the AEAD-scheme associated with the given
* TLS cipher.
*
* Requires:
*\li 'tls_cipher' != NULL.
*/
uint64_t
isc__quic_crypto_tls_cipher_aead_max_decyption_failures(
const isc_tls_cipher_t *tls_cipher);
/*%<
* Return the max decryption failures limit for the AEAD-scheme
* associated with the given TLS cipher.
*
* Requires:
*\li 'tls_cipher' != NULL.
*/
const EVP_MD *
isc__quic_crypto_tls_cipher_md(const isc_tls_cipher_t *tls_cipher);
/*%<
* Return the message digest function associated with the given TLS cipher.
*
* Requires:
*\li 'tls_cipher' != NULL.
*/
size_t
isc__quic_crypto_md_hashlen(const EVP_MD *md);
/*%<
* Return the message digest (hash) size.
*
* Requires:
*\li 'md' != NULL.
*/
const EVP_CIPHER *
isc__quic_crypto_tls_cipher_hp(const isc_tls_cipher_t *tls_cipher);
/*%<
* Return the QUIC header protection cipher associated with the given TLS
* cipher.
*
* Requires:
*\li 'tls_cipher' != NULL.
*/
const EVP_MD *
isc__quic_crypto_md_sha256(void);
/*%<
* Return the SHA256 message digest function.
*/
const EVP_CIPHER *
isc__quic_crypto_aead_aes_128_gcm(void);
/*%<
* Return the AES-128-GCM AEAD-scheme.
*/
const EVP_CIPHER *
isc__quic_crypto_cipher_aes_128_ctr(void);
/*%<
* Return the AES-128-CTR cipher.
*/
bool
isc__quic_crypto_hkdf_extract(uint8_t *dest, const EVP_MD *md,
const uint8_t *secret, const size_t secretlen,
const uint8_t *salt, const size_t saltlen);
/*%<
* Perform "HKDF-Extract" operation.
*
* The caller is responsible to specify the destination buffer that
* has enough capacity to store the output.
*
* See RFC5869 for more details.
*
* Requires:
*\li 'dest' != NULL;
*\li 'md' != NULL;
*\li 'secret' != NULL;
*\li 'secretlen' > 0.
*/
bool
isc__quic_crypto_hkdf_expand(uint8_t *dest, size_t destlen, const EVP_MD *md,
const uint8_t *secret, const size_t secretlen,
const uint8_t *info, const size_t infolen);
/*%<
* Perform "HKDF-Expand" operation.
*
* See RFC5869 for more details.
*
* Requires:
*\li 'dest' != NULL;
*\li 'destlen' > 0;
*\li 'md' != NULL;
*\li 'secret' != NULL;
*\li 'secretlen' > 0;
*\li 'info' != NULL;
*\li 'infolen' > 0.
*/
bool
isc__quic_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen,
const EVP_MD *md, const uint8_t *secret,
const size_t secretlen, const uint8_t *label,
const size_t labellen);
/*%<
* Perform "HKDF-Expand-Label" operation as defined for TLSv1.3.
*
* See RFC8446, Section 7.1 for more details.
*
* Requires:
*\li 'dest' != NULL;
*\li 'destlen' > 0;
*\li 'md' != NULL;
*\li 'secret' != NULL;
*\li 'secretlen' > 0;
*\li 'label' != NULL;
*\li 'labellen' > 0.
*/
bool
isc__quic_crypto_hkdf(uint8_t *dest, size_t destlen, const EVP_MD *md,
const uint8_t *secret, size_t secretlen,
const uint8_t *salt, size_t saltlen, const uint8_t *info,
size_t infolen);
/*%<
* Perform "HKDF" operation.
*
* See RFC5869 for more details.
*
* Requires:
*\li 'dest' != NULL;
*\li 'destlen' > 0;
*\li 'md' != NULL;
*\li 'secret' != NULL;
*\li 'secretlen' > 0;
*\li 'info' != NULL;
*\li 'infolen' > 0.
*/
bool
isc__quic_crypto_aead_ctx_encrypt_create(EVP_CIPHER_CTX **out_aead_ctx,
const EVP_CIPHER *aead,
const uint8_t *key, size_t noncelen);
/*%<
* Create AEAD encryption context.
*
* Requires:
*\li 'out_aead_ctx' != NULL && '*out_aead_ctx' == NULL;
*\li 'aead' != NULL;
*\li 'key' != NULL.
*/
bool
isc__quic_crypto_aead_ctx_decrypt_create(EVP_CIPHER_CTX **out_aead_ctx,
const EVP_CIPHER *aead,
const uint8_t *key, size_t noncelen);
/*%<
* Create AEAD decryption context.
*
* Requires:
*\li 'out_aead_ctx' != NULL && '*out_aead_ctx' == NULL;
*\li 'aead' != NULL;
*\li 'key' != NULL.
*/
bool
isc__quic_crypto_aead_encrypt(uint8_t *dest, const EVP_CIPHER *aead,
EVP_CIPHER_CTX *aead_ctx, const uint8_t *nonce,
const uint8_t *plaintext,
const size_t plaintextlen, const uint8_t *aad,
const size_t aadlen);
/*%<
* Perform authenticated encryption operation (aka "seal" in BoringSSL
* parlance).
*
* The caller is responsible to specify the destination buffer that
* has enough capacity to store the output.
*
* See RFC5116 for more details.
*
* Requires:
*\li 'dest' != NULL;
*\li 'aead' != NULL;
*\li 'aead_ctx' != NULL;
*\li 'nonce' != NULL.
*/
bool
isc__quic_crypto_aead_decrypt(uint8_t *dest, const EVP_CIPHER *aead,
EVP_CIPHER_CTX *aead_ctx, const uint8_t *nonce,
const uint8_t *ciphertext, size_t ciphertextlen,
const uint8_t *aad, const size_t aadlen);
/*%<
* Perform authenticated decryption operation (aka "open" in BoringSSL
* parlance).
*
* The caller is responsible to specify the destination buffer that
* has enough capacity to store the output.
*
* See RFC5116 for more details.
*
* Requires:
*\li 'dest' != NULL;
*\li 'aead' != NULL;
*\li 'aead_ctx' != NULL;
*\li 'nonce' != NULL;
*\li 'ciphertext' != NULL;
*\li 'ciphertextlen' > 0.
*/
bool
isc__quic_crypto_hp_cipher_ctx_encrypt_create(EVP_CIPHER_CTX **out_hp_cipher_ctx,
const EVP_CIPHER *hp_cipher,
const uint8_t *key);
/*%<
* Create header protection encryption context.
*
* See RFC9001, Section 5.4 for more details.
*
* Requires:
*\li 'out_hp_cipher_ctx' != NULL && '*out_hp_cipher_ctx' == NULL;
*\li 'hp_cipher' != NULL;
*\li 'key' != NULL.
*/
bool
isc__quic_crypto_hp_mask(uint8_t *dest, EVP_CIPHER_CTX *hp_ctx,
const uint8_t *sample);
/*%<
* Calculate header protection mask. The output buffer 'dest' should
* be at least 'ISC__QUIC_CRYPTO_HP_MASK_LEN' bytes long.
*
* See RFC9001, Section 5.4.1 for more details.
*
* Requires:
*\li 'dest' != NULL;
*\li 'hp_ctx' != NULL;
*\li 'sample' != NULL.
*/
void
isc__quic_crypto_cipher_ctx_free(EVP_CIPHER_CTX **pcipher_ctx);
/*%<
* Free a header protection or AEAD encryption context.
*
* Requires:
*\li 'pcipher_ctx' != NULL && '*pcipher_ctx' != NULL.
*/
bool
isc__quic_crypto_random(uint8_t *data, const size_t datalen);
/*%<
* Writes cryptographically-secure random data into the given
* buffer.
*
* The function relies on a user-space CSPRNG provided by the
* crypto-library (e.g. OpenSSL).
*
* Being user-space it might be preferred to 'isc_entropy_get()'
* which, ultimately, might lead to sys. calls in relatively hot
* paths. For what we need it (mostly - salt generation) this
* approach is good enough.
*
* For things that should never cross the running system boundary it
* is better to use 'isc_entropy_get()'.
*
* NOTE: See the OpenSSL's manual page for 'RAND_bytes()'. Also take
* your time to understand why 'RAND_priv_bytes()' is preferred in
* some cases (use 'isc_entropy_get()' in its stead).
*/
void
isc__quic_crypto_initialize(void);
void
isc__quic_crypto_shutdown(void);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,458 @@
/*
* 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.
*/
/*
* Native QUIC interface for OpenSSL forks implementing
* BoringSSL/LibreSSL/QuicTLS QUIC API.
*
* See here:
* https://github.com/quictls/openssl/blob/openssl-3.1.5%2Bquic/doc/man3/SSL_CTX_set_quic_method.pod
* https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#QUIC-integration
*
* LibreSSL's interface strives to be compatible with other
* libraries. That being said, there are still differences in how
* different libraries behave, in particular how they report current
* read and write levels, but that should not matter much on practice.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <openssl/ssl.h>
#include <isc/magic.h>
#include <isc/tls.h>
#include <isc/util.h>
#include "quic-int.h"
#define QUIC_NATIVE_DATA_MAGIC ISC_MAGIC('Q', 'n', 'C', 'd')
#define VALID_QUIC_NATIVE_DATA(t) ISC_MAGIC_VALID(t, QUIC_NATIVE_DATA_MAGIC)
static int
compat_encryption_level_to_native(const isc_quic_encryption_level_t level);
static void
quic_native_tlsctx_configure(isc_tlsctx_t *tlsctx);
static void
quic_native_tlsctx_keylog_callback(const isc_tls_t *tls, const char *line);
static void
quic_native_tls_init(isc_tls_t *tls, isc_mem_t *mctx);
static void
quic_native_tls_uninit(isc_tls_t *tls);
static bool
quic_native_tls_calling_method_cb(const isc_tls_t *tls);
static int
quic_native_tls_set_quic_method(isc_tls_t *tls,
const isc_tls_quic_method_t *method);
static int
quic_native_tls_provide_quic_data(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const uint8_t *data, const size_t len);
static int
quic_native_tls_do_quic_handshake(isc_tls_t *tls);
static int
quic_native_tls_process_quic_post_handshake(isc_tls_t *tls);
static int
quic_native_tls_set_quic_transport_params(isc_tls_t *tls, const uint8_t *params,
const size_t params_len);
static void
quic_native_tls_get_peer_quic_transport_params(isc_tls_t *tls,
const uint8_t **out_params,
size_t *out_params_len);
static isc_quic_encryption_level_t
quic_native_read_level(const isc_tls_t *tls);
static isc_quic_encryption_level_t
quic_native_write_level(const isc_tls_t *tls);
static isc_tls_quic_interface_t native_quic_interface =
(isc_tls_quic_interface_t){
.tlsctx_configure = quic_native_tlsctx_configure,
.tlsctx_keylog_callback = quic_native_tlsctx_keylog_callback,
.tls_init = quic_native_tls_init,
.tls_uninit = quic_native_tls_uninit,
.tls_calling_method_cb = quic_native_tls_calling_method_cb,
.tls_set_quic_method = quic_native_tls_set_quic_method,
.tls_provide_quic_data = quic_native_tls_provide_quic_data,
.tls_do_quic_handshake = quic_native_tls_do_quic_handshake,
.tls_process_quic_post_handshake =
quic_native_tls_process_quic_post_handshake,
.tls_set_quic_transport_params =
quic_native_tls_set_quic_transport_params,
.tls_get_peer_quic_transport_params =
quic_native_tls_get_peer_quic_transport_params,
.tls_quic_read_level = quic_native_read_level,
.tls_quic_write_level = quic_native_write_level
};
static int
quic_native_method_set_read_secret(isc_tls_t *tls,
enum ssl_encryption_level_t level,
const isc_tls_cipher_t *cipher,
const uint8_t *secret, size_t secret_len);
static int
quic_native_method_set_write_secret(isc_tls_t *tls,
enum ssl_encryption_level_t level,
const isc_tls_cipher_t *cipher,
const uint8_t *secret, size_t secret_len);
#ifndef HAVE_QUIC_METHOD_SET_READ_WRITE_SECRET
static int
quic_native_method_set_encryption_secrets(isc_tls_t *tls,
enum ssl_encryption_level_t level,
const uint8_t *read_secret,
const uint8_t *write_secret,
size_t secret_len);
#endif /* HAVE_QUIC_METHOD_SET_READ_WRITE_SECRET */
static int
quic_native_method_add_handshake_data(isc_tls_t *tls,
enum ssl_encryption_level_t level,
const uint8_t *hs_data, size_t hs_len);
static int
quic_native_method_flush_flight(isc_tls_t *tls);
static int
quic_native_method_send_alert(isc_tls_t *tls, enum ssl_encryption_level_t level,
uint8_t alert);
static SSL_QUIC_METHOD native_quic_method = (SSL_QUIC_METHOD){
#ifdef HAVE_QUIC_METHOD_SET_READ_WRITE_SECRET
.set_read_secret = quic_native_method_set_read_secret,
.set_write_secret = quic_native_method_set_write_secret,
#else
.set_encryption_secrets = quic_native_method_set_encryption_secrets,
#endif /* HAVE_QUIC_METHOD_SET_READ_WRITE_SECRET */
.add_handshake_data = quic_native_method_add_handshake_data,
.flush_flight = quic_native_method_flush_flight,
.send_alert = quic_native_method_send_alert
};
typedef struct quic_native_data {
uint32_t magic;
isc_mem_t *mctx;
const isc_tls_quic_method_t *method;
bool calling_method_cb;
} quic_native_data_t;
static int
compat_encryption_level_to_native(const isc_quic_encryption_level_t level) {
switch (level) {
case ISC_QUIC_ENCRYPTION_INITIAL:
return ssl_encryption_initial;
case ISC_QUIC_ENCRYPTION_EARLY_DATA:
return ssl_encryption_early_data;
case ISC_QUIC_ENCRYPTION_HANDSHAKE:
return ssl_encryption_handshake;
case ISC_QUIC_ENCRYPTION_APPLICATION:
return ssl_encryption_application;
}
UNREACHABLE();
}
static isc_quic_encryption_level_t
native_to_compat_encryption_level(int level) {
switch (level) {
case ssl_encryption_initial:
return ISC_QUIC_ENCRYPTION_INITIAL;
case ssl_encryption_early_data:
return ISC_QUIC_ENCRYPTION_EARLY_DATA;
case ssl_encryption_handshake:
return ISC_QUIC_ENCRYPTION_HANDSHAKE;
case ssl_encryption_application:
return ISC_QUIC_ENCRYPTION_APPLICATION;
}
UNREACHABLE();
}
static void
quic_native_tlsctx_configure(isc_tlsctx_t *tlsctx) {
/* dummy */
UNUSED(tlsctx);
}
/* Will not be called on LibreSSL */
static void
quic_native_tlsctx_keylog_callback(const isc_tls_t *tls, const char *line) {
/* dummy */
UNUSED(tls);
UNUSED(line);
}
static void
quic_native_tls_init(isc_tls_t *tls, isc_mem_t *mctx) {
quic_native_data_t *data = isc_mem_cget(mctx, 1, sizeof(*data));
isc_mem_attach(mctx, &data->mctx);
data->magic = QUIC_NATIVE_DATA_MAGIC;
INSIST(isc__tls_get_quic_data(tls) == NULL);
isc__tls_set_quic_data(tls, data);
}
static void
quic_native_tls_uninit(isc_tls_t *tls) {
isc_mem_t *mctx = NULL;
quic_native_data_t *data = isc__tls_get_quic_data(tls);
INSIST(VALID_QUIC_NATIVE_DATA(data));
mctx = data->mctx;
isc_mem_cput(mctx, data, 1, sizeof(*data));
isc__tls_set_quic_data(tls, NULL);
isc_mem_detach(&mctx);
}
static bool
quic_native_tls_calling_method_cb(const isc_tls_t *tls) {
quic_native_data_t *data = isc__tls_get_quic_data(tls);
INSIST(VALID_QUIC_NATIVE_DATA(data));
return data->calling_method_cb;
}
static int
quic_native_tls_set_quic_method(isc_tls_t *tls,
const isc_tls_quic_method_t *method) {
int ret = 0;
quic_native_data_t *data = isc__tls_get_quic_data(tls);
INSIST(VALID_QUIC_NATIVE_DATA(data));
ret = SSL_set_quic_method(tls, &native_quic_method);
if (ret == 0) {
return ret;
}
data->method = method;
return ret;
}
static int
quic_native_tls_provide_quic_data(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const uint8_t *data, const size_t len) {
int encryption_level = compat_encryption_level_to_native(level);
return SSL_provide_quic_data(tls, encryption_level, data, len);
}
static int
quic_native_tls_do_quic_handshake(isc_tls_t *tls) {
return SSL_do_handshake(tls);
}
static int
quic_native_tls_process_quic_post_handshake(isc_tls_t *tls) {
return SSL_process_quic_post_handshake(tls);
}
static int
quic_native_tls_set_quic_transport_params(isc_tls_t *tls, const uint8_t *params,
const size_t params_len) {
return SSL_set_quic_transport_params(tls, params, params_len);
}
static void
quic_native_tls_get_peer_quic_transport_params(isc_tls_t *tls,
const uint8_t **out_params,
size_t *out_params_len) {
SSL_get_peer_quic_transport_params(tls, out_params, out_params_len);
}
static int
quic_native_method_set_read_secret(isc_tls_t *tls,
enum ssl_encryption_level_t level,
const isc_tls_cipher_t *cipher,
const uint8_t *secret, size_t secret_len) {
quic_native_data_t *data = isc__tls_get_quic_data(tls);
isc_quic_encryption_level_t compat_level =
native_to_compat_encryption_level((int)level);
bool ret = false;
INSIST(VALID_QUIC_NATIVE_DATA(data));
data->calling_method_cb = true;
ret = data->method->set_read_secret(tls, compat_level, cipher, secret,
secret_len);
data->calling_method_cb = false;
if (!ret) {
return 0;
}
return 1;
}
static int
quic_native_method_set_write_secret(isc_tls_t *tls,
enum ssl_encryption_level_t level,
const isc_tls_cipher_t *cipher,
const uint8_t *secret, size_t secret_len) {
quic_native_data_t *data = isc__tls_get_quic_data(tls);
isc_quic_encryption_level_t compat_level =
native_to_compat_encryption_level((int)level);
bool ret = false;
INSIST(VALID_QUIC_NATIVE_DATA(data));
data->calling_method_cb = true;
ret = data->method->set_write_secret(tls, compat_level, cipher, secret,
secret_len);
data->calling_method_cb = false;
if (!ret) {
return 0;
}
return 1;
}
#ifndef HAVE_QUIC_METHOD_SET_READ_WRITE_SECRET
static int
quic_native_method_set_encryption_secrets(isc_tls_t *tls,
enum ssl_encryption_level_t level,
const uint8_t *read_secret,
const uint8_t *write_secret,
size_t secret_len) {
const isc_tls_cipher_t *cipher = SSL_get_current_cipher(tls);
int ret = 1;
RUNTIME_CHECK(cipher != NULL);
if (read_secret != NULL) {
ret = quic_native_method_set_read_secret(
tls, level, cipher, read_secret, secret_len);
}
if (ret < 1) {
return ret;
}
if (write_secret != NULL) {
ret = quic_native_method_set_write_secret(
tls, level, cipher, write_secret, secret_len);
}
return ret;
}
#endif /* HAVE_QUIC_METHOD_SET_READ_WRITE_SECRET */
static int
quic_native_method_add_handshake_data(isc_tls_t *tls,
enum ssl_encryption_level_t level,
const uint8_t *hs_data, size_t hs_len) {
quic_native_data_t *data = isc__tls_get_quic_data(tls);
isc_quic_encryption_level_t compat_level =
native_to_compat_encryption_level((int)level);
bool ret = false;
INSIST(VALID_QUIC_NATIVE_DATA(data));
data->calling_method_cb = true;
ret = data->method->add_handshake_data(tls, compat_level, hs_data,
hs_len);
data->calling_method_cb = false;
if (!ret) {
return 0;
}
return 1;
}
static int
quic_native_method_flush_flight(isc_tls_t *tls) {
/* dummy */
UNUSED(tls);
return 1;
}
static int
quic_native_method_send_alert(isc_tls_t *tls, enum ssl_encryption_level_t level,
uint8_t alert) {
quic_native_data_t *data = isc__tls_get_quic_data(tls);
isc_quic_encryption_level_t compat_level =
native_to_compat_encryption_level((int)level);
bool ret = false;
INSIST(VALID_QUIC_NATIVE_DATA(data));
data->calling_method_cb = true;
ret = data->method->send_alert(tls, compat_level, alert);
data->calling_method_cb = false;
if (!ret) {
return 0;
}
return 1;
}
static isc_quic_encryption_level_t
quic_native_read_level(const isc_tls_t *tls) {
quic_native_data_t *data = isc__tls_get_quic_data(tls);
isc_quic_encryption_level_t compat_level;
INSIST(VALID_QUIC_NATIVE_DATA(data));
compat_level =
native_to_compat_encryption_level(SSL_quic_read_level(tls));
return compat_level;
}
static isc_quic_encryption_level_t
quic_native_write_level(const isc_tls_t *tls) {
quic_native_data_t *data = isc__tls_get_quic_data(tls);
isc_quic_encryption_level_t compat_level;
INSIST(VALID_QUIC_NATIVE_DATA(data));
compat_level =
native_to_compat_encryption_level(SSL_quic_write_level(tls));
return compat_level;
}
const isc_tls_quic_interface_t *
isc__tls_get_native_quic_interface(void) {
return &native_quic_interface;
}

2266
lib/isc/quic/quic_session.c Normal file

File diff suppressed because it is too large Load Diff

149
lib/isc/quic/quic_session.h Normal file
View File

@@ -0,0 +1,149 @@
/*
* 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 <isc/ht.h>
#include <isc/ngtcp2_crypto.h>
#include <isc/ngtcp2_utils.h>
#include <isc/quic.h>
#include <isc/refcount.h>
#define QUIC_SESSION_MAGIC ISC_MAGIC('Q', 'U', 'I', 'C')
#define VALID_QUIC_SESSION(t) ISC_MAGIC_VALID(t, QUIC_SESSION_MAGIC)
#define QUIC_CID_MAGIC ISC_MAGIC('Q', 'C', 'I', 'D')
#define VALID_QUIC_CID(t) ISC_MAGIC_VALID(t, QUIC_CID_MAGIC)
typedef struct isc__quic_send_req isc__quic_send_req_t;
/*
* NOTE: for all send queues new requests are appended strictly to the
* end
*/
typedef struct isc__quic_stream_data {
int64_t stream_id;
void *stream_user_data;
bool close_cb_called;
bool fin;
/* local send queue */
ISC_LIST(isc__quic_send_req_t) send_queue;
size_t send_queue_len;
ISC_LINK(struct isc__quic_stream_data) stream_link;
} isc__quic_stream_data_t;
struct isc__quic_send_req {
isc__quic_stream_data_t *stream;
isc_buffer_t data;
bool fin;
isc_quic_send_cb_t cb;
void *cbarg;
ISC_LINK(struct isc__quic_send_req) global_link;
ISC_LINK(struct isc__quic_send_req) local_link;
};
typedef ISC_LIST(isc__quic_stream_data_t) quic_stream_list_t;
struct isc_quic_session {
unsigned int magic;
isc_refcount_t references;
bool is_server;
int state;
isc_tlsctx_t *tlsctx;
isc_tls_t *tls;
isc_mem_t *mctx;
isc_quic_session_interface_t cbs;
void *cbarg;
ngtcp2_tstamp ts;
uint64_t handshake_timeout;
uint64_t idle_timeout;
size_t max_uni_streams;
size_t max_bidi_streams;
ngtcp2_mem mem;
uint8_t secret_storage[ISC_NGTCP2_CRYPTO_STATIC_SECRET_LEN];
isc_buffer_t secret;
uint32_t orig_client_chosen_version;
uint32_t negotiated_version;
uint32_t available_versions_list_storage[8];
isc_buffer_t available_versions;
isc_buffer_t token;
uint8_t token_storage[ISC_MAX(ISC_NGTCP2_CRYPTO_MAX_RETRY_TOKEN_LEN,
ISC_NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_LEN)];
ngtcp2_token_type token_type;
isc_quic_cid_t *initial_scid;
isc_quic_cid_t *initial_dcid;
isc_quic_cid_t *odcid;
isc_quic_cid_t *rcid;
ngtcp2_path_storage path_st;
ngtcp2_conn *conn;
ngtcp2_ccerr conn_err;
bool closing;
isc_buffer_t *close_msg;
ngtcp2_tstamp last_expiry;
size_t sent_before_expiry;
bool write_after_read;
struct quic_session_streams {
isc_ht_t *idx;
quic_stream_list_t list;
isc_mempool_t *streams_pool;
} streams;
/* global send queue for all streams */
ISC_LIST(isc__quic_send_req_t) send_queue;
size_t send_queue_len;
isc_mempool_t *send_pool;
/* list of associated CIDs */
ISC_LIST(isc_quic_cid_t) cids;
size_t cids_len;
bool hs_confirmed;
};
struct isc_quic_cid {
unsigned int magic;
isc_refcount_t references;
bool source; /* local */
ngtcp2_cid cid;
isc_mem_t *mctx;
ISC_LINK(struct isc_quic_cid) global_link;
ISC_LINK(struct isc_quic_cid) local_link;
};

View File

@@ -0,0 +1,297 @@
/*
* 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.
*/
#include <ctype.h>
#include <stdbool.h>
#include <isc/mem.h>
#include <isc/tls.h>
#include <isc/util.h>
#include "quic-int.h"
/*
* We have used the following IETF draft to derive the grammar below:
*
* https://datatracker.ietf.org/doc/draft-ietf-tls-keylogfile/
*
* Both the grammar and the parser should be *painfully* accurate and
* describes what is used to be named "NSS Key Log Format".
*
* tls-keylog-entry = label " " client-random " " secret [ end-line ].
* label = "CLIENT_EARLY_TRAFFIC_SECRET" |
* "CLIENT_HANDSHAKE_TRAFFIC_SECRET" |
* "CLIENT_RANDOM" |
* "CLIENT_TRAFFIC_SECRET_0" |
* "EARLY_EXPORTER_MASTER_SECRET" |
* "EXPORTER_SECRET" |
* "SERVER_HANDSHAKE_TRAFFIC_SECRET" |
* "SERVER_TRAFFIC_SECRET_0".
* client-random = hex-byte { hex-byte }. (* 64 characters, 32 bytes *)
* secret = hex-byte { hex-byte }.
* end-line = end-char { end-char }.
* hex-byte = hex-char hex-char.
* hex-char = digit | "a" | "A" | "b" | "B" | "c" | "C" |
* "d" | "D" | "e" | "E" | "f" | "F".
* digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".
* end-char = "\n" | "\r".
*/
#define CLIENT_RANDOM_LEN (32)
typedef struct keylog_parser_state {
const char *restrict str;
isc__tls_keylog_label_t *restrict out_label;
isc_buffer_t *restrict out_client_random;
isc_buffer_t *restrict out_secret;
isc_result_t result;
} keylog_parser_state_t;
static bool
match_str(keylog_parser_state_t *restrict st, const char *s, const size_t len) {
return strncasecmp(s, st->str, len) == 0;
}
static bool
match_str_advance(keylog_parser_state_t *restrict st, const char *s,
const size_t len) {
if (!match_str(st, s, len)) {
return false;
}
st->str += len;
return true;
}
#define MATCH(ch) (st->str[0] == (ch))
#define MATCHSTR_ADVANCE(s) (match_str_advance(st, s, sizeof(s) - 1))
#define MATCH_XDIGIT() (isxdigit((unsigned char)(st->str[0])))
#define ADVANCE() (st->str++)
#define GETCH() (st->str[0])
static bool
rule_tls_keylog_entry(keylog_parser_state_t *restrict st);
static bool
rule_label(keylog_parser_state_t *restrict st);
static bool
rule_client_random(keylog_parser_state_t *restrict st);
static bool
rule_secret(keylog_parser_state_t *restrict st);
static bool
rule_endline(keylog_parser_state_t *restrict st);
static bool
rule_hex_byte(keylog_parser_state_t *restrict st, isc_buffer_t *buf);
static bool
rule_tls_keylog_entry(keylog_parser_state_t *restrict st) {
if (!rule_label(st)) {
return false;
}
if (!MATCH(' ')) {
return false;
}
ADVANCE();
if (!rule_client_random(st)) {
return false;
}
if (!MATCH(' ')) {
return false;
}
ADVANCE();
if (!rule_secret(st)) {
return false;
}
(void)rule_endline(st);
return true;
}
static bool
rule_label(keylog_parser_state_t *restrict st) {
isc__tls_keylog_label_t label = ISC__TLS_KL_ILLEGAL;
if (MATCHSTR_ADVANCE("CLIENT_")) {
if (MATCHSTR_ADVANCE("EARLY_TRAFFIC_SECRET")) {
label = ISC__TLS_KL_CLIENT_EARLY_TRAFFIC_SECRET;
} else if (MATCHSTR_ADVANCE("HANDSHAKE_TRAFFIC_SECRET")) {
label = ISC__TLS_KL_CLIENT_HANDSHAKE_TRAFFIC_SECRET;
} else if (MATCHSTR_ADVANCE("RANDOM")) {
label = ISC__TLS_KL_CLIENT_RANDOM;
} else if (MATCHSTR_ADVANCE("TRAFFIC_SECRET_0")) {
label = ISC__TLS_KL_CLIENT_TRAFFIC_SECRET_0;
} else {
return false;
}
} else if (MATCHSTR_ADVANCE("SERVER_")) {
if (MATCHSTR_ADVANCE("HANDSHAKE_TRAFFIC_SECRET")) {
label = ISC__TLS_KL_SERVER_HANDSHAKE_TRAFFIC_SECRET;
} else if (MATCHSTR_ADVANCE("TRAFFIC_SECRET_0")) {
label = ISC__TLS_KL_SERVER_TRAFFIC_SECRET_0;
} else {
return false;
}
} else if (MATCHSTR_ADVANCE("EARLY_EXPORTER_MASTER_SECRET")) {
label = ISC__TLS_KL_EARLY_EXPORTER_MASTER_SECRET;
} else if (MATCHSTR_ADVANCE("EXPORTER_SECRET")) {
label = ISC__TLS_KL_EXPORTER_SECRET;
} else {
return false;
}
if (st->out_label != NULL) {
*st->out_label = label;
}
return true;
}
static bool
rule_client_random(keylog_parser_state_t *restrict st) {
for (size_t i = 0; i < CLIENT_RANDOM_LEN; i++) {
if (!rule_hex_byte(st, st->out_client_random)) {
return false;
}
}
return true;
}
static bool
rule_secret(keylog_parser_state_t *restrict st) {
if (!rule_hex_byte(st, st->out_secret)) {
return false;
}
for (;;) {
bool ret = rule_hex_byte(st, st->out_secret);
if (ret) {
continue;
}
if (st->result != ISC_R_UNSET) {
return false;
} else {
break;
}
}
return true;
}
static bool
rule_endline(keylog_parser_state_t *restrict st) {
if (!(MATCH('\n') || MATCH('\r'))) {
return false;
}
ADVANCE();
while (MATCH('\n') || MATCH('\r')) {
ADVANCE();
}
return true;
}
static inline uint8_t
hex_subchar_val(const char ch) {
uint8_t subval = 0;
if (ch >= '0' && ch <= '9') {
subval = ch - '0';
} else {
subval = tolower(ch) - 'a' + 10;
}
return subval;
}
static inline uint8_t
hex_chars_val(const char *restrict str) {
uint8_t value = 0;
value = (hex_subchar_val(str[0]) << 4);
value += hex_subchar_val(str[1]);
return value;
}
static bool
rule_hex_byte(keylog_parser_state_t *restrict st, isc_buffer_t *buf) {
char byte_str[3];
if (!MATCH_XDIGIT()) {
return false;
}
byte_str[0] = GETCH();
ADVANCE();
if (!MATCH_XDIGIT()) {
return false;
}
byte_str[1] = GETCH();
ADVANCE();
byte_str[2] = '\0';
if (buf != NULL) {
const uint8_t byte = hex_chars_val(byte_str);
const isc_result_t result = isc_buffer_reserve(buf, 1);
if (result != ISC_R_SUCCESS) {
st->result = result;
return false;
}
isc_buffer_putuint8(buf, byte);
}
return true;
}
isc_result_t
isc__tls_parse_keylog_entry(const char *restrict line,
isc__tls_keylog_label_t *restrict out_label,
isc_buffer_t *restrict out_client_random,
isc_buffer_t *restrict out_secret) {
keylog_parser_state_t state;
bool ret;
REQUIRE(line != NULL);
state = (keylog_parser_state_t){ .str = line,
.out_label = out_label,
.out_client_random = out_client_random,
.out_secret = out_secret,
.result = ISC_R_UNSET };
ret = rule_tls_keylog_entry(&state);
if (!ret && state.result != ISC_R_UNSET) {
return state.result;
} else if (!ret) {
return ISC_R_FAILURE;
}
return ISC_R_SUCCESS;
}

414
lib/isc/quic/tls_quic.c Normal file
View File

@@ -0,0 +1,414 @@
/*
* 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.
*/
/*
* QUIC-related TLS functionality
*/
#include <stdbool.h>
#include <stdlib.h>
#include <isc/tls.h>
#include <isc/util.h>
#include "../openssl_shim.h"
#include "quic-int.h"
#include "quic_crypto.h"
static bool tls_quic_initialized = false;
static int tlsctx_quic_interface_index = 0;
static int tls_quic_interface_index = 0;
static int tls_quic_data_index = 0;
static int tls_quic_app_data_index = 0;
static int tls_quic_keylog_cb_index = 0;
static isc_mem_t *tls__quic_mctx = NULL;
void
isc__tls_quic_initialize(void) {
if (tls_quic_initialized) {
return;
}
tlsctx_quic_interface_index = CRYPTO_get_ex_new_index(
CRYPTO_EX_INDEX_SSL_CTX, 0, NULL, NULL, NULL, NULL);
tls_quic_interface_index = CRYPTO_get_ex_new_index(
CRYPTO_EX_INDEX_SSL, 0, NULL, NULL, NULL, NULL);
tls_quic_data_index = CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, 0,
NULL, NULL, NULL, NULL);
tls_quic_app_data_index = CRYPTO_get_ex_new_index(
CRYPTO_EX_INDEX_SSL, 0, NULL, NULL, NULL, NULL);
tls_quic_keylog_cb_index = CRYPTO_get_ex_new_index(
CRYPTO_EX_INDEX_SSL, 0, NULL, NULL, NULL, NULL);
isc_mem_create(&tls__quic_mctx);
isc_mem_setname(tls__quic_mctx, "QUIC TLS");
isc_mem_setdestroycheck(tls__quic_mctx, false);
tls_quic_initialized = true;
}
void
isc__tls_quic_shutdown(void) {
if (!tls_quic_initialized) {
return;
}
#ifdef HAVE_CRYPTO_FREE_EX_INDEX
RUNTIME_CHECK(CRYPTO_free_ex_index(CRYPTO_EX_INDEX_SSL,
tls_quic_keylog_cb_index) == 1);
RUNTIME_CHECK(CRYPTO_free_ex_index(CRYPTO_EX_INDEX_SSL,
tls_quic_app_data_index) == 1);
RUNTIME_CHECK(CRYPTO_free_ex_index(CRYPTO_EX_INDEX_SSL,
tls_quic_data_index) == 1);
RUNTIME_CHECK(CRYPTO_free_ex_index(CRYPTO_EX_INDEX_SSL,
tls_quic_interface_index) == 1);
RUNTIME_CHECK(CRYPTO_free_ex_index(CRYPTO_EX_INDEX_SSL,
tlsctx_quic_interface_index) == 1);
tlsctx_quic_interface_index = 0;
tls_quic_interface_index = 0;
tls_quic_data_index = 0;
tls_quic_app_data_index = 0;
tls_quic_keylog_cb_index = 0;
#endif /* HAVE_CRYPTO_FREE_EX_INDEX */
if (tls__quic_mctx != NULL) {
isc_mem_destroy(&tls__quic_mctx);
}
tls_quic_initialized = false;
}
void
isc_tls_quic_crypto_initialize(void) {
isc__quic_crypto_initialize();
}
void
isc_tls_quic_crypto_shutdown(void) {
isc__quic_crypto_shutdown();
}
const char *
isc_tls_quic_encryption_level_text(const isc_quic_encryption_level_t level) {
switch (level) {
case ISC_QUIC_ENCRYPTION_INITIAL:
return "initial";
case ISC_QUIC_ENCRYPTION_EARLY_DATA:
return "early data";
case ISC_QUIC_ENCRYPTION_HANDSHAKE:
return "handshake";
case ISC_QUIC_ENCRYPTION_APPLICATION:
return "application";
};
UNREACHABLE();
}
const isc_tls_quic_interface_t *
isc_tls_get_default_quic_interface(void) {
#ifdef HAVE_NATIVE_BORINGSSL_QUIC_API
return isc__tls_get_native_quic_interface();
#endif /* HAVE_NATIVE_BORINGSSL_QUIC_API */
#ifndef HAVE_LIBRESSL
return isc__tls_get_compat_quic_interface();
#endif /* HAVE_LIBRESSL */
/* Unexpected - we need to investigate. */
UNREACHABLE();
}
static isc_tls_quic_interface_t *
get_tls_quic_interface(const isc_tls_t *tls) {
isc_tls_quic_interface_t *quicif = NULL;
quicif = SSL_get_ex_data(tls, tls_quic_interface_index);
RUNTIME_CHECK(quicif != NULL);
return quicif;
}
void *
isc__tls_get_quic_data(const isc_tls_t *tls) {
return SSL_get_ex_data(tls, tls_quic_data_index);
}
void
isc__tls_set_quic_data(isc_tls_t *tls, void *data) {
int ret = SSL_set_ex_data(tls, tls_quic_data_index, data);
RUNTIME_CHECK(ret == 1);
}
/* Will not be called on LibreSSL */
static void
quic_keylog_callback(const isc_tls_t *tls, const char *line) {
isc_tls_quic_interface_t *quicif = NULL;
isc_tls_keylog_cb_t cb = NULL;
INSIST(tls != NULL);
INSIST(line != NULL && *line != '\0');
isc_tls_sslkeylogfile_append(line);
quicif = get_tls_quic_interface(tls);
if (quicif->tlsctx_keylog_callback != NULL) {
quicif->tlsctx_keylog_callback(tls, line);
}
cb = SSL_get_ex_data(tls, tls_quic_keylog_cb_index);
if (cb != NULL) {
cb(tls, line);
}
}
void
isc_tlsctx_quic_configure(isc_tlsctx_t *tlsctx,
const isc_tls_quic_interface_t *quic_interface) {
REQUIRE(tlsctx != NULL);
REQUIRE(quic_interface != NULL);
/* QUIC uses TLSv1.3 and newer */
#ifdef TLS1_3_VERSION
SSL_CTX_set_min_proto_version(tlsctx, TLS1_3_VERSION);
#else
/* Everything older than TLSv1.2 is disabled by default */
SSL_CTX_set_options(tlsctx, SSL_OP_NO_TLSv1_2);
#endif
#ifdef TLS1_3_VERSION
SSL_CTX_set_max_proto_version(tlsctx, TLS1_3_VERSION);
#endif
#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
/*
* Disable middle-box compatibility mode for QUIC, as it makes no sense
* to use it in that case. See RFC9001, Section 8.4.
*/
SSL_CTX_clear_options(tlsctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
#endif
INSIST(SSL_CTX_get_ex_data(tlsctx, tlsctx_quic_interface_index) ==
NULL);
RUNTIME_CHECK(SSL_CTX_set_ex_data(tlsctx, tlsctx_quic_interface_index,
(void *)quic_interface) == 1);
SSL_CTX_set_keylog_callback(tlsctx, quic_keylog_callback);
quic_interface->tlsctx_configure(tlsctx);
}
void
isc__tls_quic_init(isc_tls_t *tls) {
int ret = 0;
isc_tlsctx_t *tlsctx = NULL;
isc_tls_quic_interface_t *quicif = NULL;
REQUIRE(tls != NULL);
tlsctx = SSL_get_SSL_CTX(tls);
INSIST(tlsctx != NULL);
quicif = SSL_CTX_get_ex_data(tlsctx, tlsctx_quic_interface_index);
RUNTIME_CHECK(quicif != NULL);
ret = SSL_set_ex_data(tls, tls_quic_interface_index, quicif);
RUNTIME_CHECK(ret == 1);
quicif->tls_init(tls, tls__quic_mctx);
}
void
isc__tls_quic_uninit(isc_tls_t *tls) {
int ret = 0;
isc_tls_quic_interface_t *quicif = NULL;
REQUIRE(tls != NULL);
quicif = get_tls_quic_interface(tls);
quicif->tls_uninit(tls);
ret = SSL_set_ex_data(tls, tls_quic_interface_index, NULL);
RUNTIME_CHECK(ret == 1);
}
bool
isc__tls_is_quic(isc_tls_t *tls) {
REQUIRE(tls != NULL);
return SSL_get_ex_data(tls, tls_quic_interface_index) != NULL;
}
void
isc_tls_quic_set_app_data(isc_tls_t *tls, void *app_data) {
int ret = 0;
REQUIRE(tls != NULL);
ret = SSL_set_ex_data(tls, tls_quic_app_data_index, app_data);
RUNTIME_CHECK(ret == 1);
}
void *
isc_tls_quic_get_app_data(isc_tls_t *tls) {
REQUIRE(tls != NULL);
return SSL_get_ex_data(tls, tls_quic_app_data_index);
}
void
isc_tls_quic_set_keylog_callback(isc_tls_t *tls, isc_tls_keylog_cb_t cb) {
int ret = 0;
REQUIRE(tls != NULL);
ret = SSL_set_ex_data(tls, tls_quic_keylog_cb_index, cb);
RUNTIME_CHECK(ret == 1);
}
isc_result_t
isc_tls_set_quic_method(isc_tls_t *tls, const isc_tls_quic_method_t *method) {
isc_tls_quic_interface_t *quicif = NULL;
int ret = 0;
REQUIRE(tls != NULL);
REQUIRE(method != NULL);
quicif = get_tls_quic_interface(tls);
RUNTIME_CHECK(!quicif->tls_calling_method_cb(tls));
INSIST(method->add_handshake_data != NULL);
INSIST(method->send_alert != NULL);
INSIST(method->set_read_secret != NULL);
INSIST(method->set_write_secret != NULL);
ret = quicif->tls_set_quic_method(tls, method);
if (ret == 0) {
return ISC_R_FAILURE;
}
return ISC_R_SUCCESS;
}
isc_result_t
isc_tls_provide_quic_data(isc_tls_t *tls,
const isc_quic_encryption_level_t level,
const uint8_t *data, const size_t len) {
isc_tls_quic_interface_t *quicif = NULL;
int ret = 0;
REQUIRE(tls != NULL);
REQUIRE(len == 0 || data != NULL);
quicif = get_tls_quic_interface(tls);
RUNTIME_CHECK(!quicif->tls_calling_method_cb(tls));
ret = quicif->tls_provide_quic_data(tls, level, data, len);
if (ret == 0) {
return ISC_R_FAILURE;
}
return ISC_R_SUCCESS;
}
int
isc_tls_do_quic_handshake(isc_tls_t *tls) {
isc_tls_quic_interface_t *quicif = NULL;
REQUIRE(tls != NULL);
quicif = get_tls_quic_interface(tls);
RUNTIME_CHECK(!quicif->tls_calling_method_cb(tls));
return quicif->tls_do_quic_handshake(tls);
}
int
isc_tls_process_quic_post_handshake(isc_tls_t *tls) {
isc_tls_quic_interface_t *quicif = NULL;
REQUIRE(tls != NULL);
quicif = get_tls_quic_interface(tls);
RUNTIME_CHECK(!quicif->tls_calling_method_cb(tls));
return quicif->tls_process_quic_post_handshake(tls);
}
isc_result_t
isc_tls_set_quic_transport_params(isc_tls_t *tls, const uint8_t *params,
const size_t params_len) {
isc_tls_quic_interface_t *quicif = NULL;
int ret = 0;
REQUIRE(tls != NULL);
REQUIRE(params != NULL);
REQUIRE(params_len > 0);
quicif = get_tls_quic_interface(tls);
ret = quicif->tls_set_quic_transport_params(tls, params, params_len);
if (ret == 0) {
return ISC_R_FAILURE;
}
return ISC_R_SUCCESS;
}
void
isc_tls_get_peer_quic_transport_params(isc_tls_t *tls,
const uint8_t **out_params,
size_t *out_params_len) {
isc_tls_quic_interface_t *quicif = NULL;
REQUIRE(tls != NULL);
REQUIRE(out_params != NULL && *out_params == NULL);
REQUIRE(out_params_len != NULL && *out_params_len == 0);
quicif = get_tls_quic_interface(tls);
quicif->tls_get_peer_quic_transport_params(tls, out_params,
out_params_len);
}
isc_quic_encryption_level_t
isc_tls_quic_read_level(const isc_tls_t *tls) {
isc_tls_quic_interface_t *quicif = NULL;
REQUIRE(tls != NULL);
quicif = get_tls_quic_interface(tls);
RUNTIME_CHECK(!quicif->tls_calling_method_cb(tls));
return quicif->tls_quic_read_level(tls);
}
isc_quic_encryption_level_t
isc_tls_quic_write_level(const isc_tls_t *tls) {
isc_tls_quic_interface_t *quicif = NULL;
REQUIRE(tls != NULL);
quicif = get_tls_quic_interface(tls);
RUNTIME_CHECK(!quicif->tls_calling_method_cb(tls));
return quicif->tls_quic_write_level(tls);
}

View File

@@ -51,9 +51,15 @@
#include "openssl_shim.h"
#ifdef HAVE_LIBNGTCP2
#include "quic/quic-int.h"
#endif /* HAVE_LIBNGTCP2 */
#define COMMON_SSL_OPTIONS \
(SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION)
static bool sslkeylogfile_enabled = false;
void
isc_tlsctx_free(isc_tlsctx_t **ctxp) {
SSL_CTX *ctx = NULL;
@@ -75,14 +81,32 @@ isc_tlsctx_attach(isc_tlsctx_t *src, isc_tlsctx_t **ptarget) {
*ptarget = src;
}
void
isc__sslkeylog_init(void) __attribute__((__constructor__));
void
isc__sslkeylog_init(void) {
if (getenv("SSLKEYLOGFILE") != NULL) {
sslkeylogfile_enabled = true;
}
}
/*
* Callback invoked by the SSL library whenever a new TLS pre-master secret
* needs to be logged.
*/
void
isc_tls_sslkeylogfile_append(const char *line) {
if (sslkeylogfile_enabled) {
isc_log_write(ISC_LOGCATEGORY_SSLKEYLOG, ISC_LOGMODULE_CRYPTO,
ISC_LOG_INFO, "%s", line);
}
}
static void
sslkeylogfile_append(const SSL *ssl ISC_ATTR_UNUSED, const char *line) {
isc_log_write(ISC_LOGCATEGORY_SSLKEYLOG, ISC_LOGMODULE_CRYPTO,
ISC_LOG_INFO, "%s", line);
UNUSED(ssl);
isc_tls_sslkeylogfile_append(line);
}
/*
@@ -92,7 +116,7 @@ sslkeylogfile_append(const SSL *ssl ISC_ATTR_UNUSED, const char *line) {
*/
static void
sslkeylogfile_init(isc_tlsctx_t *ctx) {
if (getenv("SSLKEYLOGFILE") != NULL) {
if (sslkeylogfile_enabled) {
SSL_CTX_set_keylog_callback(ctx, sslkeylogfile_append);
}
}
@@ -649,12 +673,36 @@ isc_tls_create(isc_tlsctx_t *ctx) {
return newctx;
}
#ifdef HAVE_LIBNGTCP2
isc_tls_t *
isc_tls_create_quic(isc_tlsctx_t *ctx) {
isc_tls_t *newtls = NULL;
REQUIRE(ctx != NULL);
newtls = isc_tls_create(ctx);
if (newtls != NULL) {
isc__tls_quic_init(newtls);
}
return newtls;
}
#endif /* HAVE_LIBNGTCP2 */
void
isc_tls_free(isc_tls_t **tlsp) {
isc_tls_t *tls = NULL;
REQUIRE(tlsp != NULL && *tlsp != NULL);
tls = *tlsp;
#ifdef HAVE_LIBNGTCP2
if (isc__tls_is_quic(tls)) {
isc__tls_quic_uninit(tls);
}
#endif /* HAVE_LIBNGTCP2 */
*tlsp = NULL;
SSL_free(tls);
}

View File

@@ -145,6 +145,51 @@ proxyudp_test_SOURCES = \
netmgr_common.c \
uv_wrap.h
if HAVE_LIBNGTCP2
check_PROGRAMS += \
ngtcp2_integration_test \
quic_session_test \
quic_tls_test
ngtcp2_integration_test_CPPFLAGS = \
$(AM_CPPFLAGS) \
$(LIBNGTCP2_CFLAGS) \
$(OPENSSL_CFLAGS)
ngtcp2_integration_test_LDADD = \
$(LDADD) \
$(LIBNGTCP2_LIBS)
ngtcp2_integration_test_SOURCES = \
ngtcp2_integration_test.c
quic_session_test_CPPFLAGS = \
$(AM_CPPFLAGS) \
$(LIBNGTCP2_CFLAGS) \
$(OPENSSL_CFLAGS)
quic_session_test_LDADD = \
$(LDADD) \
$(LIBNGTCP2_LIBS)
quic_session_test_SOURCES = \
quic_session_test.c
quic_tls_test_CPPFLAGS = \
$(AM_CPPFLAGS) \
$(LIBNGTCP2_CFLAGS) \
$(OPENSSL_CFLAGS)
quic_tls_test_LDADD = \
$(LDADD) \
$(LIBNGTCP2_LIBS)
quic_tls_test_SOURCES = \
quic_tls_test.c
endif HAVE_LIBNGTCP2
random_test_LDADD = \
$(LDADD) \
-lm

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,688 @@
/*
* 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.
*/
#include <ngtcp2/ngtcp2.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/buffer.h>
#include <isc/entropy.h>
#include <isc/lib.h>
#include <isc/mem.h>
#include <isc/ngtcp2_crypto.h>
#include <isc/ngtcp2_utils.h>
#include <isc/os.h>
#include <isc/quic.h>
#include <isc/random.h>
#include <isc/time.h>
#include <isc/tls.h>
#include "../../lib/isc/quic/quic_session.h"
#include <tests/isc.h>
/* the unit test infrastructure */
#define TEST_SERVER_PORT (9153)
#define TEST_CLIENT_PORT (9154)
#define INITIAL_TIMEOUT (isc_ngtcp2_make_duration(15, 0))
static isc_tlsctx_t *default_server_tlsctx = NULL;
static isc_tlsctx_t *default_client_tlsctx = NULL;
static isc_tlsctx_t *server_tlsctx = NULL;
static isc_tlsctx_t *client_tlsctx = NULL;
static isc_sockaddr_t server_addr = { 0 };
static isc_sockaddr_t client_addr = { 0 };
static isc_sockaddr_t migrate_client_addr = { 0 };
static isc_quic_session_t *client_session = NULL;
static isc_quic_session_t *server_session = NULL;
static const uint32_t proto_preference_list[] = { NGTCP2_PROTO_VER_V2,
NGTCP2_PROTO_VER_V1 };
static const size_t proto_preference_list_len =
(sizeof(proto_preference_list) / sizeof(proto_preference_list[0]));
static uint8_t client_static_secret[ISC_NGTCP2_CRYPTO_STATIC_SECRET_LEN];
static uint8_t server_static_secret[ISC_NGTCP2_CRYPTO_STATIC_SECRET_LEN];
static const uint8_t data_ping[] = { 'P', 'I', 'N', 'G' };
/* static const uint8_t data_pong[] = { 'P', 'O', 'N', 'G' }; */
typedef struct quic_test_session_manager {
uint64_t ts;
bool ts_set;
uint64_t last_timer_update;
bool hs_completed;
ISC_LIST(isc_quic_cid_t) managed_cids;
size_t managed_cids_len;
size_t opened_streams;
int64_t last_stream_id;
bool closed;
uint32_t closing_timeout_ms;
size_t sends;
isc_buffer_t *input;
isc_buffer_t *output;
} quic_test_session_manager_t;
static quic_test_session_manager_t client_sm;
static quic_test_session_manager_t server_sm;
static void
quic_sm_init(quic_test_session_manager_t *sm) {
*sm = (quic_test_session_manager_t){
.ts = isc_time_monotonic(),
.managed_cids = ISC_LIST_INITIALIZER,
};
}
static void
quic_sm_uninit(quic_test_session_manager_t *sm) {
if (!ISC_LIST_EMPTY(sm->managed_cids)) {
isc_quic_cid_t *current = NULL, *next = NULL;
for (current = ISC_LIST_HEAD(sm->managed_cids); current != NULL;
current = next)
{
next = ISC_LIST_NEXT(current, global_link);
isc_quic_cid_detach(&current);
sm->managed_cids_len--;
}
}
}
static uint64_t
quic_sm_get_current_ts_cb(quic_test_session_manager_t *sm) {
if (sm->ts_set) {
sm->ts_set = false;
} else {
sm->ts += isc_ngtcp2_make_duration(0,
4 + isc_random_uniform(12));
}
return sm->ts;
}
static void
quic_sm_set_next_ts(quic_test_session_manager_t *sm, uint64_t ts) {
sm->ts = ts;
sm->ts_set = true;
}
static void
quic_sm_timer_update_cb(isc_quic_session_t *restrict session,
const uint32_t timeout_ms,
quic_test_session_manager_t *sm) {
UNUSED(session);
if (timeout_ms != 0) {
printf("new_timeout: %" PRIu32 "\n", timeout_ms);
}
sm->last_timer_update = isc_ngtcp2_make_duration(0, timeout_ms);
}
static bool
quic_sm_assoc_conn_cid_cb(isc_quic_session_t *restrict session,
isc_region_t *restrict cid_data, const bool source,
quic_test_session_manager_t *sm,
isc_quic_cid_t **restrict pcid);
static bool
quic_sm_gen_unique_cid_cb(isc_quic_session_t *restrict session,
const size_t cidlen, const bool source,
quic_test_session_manager_t *sm,
isc_quic_cid_t **restrict pcid) {
ngtcp2_cid ngcid;
isc_region_t cid_data = { 0 };
isc_ngtcp2_gen_cid(&ngcid, cidlen);
isc_ngtcp2_cid_region(&ngcid, &cid_data);
const bool ret = quic_sm_assoc_conn_cid_cb(session, &cid_data, source,
sm, pcid);
return ret;
}
static bool
quic_sm_on_handshake_cb(isc_quic_session_t *session,
quic_test_session_manager_t *sm) {
UNUSED(session);
puts("hs");
sm->hs_completed = true;
return true;
}
static bool
quic_sm_on_on_remote_stream_open_cb(isc_quic_session_t *session,
const int64_t streamd_id,
quic_test_session_manager_t *sm) {
sm->last_stream_id = streamd_id;
sm->opened_streams++;
isc_quic_session_set_stream_user_data(session, streamd_id, sm);
return true;
}
static bool
quic_sm_on_stream_close_cb(isc_quic_session_t *session,
const int64_t streamd_id, const bool app_error_set,
const uint64_t app_error_code,
quic_test_session_manager_t *sm,
void *stream_user_data) {
UNUSED(app_error_set);
UNUSED(app_error_code);
INSIST(isc_quic_session_get_stream_user_data(session, streamd_id) ==
stream_user_data);
INSIST(isc_quic_session_get_stream_user_data(session, streamd_id) ==
sm);
sm->opened_streams--;
return true;
}
static bool
quic_sm_on_recv_stream_data_cb(isc_quic_session_t *session,
const int64_t streamd_id, const bool fin,
const uint64_t offset,
const isc_region_t *restrict data,
quic_test_session_manager_t *sm,
void *stream_user_data) {
UNUSED(offset);
UNUSED(data);
INSIST(isc_quic_session_get_stream_user_data(session, streamd_id) ==
stream_user_data);
INSIST(isc_quic_session_get_stream_user_data(session, streamd_id) ==
sm);
if (fin) {
isc_quic_session_shutdown_stream(session, streamd_id, true);
}
sm->sends++;
return true;
}
static void
quic_sm_on_conn_close_cb(isc_quic_session_t *session,
const uint32_t closing_timeout_ms,
quic_test_session_manager_t *sm) {
UNUSED(session);
sm->closed = true;
sm->closing_timeout_ms = closing_timeout_ms;
}
static bool
quic_sm_assoc_conn_cid_cb(isc_quic_session_t *restrict session,
isc_region_t *restrict cid_data, const bool source,
quic_test_session_manager_t *sm,
isc_quic_cid_t **restrict pcid) {
isc_quic_cid_t *new_cid = NULL;
UNUSED(session);
isc_quic_cid_create(mctx, cid_data, source, &new_cid);
ISC_LIST_APPEND(sm->managed_cids, new_cid, global_link);
sm->managed_cids_len++;
isc_quic_cid_attach(new_cid, pcid);
return true;
}
static void
quic_sm_deassoc_conn_cid(isc_quic_session_t *restrict session,
quic_test_session_manager_t *sm,
isc_quic_cid_t **restrict pcid) {
isc_quic_cid_t *cid = NULL;
UNUSED(session);
cid = *pcid;
if (!ISC_LINK_LINKED(cid, global_link)) {
return;
}
ISC_LIST_UNLINK(sm->managed_cids, cid, global_link);
isc_quic_cid_detach(&cid);
isc_quic_cid_detach(pcid);
}
static void
quic_sm_send_cb(isc_quic_session_t *restrict session, const int64_t stream_id,
const isc_result_t result, void *cbarg,
quic_test_session_manager_t *sm) {
REQUIRE(session != NULL);
REQUIRE(result == ISC_R_SUCCESS);
REQUIRE(cbarg == sm);
REQUIRE(sm->last_stream_id == stream_id);
}
static isc_quic_session_interface_t callbacks = {
.get_current_ts =
(isc_quic_get_current_ts_cb_t)quic_sm_get_current_ts_cb,
.timer_update = (isc_quic_timer_update_cb_t)quic_sm_timer_update_cb,
.gen_unique_cid =
(isc_quic_gen_unique_cid_cb_t)quic_sm_gen_unique_cid_cb,
.assoc_conn_cid =
(isc_quic_assoc_conn_cid_cb_t)quic_sm_assoc_conn_cid_cb,
.deassoc_conn_cid =
(isc_quic_deassoc_conn_cid_cb_t)quic_sm_deassoc_conn_cid,
.on_handshake = (isc_quic_on_handshake_cb_t)quic_sm_on_handshake_cb,
.on_remote_stream_open = (isc_quic_on_remote_stream_open_cb_t)
quic_sm_on_on_remote_stream_open_cb,
.on_stream_close =
(isc_quic_on_stream_close_cb_t)quic_sm_on_stream_close_cb,
.on_recv_stream_data = (isc_quic_on_recv_stream_data_cb_t)
quic_sm_on_recv_stream_data_cb,
.on_conn_close = (isc_quic_on_conn_close_cb_t)quic_sm_on_conn_close_cb
};
static isc_tlsctx_t *
create_quic_tls_context(const bool is_server,
const isc_tls_quic_interface_t *iface) {
isc_tlsctx_t *tlsctx = NULL;
isc_result_t result;
if (is_server) {
result = isc_tlsctx_createserver(NULL, NULL, &tlsctx);
} else {
result = isc_tlsctx_createclient(&tlsctx);
}
if (result != ISC_R_SUCCESS) {
return NULL;
}
isc_tlsctx_set_random_session_id_context(tlsctx);
isc_tlsctx_quic_configure(tlsctx, iface);
return tlsctx;
}
static int
quic_session_testset_setup(void **state) {
int ret;
struct in6_addr in6;
UNUSED(state);
isc_tls_quic_crypto_initialize();
default_server_tlsctx = create_quic_tls_context(
true, isc_tls_get_default_quic_interface());
if (default_server_tlsctx == NULL) {
return -1;
}
default_client_tlsctx = create_quic_tls_context(
false, isc_tls_get_default_quic_interface());
if (default_client_tlsctx == NULL) {
return -1;
}
server_tlsctx = default_server_tlsctx;
client_tlsctx = default_client_tlsctx;
isc_sockaddr_fromin6(&server_addr, &in6addr_loopback, TEST_SERVER_PORT);
isc_sockaddr_fromin6(&client_addr, &in6addr_loopback, TEST_CLIENT_PORT);
ret = inet_pton(AF_INET6, "2a03:dead:beef:34b5:7a18:f3c7:57dc:9ee1",
&in6);
if (ret != 1) {
return -1;
}
isc_sockaddr_fromin6(&migrate_client_addr, &in6, 1);
isc_entropy_get((void *)server_static_secret,
sizeof(server_static_secret));
isc_entropy_get((void *)client_static_secret,
sizeof(client_static_secret));
return 0;
}
static int
quic_session_testset_teardown(void **state) {
UNUSED(state);
isc_tlsctx_free(&default_client_tlsctx);
isc_tlsctx_free(&default_server_tlsctx);
isc_tls_quic_crypto_shutdown();
return 0;
}
static int
quic_session_test_setup(void **state) {
UNUSED(state);
quic_sm_init(&server_sm);
isc_quic_session_create(
mctx, default_server_tlsctx, &callbacks, &server_sm,
&server_addr, &client_addr, INITIAL_TIMEOUT, INITIAL_TIMEOUT,
UINT16_MAX, UINT16_MAX, 0, proto_preference_list,
proto_preference_list_len, server_static_secret,
sizeof(server_static_secret), true, &server_session);
quic_sm_init(&client_sm);
isc_quic_session_create(
mctx, default_client_tlsctx, &callbacks, &client_sm,
&client_addr, &server_addr, INITIAL_TIMEOUT, INITIAL_TIMEOUT,
UINT16_MAX, UINT16_MAX, NGTCP2_PROTO_VER_V1,
proto_preference_list, proto_preference_list_len,
client_static_secret, sizeof(client_static_secret), false,
&client_session);
return 0;
}
static int
quic_session_test_teardown(void **state) {
UNUSED(state);
if (client_session != NULL) {
isc_quic_session_detach(&client_session);
}
quic_sm_uninit(&client_sm);
if (server_session != NULL) {
isc_quic_session_detach(&server_session);
}
quic_sm_uninit(&server_sm);
return 0;
}
/* tests */
ISC_RUN_TEST_IMPL(quic_session_simple_test) {
ssize_t written = 0;
uint8_t pkt_client[1500];
uint8_t pkt_server[1500];
isc_result_t result = ISC_R_SUCCESS;
isc_region_t scid = { 0 }, dcid = { 0 };
uint32_t version = 0;
bool is_long = false;
isc_region_t pkt_reg = { 0 }, send_data = { 0 };
result = isc_quic_session_connect(client_session, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written == 1200);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_client, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
assert_true(result == ISC_R_SUCCESS);
pkt_reg = (isc_region_t){ .base = pkt_client, .length = written };
written = 0;
result = isc_quic_session_read_pkt(server_session, version, &dcid,
&scid, &pkt_reg, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_server, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
pkt_reg = (isc_region_t){ .base = pkt_server, .length = written };
written = 0;
result = isc_quic_session_read_pkt(client_session, version, &dcid,
&scid, &pkt_reg, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_client, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
pkt_reg = (isc_region_t){ .base = pkt_client, .length = written };
written = 0;
result = isc_quic_session_read_pkt(server_session, version, &dcid,
&scid, &pkt_reg, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_server, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
assert_true(result == ISC_R_SUCCESS);
pkt_reg = (isc_region_t){ .base = pkt_server, .length = written };
written = 0;
result = isc_quic_session_read_pkt(client_session, version, &dcid,
&scid, &pkt_reg, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_server, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
assert_true(result == ISC_R_SUCCESS);
pkt_reg = (isc_region_t){ .base = pkt_client, .length = written };
written = 0;
result = isc_quic_session_read_pkt(server_session, version, &dcid,
&scid, &pkt_reg, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_server, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
assert_false(is_long);
assert_true(result == ISC_R_SUCCESS);
pkt_reg = (isc_region_t){ .base = pkt_server, .length = written };
written = 0;
result = isc_quic_session_read_pkt(client_session, version, &dcid,
&scid, &pkt_reg, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written == 0);
assert_true(server_sm.hs_completed);
assert_true(client_sm.hs_completed);
int64_t client_stream_id = -1;
result = isc_quic_session_open_stream(client_session, true, &client_sm,
&client_stream_id);
assert_true(result == ISC_R_SUCCESS);
assert_true(client_stream_id != -1);
send_data = (isc_region_t){ .base = (uint8_t *)data_ping,
.length = sizeof(data_ping) };
result = isc_quic_session_send_data(
client_session, client_stream_id, &send_data, true,
(isc_quic_send_cb_t)quic_sm_send_cb, &client_sm);
written = 0;
result = isc_quic_session_write_pkt(client_session, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
pkt_reg = (isc_region_t){ .base = pkt_client, .length = written };
written = 0;
result = isc_quic_session_read_pkt(server_session, version, &dcid,
&scid, &pkt_reg, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written == 0);
written = 0;
result = isc_quic_session_write_pkt(client_session, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
pkt_reg = (isc_region_t){ .base = pkt_client, .length = written };
written = 0;
result = isc_quic_session_read_pkt(server_session, version, &dcid,
&scid, &pkt_reg, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written == 0);
written = 0;
result = isc_quic_session_write_pkt(server_session, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
pkt_reg = (isc_region_t){ .base = pkt_server, .length = written };
written = 0;
result = isc_quic_session_read_pkt(client_session, version, &dcid,
&scid, &pkt_reg, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written == 0);
assert_true(client_sm.opened_streams > 0);
assert_true(server_sm.sends > 0);
result = isc_quic_session_update_local_address(client_session,
&migrate_client_addr);
assert_true(result == ISC_R_SUCCESS);
written = 0;
result = isc_quic_session_write_pkt(client_session, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_client, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
assert_false(is_long);
assert_true(result == ISC_R_SUCCESS);
pkt_reg = (isc_region_t){ .base = pkt_client, .length = written };
written = 0;
result = isc_quic_session_read_pkt(server_session, version, &dcid,
&scid, &pkt_reg, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written == 0);
written = 0;
result = isc_quic_session_write_pkt(server_session, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
pkt_reg = (isc_region_t){ .base = pkt_server, .length = written };
written = 0;
result = isc_quic_session_read_pkt(client_session, version, &dcid,
&scid, &pkt_reg, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_client, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
assert_false(is_long);
assert_true(result == ISC_R_SUCCESS);
pkt_reg = (isc_region_t){ .base = pkt_client, .length = written };
written = 0;
result = isc_quic_session_read_pkt(server_session, version, &dcid,
&scid, &pkt_reg, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written == 0);
result = isc_quic_session_write_pkt(server_session, pkt_server,
sizeof(pkt_server), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written > 0);
result = isc_ngtcp2_decode_pkt_header_data(
pkt_server, written, ISC_QUIC_SERVER_SCID_LEN, &is_long, &scid,
&dcid, &version);
assert_false(is_long);
assert_true(result == ISC_R_SUCCESS);
pkt_reg = (isc_region_t){ .base = pkt_server, .length = written };
written = 0;
result = isc_quic_session_read_pkt(client_session, version, &dcid,
&scid, &pkt_reg, pkt_client,
sizeof(pkt_client), &written);
assert_true(result == ISC_R_SUCCESS);
assert_true(written == 0);
/* printf("%" PRId64 "\n", written); */
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY_CUSTOM(quic_session_simple_test, quic_session_test_setup,
quic_session_test_teardown)
ISC_TEST_LIST_END
ISC_TEST_MAIN_CUSTOM(quic_session_testset_setup, quic_session_testset_teardown);

1069
tests/isc/quic_tls_test.c Normal file

File diff suppressed because it is too large Load Diff