Skip to content

Confirmed mismatch in PskIdentity.identity<1..2^16-1> under the baseline case #10905

Description

@LiD0209

Confirmed mismatch in PskIdentity.identity<1..2^16-1> under the baseline case

Summary

This is a real issue on the current workspace build. The TLS 1.3 resumption path accepts a zero-length ticket, treats it as a configured PSK identity, and emits that value as PskIdentity.identity in ClientHello. A targeted local reproducer was run on 2026-08-04 and observed an on-wire identity length of 0.

The runtime evidence for this finding is recorded directly in the Runtime Confirmation section below.

RFC Basis

  • Official standard: RFC 8446

  • Section: RFC 8446 Section 4.2.11, lines 3084-3087

struct {
opaque identity<1..2^16-1>;
uint32 obfuscated_ticket_age;
} PskIdentity;

Interpretation:

When encoding PskIdentity.identity, the value must contain between 1 and 2^16-1 octets. Encoding an empty identity is not allowed.

Additional RFC context:

  • RFC 8446 immediately describes identity as a label for a key, for example a resumption ticket or an externally provisioned PSK label.
  • Appendix B.3.4 defines the resumption ticket itself as opaque ticket<1..2^16-1>;.

For ticket-based resumption, the non-empty constraint therefore applies both to the source ticket and to the later PskIdentity.identity field.

Code Path

The relevant TLS 1.3 client flow is:

  1. ssl_tls13_parse_new_session_ticket() accepts ticket_len from the peer and stores it without enforcing the RFC lower bound.
  2. ssl_tls13_has_configured_ticket() only checks session->ticket != NULL, not session->ticket_len > 0.
  3. ssl_tls13_ticket_get_identity() returns session->ticket_len unchanged as the PSK identity length.
  4. ssl_tls13_write_identity() writes that length directly into ClientHello.

Key snippets:

library/ssl_tls13_client.c:2899-2920

/* Ticket */
MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, 2);
ticket_len = MBEDTLS_GET_UINT16_BE(p, 0);
p += 2;
MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, ticket_len);
MBEDTLS_SSL_DEBUG_BUF(3, "received ticket", p, ticket_len);

/* Check if we previously received a ticket already. */
if (session->ticket != NULL || session->ticket_len > 0) {
    mbedtls_free(session->ticket);
    session->ticket = NULL;
    session->ticket_len = 0;
}

if ((ticket = mbedtls_calloc(1, ticket_len)) == NULL) {
    MBEDTLS_SSL_DEBUG_MSG(1, ("ticket alloc failed"));
    return MBEDTLS_ERR_SSL_ALLOC_FAILED;
}
memcpy(ticket, p, ticket_len);
p += ticket_len;
session->ticket = ticket;
session->ticket_len = ticket_len;

library/ssl_tls13_client.c:686-693

static int ssl_tls13_has_configured_ticket(mbedtls_ssl_context *ssl)
{
    mbedtls_ssl_session *session = ssl->session_negotiate;
    return ssl->handshake->resume &&
           session != NULL && session->ticket != NULL &&
           mbedtls_ssl_conf_tls13_is_kex_mode_enabled(
        ssl, mbedtls_ssl_tls13_session_get_ticket_flags(
            session, MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ALL));
}

library/ssl_tls13_client.c:708-722

static int ssl_tls13_ticket_get_identity(mbedtls_ssl_context *ssl,
                                         psa_algorithm_t *hash_alg,
                                         const unsigned char **identity,
                                         size_t *identity_len)
{
    mbedtls_ssl_session *session = ssl->session_negotiate;

    if (!ssl_tls13_has_configured_ticket(ssl)) {
        return -1;
    }

    *hash_alg = ssl_tls13_get_ciphersuite_hash_alg(session->ciphersuite);
    *identity = session->ticket;
    *identity_len = session->ticket_len;
    return 0;
}

library/ssl_tls13_client.c:813-817

MBEDTLS_SSL_CHK_BUF_PTR(buf, end, 6 + identity_len);

MBEDTLS_PUT_UINT16_BE(identity_len, buf, 0);
memcpy(buf + 2, identity, identity_len);
MBEDTLS_PUT_UINT32_BE(obfuscated_ticket_age, buf, 2 + identity_len);

Runtime Confirmation

Reproducer:

  • Run date: 2026-08-04

Observed output excerpt:

mbedtls_calloc(1, 0) returned NON-NULL
mbedtls_ssl_handshake returned -26880
OfferedPsks.identities encoded length = 6
first PskIdentity.identity length = 0
RESULT: ClientHello encodes identities length 6 with a zero-length ticket identity.

What this proves:

  • On this build, mbedtls_calloc(1, 0) returns a non-null pointer, so a zero-length ticket survives parsing.
  • The client reaches the point of emitting ClientHello and writes PskIdentity.identity with encoded length 0.
  • -26880 is MBEDTLS_ERR_SSL_WANT_READ, which is expected because the reproducer stops after sending ClientHello and before receiving any server response.

Conclusion

The report should remain issue_found. This is not just a static possibility: the current build actually emits a zero-length PskIdentity.identity, violating RFC 8446.

Remaining Uncertainty

Cross-platform reachability depends on allocator behavior for calloc(1, 0). However, the current workspace build reproduces the issue directly, and the underlying parser still lacks an explicit RFC-compliant lower-bound check for ticket length.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions