Open andrewkdinh opened 19 hours ago
I will just put quic summary of what quic-go token protector is doing.
The key to protect can be either randomly generated or provided by application. Comment in transport.go reads as follows:
63 // The TokenGeneratorKey is used to encrypt session resumption tokens.
64 // If no key is configured, a random key will be generated.
65 // If multiple servers are authoritative for the same domain, they should use the same key,
66 // see section 8.1.3 of RFC 9000 for details.
67 TokenGeneratorKey *TokenGeneratorKey
the key is created in transport.init() function:
233 if t.TokenGeneratorKey == nil {
234 var key TokenGeneratorKey
235 if _, err := rand.Read(key[:]); err != nil {
236 t.initErr = err
237 return
238 }
239 t.TokenGeneratorKey = &key
240 }
The TokenGeneratorKey type is defined in internal/handshake/token_protector.go as 32 byte array (256bits)
The token protector uses AEAD with AES-256. To see how things are marshaled to packet take a look at NewRetryToken
in internal/handshake/token_generator.go
52 // NewRetryToken generates a new token for a Retry for a given source address
53 func (g *TokenGenerator) NewRetryToken(
54 raddr net.Addr,
55 origDestConnID protocol.ConnectionID,
56 retrySrcConnID protocol.ConnectionID,
57 ) ([]byte, error) {
58 data, err := asn1.Marshal(token{
59 IsRetryToken: true,
60 RemoteAddr: encodeRemoteAddr(raddr),
61 OriginalDestConnectionID: origDestConnID.Bytes(),
62 RetrySrcConnectionID: retrySrcConnID.Bytes(),
63 Timestamp: time.Now().UnixNano(),
64 })
65 if err != nil {
66 return nil, err
67 }
68 return g.tokenProtector.NewToken(data)
69 }
it creates token (blob), encodes it as asn1 and passes it to protector at line 68.
37 // NewToken encodes data into a new token.
38 func (s *tokenProtectorImpl) NewToken(data []byte) ([]byte, error) {
39 var nonce [tokenNonceSize]byte
40 if _, err := rand.Read(nonce[:]); err != nil {
41 return nil, err
42 }
43 aead, aeadNonce, err := s.createAEAD(nonce[:])
44 if err != nil {
45 return nil, err
46 }
47 return append(nonce[:], aead.Seal(nil, aeadNonce, data, nil)...), nil
48 }
at line 47 we append token protected by aead to nonce. So this is something what appears in packet: [ 32 bytes nonce | token sealed by aead ]
The verification is reverse process.
For MVP the key should be just ephemeral, generated at server start up.
As a successor for https://github.com/openssl/project/issues/912, we need to encrypt the token so that it cannot be tampered with by malicious actors and is completely opaque to the client. We need to decide if the key should be stored to/loaded from a file, or maybe ephemeral by being generated when the server starts up. We can take notes from quic-go: https://github.com/quic-go/quic-go/blob/master/internal/handshake/token_protector.go