validate version tag in CHLO to prevent version downgrade attacks

fixes #360
This commit is contained in:
Marten Seemann
2016-12-22 17:54:32 +07:00
committed by Lucas Clemente
parent f72154e30b
commit d39c2a3027
2 changed files with 70 additions and 1 deletions

View File

@@ -3,6 +3,7 @@ package handshake
import (
"bytes"
"crypto/rand"
"encoding/binary"
"io"
"net"
"sync"
@@ -102,10 +103,21 @@ func (h *CryptoSetup) handleMessage(chloData []byte, cryptoData map[Tag][]byte)
return false, qerr.Error(qerr.CryptoMessageParameterNotFound, "SNI required")
}
_, ok = cryptoData[TagVER]
// prevent version downgrade attacks
// see https://groups.google.com/a/chromium.org/forum/#!topic/proto-quic/N-de9j63tCk for a discussion and examples
verSlice, ok := cryptoData[TagVER]
if !ok {
return false, qerr.Error(qerr.InvalidCryptoMessageParameter, "client hello missing version tag")
}
if len(verSlice) != 4 {
return false, qerr.Error(qerr.InvalidCryptoMessageParameter, "incorrect version tag")
}
verTag := binary.LittleEndian.Uint32(verSlice)
ver := protocol.VersionTagToNumber(verTag)
// If the client's preferred version is not the version we are currently speaking, then the client went through a version negotiation. In this case, we need to make sure that we actually do not support this version and that it wasn't a downgrade attack.
if ver != h.version && protocol.IsSupportedVersion(ver) {
return false, qerr.Error(qerr.VersionNegotiationMismatch, "Downgrade attack detected")
}
var reply []byte
var err error

View File

@@ -359,6 +359,63 @@ var _ = Describe("Crypto setup", func() {
Expect(err).To(MatchError(qerr.Error(qerr.InvalidCryptoMessageParameter, "client hello missing version tag")))
})
It("rejects CHLOs with a version tag that has the wrong length", func() {
WriteHandshakeMessage(&stream.dataToRead, TagCHLO, map[Tag][]byte{
TagSCID: scfg.ID,
TagSNI: []byte("quic.clemente.io"),
TagPUBS: []byte("pubs"),
TagNONC: nonce32,
TagSTK: validSTK,
TagKEXS: kexs,
TagAEAD: aead,
TagVER: []byte{0x13, 0x37}, // should be 4 bytes
})
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.InvalidCryptoMessageParameter, "incorrect version tag")))
})
It("detects version downgrade attacks", func() {
highestSupportedVersion := protocol.SupportedVersions[len(protocol.SupportedVersions)-1]
lowestSupportedVersion := protocol.SupportedVersions[0]
Expect(highestSupportedVersion).ToNot(Equal(lowestSupportedVersion))
cs.version = highestSupportedVersion
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, protocol.VersionNumberToTag(lowestSupportedVersion))
WriteHandshakeMessage(&stream.dataToRead, TagCHLO, map[Tag][]byte{
TagSCID: scfg.ID,
TagSNI: []byte("quic.clemente.io"),
TagPUBS: []byte("pubs"),
TagNONC: nonce32,
TagSTK: validSTK,
TagKEXS: kexs,
TagAEAD: aead,
TagVER: b,
})
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.VersionNegotiationMismatch, "Downgrade attack detected")))
})
It("accepts a non-matching version tag in the CHLO, if it is an unsupported version", func() {
supportedVersion := protocol.SupportedVersions[0]
unsupportedVersion := supportedVersion + 1000
Expect(protocol.IsSupportedVersion(unsupportedVersion)).To(BeFalse())
cs.version = supportedVersion
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, protocol.VersionNumberToTag(unsupportedVersion))
WriteHandshakeMessage(&stream.dataToRead, TagCHLO, map[Tag][]byte{
TagSCID: scfg.ID,
TagSNI: []byte("quic.clemente.io"),
TagPUBS: []byte("pubs"),
TagNONC: nonce32,
TagSTK: validSTK,
TagKEXS: kexs,
TagAEAD: aead,
TagVER: b,
})
err := cs.HandleCryptoStream()
Expect(err).ToNot(HaveOccurred())
})
It("errors if the AEAD tag is missing", func() {
WriteHandshakeMessage(&stream.dataToRead, TagCHLO, map[Tag][]byte{
TagSCID: scfg.ID,