#include <stdint.h>
+#include <openssl/aes.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/kdf.h>
+#include <openssl/opensslv.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <isc/buffer.h>
#include <isc/crypto.h>
+#include <isc/endian.h>
#include <isc/hmac.h>
#include <isc/log.h>
#include <isc/magic.h>
#include <openssl/aead.h>
#endif /* HAVE_OPENSSL_AEAD_H */
+#if HAVE_CRYPTO_CHACHA_20
+#include <openssl/chacha.h>
+#endif /* HAVE_CRYPTO_CHACHA_20 */
+
#ifdef HAVE_OPENSSL_HKDF_H
#include <openssl/hkdf.h>
#endif /* HAVE_OPENSSL_HKDF_H */
uint8_t secret[];
};
+struct isc_crypto_quic_hp_protect {
+ uint32_t magic;
+ isc_crypto_quic_hp_protect_algorithm_t algorithm;
+ isc_mem_t *mctx;
+ union {
+ AES_KEY aes;
+#if HAVE_CRYPTO_CHACHA_20
+ uint8_t chacha20[32];
+#else /* HAVE_CRYPTO_CHACHA_20 */
+ EVP_CIPHER_CTX *chacha20;
+#endif /* HAVE_CRYPTO_CHACHA_20 */
+ } key;
+};
+
#ifdef HAVE_EVP_AEAD_CTX_NEW
STATIC_ASSERT(ISC_TYPES_COMPATIBLE(isc_crypto_aead_t, EVP_AEAD_CTX),
"isc_crypto_aead_t is not compatible with EVP_AEAD_CTX");
"isc_crypto_aead_t is not compatible with EVP_CIPHER_CTX");
#endif /* HAVE_EVP_AEAD_CTX_NEW */
+constexpr uint32_t crypto_quic_hp_protect_magic = ISC_MAGIC('C', 'Q', 'h', 'p');
+
static isc_mem_t *isc__crypto_mctx = NULL;
#define md_register_algorithm(alg, upperalg) \
#endif /* HAVE_OPENSSL_HKDF_H */
+void
+isc_crypto_quic_hp_protect_destroy(isc_crypto_quic_hp_protect_t **protp) {
+ isc_crypto_quic_hp_protect_t *prot;
+
+ REQUIRE(protp != NULL && *protp != NULL &&
+ (*protp)->magic == crypto_quic_hp_protect_magic);
+
+ prot = MOVE_OWNERSHIP(*protp);
+
+ prot->magic = 0x00;
+
+#if !HAVE_CRYPTO_CHACHA_20
+ if (prot->algorithm == ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20) {
+ EVP_CIPHER_CTX_free(prot->key.chacha20);
+ }
+#endif /* HAVE_CRYPTO_CHACHA_20 */
+
+ isc_safe_memwipe(&prot->key, ISC_MAX(sizeof(prot->key.aes),
+ sizeof(prot->key.chacha20)));
+
+ isc_mem_putanddetach(&prot->mctx, prot, sizeof(*prot));
+}
+
+isc_result_t
+isc_crypto_quic_hp_protect_create(
+ isc_mem_t *mctx, isc_constregion_t key,
+ isc_crypto_quic_hp_protect_algorithm_t algorithm,
+ isc_crypto_quic_hp_protect_t **protp) {
+ isc_crypto_quic_hp_protect_t *prot;
+ isc_result_t result;
+
+ REQUIRE(protp != NULL && *protp == NULL);
+ REQUIRE(key.base != NULL);
+
+ prot = isc_mem_get(mctx, sizeof(*prot));
+ *prot = (isc_crypto_quic_hp_protect_t){
+ .magic = crypto_quic_hp_protect_magic,
+ .algorithm = algorithm,
+ .mctx = isc_mem_ref(mctx),
+ };
+
+ switch (algorithm) {
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128:
+ INSIST(key.length == isc_crypto_aes128gcm_key_length);
+ if (AES_set_encrypt_key(key.base, 128, &prot->key.aes) != 0) {
+ CLEANUP(CRYPTO_ERROR("AES_set_encrypt_key"));
+ }
+ break;
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES256:
+ INSIST(key.length == isc_crypto_aes256gcm_key_length);
+ if (AES_set_encrypt_key(key.base, 256, &prot->key.aes) != 0) {
+ CLEANUP(CRYPTO_ERROR("AES_set_encrypt_key"));
+ }
+ break;
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20:
+ INSIST(key.length == isc_crypto_chacha20poly1305_key_length);
+ if (isc_crypto_fips_mode()) {
+ CLEANUP(ISC_R_NOTIMPLEMENTED);
+ }
+#if HAVE_CRYPTO_CHACHA_20
+ memmove(prot->key.chacha20, key.base,
+ isc_crypto_chacha20poly1305_key_length);
+#else /* HAVE_CRYPTO_CHACHA_20 */
+ prot->key.chacha20 = EVP_CIPHER_CTX_new();
+ if (prot->key.chacha20 == NULL) {
+ CLEANUP(CRYPTO_ERROR("EVP_CIPHER_CTX_new"));
+ }
+
+ if (EVP_EncryptInit_ex(prot->key.chacha20, EVP_chacha20(), NULL,
+ key.base, NULL) != 1)
+ {
+ EVP_CIPHER_CTX_free(prot->key.chacha20);
+ CLEANUP(CRYPTO_ERROR("EVP_EncryptInit_ex"));
+ }
+#endif /* HAVE_CRYPTO_CHACHA_20 */
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ *protp = prot;
+
+ return ISC_R_SUCCESS;
+
+cleanup:
+ isc_mem_putanddetach(&prot->mctx, prot, sizeof(*prot));
+ return result;
+}
+
+isc_result_t
+isc_crypto_quic_hp_protect_mask(isc_crypto_quic_hp_protect_t *prot,
+ uint8_t *out, const uint8_t *sample) {
+ static const uint8_t zeros[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
+#if HAVE_CRYPTO_CHACHA_20
+ const uint8_t *nonce;
+ uint64_t counter;
+#else /* HAVE_CRYPTO_CHACHA_20 */
+ int len;
+#endif /* HAVE_CRYPTO_CHACHA_20 */
+
+ REQUIRE(prot != NULL && prot->magic == crypto_quic_hp_protect_magic);
+ REQUIRE(out != NULL && sample != NULL);
+
+ switch (prot->algorithm) {
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128:
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES256:
+ AES_ecb_encrypt(sample, out, &prot->key.aes, 1);
+ break;
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20:
+#if HAVE_CRYPTO_CHACHA_20
+ /*
+ * `CRYPTO_chacha_20` is the IETF variant of ChaCha20 with a
+ * 32-bit counter and a 96-bit nonce in BoringSSL and its forks
+ * such as AWS-LC.
+ *
+ * However, LibreSSL implements the original paper with 64-bit
+ * counter and a 64-bit nonce. This can easily be turned into
+ * the IETF variant by reading 32-bits more to the counter from
+ * the sample and using the rest and the nonce. The ChaCha20
+ * block state will be equivalent.
+ */
+#ifdef LIBRESSL_VERSION_NUMBER
+ counter = ISC_U8TO64_LE(sample);
+ nonce = sample + 8;
+#else /* LIBRESSL_VERSION_NUMBER */
+ counter = ISC_U8TO32_LE(sample);
+ nonce = sample + 4;
+#endif /* LIBRESSL_VERSION_NUMBER */
+ CRYPTO_chacha_20(out, zeros, sizeof(zeros), prot->key.chacha20,
+ nonce, counter);
+
+#else /* HAVE_CRYPTO_CHACHA_20 */
+ if (EVP_EncryptInit_ex(prot->key.chacha20, NULL, NULL, NULL,
+ sample) != 1)
+ {
+ return CRYPTO_ERROR("EVP_EncryptInit_ex");
+ }
+
+ if (EVP_EncryptUpdate(prot->key.chacha20, out, &len, zeros,
+ sizeof(zeros)) != 1)
+ {
+ return CRYPTO_ERROR("EVP_EncryptUpdate");
+ }
+
+ if (EVP_EncryptFinal_ex(prot->key.chacha20, out + sizeof(zeros),
+ &len) != 1)
+ {
+ return CRYPTO_ERROR("EVP_EncryptFinal_ex");
+ }
+#endif /* HAVE_CRYPTO_CHACHA_20 */
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return ISC_R_SUCCESS;
+}
+
#ifdef HAVE_FIPS_MODE
bool
isc_crypto_fips_mode(void) {
uint8_t secret[];
};
+struct isc_crypto_quic_hp_protect {
+ uint32_t magic;
+ isc_mem_t *mctx;
+ EVP_CIPHER_CTX *ctx;
+};
+
STATIC_ASSERT(ISC_TYPES_COMPATIBLE(isc_crypto_aead_t, EVP_CIPHER_CTX),
"isc_crypto_aead_t is not compatible with EVP_CIPHER_CTX");
+constexpr uint32_t quic_hp_protect_magic = ISC_MAGIC('C', 'Q', 'h', 'p');
constexpr uint32_t hmac_key_magic = ISC_MAGIC('H', 'M', 'A', 'C');
static OSSL_PROVIDER *base = NULL, *fips = NULL;
static EVP_CIPHER *evp_aes_256_gcm = NULL;
static EVP_CIPHER *evp_chacha20poly1305 = NULL;
+/* QUIC Header Protection */
+static EVP_CIPHER *evp_aes_128_ctr = NULL;
+static EVP_CIPHER *evp_aes_256_ctr = NULL;
+static EVP_CIPHER *evp_chacha20 = NULL;
+
static isc_constregion_t md_to_name[ISC_MD_MAX] = {
[ISC_MD_UNKNOWN] = { NULL, 0 },
[ISC_MD_MD5] = { "MD5", sizeof("MD5") - 1 },
INSIST(evp_chacha20poly1305 == NULL);
evp_chacha20poly1305 =
EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL);
+
+ INSIST(evp_chacha20 == NULL);
+ evp_chacha20 = EVP_CIPHER_fetch(NULL, "ChaCha20", NULL);
}
md_register_algorithm(SHA1);
INSIST(evp_aes_256_gcm == NULL);
evp_aes_256_gcm = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL);
+ INSIST(evp_aes_128_ctr == NULL);
+ evp_aes_128_ctr = EVP_CIPHER_fetch(NULL, "AES-128-CTR", NULL);
+
+ INSIST(evp_aes_256_ctr == NULL);
+ evp_aes_256_ctr = EVP_CIPHER_fetch(NULL, "AES-256-CTR", NULL);
+
/* We _must_ have HMAC */
evp_hmac = EVP_MAC_fetch(NULL, "HMAC", NULL);
if (evp_hmac == NULL) {
EVP_MAC_free(evp_hmac);
evp_hmac = NULL;
+ EVP_CIPHER_free(evp_chacha20);
+ evp_chacha20 = NULL;
+
+ EVP_CIPHER_free(evp_aes_256_ctr);
+ evp_aes_256_ctr = NULL;
+
+ EVP_CIPHER_free(evp_aes_128_ctr);
+ evp_aes_128_ctr = NULL;
+
EVP_CIPHER_free(evp_chacha20poly1305);
evp_chacha20poly1305 = NULL;
return result;
}
+void
+isc_crypto_quic_hp_protect_destroy(isc_crypto_quic_hp_protect_t **protp) {
+ isc_crypto_quic_hp_protect_t *prot;
+
+ REQUIRE(protp != NULL && *protp != NULL &&
+ (*protp)->magic == quic_hp_protect_magic);
+
+ prot = MOVE_OWNERSHIP(*protp);
+ prot->magic = 0x00;
+ EVP_CIPHER_CTX_free(prot->ctx);
+ isc_mem_putanddetach(&prot->mctx, prot, sizeof(*prot));
+}
+
+isc_result_t
+isc_crypto_quic_hp_protect_create(
+ isc_mem_t *mctx, isc_constregion_t key,
+ isc_crypto_quic_hp_protect_algorithm_t algorithm,
+ isc_crypto_quic_hp_protect_t **protp) {
+ isc_crypto_quic_hp_protect_t *prot;
+ const EVP_CIPHER *evp;
+ EVP_CIPHER_CTX *ctx;
+ size_t len;
+
+ REQUIRE(protp != NULL && *protp == NULL);
+ REQUIRE(key.base != NULL);
+
+ switch (algorithm) {
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128:
+ len = isc_crypto_aes128gcm_key_length;
+ evp = evp_aes_128_ctr;
+ break;
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES256:
+ len = isc_crypto_aes256gcm_key_length;
+ evp = evp_aes_256_ctr;
+ break;
+ case ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20:
+ len = isc_crypto_chacha20poly1305_key_length;
+ evp = evp_chacha20;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ INSIST(key.length == len);
+
+ if (evp == NULL) {
+ return ISC_R_NOTIMPLEMENTED;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ return CRYPTO_ERROR("EVP_CIPHER_CTX_new");
+ }
+
+ if (EVP_CipherInit_ex2(ctx, evp, key.base, NULL, 1, NULL) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ return CRYPTO_ERROR("EVP_CipherInit_ex2");
+ }
+
+ prot = isc_mem_get(mctx, sizeof(*prot));
+ *prot = (isc_crypto_quic_hp_protect_t){
+ .magic = quic_hp_protect_magic,
+ .ctx = ctx,
+ };
+ isc_mem_attach(mctx, &prot->mctx);
+
+ *protp = prot;
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+isc_crypto_quic_hp_protect_mask(isc_crypto_quic_hp_protect_t *prot,
+ uint8_t *out, const uint8_t *sample) {
+ static const uint8_t zeros[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
+ isc_result_t result;
+ int len;
+
+ REQUIRE(prot != NULL && prot->magic == quic_hp_protect_magic);
+ REQUIRE(out != NULL && sample != NULL);
+
+ if (EVP_EncryptInit_ex2(prot->ctx, NULL, NULL, sample, NULL) != 1) {
+ CLEANUP(CRYPTO_ERROR("EVP_EncryptInit_ex2"));
+ }
+
+ if (EVP_EncryptUpdate(prot->ctx, out, &len, zeros, sizeof(zeros)) != 1)
+ {
+ CLEANUP(CRYPTO_ERROR("EVP_EncryptUpdate"));
+ }
+
+ if (EVP_EncryptFinal_ex(prot->ctx, out + sizeof(zeros), &len) != 1) {
+ CLEANUP(CRYPTO_ERROR("EVP_EncryptFinal_ex"));
+ }
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ return result;
+}
+
bool
isc_crypto_fips_mode(void) {
return EVP_default_properties_is_fips_enabled(NULL) != 0;
typedef struct evp_cipher_ctx_st isc_crypto_aead_t;
#endif /* HAVE_EVP_AEAD_CTX_NEW */
+/**
+ * \brief
+ * Context to a mask generator to protect QUIC packet headers as specified by
+ * RFC 9001, Section 5.4. [1]
+ *
+ * [1]: https://datatracker.ietf.org/doc/html/rfc9001#section-5.4
+ */
+typedef struct isc_crypto_quic_hp_protect isc_crypto_quic_hp_protect_t;
+
typedef enum isc_crypto_aead_algorithm {
ISC_CRYPTO_AEAD_ALGORITHM_INVALID = 0,
ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM = 1,
ISC_CRYPTO_AEAD_DIRECTION_MAX = 3,
} isc_crypto_aead_direction_t;
+typedef enum isc_crypto_quic_hp_protect_algorithm {
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_INVALID = 0,
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128 = 1,
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES256 = 2,
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20 = 3,
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_MAX = 4,
+} isc_crypto_quic_hp_protect_algorithm_t;
+
constexpr size_t isc_crypto_aes128gcm_key_length = 16;
constexpr size_t isc_crypto_aes256gcm_key_length = 32;
constexpr size_t isc_crypto_chacha20poly1305_key_length = 32;
* \retval ISC_R_CRYPTOFAILURE on libcrypto failure
*/
+void
+isc_crypto_quic_hp_protect_destroy(isc_crypto_quic_hp_protect_t **protp);
+/**<
+ * \brief
+ * Destroy a QUIC header protection context.
+ *
+ * Requires:
+ * - `*protp` is a valid QUIC header protection context.
+ */
+
+isc_result_t
+isc_crypto_quic_hp_protect_create(
+ isc_mem_t *mctx, isc_constregion_t key,
+ isc_crypto_quic_hp_protect_algorithm_t algorithm,
+ isc_crypto_quic_hp_protect_t **protp);
+/**<
+ * \brief
+ * Create a new QUIC header protection context.
+ *
+ * Requires:
+ * - `mctx` is a valid memory context
+ * - `key` is a valid AEAD-key that has the correct key size for
+ * `aead_algorithm`.
+ * - `aead_algorithm` is a valid AEAD type.
+ * - `protp != NULL` and `*protp == NULL`
+ *
+ * \retval ISC_R_SUCCESS on success
+ * \retval ISC_R_CRYPTOFAILURE on libcrypto failure
+ */
+
+isc_result_t
+isc_crypto_quic_hp_protect_mask(isc_crypto_quic_hp_protect_t *prot,
+ uint8_t *out, const uint8_t *sample);
+/**<
+ * \brief
+ * Generate a QUIC header protection mask.
+ *
+ * Requires:
+ * - `prot` is a valid QUIC header protection context.
+ * - `out` is non-`NULL` and points to 16 bytes.
+ * - `sample` is non-NULL and points to 16 bytes of sampleable data.
+ *
+ * \retval ISC_R_SUCCESS on success
+ * \retval ISC_R_CRYPTOFAILURE on libcrypto failure
+ */
+
bool
isc_crypto_fips_mode(void);
/*
'BIO_write_ex': '#include <openssl/bio.h>',
'EVP_PKEY_eq': '#include <openssl/evp.h>',
'SSL_CTX_set1_cert_store': '#include <openssl/ssl.h>',
+ 'CRYPTO_chacha_20': '#include <openssl/chacha.h>',
}
config.set10(
'HAVE_@0@'.format(fn.to_upper()),
#include <tests/isc.h>
typedef struct aead_testcase aead_testcase_t;
+typedef struct quic_hp_testcase quic_hp_testcase_t;
struct aead_testcase {
isc_crypto_aead_algorithm_t algorithm;
const uint8_t additional_data[8];
};
+struct quic_hp_testcase {
+ isc_crypto_quic_hp_protect_algorithm_t algorithm;
+ size_t keylen;
+ const uint8_t mask[5];
+ const uint8_t key[32];
+ const uint8_t sample[16];
+};
static void
test_aead(const aead_testcase_t *const testcase) {
assert_memory_equal(expected, actual, 32);
}
+ISC_RUN_TEST_IMPL(quic_hp_protect) {
+ isc_crypto_quic_hp_protect_t *prot = NULL;
+ isc_constregion_t key;
+ uint8_t generated[16];
+ isc_result_t result;
+ size_t i;
+
+ static const quic_hp_testcase_t testcases[] = {
+ {
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128,
+ isc_crypto_aes128gcm_key_length,
+ { 0x2e, 0xc0, 0xd8, 0x35, 0x6a },
+ { 0xc2, 0x06, 0xb8, 0xd9, 0xb9, 0xf0, 0xf3, 0x76, 0x44,
+ 0x43, 0x0b, 0x49, 0x0e, 0xea, 0xa3, 0x14 },
+ { 0x2c, 0xd0, 0x99, 0x1c, 0xd2, 0x5b, 0x0a, 0xac, 0x40,
+ 0x6a, 0x58, 0x16, 0xb6, 0x39, 0x41, 0x00 },
+ },
+
+ };
+
+ static const quic_hp_testcase_t non_fips_testcases[] = {
+ {
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20,
+ isc_crypto_chacha20poly1305_key_length,
+ { 0xae, 0xfe, 0xfe, 0x7d, 0x03 },
+ { 0x25, 0xa2, 0x82, 0xb9, 0xe8, 0x2f, 0x06, 0xf2,
+ 0x1f, 0x48, 0x89, 0x17, 0xa4, 0xfc, 0x8f, 0x1b,
+ 0x73, 0x57, 0x36, 0x85, 0x60, 0x85, 0x97, 0xd0,
+ 0xef, 0xcb, 0x07, 0x6b, 0x0a, 0xb7, 0xa7, 0xa4 },
+ { 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57,
+ 0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb },
+ },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(testcases); i++) {
+ key = (isc_constregion_t){
+ .base = testcases[i].key,
+ .length = testcases[i].keylen,
+ };
+
+ result = isc_crypto_quic_hp_protect_create(
+ isc_g_mctx, key, testcases[i].algorithm, &prot);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_crypto_quic_hp_protect_mask(prot, generated,
+ testcases[i].sample);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_memory_equal(generated, testcases[i].mask,
+ sizeof(testcases[i].mask));
+
+ isc_crypto_quic_hp_protect_destroy(&prot);
+ }
+
+ if (isc_crypto_fips_mode()) {
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(non_fips_testcases); i++) {
+ key = (isc_constregion_t){
+ .base = testcases[i].key,
+ .length = testcases[i].keylen,
+ };
+
+ result = isc_crypto_quic_hp_protect_create(
+ isc_g_mctx, key, testcases[i].algorithm, &prot);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_crypto_quic_hp_protect_mask(prot, generated,
+ testcases[i].sample);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_memory_equal(generated, testcases[i].mask,
+ sizeof(testcases[i].mask));
+
+ isc_crypto_quic_hp_protect_destroy(&prot);
+ }
+}
+
ISC_TEST_LIST_START
ISC_TEST_ENTRY(aead)
ISC_TEST_ENTRY(hkdf)
ISC_TEST_ENTRY(hkdf_expand_label)
+ISC_TEST_ENTRY(quic_hp_protect)
ISC_TEST_LIST_END
ISC_TEST_MAIN