runner: Retain past epochs This is the test implementation, so we won't be particularly careful about this. This resolves a TODO in resetCipher. Later it will be used for the new retransmit testing machinery to read or write from past epochs when we expect it. Bug: 42290594 Change-Id: I8c51fc7adfbb8e671bd1c810c0fc6fa504e4a9ab Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/72650 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Nick Harper <nharper@chromium.org>
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go index a2c7d6b..e8b6c0a 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go
@@ -173,6 +173,11 @@ return c.conn.SetWriteDeadline(t) } +// Arbitrarily cap the number of past epochs to 4. This is far more than is +// necessary. We set a limit only so tests can freely trigger unboundedly many +// KeyUpdates. +const maxEpochs = 4 + type epochState struct { epoch uint16 cipher any // cipher algorithm @@ -191,6 +196,7 @@ wireVersion uint16 // wire version isDTLS bool epoch epochState + pastEpochs []epochState nextEpoch epochState @@ -216,6 +222,30 @@ return err } +func (hc *halfConn) getEpoch(epochValue uint16) (*epochState, bool) { + if hc.epoch.epoch == epochValue { + return &hc.epoch, true + } + for i := range hc.pastEpochs { + if hc.pastEpochs[i].epoch == epochValue { + return &hc.pastEpochs[i], true + } + } + return nil, false +} + +func (hc *halfConn) changeEpoch(epoch epochState) { + if len(hc.pastEpochs) < maxEpochs { + hc.pastEpochs = append(hc.pastEpochs, hc.epoch) + } else { + for i := 1; i < len(hc.pastEpochs); i++ { + hc.pastEpochs[i-1] = hc.pastEpochs[i] + } + hc.pastEpochs[len(hc.pastEpochs)-1] = hc.epoch + } + hc.epoch = epoch +} + func (hc *halfConn) newEpochState(epoch uint16, cipher any, mac macFunction) epochState { ret := epochState{epoch: epoch, cipher: cipher, mac: mac} if hc.isDTLS { @@ -246,7 +276,8 @@ if hc.nextEpoch.cipher == nil { return alertInternalError } - hc.epoch, hc.nextEpoch = hc.nextEpoch, epochState{} + hc.changeEpoch(hc.nextEpoch) + hc.nextEpoch = epochState{} if hc.config.Bugs.NullAllCiphers { hc.epoch.cipher = nullCipher{} @@ -279,18 +310,19 @@ newEpoch.cipher = nullCipher{} } hc.trafficSecret = secret - hc.epoch = newEpoch + hc.changeEpoch(newEpoch) } // resetCipher resets the cipher state back to no encryption to be able // to send an unencrypted ClientHello in response to HelloRetryRequest // after 0-RTT data was rejected. func (hc *halfConn) resetCipher() { - // In all cases, the cipher is set to nil so that second ClientHello - // will be sent with no encryption (instead of with early data keys). - hc.epoch.cipher = nil - // TODO(crbug.com/42290594): Instead, retain both the initial and 0-RTT - // epoch states as separate epochs and discard the 0-RTT one. + initialEpoch, ok := hc.getEpoch(0) + if !ok { + panic("tls: could not find initial epoch") + } + hc.epoch = *initialEpoch + hc.pastEpochs = nil } // incSeq increments the sequence number.