Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions docs/api/QUIC_CREDENTIAL_CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ typedef struct QUIC_CREDENTIAL_CONFIG {
QUIC_CREDENTIAL_LOAD_COMPLETE_HANDLER AsyncHandler; // Optional
QUIC_ALLOWED_CIPHER_SUITE_FLAGS AllowedCipherSuites;// Optional
const char* CaCertificateFile; // Optional
const uint8_t* CaCertificateBlob; // Optional
uint32_t CaCertificateBlobLength; // Optional
} QUIC_CREDENTIAL_CONFIG;
```

Expand Down Expand Up @@ -159,7 +161,16 @@ Obtain the peer certificate using a faster in-process API call. Only available o

`QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE`

Enable CA certificate file provided in the `CaCertificateFile` member.
Enable CA certificate store file whose filename is provided in the `CaCertificateFile` member.
This is a text file containing certificates in `-----BEGIN CERTIFICATE-----` / `-----END CERTIFICATE-----` blocks.
The file may also contain certificate revocation lists (CRLs) in `-----BEGIN X509 CRL-----` / `-----END X509 CRL-----` blocks.
Only valid for OpenSSL.

`QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_BLOB`

Enable CA certificate store blob provided in memory by the `CaCertificateBlob` and `CaCertificateBlobLength` members.
Its format is the same as used for `QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE`.
Only valid for OpenSSL.

`QUIC_CREDENTIAL_FLAG_DISABLE_AIA`

Expand Down Expand Up @@ -203,10 +214,21 @@ A set of flags indicating which cipher suites are available to negotiate. Must b

#### `CaCertificateFile`

Optional pointer to CA certificate file that will be used when
Optional pointer to CA certificate filename that will be used when
validating the peer certificate. This allows the use of a private CA.
Must be used with `QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE`.

#### `CaCertificateBlob`

Optional pointer to CA certificate data that will be used when
validating the peer certificate. This allows the use of a private CA.
Must be used with `QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_BLOB` and `CaCertificateBlobLength`.

#### `CaCertificateBlobLength`

Optional length of CA certificate data pointed to by `CaCertificateBlob`.
Must be used with `QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_BLOB` and `CaCertificateBlob`.

# Remarks

TODO
Expand Down
7 changes: 7 additions & 0 deletions src/cs/lib/msquic_generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ internal enum QUIC_CREDENTIAL_FLAGS
INPROC_PEER_CERTIFICATE = 0x00080000,
SET_CA_CERTIFICATE_FILE = 0x00100000,
DISABLE_AIA = 0x00200000,
SET_CA_CERTIFICATE_BLOB = 0x00400000,
}

[System.Flags]
Expand Down Expand Up @@ -328,6 +329,12 @@ internal unsafe partial struct QUIC_CREDENTIAL_CONFIG
[NativeTypeName("const char *")]
internal sbyte* CaCertificateFile;

[NativeTypeName("const uint8_t *")]
internal byte* CaCertificateBlob;

[NativeTypeName("uint32_t")]
internal uint CaCertificateBlobLength;

internal ref QUIC_CERTIFICATE_HASH* CertificateHash
{
get
Expand Down
3 changes: 3 additions & 0 deletions src/inc/msquic.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ typedef enum QUIC_CREDENTIAL_FLAGS {
QUIC_CREDENTIAL_FLAG_INPROC_PEER_CERTIFICATE = 0x00080000, // Schannel only
QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE = 0x00100000, // OpenSSL only currently
QUIC_CREDENTIAL_FLAG_DISABLE_AIA = 0x00200000, // Schannel only currently
QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_BLOB = 0x00400000, // OpenSSL only currently
} QUIC_CREDENTIAL_FLAGS;

DEFINE_ENUM_FLAG_OPERATORS(QUIC_CREDENTIAL_FLAGS)
Expand Down Expand Up @@ -416,6 +417,8 @@ typedef struct QUIC_CREDENTIAL_CONFIG {
QUIC_CREDENTIAL_LOAD_COMPLETE_HANDLER AsyncHandler; // Optional
QUIC_ALLOWED_CIPHER_SUITE_FLAGS AllowedCipherSuites;// Optional
const char* CaCertificateFile; // Optional
const uint8_t* CaCertificateBlob; // Optional
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two new members are exclusive with CaCertificateFile, I think it would make sense to group them in a union or to re-use CaCertificateFile as suggeseted by #5715 (since CaCertificateBlob is meant to hold a string IIUC)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that could work. The certificate store format is traditionally a text file, though as far as I know nothing in OpenSSL stops you from passing a binary blob that happens to have a ----- BEGIN CERTIFICATE ----- text block inside it. OpenSSL ignores anything outside these blocks. That's why mentally I came up with uint8_t, but it ultimately doesn't matter.

I'll look into the naming/union more here.

uint32_t CaCertificateBlobLength; // Optional
} QUIC_CREDENTIAL_CONFIG;

//
Expand Down
117 changes: 111 additions & 6 deletions src/platform/tls_openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2135,20 +2135,125 @@ CxPlatTlsSecConfigCreate(
}
}

if (CredConfigFlags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE &&
if ((CredConfigFlags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE) &&
CredConfig->CaCertificateFile) {
X509_STORE* CertStore = SSL_CTX_get_cert_store(SecurityConfig->SSLCtx);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation behind that change? SSL_CTX_load_verify_locations seems to me the typical way to handle this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read somewhere that SSL_CTX_load_verify_locations is deprecated. But maybe it isn't anymore?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I could find this commit:
openssl/openssl@6dcb100

if (CertStore == NULL) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"SSL_CTX_get_cert_store failed");
Status = QUIC_STATUS_TLS_ERROR;
goto Exit;
}

Ret =
SSL_CTX_load_verify_locations(
SecurityConfig->SSLCtx,
CredConfig->CaCertificateFile,
NULL);
X509_STORE_load_file(
CertStore,
CredConfig->CaCertificateFile);
if (Ret != 1) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"SSL_CTX_load_verify_locations failed");
"X509_STORE_load_file failed");
Status = QUIC_STATUS_TLS_ERROR;
goto Exit;
}
}

if (CredConfigFlags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_BLOB &&
CredConfig->CaCertificateBlob) {
X509_STORE* CertStore = NULL;
BIO* CertBio = NULL;
STACK_OF(X509_INFO)* CertStack = NULL;

Status = QUIC_STATUS_TLS_ERROR;

// BIO_new_mem_buf() takes 'int' length
if (CredConfig->CaCertificateBlobLength > (unsigned)INT_MAX) {
Copy link
Copy Markdown
Collaborator

@guhetier guhetier Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try to use sized integer types, and this is the type of CaCertificateBlobLength

Suggested change
if (CredConfig->CaCertificateBlobLength > (unsigned)INT_MAX) {
if (CredConfig->CaCertificateBlobLength > (uint32_t)INT_MAX) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My logic was that I only cared that the right side is an unsigned type for comparison, and unsigned is the unsigned version of int. In other situations, it would be more robust against type changes. This is an artifact of my coding style showing through, and not something from this particular situation.

However, here, it doesn't matter. If int were to someday become 64-bit, a uint32_t cast here would do an unsigned compare against 0xFFFFFFFF rather than 0x7FFFFFFFFFFFFFFF. It probably would cause a truncation-of-constant warning. But CaCertificateBlobLength as a uint32_t can't be larger than 0xFFFFFFFF anyway, so functionality-wise, it doesn't matter.

I'm just showing my paranoid style that led to why it's (unsigned) here rather than (uint32_t). But it's fine either way, so I don't mind committing it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your reasoning, it makes sense. I was thinking the other way, casting to the type of CaCertificateBlobLength as the maximum value that the parameter can takes.

Either works here for me.

QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"CaCertificateBlobLength too large");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}

CertStore = SSL_CTX_get_cert_store(SecurityConfig->SSLCtx);
if (CertStore == NULL) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"SSL_CTX_get_cert_store failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}

CertBio = BIO_new_mem_buf(CredConfig->CaCertificateBlob, (int)CredConfig->CaCertificateBlobLength);
if (CertBio == NULL) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"BIO_new_mem_buf failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}

CertStack = PEM_X509_INFO_read_bio(CertBio, NULL, NULL, NULL);
if (!CertStack) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"PEM_X509_INFO_read_bio failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}

for (int i = 0, max = sk_X509_INFO_num(CertStack); i < max; i++) {
X509_INFO* CertInfo = sk_X509_INFO_value(CertStack, i);
if (CertInfo->x509) {
Ret = X509_STORE_add_cert(CertStore, CertInfo->x509);
if (!Ret) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"X509_STORE_add_cert failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}
}
if (CertInfo->crl) {
Ret = X509_STORE_add_crl(CertStore, CertInfo->crl);
if (!Ret) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"X509_STORE_add_crl failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}
}
}

Status = QUIC_STATUS_SUCCESS;

BlobExit:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please extract the code above in an helper function, if it needs it own cleanup section? It will be less error prone.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that sounds better; will do.

if (CertStack != NULL) {
sk_X509_INFO_pop_free(CertStack, X509_INFO_free);
}
if (CertBio != NULL) {
BIO_free(CertBio);
}

if (!QUIC_SUCCEEDED(Status)) {
goto Exit;
}
}
Expand Down
117 changes: 111 additions & 6 deletions src/platform/tls_quictls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1484,20 +1484,125 @@ CxPlatTlsSecConfigCreate(
}
}

if (CredConfigFlags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE &&
if ((CredConfigFlags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE) &&
CredConfig->CaCertificateFile) {
X509_STORE* CertStore = SSL_CTX_get_cert_store(SecurityConfig->SSLCtx);
if (CertStore == NULL) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"SSL_CTX_get_cert_store failed");
Status = QUIC_STATUS_TLS_ERROR;
goto Exit;
}

Ret =
SSL_CTX_load_verify_locations(
SecurityConfig->SSLCtx,
CredConfig->CaCertificateFile,
NULL);
X509_STORE_load_file(
CertStore,
CredConfig->CaCertificateFile);
if (Ret != 1) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"SSL_CTX_load_verify_locations failed");
"X509_STORE_load_file failed");
Status = QUIC_STATUS_TLS_ERROR;
goto Exit;
}
}

if (CredConfigFlags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_BLOB &&
CredConfig->CaCertificateBlob) {
X509_STORE* CertStore = NULL;
BIO* CertBio = NULL;
STACK_OF(X509_INFO)* CertStack = NULL;

Status = QUIC_STATUS_TLS_ERROR;

// BIO_new_mem_buf() takes 'int' length
if (CredConfig->CaCertificateBlobLength > (unsigned)INT_MAX) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"CaCertificateBlobLength too large");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}

CertStore = SSL_CTX_get_cert_store(SecurityConfig->SSLCtx);
if (CertStore == NULL) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"SSL_CTX_get_cert_store failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}

CertBio = BIO_new_mem_buf(CredConfig->CaCertificateBlob, (int)CredConfig->CaCertificateBlobLength);
if (CertBio == NULL) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"BIO_new_mem_buf failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}

CertStack = PEM_X509_INFO_read_bio(CertBio, NULL, NULL, NULL);
if (!CertStack) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"PEM_X509_INFO_read_bio failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}

for (int i = 0, max = sk_X509_INFO_num(CertStack); i < max; i++) {
X509_INFO* CertInfo = sk_X509_INFO_value(CertStack, i);
if (CertInfo->x509) {
Ret = X509_STORE_add_cert(CertStore, CertInfo->x509);
if (!Ret) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"X509_STORE_add_cert failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}
}
if (CertInfo->crl) {
Ret = X509_STORE_add_crl(CertStore, CertInfo->crl);
if (!Ret) {
QuicTraceEvent(
LibraryErrorStatus,
"[ lib] ERROR, %u, %s.",
ERR_get_error(),
"X509_STORE_add_crl failed");
Status = QUIC_STATUS_TLS_ERROR;
goto BlobExit;
}
}
}

Status = QUIC_STATUS_SUCCESS;

BlobExit:
if (CertStack != NULL) {
sk_X509_INFO_pop_free(CertStack, X509_INFO_free);
}
if (CertBio != NULL) {
BIO_free(CertBio);
}

if (!QUIC_SUCCEEDED(Status)) {
goto Exit;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/platform/tls_schannel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,8 @@ CxPlatTlsSecConfigCreate(
return QUIC_STATUS_INVALID_PARAMETER;
}

if (CredConfig->Flags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE) {
if ((CredConfig->Flags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE) ||
(CredConfig->Flags & QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_BLOB)) {
return QUIC_STATUS_NOT_SUPPORTED;
}

Expand Down
Loading