From: Andrew Dinh Date: Thu, 12 Dec 2024 15:28:52 +0000 (-0600) Subject: Encrypt the address validation token X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7c20ae84fef1c62e05a8809463d5940c492a45f9;p=thirdparty%2Fopenssl.git Encrypt the address validation token Store an EVP_CIPHER_CTX context with an ephemeral key set in port and use it to encrypt/decrypt the validation token. Reviewed-by: Neil Horman Reviewed-by: Saša Nedvědický Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/26165) --- diff --git a/ssl/quic/quic_port.c b/ssl/quic/quic_port.c index 05bf333299e..6185005aecb 100644 --- a/ssl/quic/quic_port.c +++ b/ssl/quic/quic_port.c @@ -17,6 +17,7 @@ #include "quic_channel_local.h" #include "quic_engine_local.h" #include "../ssl_local.h" +#include /* * QUIC Port Structure @@ -71,6 +72,26 @@ typedef struct validation_token { unsigned char is_retry; } QUIC_VALIDATION_TOKEN; +/* + * Maximum length of a marshalled validation token. + * + * - timestamp is 8 bytes + * - odcid and rscid are maximally 42 bytes in total + * - remote_addr_len is a size_t (8 bytes) + * - remote_addr is in the worst case 110 bytes (in the case of using a + * maximally sized AF_UNIX socket) + * - is_retry is a single byte + */ +#define MARSHALLED_TOKEN_MAX_LEN 197 + +/* + * Maximum length of an encrypted marshalled validation token. + * + * This will include the size of the marshalled validation token plus a 16 byte + * tag and a 12 byte IV, so in total 197 bytes. + */ +#define ENCRYPTED_TOKEN_MAX_LEN (MARSHALLED_TOKEN_MAX_LEN + 16 + 12) + DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL); DEFINE_LIST_OF_IMPL(incoming_ch, QUIC_CHANNEL); DEFINE_LIST_OF_IMPL(port, QUIC_PORT); @@ -107,6 +128,10 @@ void ossl_quic_port_free(QUIC_PORT *port) static int port_init(QUIC_PORT *port) { size_t rx_short_dcid_len = (port->is_multi_conn ? INIT_DCID_LEN : 0); + int key_len; + EVP_CIPHER *cipher = NULL; + unsigned char *token_key = NULL; + int ret = 0; if (port->engine == NULL || port->channel_ctx == NULL) goto err; @@ -138,11 +163,25 @@ static int port_init(QUIC_PORT *port) ossl_list_port_insert_tail(&port->engine->port_list, port); port->on_engine_list = 1; port->bio_changed = 1; - return 1; + /* Generate random key for token encryption */ + if ((port->token_ctx = EVP_CIPHER_CTX_new()) == NULL + || (cipher = EVP_CIPHER_fetch(port->engine->libctx, + "AES-256-GCM", NULL)) == NULL + || !EVP_EncryptInit_ex(port->token_ctx, cipher, NULL, NULL, NULL) + || (key_len = EVP_CIPHER_CTX_get_key_length(port->token_ctx)) <= 0 + || (token_key = OPENSSL_malloc(key_len)) == NULL + || !RAND_bytes_ex(port->engine->libctx, token_key, key_len, 0) + || !EVP_EncryptInit_ex(port->token_ctx, NULL, NULL, token_key, NULL)) + goto err; + + ret = 1; err: - port_cleanup(port); - return 0; + EVP_CIPHER_free(cipher); + OPENSSL_free(token_key); + if (!ret) + port_cleanup(port); + return ret; } static void port_cleanup(QUIC_PORT *port) @@ -165,6 +204,9 @@ static void port_cleanup(QUIC_PORT *port) ossl_list_port_remove(&port->engine->port_list, port); port->on_engine_list = 0; } + + EVP_CIPHER_CTX_free(port->token_ctx); + port->token_ctx = NULL; } static void port_transition_failed(QUIC_PORT *port) @@ -717,21 +759,21 @@ static int generate_retry_token(BIO_ADDR *peer, QUIC_CONN_ID odcid, /** * @brief Marshals a validation token into a new buffer. * - * Dynamically allocates |buffer| and stores the size of |buffer| in BUFFER_LEN. - * Note that it will also allocate an extra QUIC_RETRY_INTEGRITY_TAG_LEN bytes. - * The caller is responsible for freeing |buffer|. + * |buffer| should already be allocated and at least MARSHALLED_TOKEN_MAX_LEN + * bytes long. Stores the actual data stored in |buffer| in |buffer_len|. * * @param token Validation token. - * @param buffer Callee will allocate a buffer and store the address here. - * @param buffer_len Size of |buffer|. + * @param buffer Address to store the marshalled token. + * @param buffer_len Size of data stored in |buffer|. */ static int marshal_validation_token(QUIC_VALIDATION_TOKEN *token, - unsigned char **buffer, size_t *buffer_len) + unsigned char *buffer, size_t *buffer_len) { WPACKET wpkt = {0}; BUF_MEM *buf_mem = BUF_MEM_new(); - if (buf_mem == NULL || (token->is_retry != 0 && token->is_retry != 1)) + if (buffer == NULL || buf_mem == NULL + || (token->is_retry != 0 && token->is_retry != 1)) return 0; if (!WPACKET_init(&wpkt, buf_mem) @@ -744,20 +786,127 @@ static int marshal_validation_token(QUIC_VALIDATION_TOKEN *token, || !WPACKET_sub_memcpy_u8(&wpkt, &token->rscid.id, token->rscid.id_len))) || !WPACKET_sub_memcpy_u8(&wpkt, token->remote_addr, token->remote_addr_len) - || !WPACKET_allocate_bytes(&wpkt, QUIC_RETRY_INTEGRITY_TAG_LEN, NULL) || !WPACKET_get_total_written(&wpkt, buffer_len) + || *buffer_len > MARSHALLED_TOKEN_MAX_LEN || !WPACKET_finish(&wpkt)) { WPACKET_cleanup(&wpkt); BUF_MEM_free(buf_mem); return 0; } - *buffer = (unsigned char *)buf_mem->data; - buf_mem->data = NULL; + memcpy(buffer, buf_mem->data, *buffer_len); BUF_MEM_free(buf_mem); return 1; } +/** + * @brief Encrypts a validation token using AES-256-GCM + * + * @param port The QUIC port containing the encryption key + * @param plaintext The data to encrypt + * @param pt_len Length of the plaintext + * @param ciphertext Buffer to receive encrypted data. If NULL, ct_len will be + * set to the required buffer size and function returns + * immediately. + * @param ct_len Pointer to size_t that will receive the ciphertext length. + * This also includes bytes for QUIC_RETRY_INTEGRITY_TAG_LEN. + * + * @return 1 on success, 0 on failure + * + * The ciphertext format is: + * [EVP_GCM_IV_LEN bytes IV][encrypted data][EVP_GCM_TAG_LEN bytes tag] + */ +static int encrypt_validation_token(const QUIC_PORT *port, + const unsigned char *plaintext, + size_t pt_len, + unsigned char *ciphertext, + size_t *ct_len) +{ + int iv_len, len, ret = 0; + size_t tag_len; + unsigned char *iv = ciphertext, *data, *tag; + + if ((tag_len = EVP_CIPHER_CTX_get_tag_length(port->token_ctx)) == 0 + || (iv_len = EVP_CIPHER_CTX_get_iv_length(port->token_ctx)) <= 0) + goto err; + + *ct_len = iv_len + pt_len + tag_len + QUIC_RETRY_INTEGRITY_TAG_LEN; + if (ciphertext == NULL) { + ret = 1; + goto err; + } + + data = ciphertext + iv_len; + tag = data + pt_len; + + if (!RAND_bytes_ex(port->engine->libctx, ciphertext, iv_len, 0) + || !EVP_EncryptInit_ex(port->token_ctx, NULL, NULL, NULL, iv) + || !EVP_EncryptUpdate(port->token_ctx, data, &len, plaintext, pt_len) + || !EVP_EncryptFinal_ex(port->token_ctx, data + pt_len, &len) + || !EVP_CIPHER_CTX_ctrl(port->token_ctx, EVP_CTRL_GCM_GET_TAG, tag_len, tag)) + goto err; + + ret = 1; +err: + return ret; +} + +/** + * @brief Decrypts a validation token using AES-256-GCM + * + * @param port The QUIC port containing the decryption key + * @param ciphertext The encrypted data (including IV and tag) + * @param ct_len Length of the ciphertext + * @param plaintext Buffer to receive decrypted data. If NULL, pt_len will be + * set to the required buffer size. + * @param pt_len Pointer to size_t that will receive the plaintext length + * + * @return 1 on success, 0 on failure + * + * Expected ciphertext format: + * [EVP_GCM_IV_LEN bytes IV][encrypted data][EVP_GCM_TAG_LEN bytes tag] + */ +static int decrypt_validation_token(const QUIC_PORT *port, + const unsigned char *ciphertext, + size_t ct_len, + unsigned char *plaintext, + size_t *pt_len) +{ + int iv_len, len = 0, ret = 0; + size_t tag_len; + const unsigned char *iv = ciphertext, *data, *tag; + + if ((tag_len = EVP_CIPHER_CTX_get_tag_length(port->token_ctx)) == 0 + || (iv_len = EVP_CIPHER_CTX_get_iv_length(port->token_ctx)) <= 0) + goto err; + + /* Prevent decryption of a buffer that is not within reasonable bounds */ + if (ct_len < (iv_len + tag_len) || ct_len > ENCRYPTED_TOKEN_MAX_LEN) + goto err; + + *pt_len = ct_len - iv_len - tag_len; + if (plaintext == NULL) { + ret = 1; + goto err; + } + + data = ciphertext + iv_len; + tag = ciphertext + ct_len - tag_len; + + if (!EVP_DecryptInit_ex(port->token_ctx, NULL, NULL, NULL, iv) + || !EVP_DecryptUpdate(port->token_ctx, plaintext, &len, data, + ct_len - iv_len - tag_len) + || !EVP_CIPHER_CTX_ctrl(port->token_ctx, EVP_CTRL_GCM_SET_TAG, tag_len, + (void *)tag) + || !EVP_DecryptFinal_ex(port->token_ctx, plaintext + len, &len)) + goto err; + + ret = 1; + +err: + return ret; +} + /** * @brief Parses contents of a buffer into a validation token. * @@ -833,13 +982,21 @@ static void port_send_retry(QUIC_PORT *port, QUIC_PKT_HDR *client_hdr) { BIO_MSG msg[1]; - unsigned char buffer[512], *token_buf = NULL; + /* + * Buffer is used for both marshalling the token as well as for the RETRY + * packet. The size of buffer should not be less than + * MARSHALLED_TOKEN_MAX_LEN. + */ + unsigned char buffer[512]; + unsigned char ct_buf[ENCRYPTED_TOKEN_MAX_LEN]; WPACKET wpkt; - size_t written, token_buf_len; + size_t written, token_buf_len, ct_len; QUIC_PKT_HDR hdr = {0}; QUIC_VALIDATION_TOKEN token = {0}; int ok; + if (!ossl_assert(sizeof(buffer) >= MARSHALLED_TOKEN_MAX_LEN)) + return; /* * 17.2.5.1 Sending a Retry packet * dst ConnId is src ConnId we got from client @@ -859,20 +1016,25 @@ static void port_send_retry(QUIC_PORT *port, /* Generate retry validation token */ if (!generate_retry_token(peer, client_hdr->dst_conn_id, hdr.src_conn_id, &token) - || !marshal_validation_token(&token, &token_buf, &token_buf_len) - || !ossl_assert(token_buf_len >= QUIC_RETRY_INTEGRITY_TAG_LEN)) + || !marshal_validation_token(&token, buffer, &token_buf_len) + || !encrypt_validation_token(port, buffer, token_buf_len, NULL, + &ct_len) + || ct_len > ENCRYPTED_TOKEN_MAX_LEN + || !encrypt_validation_token(port, buffer, token_buf_len, ct_buf, + &ct_len) + || !ossl_assert(ct_len >= QUIC_RETRY_INTEGRITY_TAG_LEN)) goto err; hdr.dst_conn_id = client_hdr->src_conn_id; hdr.type = QUIC_PKT_TYPE_RETRY; hdr.fixed = 1; hdr.version = 1; - hdr.len = token_buf_len; - hdr.data = token_buf; + hdr.len = ct_len; + hdr.data = ct_buf; ok = ossl_quic_calculate_retry_integrity_tag(port->engine->libctx, port->engine->propq, &hdr, &client_hdr->dst_conn_id, - token_buf + token_buf_len + ct_buf + ct_len - QUIC_RETRY_INTEGRITY_TAG_LEN); if (ok == 0) goto err; @@ -912,7 +1074,6 @@ static void port_send_retry(QUIC_PORT *port, err: cleanup_validation_token(&token); - OPENSSL_free(token_buf); } /** @@ -1041,11 +1202,16 @@ static int port_validate_token(QUIC_PKT_HDR *hdr, QUIC_PORT *port, int ret = 0; QUIC_VALIDATION_TOKEN token = { 0 }; unsigned long long time_diff; - size_t remote_addr_len; - unsigned char *remote_addr = NULL; + size_t remote_addr_len, dec_token_len; + unsigned char *remote_addr = NULL, dec_token[MARSHALLED_TOKEN_MAX_LEN]; OSSL_TIME now = ossl_time_now(); - if (!parse_validation_token(&token, hdr->token, hdr->token_len)) + if (!decrypt_validation_token(port, hdr->token, hdr->token_len, NULL, + &dec_token_len) + || dec_token_len > MARSHALLED_TOKEN_MAX_LEN + || !decrypt_validation_token(port, hdr->token, hdr->token_len, + dec_token, &dec_token_len) + || !parse_validation_token(&token, dec_token, dec_token_len)) goto err; /* diff --git a/ssl/quic/quic_port_local.h b/ssl/quic/quic_port_local.h index a60ad00dd3d..5b08266360f 100644 --- a/ssl/quic/quic_port_local.h +++ b/ssl/quic/quic_port_local.h @@ -110,6 +110,9 @@ struct quic_port_st { /* Has the BIO been changed since we last updated reactor pollability? */ unsigned int bio_changed : 1; + + /* AES-256 GCM context for token encryption */ + EVP_CIPHER_CTX *token_ctx; }; # endif diff --git a/test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt b/test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt index c73b4cc4ae7..2a5239b16d7 100644 --- a/test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt +++ b/test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt @@ -82,7 +82,7 @@ Sent Packet Sent Datagram Length: 1200 Received Datagram - Length: 63 + Length: 91 Sent Frame: Crypto Offset: 0 Len: 263 @@ -92,8 +92,8 @@ Sent Packet Version: 0x00000001 Destination Conn Id: 0x???????????????? Source Conn Id: - Payload length: 1146 - Token: ???????????????????????????????????????????????????????????????? + Payload length: 1118 + Token: ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? Packet Number: 0x00000001 Sent Datagram Length: 1200 @@ -305,8 +305,8 @@ Sent Packet Version: 0x00000001 Destination Conn Id: 0x???????????????? Source Conn Id: - Payload length: 1065 - Token: ???????????????????????????????????????????????????????????????? + Payload length: 1037 + Token: ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? Packet Number: 0x00000002 Sent Packet Packet Type: Handshake diff --git a/test/recipes/75-test_quicapi_data/ssltraceref.txt b/test/recipes/75-test_quicapi_data/ssltraceref.txt index 19762e6bab7..184bd549d99 100644 --- a/test/recipes/75-test_quicapi_data/ssltraceref.txt +++ b/test/recipes/75-test_quicapi_data/ssltraceref.txt @@ -80,7 +80,7 @@ Sent Packet Sent Datagram Length: 1200 Received Datagram - Length: 63 + Length: 91 Sent Frame: Crypto Offset: 0 Len: 256 @@ -90,8 +90,8 @@ Sent Packet Version: 0x00000001 Destination Conn Id: 0x???????????????? Source Conn Id: - Payload length: 1146 - Token: ???????????????????????????????????????????????????????????????? + Payload length: 1118 + Token: ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? Packet Number: 0x00000001 Sent Datagram Length: 1200 @@ -303,8 +303,8 @@ Sent Packet Version: 0x00000001 Destination Conn Id: 0x???????????????? Source Conn Id: - Payload length: 1065 - Token: ???????????????????????????????????????????????????????????????? + Payload length: 1037 + Token: ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? Packet Number: 0x00000002 Sent Packet Packet Type: Handshake