Skip to content

[Bug]: Client Certificate Extensions Offered-List Inconsistency #10319

@LiD0209

Description

@LiD0209

Contact Details

lxd_dong@bupt.edu.cn

Version

V5.9.1

Description

Client Certificate Extensions Offered-List Inconsistency

Conclusion

This note concerns extensions in a client-sent TLS 1.3 Certificate message:
they must correspond to extensions previously offered by the server in
CertificateRequest.

wolfSSL parses CertificateEntry extensions, but in the current default build
there is no stable hard-check path showing that a client certificate extension
is rejected solely because it was not present in the server's earlier
CertificateRequest extension list. This should not be classified as fully
satisfied.

Standard Text

RFC 8446 Section 4.4.2, "Certificate":
https://www.rfc-editor.org/rfc/rfc8446.html#section-4.4.2

extensions:  A set of extension values for the CertificateEntry.  The
"Extension" format is defined in Section 4.2.  Valid extensions
for server certificates at present include the OCSP Status
extension [RFC6066] and the SignedCertificateTimestamp extension
[RFC6962]; future extensions may be defined for this message as
well.  Extensions in the Certificate message from the server MUST
correspond to ones from the ClientHello message.  Extensions in
the Certificate message from the client MUST correspond to
extensions in the CertificateRequest message from the server.  If
an extension applies to the entire chain, it SHOULD be included in
the first CertificateEntry.

The directly relevant sentence for this case is:

Extensions in the Certificate message from the client MUST correspond to
extensions in the CertificateRequest message from the server.

wolfSSL Source Evidence

CertificateEntry Extensions Are Parsed

In internal.c, wolfSSL extracts each
CertificateEntry extension block and passes it into TLSX_Parse():

args->exts[args->totalCerts].length = extSz;
args->exts[args->totalCerts].buffer = input + args->idx;
args->idx += extSz;
listSz -= extSz + OPAQUE16_LEN;
WOLFSSL_MSG_EX("\tParsing %d bytes of cert extensions",
    args->exts[args->totalCerts].length);
#if !defined(NO_TLS)
#if defined(HAVE_CERTIFICATE_STATUS_REQUEST)
ssl->response_idx = args->totalCerts;
#endif
ret = TLSX_Parse(ssl, args->exts[args->totalCerts].buffer,
    (word16)args->exts[args->totalCerts].length,
    certificate, NULL);
#endif /* !NO_TLS */

So the issue is not that certificate extensions are ignored entirely. The
question is whether wolfSSL enforces the offered-list correspondence required
by RFC 8446.

Generic Extension Parser

In tls.c, TLSX_Parse() treats
client_hello and certificate_request as request-like messages:

byte isRequest = (msgType == client_hello ||
                  msgType == certificate_request);

The certificate message is not included in this isRequest condition.

Message-Position Check Is Not the Same as Offered-List Check

For status_request, tls.c allows the
extension in a TLS 1.3 certificate message:

if (IsAtLeastTLSv1_3(ssl->version)) {
    if (msgType != client_hello &&
        msgType != certificate_request &&
        msgType != certificate)
        return EXT_NOT_ALLOWED;
}
ret = CSR_PARSE(ssl, input + offset, size, isRequest);

This checks whether the extension type is allowed in the message. It does not
by itself prove that the extension was offered in the previous
CertificateRequest.

The Stronger Check Is Feature-Dependent

When certificate status request support is enabled,
tls.c contains logic that can reject an
unexpected status_request:

if (!csr) /* unexpected extension */
    return TLSX_HandleUnsupportedExtension(ssl);

The rejection helper sends unsupported_extension; see
tls.c:

int TLSX_HandleUnsupportedExtension(WOLFSSL* ssl)
{
    SendAlert(ssl, alert_fatal, unsupported_extension);
    WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION);
    return UNSUPPORTED_EXTENSION;
}

However, in the current default build this path is compiled out. The relevant
macros in tls.c reduce CSR_PARSE to a
successful no-op:

#define CSR_FREE_ALL(data, heap) WC_DO_NOTHING
#define CSR_GET_SIZE(a, b)    0
#define CSR_WRITE(a, b, c)    0
#define CSR_PARSE(a, b, c, d) 0

The default build configuration also shows that
HAVE_CERTIFICATE_STATUS_REQUEST and HAVE_OCSP are not enabled; see
options.h.

Evidence Scope

The minimal runtime probe is
test_id253_254_tlsx_parse_runtime.c.
In the current default static build, the client object initializes, while the
server object does not initialize cleanly. Therefore, this client-certificate
case is mainly based on source-path analysis, while the server-certificate case has direct runtime evidence.

Classification

The current wolfSSL behavior is closer to checking whether an extension type is
generally allowed in a certificate message, not to enforcing a hard
offered-list correspondence against the previous CertificateRequest.

Reproduction steps

No response

Relevant log output

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions