go: crypto/tls: randomly generate ticket_age_add [freeze exception]

crypto/tls always sets newSessionTicketMsgTLS13.ageAdd to 0, which makes it so that clients resuming a session can’t obfuscate the obfusacted_ticket_age. This violates the TLS 1.3 spec (RFC 8446, section 4.6.1):

ticket_age_add: A securely generated, random 32-bit value that is used to obscure the age of the ticket that the client includes in the “pre_shared_key” extension. The client-side ticket age is added to this value modulo 2^32 to obtain the value that is transmitted by the client. The server MUST generate a fresh value for each ticket it sends.

See the sendSessionTickets() function.

How to reproduce

  • Run a simple TLS server: https://go.dev/play/p/t2moO8mDTmb (notice I set srv.SetKeepAlivesEnabled(false); we don’t want connection reuse)
  • open Wireshark, listen on loopback interface and filter on tls.handshake
  • curl -k https://localhost:8443 https://localhost:8443

In Wireshark, open the second Client Hello message, look at the pre_shared_key extension and you’ll see that obfuscated_ticket_age is 0 (or very close to 0).

Proposed fix

Given that you don’t check the obfuscated_ticket_age, it’s enough to assign ageAdd a random value each time.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 15 (8 by maintainers)

Commits related to this issue

Most upvoted comments

cc @golang/security

If the fix is easy and we can get a CL in before the end of the week I think it’s fine. Up to the security team to decide about a CVE/backport.

Ah, yes, that’s an oversight, thank you.

I would argue this is worth a freeze exception, because the fix is very simple, we are early in the freeze, and it fixes a privacy issue (allowing network observers to correlate successive connections even if the client changed location). Maybe even worth a CVE and a backport? Not sure. /cc @golang/release

If we ever expose the session ticket structure (which might be something that happens as part of #25351 #46718 #19199 #6379), we might want to add the obfuscated_ticket_age to it (or start generating it from the key material?) in case other implementations want to use it, but for now we are the only consumers of the tickets and indeed don’t care about it.

(While at it, let’s also add a note about why ticket_nonce is always zero, which is simply that we only ever send one ticket. It’s a good comment to have in there in case that invariant ever changes.)