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
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:
ssl_tls13_parse_new_session_ticket() accepts ticket_len from the peer and stores it without enforcing the RFC lower bound.
ssl_tls13_has_configured_ticket() only checks session->ticket != NULL, not session->ticket_len > 0.
ssl_tls13_ticket_get_identity() returns session->ticket_len unchanged as the PSK identity length.
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:
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.
Confirmed mismatch in
PskIdentity.identity<1..2^16-1>under the baseline caseSummary
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.identityin ClientHello. A targeted local reproducer was run on2026-08-04and observed an on-wire identity length of0.The runtime evidence for this finding is recorded directly in the
Runtime Confirmationsection below.RFC Basis
Official standard: RFC 8446
Section: RFC 8446 Section 4.2.11, lines 3084-3087
Interpretation:
When encoding
PskIdentity.identity, the value must contain between 1 and2^16-1octets. Encoding an empty identity is not allowed.Additional RFC context:
identityas a label for a key, for example a resumption ticket or an externally provisioned PSK label.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.identityfield.Code Path
The relevant TLS 1.3 client flow is:
ssl_tls13_parse_new_session_ticket()acceptsticket_lenfrom the peer and stores it without enforcing the RFC lower bound.ssl_tls13_has_configured_ticket()only checkssession->ticket != NULL, notsession->ticket_len > 0.ssl_tls13_ticket_get_identity()returnssession->ticket_lenunchanged as the PSK identity length.ssl_tls13_write_identity()writes that length directly into ClientHello.Key snippets:
library/ssl_tls13_client.c:2899-2920library/ssl_tls13_client.c:686-693library/ssl_tls13_client.c:708-722library/ssl_tls13_client.c:813-817Runtime Confirmation
Reproducer:
2026-08-04Observed output excerpt:
What this proves:
mbedtls_calloc(1, 0)returns a non-null pointer, so a zero-length ticket survives parsing.PskIdentity.identitywith encoded length0.-26880isMBEDTLS_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-lengthPskIdentity.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.