This commit is contained in:
Zhi Guan
2026-06-23 00:01:28 +08:00
parent 79ed4489e3
commit 967c66ff7d
5 changed files with 693 additions and 4 deletions

View File

@@ -148,6 +148,7 @@ option(ENABLE_SDF "Enable SDF module" ON)
option(ENABLE_ASM_UNDERSCORE_PREFIX "Add prefix `_` to assembly symbols" ON) option(ENABLE_ASM_UNDERSCORE_PREFIX "Add prefix `_` to assembly symbols" ON)
option(ENABLE_TLS "Enable TLS and TLCP protocol support" 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_TLS_DEBUG "Enable TLS and TLCP print debug message" OFF)
option (ENABLE_SM2_ENC_PRE_COMPUTE "Enable SM2 encryption precomputing" ON) option (ENABLE_SM2_ENC_PRE_COMPUTE "Enable SM2 encryption precomputing" ON)
@@ -616,7 +617,6 @@ if (ENABLE_TLS)
src/tls_sct.c src/tls_sct.c
src/tls_ocsp.c src/tls_ocsp.c
src/tls_cookie.c src/tls_cookie.c
src/quic.c
src/tls_trace.c src/tls_trace.c
src/tls_vrf.c src/tls_vrf.c
src/tlcp.c src/tlcp.c
@@ -630,7 +630,18 @@ if (ENABLE_TLS)
tools/tls13_client.c tools/tls13_client.c
tools/tls13_server.c tools/tls13_server.c
tools/sctverify.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() endif()
@@ -920,7 +931,7 @@ endif()
# #
set(CPACK_PACKAGE_NAME "GmSSL") set(CPACK_PACKAGE_NAME "GmSSL")
set(CPACK_PACKAGE_VENDOR "GmSSL develop team") 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_PACKAGE_DESCRIPTION_FILE ${PROJECT_SOURCE_DIR}/README.md)
set(CPACK_NSIS_MODIFY_PATH ON) set(CPACK_NSIS_MODIFY_PATH ON)
include(CPack) include(CPack)

112
include/gmssl/quic.h Normal file
View File

@@ -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 <stdint.h>
#include <stdlib.h>
#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

View File

@@ -18,7 +18,7 @@ extern "C" {
#define GMSSL_VERSION_NUM 30300 #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); int gmssl_version_num(void);
const char *gmssl_version_str(void); const char *gmssl_version_str(void);

371
src/quic.c Normal file
View File

@@ -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 <string.h>
#include <gmssl/quic.h>
#include <gmssl/tls.h>
#include <gmssl/hkdf.h>
#include <gmssl/digest.h>
#include <gmssl/mem.h>
#include <gmssl/error.h>
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 = &params->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
}

195
tests/quictest.c Normal file
View File

@@ -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 <stdio.h>
#include <string.h>
#include <gmssl/quic.h>
#include <gmssl/error.h>
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(&params);
if (quic_transport_params_add(&params,
QUIC_transport_param_original_destination_connection_id,
odcid, sizeof(odcid)) != 1
|| quic_transport_params_add_varint(&params,
QUIC_transport_param_max_idle_timeout, 30) != 1
|| quic_transport_params_add_varint(&params,
QUIC_transport_param_initial_max_data, 4096) != 1
|| quic_transport_params_add(&params,
QUIC_transport_param_disable_active_migration, NULL, 0) != 1) {
error_print();
return -1;
}
if (quic_transport_params_to_bytes(&params, &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;
}