Skip to content

Commit

Permalink
count number of failed decryptions, error when lifetime-limit is reached
Browse files Browse the repository at this point in the history
  • Loading branch information
marten-seemann committed Sep 14, 2020
1 parent 3a4de20 commit 45246da
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 8 deletions.
41 changes: 33 additions & 8 deletions internal/handshake/updatable_aead.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handshake
import (
"crypto"
"crypto/cipher"
"crypto/tls"
"encoding/binary"
"fmt"
"os"
Expand All @@ -18,7 +19,7 @@ import (

// By setting this environment variable, the key update interval can be adjusted.
// This is not needed in production, but useful for integration and interop testing.
// Note that no mattter what value is set, a key update is only initiated once it is
// Note that no matter what value is set, a key update is only initiated once it is
// permitted (i.e. once an ACK for a packet sent at the current key phase has been received).
const keyUpdateEnv = "QUIC_GO_KEY_UPDATE_INTERVAL"

Expand Down Expand Up @@ -47,7 +48,10 @@ type updatableAEAD struct {
keyPhase protocol.KeyPhase
largestAcked protocol.PacketNumber
firstPacketNumber protocol.PacketNumber
keyUpdateInterval uint64

keyUpdateInterval uint64
invalidPacketLimit uint64
invalidPacketCount uint64

// Time when the keys should be dropped. Keys are dropped on the next call to Open().
prevRcvAEADExpiry time.Time
Expand Down Expand Up @@ -127,9 +131,7 @@ func (a *updatableAEAD) SetReadKey(suite *qtls.CipherSuiteTLS13, trafficSecret [
a.rcvAEAD = createAEAD(suite, trafficSecret)
a.headerDecrypter = newHeaderProtector(suite, trafficSecret, false)
if a.suite == nil {
a.nonceBuf = make([]byte, a.rcvAEAD.NonceSize())
a.aeadOverhead = a.rcvAEAD.Overhead()
a.suite = suite
a.setAEADParameters(a.rcvAEAD, suite)
}

a.nextRcvTrafficSecret = a.getNextTrafficSecret(suite.Hash, trafficSecret)
Expand All @@ -142,16 +144,39 @@ func (a *updatableAEAD) SetWriteKey(suite *qtls.CipherSuiteTLS13, trafficSecret
a.sendAEAD = createAEAD(suite, trafficSecret)
a.headerEncrypter = newHeaderProtector(suite, trafficSecret, false)
if a.suite == nil {
a.nonceBuf = make([]byte, a.sendAEAD.NonceSize())
a.aeadOverhead = a.sendAEAD.Overhead()
a.suite = suite
a.setAEADParameters(a.sendAEAD, suite)
}

a.nextSendTrafficSecret = a.getNextTrafficSecret(suite.Hash, trafficSecret)
a.nextSendAEAD = createAEAD(suite, a.nextSendTrafficSecret)
}

func (a *updatableAEAD) setAEADParameters(aead cipher.AEAD, suite *qtls.CipherSuiteTLS13) {
a.nonceBuf = make([]byte, aead.NonceSize())
a.aeadOverhead = aead.Overhead()
a.suite = suite
switch suite.ID {
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384:
a.invalidPacketLimit = protocol.InvalidPacketLimitAES
case tls.TLS_CHACHA20_POLY1305_SHA256:
a.invalidPacketLimit = protocol.InvalidPacketLimitChaCha
default:
panic(fmt.Sprintf("unknown cipher suite %d", suite.ID))
}
}

func (a *updatableAEAD) Open(dst, src []byte, rcvTime time.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, ad []byte) ([]byte, error) {
dec, err := a.open(dst, src, rcvTime, pn, kp, ad)
if err == ErrDecryptionFailed {
a.invalidPacketCount++
if a.invalidPacketCount >= a.invalidPacketLimit {
return nil, qerr.AEADLimitReached
}
}
return dec, err
}

func (a *updatableAEAD) open(dst, src []byte, rcvTime time.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, ad []byte) ([]byte, error) {
if a.prevRcvAEAD != nil && !a.prevRcvAEADExpiry.IsZero() && rcvTime.After(a.prevRcvAEADExpiry) {
a.prevRcvAEAD = nil
a.logger.Debugf("Dropping key phase %d", a.keyPhase-1)
Expand Down
11 changes: 11 additions & 0 deletions internal/handshake/updatable_aead_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

mocklogging "github.com/lucas-clemente/quic-go/internal/mocks/logging"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/qerr"
"github.com/lucas-clemente/quic-go/internal/qtls"
"github.com/lucas-clemente/quic-go/internal/utils"

Expand Down Expand Up @@ -116,6 +117,16 @@ var _ = Describe("Updatable AEAD", func() {
Expect(err).To(MatchError(ErrDecryptionFailed))
})

It("returns an AEAD_LIMIT_REACHED error when reaching the AEAD limit", func() {
client.invalidPacketLimit = 10
for i := 0; i < 9; i++ {
_, err := client.Open(nil, []byte("foobar"), time.Now(), protocol.PacketNumber(i), protocol.KeyPhaseZero, []byte("ad"))
Expect(err).To(MatchError(ErrDecryptionFailed))
}
_, err := client.Open(nil, []byte("foobar"), time.Now(), 10, protocol.KeyPhaseZero, []byte("ad"))
Expect(err).To(MatchError(qerr.AEADLimitReached))
})

Context("key updates", func() {
Context("receiving key updates", func() {
It("updates keys", func() {
Expand Down
7 changes: 7 additions & 0 deletions internal/protocol/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,10 @@ const MaxMaxAckDelay = (1<<14 - 1) * time.Millisecond

// MaxConnIDLen is the maximum length of the connection ID
const MaxConnIDLen = 20

// InvalidPacketLimitAES is the maximum number of packets that we can fail to decrypt when using
// AEAD_AES_128_GCM or AEAD_AES_265_GCM.
const InvalidPacketLimitAES = 1 << 54

// InvalidPacketLimitChaCha is the maximum number of packets that we can fail to decrypt when using AEAD_CHACHA20_POLY1305.
const InvalidPacketLimitChaCha = 1 << 36

0 comments on commit 45246da

Please sign in to comment.