forked from quic-go/quic-go
260 lines
8.0 KiB
Go
260 lines
8.0 KiB
Go
package handshake
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"time"
|
|
|
|
"github.com/lucas-clemente/quic-go/protocol"
|
|
"github.com/lucas-clemente/quic-go/qerr"
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
type mockCertManager struct {
|
|
setDataCalledWith []byte
|
|
leafCert []byte
|
|
}
|
|
|
|
func (m *mockCertManager) SetData(data []byte) error {
|
|
m.setDataCalledWith = data
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCertManager) GetLeafCert() []byte {
|
|
return m.leafCert
|
|
}
|
|
|
|
var _ = Describe("Crypto setup", func() {
|
|
var cs *cryptoSetupClient
|
|
var certManager *mockCertManager
|
|
var stream *mockStream
|
|
|
|
BeforeEach(func() {
|
|
stream = &mockStream{}
|
|
certManager = &mockCertManager{}
|
|
csInt, err := NewCryptoSetupClient(0, protocol.Version36, stream)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
cs = csInt.(*cryptoSetupClient)
|
|
cs.certManager = certManager
|
|
})
|
|
|
|
Context("Reading SHLOs", func() {
|
|
var tagMap map[Tag][]byte
|
|
|
|
BeforeEach(func() {
|
|
tagMap = make(map[Tag][]byte)
|
|
})
|
|
|
|
It("rejects handshake messages with the wrong message tag", func() {
|
|
WriteHandshakeMessage(&stream.dataToRead, TagCHLO, tagMap)
|
|
err := cs.HandleCryptoStream()
|
|
Expect(err).To(MatchError(qerr.InvalidCryptoMessageType))
|
|
})
|
|
|
|
It("errors on invalid handshake messages", func() {
|
|
b := &bytes.Buffer{}
|
|
WriteHandshakeMessage(b, TagCHLO, tagMap)
|
|
stream.dataToRead.Write(b.Bytes()[:b.Len()-2]) // cut the handshake message
|
|
err := cs.HandleCryptoStream()
|
|
// note that if this was a complete handshake message, HandleCryptoStream would fail with a qerr.InvalidCryptoMessageType
|
|
Expect(err).To(MatchError(qerr.HandshakeFailed))
|
|
})
|
|
|
|
It("passes the message on for parsing, and reads the source address token", func() {
|
|
stk := []byte("foobar")
|
|
tagMap[TagSTK] = stk
|
|
WriteHandshakeMessage(&stream.dataToRead, TagREJ, tagMap)
|
|
// this will throw a qerr.HandshakeFailed due to an EOF in WriteHandshakeMessage
|
|
// this is because the mockStream doesn't block if there's no data to read
|
|
err := cs.HandleCryptoStream()
|
|
Expect(err).To(MatchError(qerr.HandshakeFailed))
|
|
Expect(cs.stk).Should(Equal(stk))
|
|
})
|
|
|
|
It("saves the server nonce", func() {
|
|
nonc := []byte("servernonce")
|
|
tagMap[TagSNO] = nonc
|
|
err := cs.handleREJMessage(tagMap)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.sno).To(Equal(nonc))
|
|
})
|
|
|
|
It("passes the certificates to the CertManager", func() {
|
|
tagMap[TagCERT] = []byte("cert")
|
|
err := cs.handleREJMessage(tagMap)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(certManager.setDataCalledWith).To(Equal(tagMap[TagCERT]))
|
|
})
|
|
|
|
Context("Reading server configs", func() {
|
|
It("reads a server config", func() {
|
|
b := &bytes.Buffer{}
|
|
scfg := getDefaultServerConfigClient()
|
|
WriteHandshakeMessage(b, TagSCFG, scfg)
|
|
tagMap[TagSCFG] = b.Bytes()
|
|
err := cs.handleREJMessage(tagMap)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.serverConfig).ToNot(BeNil())
|
|
Expect(cs.serverConfig.ID).To(Equal(scfg[TagSCID]))
|
|
})
|
|
|
|
It("generates a client nonce after reading a server config", func() {
|
|
b := &bytes.Buffer{}
|
|
WriteHandshakeMessage(b, TagSCFG, getDefaultServerConfigClient())
|
|
tagMap[TagSCFG] = b.Bytes()
|
|
err := cs.handleREJMessage(tagMap)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.nonc).To(HaveLen(32))
|
|
})
|
|
|
|
It("only generates a client nonce once, when reading multiple server configs", func() {
|
|
b := &bytes.Buffer{}
|
|
WriteHandshakeMessage(b, TagSCFG, getDefaultServerConfigClient())
|
|
tagMap[TagSCFG] = b.Bytes()
|
|
err := cs.handleREJMessage(tagMap)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
nonc := cs.nonc
|
|
Expect(nonc).ToNot(BeEmpty())
|
|
err = cs.handleREJMessage(tagMap)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.nonc).To(Equal(nonc))
|
|
})
|
|
|
|
It("passes on errors from reading the server config", func() {
|
|
b := &bytes.Buffer{}
|
|
WriteHandshakeMessage(b, TagSHLO, make(map[Tag][]byte))
|
|
tagMap[TagSCFG] = b.Bytes()
|
|
_, origErr := parseServerConfig(b.Bytes())
|
|
err := cs.handleREJMessage(tagMap)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err).To(MatchError(origErr))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("CHLO generation", func() {
|
|
It("is longer than the miminum client hello size", func() {
|
|
err := cs.sendCHLO()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.cryptoStream.(*mockStream).dataWritten.Len()).To(BeNumerically(">", protocol.ClientHelloMinimumSize))
|
|
})
|
|
|
|
It("saves the last sent CHLO", func() {
|
|
// send first CHLO
|
|
err := cs.sendCHLO()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.cryptoStream.(*mockStream).dataWritten.Bytes()).To(Equal(cs.lastSentCHLO))
|
|
cs.cryptoStream.(*mockStream).dataWritten.Reset()
|
|
firstCHLO := cs.lastSentCHLO
|
|
// send second CHLO
|
|
cs.sno = []byte("foobar")
|
|
err = cs.sendCHLO()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.cryptoStream.(*mockStream).dataWritten.Bytes()).To(Equal(cs.lastSentCHLO))
|
|
Expect(cs.lastSentCHLO).ToNot(Equal(firstCHLO))
|
|
})
|
|
|
|
It("has the right values for an inchoate CHLO", func() {
|
|
tags := cs.getTags()
|
|
Expect(tags).To(HaveKey(TagSNI))
|
|
Expect(tags[TagPDMD]).To(Equal([]byte("X509")))
|
|
Expect(tags[TagVER]).To(Equal([]byte("Q036")))
|
|
})
|
|
|
|
It("includes the server config id, if available", func() {
|
|
id := []byte("foobar")
|
|
cs.serverConfig = &serverConfigClient{ID: id}
|
|
tags := cs.getTags()
|
|
Expect(tags[TagSCID]).To(Equal(id))
|
|
})
|
|
|
|
It("includes the source address token, if available", func() {
|
|
cs.stk = []byte("sourceaddresstoken")
|
|
tags := cs.getTags()
|
|
Expect(tags[TagSTK]).To(Equal(cs.stk))
|
|
})
|
|
|
|
It("includes the server nonce, if available", func() {
|
|
cs.sno = []byte("foobar")
|
|
tags := cs.getTags()
|
|
Expect(tags[TagSNO]).To(Equal(cs.sno))
|
|
})
|
|
|
|
It("doesn't include optional values, if not available", func() {
|
|
tags := cs.getTags()
|
|
Expect(tags).ToNot(HaveKey(TagSCID))
|
|
Expect(tags).ToNot(HaveKey(TagSNO))
|
|
Expect(tags).ToNot(HaveKey(TagSTK))
|
|
})
|
|
})
|
|
|
|
Context("Diversification Nonces", func() {
|
|
It("sets a diversification nonce", func() {
|
|
nonce := []byte("foobar")
|
|
err := cs.SetDiversificationNonce(nonce)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.diversificationNonce).To(Equal(nonce))
|
|
})
|
|
|
|
It("doesn't do anything when called multiple times with the same nonce", func() {
|
|
nonce := []byte("foobar")
|
|
err := cs.SetDiversificationNonce(nonce)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = cs.SetDiversificationNonce(nonce)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cs.diversificationNonce).To(Equal(nonce))
|
|
})
|
|
|
|
It("rejects a different diversification nonce", func() {
|
|
nonce1 := []byte("foobar")
|
|
nonce2 := []byte("raboof")
|
|
err := cs.SetDiversificationNonce(nonce1)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = cs.SetDiversificationNonce(nonce2)
|
|
Expect(err).To(MatchError(errConflictingDiversificationNonces))
|
|
})
|
|
})
|
|
|
|
Context("Client Nonce generation", func() {
|
|
BeforeEach(func() {
|
|
cs.serverConfig = &serverConfigClient{}
|
|
cs.serverConfig.obit = []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}
|
|
})
|
|
|
|
It("generates a client nonce", func() {
|
|
now := time.Now()
|
|
err := cs.generateClientNonce()
|
|
Expect(cs.nonc).To(HaveLen(32))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(time.Unix(int64(binary.BigEndian.Uint32(cs.nonc[0:4])), 0)).To(BeTemporally("~", now, 1*time.Second))
|
|
Expect(cs.nonc[4:12]).To(Equal(cs.serverConfig.obit))
|
|
})
|
|
|
|
It("uses random values for the last 20 bytes", func() {
|
|
err := cs.generateClientNonce()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
nonce1 := cs.nonc
|
|
cs.nonc = []byte{}
|
|
err = cs.generateClientNonce()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
nonce2 := cs.nonc
|
|
Expect(nonce1[4:12]).To(Equal(nonce2[4:12]))
|
|
Expect(nonce1[12:]).ToNot(Equal(nonce2[12:]))
|
|
})
|
|
|
|
It("errors if a client nonce has already been generated", func() {
|
|
err := cs.generateClientNonce()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = cs.generateClientNonce()
|
|
Expect(err).To(MatchError(errClientNonceAlreadyExists))
|
|
})
|
|
|
|
It("errors if no OBIT value is available", func() {
|
|
cs.serverConfig.obit = []byte{}
|
|
err := cs.generateClientNonce()
|
|
Expect(err).To(MatchError(errNoObitForClientNonce))
|
|
})
|
|
})
|
|
})
|