From 967c66ff7d263115af7d5ba2d8e38adf1bff3fad Mon Sep 17 00:00:00 2001 From: Zhi Guan Date: Tue, 23 Jun 2026 00:01:28 +0800 Subject: [PATCH] Add QUIC --- CMakeLists.txt | 17 +- include/gmssl/quic.h | 112 ++++++++++++ include/gmssl/version.h | 2 +- src/quic.c | 371 ++++++++++++++++++++++++++++++++++++++++ tests/quictest.c | 195 +++++++++++++++++++++ 5 files changed, 693 insertions(+), 4 deletions(-) create mode 100644 include/gmssl/quic.h create mode 100644 src/quic.c create mode 100644 tests/quictest.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a875276e..00972875 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,7 @@ option(ENABLE_SDF "Enable SDF module" ON) option(ENABLE_ASM_UNDERSCORE_PREFIX "Add prefix `_` to assembly symbols" ON) option(ENABLE_TLS "Enable TLS and TLCP protocol support" ON) +option(ENABLE_QUIC "Enable QUIC support" ON) option(ENABLE_TLS_DEBUG "Enable TLS and TLCP print debug message" OFF) option (ENABLE_SM2_ENC_PRE_COMPUTE "Enable SM2 encryption precomputing" ON) @@ -616,7 +617,6 @@ if (ENABLE_TLS) src/tls_sct.c src/tls_ocsp.c src/tls_cookie.c - src/quic.c src/tls_trace.c src/tls_vrf.c src/tlcp.c @@ -630,7 +630,18 @@ if (ENABLE_TLS) tools/tls13_client.c tools/tls13_server.c tools/sctverify.c) - list(APPEND tests tls tls13 tls_ocsp quic) + list(APPEND tests tls tls13 tls_ocsp) +endif() + +if (ENABLE_QUIC) + if (ENABLE_TLS AND ENABLE_SHA2) + message(STATUS "ENABLE_QUIC is ON") + add_definitions(-DENABLE_QUIC) + list(APPEND src src/quic.c) + list(APPEND tests quic) + else() + message(STATUS "ENABLE_QUIC requires ENABLE_TLS and ENABLE_SHA2; disabled") + endif() endif() @@ -920,7 +931,7 @@ endif() # set(CPACK_PACKAGE_NAME "GmSSL") set(CPACK_PACKAGE_VENDOR "GmSSL develop team") -set(CPACK_PACKAGE_VERSION "3.3.0-dev.1160") +set(CPACK_PACKAGE_VERSION "3.3.0-dev.1161") set(CPACK_PACKAGE_DESCRIPTION_FILE ${PROJECT_SOURCE_DIR}/README.md) set(CPACK_NSIS_MODIFY_PATH ON) include(CPack) diff --git a/include/gmssl/quic.h b/include/gmssl/quic.h new file mode 100644 index 00000000..1308b5e2 --- /dev/null +++ b/include/gmssl/quic.h @@ -0,0 +1,112 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#ifndef GMSSL_QUIC_H +#define GMSSL_QUIC_H + + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define QUIC_VERSION_V1 0x00000001 + +#define QUIC_INITIAL_SECRET_SIZE 32 +#define QUIC_INITIAL_KEY_SIZE 16 +#define QUIC_INITIAL_IV_SIZE 12 +#define QUIC_INITIAL_HP_KEY_SIZE 16 + +#define QUIC_TRANSPORT_PARAM_MAX_COUNT 32 +#define QUIC_TRANSPORT_PARAM_MAX_SIZE 512 + + +typedef enum { + QUIC_transport_param_original_destination_connection_id = 0x00, + QUIC_transport_param_max_idle_timeout = 0x01, + QUIC_transport_param_stateless_reset_token = 0x02, + QUIC_transport_param_max_udp_payload_size = 0x03, + QUIC_transport_param_initial_max_data = 0x04, + QUIC_transport_param_initial_max_stream_data_bidi_local = 0x05, + QUIC_transport_param_initial_max_stream_data_bidi_remote = 0x06, + QUIC_transport_param_initial_max_stream_data_uni = 0x07, + QUIC_transport_param_initial_max_streams_bidi = 0x08, + QUIC_transport_param_initial_max_streams_uni = 0x09, + QUIC_transport_param_ack_delay_exponent = 0x0a, + QUIC_transport_param_max_ack_delay = 0x0b, + QUIC_transport_param_disable_active_migration = 0x0c, + QUIC_transport_param_preferred_address = 0x0d, + QUIC_transport_param_active_connection_id_limit = 0x0e, + QUIC_transport_param_initial_source_connection_id = 0x0f, + QUIC_transport_param_retry_source_connection_id = 0x10, +} QUIC_TRANSPORT_PARAM_ID; + +typedef enum { + QUIC_encryption_initial = 0, + QUIC_encryption_early_data = 1, + QUIC_encryption_handshake = 2, + QUIC_encryption_application = 3, +} QUIC_ENCRYPTION_LEVEL; + + +typedef struct { + uint8_t client_secret[QUIC_INITIAL_SECRET_SIZE]; + uint8_t server_secret[QUIC_INITIAL_SECRET_SIZE]; +} QUIC_INITIAL_SECRETS; + +typedef struct { + uint8_t key[QUIC_INITIAL_KEY_SIZE]; + uint8_t iv[QUIC_INITIAL_IV_SIZE]; + uint8_t hp[QUIC_INITIAL_HP_KEY_SIZE]; // hp = header protection key +} QUIC_INITIAL_KEYS; + +typedef struct { + uint64_t id; + uint8_t data[QUIC_TRANSPORT_PARAM_MAX_SIZE]; + size_t datalen; +} QUIC_TRANSPORT_PARAM; + +typedef struct { + QUIC_TRANSPORT_PARAM params[QUIC_TRANSPORT_PARAM_MAX_COUNT]; + size_t params_count; +} QUIC_TRANSPORT_PARAMS; + + +size_t quic_varint_size(uint64_t val); +int quic_varint_to_bytes(uint64_t val, uint8_t **out, size_t *outlen); +int quic_varint_from_bytes(uint64_t *val, const uint8_t **in, size_t *inlen); + +void quic_transport_params_init(QUIC_TRANSPORT_PARAMS *params); +int quic_transport_params_add(QUIC_TRANSPORT_PARAMS *params, + uint64_t id, const uint8_t *data, size_t datalen); +int quic_transport_params_add_varint(QUIC_TRANSPORT_PARAMS *params, + uint64_t id, uint64_t val); +int quic_transport_params_get(const QUIC_TRANSPORT_PARAMS *params, + uint64_t id, const uint8_t **data, size_t *datalen); +int quic_transport_params_get_varint(const QUIC_TRANSPORT_PARAMS *params, + uint64_t id, uint64_t *val); +int quic_transport_params_to_bytes(const QUIC_TRANSPORT_PARAMS *params, + uint8_t **out, size_t *outlen); +int quic_transport_params_from_bytes(QUIC_TRANSPORT_PARAMS *params, + const uint8_t **in, size_t *inlen); + +int quic_derive_initial_secrets(const uint8_t *dcid, size_t dcid_len, QUIC_INITIAL_SECRETS *secrets); +int quic_derive_initial_client_keys(const QUIC_INITIAL_SECRETS *secrets, QUIC_INITIAL_KEYS *keys); +int quic_derive_initial_server_keys(const QUIC_INITIAL_SECRETS *secrets, QUIC_INITIAL_KEYS *keys); +int quic_derive_initial_keys(const uint8_t secret[QUIC_INITIAL_SECRET_SIZE], QUIC_INITIAL_KEYS *keys); + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/gmssl/version.h b/include/gmssl/version.h index 234b3abe..3f37d62c 100644 --- a/include/gmssl/version.h +++ b/include/gmssl/version.h @@ -18,7 +18,7 @@ extern "C" { #define GMSSL_VERSION_NUM 30300 -#define GMSSL_VERSION_STR "GmSSL 3.3.0-dev.1160" +#define GMSSL_VERSION_STR "GmSSL 3.3.0-dev.1161" int gmssl_version_num(void); const char *gmssl_version_str(void); diff --git a/src/quic.c b/src/quic.c new file mode 100644 index 00000000..7aabe2b3 --- /dev/null +++ b/src/quic.c @@ -0,0 +1,371 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + + +#include +#include +#include +#include +#include +#include +#include + + +static const uint8_t quic_v1_initial_salt[20] = { + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, + 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, + 0xcc, 0xbb, 0x7f, 0x0a, +}; + + +size_t quic_varint_size(uint64_t val) +{ + if (val <= 63) { + return 1; + } else if (val <= 16383) { + return 2; + } else if (val <= 1073741823) { + return 4; + } else if (val <= 4611686018427387903ULL) { + return 8; + } + return 0; +} + +int quic_varint_to_bytes(uint64_t val, uint8_t **out, size_t *outlen) +{ + size_t len; + + if (!outlen) { + error_print(); + return -1; + } + + len = quic_varint_size(val); + if (!len) { + error_print(); + return -1; + } + + if (out && *out) { + switch (len) { + case 1: + *(*out)++ = (uint8_t)val; + break; + case 2: + *(*out)++ = (uint8_t)(0x40 | (val >> 8)); + *(*out)++ = (uint8_t)val; + break; + case 4: + *(*out)++ = (uint8_t)(0x80 | (val >> 24)); + *(*out)++ = (uint8_t)(val >> 16); + *(*out)++ = (uint8_t)(val >> 8); + *(*out)++ = (uint8_t)val; + break; + case 8: + *(*out)++ = (uint8_t)(0xc0 | (val >> 56)); + *(*out)++ = (uint8_t)(val >> 48); + *(*out)++ = (uint8_t)(val >> 40); + *(*out)++ = (uint8_t)(val >> 32); + *(*out)++ = (uint8_t)(val >> 24); + *(*out)++ = (uint8_t)(val >> 16); + *(*out)++ = (uint8_t)(val >> 8); + *(*out)++ = (uint8_t)val; + break; + default: + error_print(); + return -1; + } + } + + *outlen += len; + return 1; +} + +int quic_varint_from_bytes(uint64_t *val, const uint8_t **in, size_t *inlen) +{ + uint8_t first; + size_t len; + uint64_t ret; + size_t i; + + if (!val || !in || !*in || !inlen || !*inlen) { + error_print(); + return -1; + } + + first = **in; + len = (size_t)1 << (first >> 6); + if (*inlen < len) { + error_print(); + return -1; + } + + ret = first & 0x3f; + for (i = 1; i < len; i++) { + ret <<= 8; + ret |= (*in)[i]; + } + + *val = ret; + *in += len; + *inlen -= len; + return 1; +} + +void quic_transport_params_init(QUIC_TRANSPORT_PARAMS *params) +{ + if (params) { + memset(params, 0, sizeof(*params)); + } +} + +int quic_transport_params_add(QUIC_TRANSPORT_PARAMS *params, + uint64_t id, const uint8_t *data, size_t datalen) +{ + QUIC_TRANSPORT_PARAM *param; + size_t i; + + if (!params || (!data && datalen)) { + error_print(); + return -1; + } + if (!quic_varint_size(id) || datalen > QUIC_TRANSPORT_PARAM_MAX_SIZE) { + error_print(); + return -1; + } + if (params->params_count >= QUIC_TRANSPORT_PARAM_MAX_COUNT) { + error_print(); + return -1; + } + for (i = 0; i < params->params_count; i++) { + if (params->params[i].id == id) { + error_print(); + return -1; + } + } + + param = ¶ms->params[params->params_count++]; + param->id = id; + param->datalen = datalen; + if (datalen) { + memcpy(param->data, data, datalen); + } + return 1; +} + +int quic_transport_params_add_varint(QUIC_TRANSPORT_PARAMS *params, + uint64_t id, uint64_t val) +{ + uint8_t buf[8]; + uint8_t *p = buf; + size_t len = 0; + + if (quic_varint_to_bytes(val, &p, &len) != 1) { + error_print(); + return -1; + } + if (quic_transport_params_add(params, id, buf, len) != 1) { + error_print(); + return -1; + } + return 1; +} + +int quic_transport_params_get(const QUIC_TRANSPORT_PARAMS *params, + uint64_t id, const uint8_t **data, size_t *datalen) +{ + size_t i; + + if (!params || !data || !datalen) { + error_print(); + return -1; + } + + for (i = 0; i < params->params_count; i++) { + if (params->params[i].id == id) { + *data = params->params[i].datalen ? params->params[i].data : NULL; + *datalen = params->params[i].datalen; + return 1; + } + } + + return 0; +} + +int quic_transport_params_get_varint(const QUIC_TRANSPORT_PARAMS *params, + uint64_t id, uint64_t *val) +{ + const uint8_t *data; + size_t datalen; + int ret; + + if (!val) { + error_print(); + return -1; + } + if ((ret = quic_transport_params_get(params, id, &data, &datalen)) != 1) { + return ret; + } + if (quic_varint_from_bytes(val, &data, &datalen) != 1 || datalen != 0) { + error_print(); + return -1; + } + return 1; +} + +int quic_transport_params_to_bytes(const QUIC_TRANSPORT_PARAMS *params, + uint8_t **out, size_t *outlen) +{ + size_t i; + + if (!params || !outlen) { + error_print(); + return -1; + } + + for (i = 0; i < params->params_count; i++) { + if (quic_varint_to_bytes(params->params[i].id, out, outlen) != 1 + || quic_varint_to_bytes(params->params[i].datalen, out, outlen) != 1) { + error_print(); + return -1; + } + if (out && *out) { + memcpy(*out, params->params[i].data, params->params[i].datalen); + *out += params->params[i].datalen; + } + *outlen += params->params[i].datalen; + } + + return 1; +} + +int quic_transport_params_from_bytes(QUIC_TRANSPORT_PARAMS *params, + const uint8_t **in, size_t *inlen) +{ + uint64_t id; + uint64_t len; + + if (!params || !in || !inlen || (!*in && *inlen)) { + error_print(); + return -1; + } + + quic_transport_params_init(params); + + while (*inlen) { + if (quic_varint_from_bytes(&id, in, inlen) != 1 + || quic_varint_from_bytes(&len, in, inlen) != 1) { + error_print(); + return -1; + } + if (len > *inlen || len > QUIC_TRANSPORT_PARAM_MAX_SIZE) { + error_print(); + return -1; + } + if (quic_transport_params_add(params, id, *in, (size_t)len) != 1) { + error_print(); + return -1; + } + *in += len; + *inlen -= len; + } + + return 1; +} + +int quic_derive_initial_secrets(const uint8_t *dcid, size_t dcid_len, QUIC_INITIAL_SECRETS *secrets) +{ +#if defined(ENABLE_SHA2) + /* QUIC v1 Initial secrets always use SHA-256, independent of the TLS cipher suite. */ + const DIGEST *digest = DIGEST_sha256(); + uint8_t initial_secret[QUIC_INITIAL_SECRET_SIZE]; + size_t initial_secret_len; + int ret = -1; + + if ((!dcid && dcid_len) || !secrets) { + error_print(); + return -1; + } + + if (hkdf_extract(digest, quic_v1_initial_salt, sizeof(quic_v1_initial_salt), + dcid, dcid_len, initial_secret, &initial_secret_len) != 1 + || initial_secret_len != QUIC_INITIAL_SECRET_SIZE + || tls13_hkdf_expand_label(digest, initial_secret, "client in", NULL, 0, + QUIC_INITIAL_SECRET_SIZE, secrets->client_secret) != 1 + || tls13_hkdf_expand_label(digest, initial_secret, "server in", NULL, 0, + QUIC_INITIAL_SECRET_SIZE, secrets->server_secret) != 1) { + error_print(); + goto end; + } + + ret = 1; + +end: + gmssl_secure_clear(initial_secret, sizeof(initial_secret)); + return ret; +#else + error_print(); + return -1; +#endif +} + +int quic_derive_initial_client_keys(const QUIC_INITIAL_SECRETS *secrets, QUIC_INITIAL_KEYS *keys) +{ + if (!secrets) { + error_print(); + return -1; + } + if (quic_derive_initial_keys(secrets->client_secret, keys) != 1) { + error_print(); + return -1; + } + return 1; +} + +int quic_derive_initial_server_keys(const QUIC_INITIAL_SECRETS *secrets, QUIC_INITIAL_KEYS *keys) +{ + if (!secrets) { + error_print(); + return -1; + } + if (quic_derive_initial_keys(secrets->server_secret, keys) != 1) { + error_print(); + return -1; + } + return 1; +} + +int quic_derive_initial_keys(const uint8_t secret[QUIC_INITIAL_SECRET_SIZE], QUIC_INITIAL_KEYS *keys) +{ +#if defined(ENABLE_SHA2) + const DIGEST *digest = DIGEST_sha256(); + + if (!secret || !keys) { + error_print(); + return -1; + } + + if (tls13_hkdf_expand_label(digest, secret, "quic key", NULL, 0, + QUIC_INITIAL_KEY_SIZE, keys->key) != 1 + || tls13_hkdf_expand_label(digest, secret, "quic iv", NULL, 0, + QUIC_INITIAL_IV_SIZE, keys->iv) != 1 + || tls13_hkdf_expand_label(digest, secret, "quic hp", NULL, 0, + QUIC_INITIAL_HP_KEY_SIZE, keys->hp) != 1) { + error_print(); + return -1; + } + + return 1; +#else + error_print(); + return -1; +#endif +} diff --git a/tests/quictest.c b/tests/quictest.c new file mode 100644 index 00000000..3950b7db --- /dev/null +++ b/tests/quictest.c @@ -0,0 +1,195 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +#include +#include +#include +#include + + +static int quic_test_varint(void) +{ + static const struct { + uint64_t val; + size_t len; + } tests[] = { + { 0, 1 }, + { 63, 1 }, + { 64, 2 }, + { 16383, 2 }, + { 16384, 4 }, + { 1073741823, 4 }, + { 1073741824, 8 }, + { 4611686018427387903ULL, 8 }, + }; + size_t i; + + for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + uint8_t buf[8]; + uint8_t *p = buf; + const uint8_t *cp = buf; + size_t len = 0; + size_t inlen; + uint64_t val; + + if (quic_varint_size(tests[i].val) != tests[i].len) { + error_print(); + return -1; + } + if (quic_varint_to_bytes(tests[i].val, &p, &len) != 1) { + error_print(); + return -1; + } + if (len != tests[i].len || p != buf + tests[i].len) { + error_print(); + return -1; + } + + inlen = len; + if (quic_varint_from_bytes(&val, &cp, &inlen) != 1) { + error_print(); + return -1; + } + if (val != tests[i].val || inlen != 0 || cp != buf + len) { + error_print(); + return -1; + } + } + + printf("%s() ok\n", __FUNCTION__); + return 1; +} + +static int quic_test_transport_params(void) +{ + const uint8_t odcid[] = { + 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, + }; + QUIC_TRANSPORT_PARAMS params; + QUIC_TRANSPORT_PARAMS decoded; + uint8_t buf[256]; + uint8_t *p = buf; + const uint8_t *cp = buf; + size_t len = 0; + size_t inlen; + const uint8_t *data; + size_t datalen; + uint64_t val; + + quic_transport_params_init(¶ms); + + if (quic_transport_params_add(¶ms, + QUIC_transport_param_original_destination_connection_id, + odcid, sizeof(odcid)) != 1 + || quic_transport_params_add_varint(¶ms, + QUIC_transport_param_max_idle_timeout, 30) != 1 + || quic_transport_params_add_varint(¶ms, + QUIC_transport_param_initial_max_data, 4096) != 1 + || quic_transport_params_add(¶ms, + QUIC_transport_param_disable_active_migration, NULL, 0) != 1) { + error_print(); + return -1; + } + + if (quic_transport_params_to_bytes(¶ms, &p, &len) != 1) { + error_print(); + return -1; + } + + inlen = len; + if (quic_transport_params_from_bytes(&decoded, &cp, &inlen) != 1) { + error_print(); + return -1; + } + if (inlen != 0 || cp != buf + len) { + error_print(); + return -1; + } + + if (quic_transport_params_get(&decoded, + QUIC_transport_param_original_destination_connection_id, + &data, &datalen) != 1) { + error_print(); + return -1; + } + if (datalen != sizeof(odcid) || memcmp(data, odcid, sizeof(odcid)) != 0) { + error_print(); + return -1; + } + + if (quic_transport_params_get_varint(&decoded, + QUIC_transport_param_max_idle_timeout, &val) != 1 || val != 30) { + error_print(); + return -1; + } + if (quic_transport_params_get_varint(&decoded, + QUIC_transport_param_initial_max_data, &val) != 1 || val != 4096) { + error_print(); + return -1; + } + if (quic_transport_params_get(&decoded, + QUIC_transport_param_disable_active_migration, + &data, &datalen) != 1 || data != NULL || datalen != 0) { + error_print(); + return -1; + } + + printf("%s() ok\n", __FUNCTION__); + return 1; +} + +static int quic_test_initial_keys(void) +{ +#ifdef ENABLE_SHA2 + const uint8_t dcid[] = { + 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, + }; + QUIC_INITIAL_SECRETS secrets; + QUIC_INITIAL_KEYS client_keys; + QUIC_INITIAL_KEYS server_keys; + + memset(&secrets, 0, sizeof(secrets)); + memset(&client_keys, 0, sizeof(client_keys)); + memset(&server_keys, 0, sizeof(server_keys)); + + if (quic_derive_initial_secrets(dcid, sizeof(dcid), &secrets) != 1 + || quic_derive_initial_client_keys(&secrets, &client_keys) != 1 + || quic_derive_initial_server_keys(&secrets, &server_keys) != 1) { + error_print(); + return -1; + } + if (memcmp(secrets.client_secret, secrets.server_secret, + QUIC_INITIAL_SECRET_SIZE) == 0) { + error_print(); + return -1; + } + if (memcmp(client_keys.key, server_keys.key, QUIC_INITIAL_KEY_SIZE) == 0 + || memcmp(client_keys.iv, server_keys.iv, QUIC_INITIAL_IV_SIZE) == 0 + || memcmp(client_keys.hp, server_keys.hp, QUIC_INITIAL_HP_KEY_SIZE) == 0) { + error_print(); + return -1; + } +#endif + + printf("%s() ok\n", __FUNCTION__); + return 1; +} + +int main(void) +{ + if (quic_test_varint() != 1 + || quic_test_transport_params() != 1 + || quic_test_initial_keys() != 1) { + error_print(); + return 1; + } + + return 0; +}