route/vendor/github.com/lucas-clemente/quic-go/internal/handshake/crypto_setup_client_test.go

1008 lines
36 KiB
Go

package handshake
import (
"bytes"
"crypto/x509"
"encoding/binary"
"errors"
"fmt"
"time"
"github.com/lucas-clemente/quic-go/internal/crypto"
"github.com/lucas-clemente/quic-go/internal/mocks/crypto"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/testdata"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/qerr"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
type keyDerivationValues struct {
forwardSecure bool
sharedSecret []byte
nonces []byte
connID protocol.ConnectionID
chlo []byte
scfg []byte
cert []byte
divNonce []byte
pers protocol.Perspective
}
type mockCertManager struct {
setDataCalledWith []byte
setDataError error
commonCertificateHashes []byte
chain []*x509.Certificate
leafCert []byte
leafCertHash uint64
leafCertHashError error
verifyServerProofResult bool
verifyServerProofCalled bool
verifyError error
verifyCalled bool
}
var _ crypto.CertManager = &mockCertManager{}
func (m *mockCertManager) SetData(data []byte) error {
m.setDataCalledWith = data
return m.setDataError
}
func (m *mockCertManager) GetCommonCertificateHashes() []byte {
return m.commonCertificateHashes
}
func (m *mockCertManager) GetLeafCert() []byte {
return m.leafCert
}
func (m *mockCertManager) GetLeafCertHash() (uint64, error) {
return m.leafCertHash, m.leafCertHashError
}
func (m *mockCertManager) VerifyServerProof(proof, chlo, serverConfigData []byte) bool {
m.verifyServerProofCalled = true
return m.verifyServerProofResult
}
func (m *mockCertManager) Verify(hostname string) error {
m.verifyCalled = true
return m.verifyError
}
func (m *mockCertManager) GetChain() []*x509.Certificate {
return m.chain
}
var _ = Describe("Client Crypto Setup", func() {
var (
cs *cryptoSetupClient
certManager *mockCertManager
stream *mockStream
keyDerivationCalledWith *keyDerivationValues
shloMap map[Tag][]byte
handshakeEvent chan struct{}
paramsChan chan TransportParameters
)
BeforeEach(func() {
shloMap = map[Tag][]byte{
TagPUBS: []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f},
TagVER: []byte{},
}
keyDerivation := func(forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo []byte, scfg []byte, cert []byte, divNonce []byte, pers protocol.Perspective) (crypto.AEAD, error) {
keyDerivationCalledWith = &keyDerivationValues{
forwardSecure: forwardSecure,
sharedSecret: sharedSecret,
nonces: nonces,
connID: connID,
chlo: chlo,
scfg: scfg,
cert: cert,
divNonce: divNonce,
pers: pers,
}
return mockcrypto.NewMockAEAD(mockCtrl), nil
}
stream = newMockStream()
certManager = &mockCertManager{}
version := protocol.Version39
// use a buffered channel here, so that we can parse a SHLO without having to receive the TransportParameters to avoid blocking
paramsChan = make(chan TransportParameters, 1)
handshakeEvent = make(chan struct{}, 2)
csInt, err := NewCryptoSetupClient(
stream,
"hostname",
0,
version,
nil,
&TransportParameters{IdleTimeout: protocol.DefaultIdleTimeout},
paramsChan,
handshakeEvent,
protocol.Version39,
nil,
)
Expect(err).ToNot(HaveOccurred())
cs = csInt.(*cryptoSetupClient)
cs.certManager = certManager
cs.keyDerivation = keyDerivation
cs.keyExchange = func() crypto.KeyExchange { return &mockKEX{ephermal: true} }
cs.nullAEAD = mockcrypto.NewMockAEAD(mockCtrl)
cs.cryptoStream = stream
})
Context("Reading REJ", func() {
var tagMap map[Tag][]byte
BeforeEach(func() {
tagMap = make(map[Tag][]byte)
})
It("rejects handshake messages with the wrong message tag", func() {
HandshakeMessage{Tag: TagCHLO, Data: tagMap}.Write(&stream.dataToRead)
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.InvalidCryptoMessageType))
})
It("errors on invalid handshake messages", func() {
stream.dataToRead.Write([]byte("invalid message"))
err := cs.HandleCryptoStream()
Expect(err).To(HaveOccurred())
Expect(err.(*qerr.QuicError).ErrorCode).To(Equal(qerr.HandshakeFailed))
})
It("passes the message on for parsing, and reads the source address token", func() {
stk := []byte("foobar")
tagMap[TagSTK] = stk
HandshakeMessage{Tag: TagREJ, Data: tagMap}.Write(&stream.dataToRead)
done := make(chan struct{})
go func() {
defer GinkgoRecover()
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.HandshakeFailed, errMockStreamClosing.Error())))
close(done)
}()
Eventually(func() []byte { return cs.stk }).Should(Equal(stk))
// make the go routine return
stream.close()
Eventually(done).Should(BeClosed())
})
It("saves the proof", func() {
proof := []byte("signature for the server config")
tagMap[TagPROF] = proof
err := cs.handleREJMessage(tagMap)
Expect(err).ToNot(HaveOccurred())
Expect(cs.proof).To(Equal(proof))
})
It("saves the last sent CHLO for signature validation, when receiving the proof", func() {
chlo := []byte("last sent CHLO")
cs.lastSentCHLO = chlo
err := cs.handleREJMessage(tagMap)
Expect(err).ToNot(HaveOccurred())
Expect(cs.chloForSignature).To(BeEmpty())
tagMap[TagPROF] = []byte("signature")
err = cs.handleREJMessage(tagMap)
Expect(err).ToNot(HaveOccurred())
Expect(cs.chloForSignature).To(Equal(chlo))
})
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))
})
Context("validating the Version list", func() {
It("doesn't care about the version list if there was no version negotiation", func() {
Expect(cs.validateVersionList([]byte{0})).To(BeTrue())
})
It("detects a downgrade attack if the number of versions is not equal", func() {
cs.negotiatedVersions = []protocol.VersionNumber{protocol.VersionWhatever}
Expect(cs.validateVersionList(bytes.Repeat([]byte{'f'}, 2*4))).To(BeFalse())
})
It("detects a downgrade attack", func() {
cs.negotiatedVersions = []protocol.VersionNumber{12}
b := &bytes.Buffer{}
utils.BigEndian.WriteUint32(b, 11)
Expect(cs.validateVersionList(b.Bytes())).To(BeFalse())
})
It("errors if the version tags are invalid", func() {
cs.negotiatedVersions = []protocol.VersionNumber{protocol.VersionWhatever}
Expect(cs.validateVersionList([]byte{0, 1, 2})).To(BeFalse()) // 1 byte too short
})
It("returns the right error when detecting a downgrade attack", func() {
cs.negotiatedVersions = []protocol.VersionNumber{protocol.VersionWhatever}
cs.receivedSecurePacket = true
_, err := cs.handleSHLOMessage(map[Tag][]byte{
TagPUBS: []byte{0},
TagVER: []byte{0, 1},
})
Expect(err).To(MatchError(qerr.Error(qerr.VersionNegotiationMismatch, "Downgrade attack detected")))
})
})
Context("Certificates", func() {
BeforeEach(func() {
cs.serverConfig = &serverConfigClient{}
})
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]))
})
It("returns an InvalidCryptoMessageParameter error if it can't parse the cert chain", func() {
tagMap[TagCERT] = []byte("cert")
certManager.setDataError = errors.New("can't parse")
err := cs.handleREJMessage(tagMap)
Expect(err).To(MatchError(qerr.Error(qerr.InvalidCryptoMessageParameter, "Certificate data invalid")))
})
Context("verifying the certificate chain", func() {
It("returns a ProofInvalid error if the certificate chain is not valid", func() {
tagMap[TagCERT] = []byte("cert")
certManager.verifyError = errors.New("invalid")
err := cs.handleREJMessage(tagMap)
Expect(err).To(MatchError(qerr.ProofInvalid))
})
It("verifies the certificate", func() {
certManager.verifyServerProofResult = true
tagMap[TagCERT] = []byte("cert")
err := cs.handleREJMessage(tagMap)
Expect(err).ToNot(HaveOccurred())
Expect(certManager.verifyCalled).To(BeTrue())
})
})
Context("verifying the signature", func() {
BeforeEach(func() {
tagMap[TagCERT] = []byte("cert")
tagMap[TagPROF] = []byte("proof")
certManager.leafCert = []byte("leafcert")
})
It("rejects wrong signature", func() {
certManager.verifyServerProofResult = false
err := cs.handleREJMessage(tagMap)
Expect(err).To(MatchError(qerr.ProofInvalid))
Expect(certManager.verifyServerProofCalled).To(BeTrue())
})
It("accepts correct signatures", func() {
certManager.verifyServerProofResult = true
err := cs.handleREJMessage(tagMap)
Expect(err).ToNot(HaveOccurred())
Expect(certManager.verifyServerProofCalled).To(BeTrue())
})
It("doesn't try to verify the signature if the certificate is missing", func() {
delete(tagMap, TagCERT)
certManager.leafCert = nil
err := cs.handleREJMessage(tagMap)
Expect(err).ToNot(HaveOccurred())
Expect(certManager.verifyServerProofCalled).To(BeFalse())
})
It("doesn't try to verify the signature if the server config is missing", func() {
cs.serverConfig = nil
err := cs.handleREJMessage(tagMap)
Expect(err).ToNot(HaveOccurred())
Expect(certManager.verifyServerProofCalled).To(BeFalse())
})
It("doesn't try to verify the signature if the signature is missing", func() {
delete(tagMap, TagPROF)
err := cs.handleREJMessage(tagMap)
Expect(err).ToNot(HaveOccurred())
Expect(certManager.verifyServerProofCalled).To(BeFalse())
})
})
})
Context("Reading server configs", func() {
It("reads a server config", func() {
b := &bytes.Buffer{}
scfg := getDefaultServerConfigClient()
HandshakeMessage{Tag: TagSCFG, Data: scfg}.Write(b)
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("rejects expired server configs", func() {
b := &bytes.Buffer{}
scfg := getDefaultServerConfigClient()
scfg[TagEXPY] = []byte{0x80, 0x54, 0x72, 0x4F, 0, 0, 0, 0} // 2012-03-28
HandshakeMessage{Tag: TagSCFG, Data: scfg}.Write(b)
tagMap[TagSCFG] = b.Bytes()
// make sure we actually set TagEXPY correct
serverConfig, err := parseServerConfig(b.Bytes())
Expect(err).ToNot(HaveOccurred())
Expect(serverConfig.expiry.Year()).To(Equal(2012))
// now try to read this server config in the crypto setup
err = cs.handleREJMessage(tagMap)
Expect(err).To(MatchError(qerr.CryptoServerConfigExpired))
})
It("generates a client nonce after reading a server config", func() {
b := &bytes.Buffer{}
HandshakeMessage{Tag: TagSCFG, Data: getDefaultServerConfigClient()}.Write(b)
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{}
HandshakeMessage{Tag: TagSCFG, Data: getDefaultServerConfigClient()}.Write(b)
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{}
HandshakeMessage{Tag: TagSHLO, Data: make(map[Tag][]byte)}.Write(b)
tagMap[TagSCFG] = b.Bytes()
_, origErr := parseServerConfig(b.Bytes())
err := cs.handleREJMessage(tagMap)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(origErr))
})
})
})
Context("Reading SHLO", func() {
BeforeEach(func() {
kex, err := crypto.NewCurve25519KEX()
Expect(err).ToNot(HaveOccurred())
serverConfig := &serverConfigClient{
kex: kex,
}
cs.serverConfig = serverConfig
cs.receivedSecurePacket = true
})
It("rejects unencrypted SHLOs", func() {
cs.receivedSecurePacket = false
_, err := cs.handleSHLOMessage(shloMap)
Expect(err).To(MatchError(qerr.Error(qerr.CryptoEncryptionLevelIncorrect, "unencrypted SHLO message")))
Expect(handshakeEvent).ToNot(Receive())
Expect(handshakeEvent).ToNot(BeClosed())
})
It("rejects SHLOs without a PUBS", func() {
delete(shloMap, TagPUBS)
_, err := cs.handleSHLOMessage(shloMap)
Expect(err).To(MatchError(qerr.Error(qerr.CryptoMessageParameterNotFound, "PUBS")))
Expect(handshakeEvent).ToNot(BeClosed())
})
It("rejects SHLOs without a version list", func() {
delete(shloMap, TagVER)
_, err := cs.handleSHLOMessage(shloMap)
Expect(err).To(MatchError(qerr.Error(qerr.InvalidCryptoMessageParameter, "server hello missing version list")))
Expect(handshakeEvent).ToNot(BeClosed())
})
It("accepts a SHLO after a version negotiation", func() {
ver := protocol.SupportedVersions[0]
cs.negotiatedVersions = []protocol.VersionNumber{ver}
cs.receivedSecurePacket = true
b := &bytes.Buffer{}
utils.BigEndian.WriteUint32(b, uint32(ver))
shloMap[TagVER] = b.Bytes()
_, err := cs.handleSHLOMessage(shloMap)
Expect(err).ToNot(HaveOccurred())
})
It("reads the server nonce, if set", func() {
shloMap[TagSNO] = []byte("server nonce")
_, err := cs.handleSHLOMessage(shloMap)
Expect(err).ToNot(HaveOccurred())
Expect(cs.sno).To(Equal(shloMap[TagSNO]))
})
It("creates a forwardSecureAEAD", func() {
shloMap[TagSNO] = []byte("server nonce")
_, err := cs.handleSHLOMessage(shloMap)
Expect(err).ToNot(HaveOccurred())
Expect(cs.forwardSecureAEAD).ToNot(BeNil())
})
It("reads the connection paramaters", func() {
shloMap[TagICSL] = []byte{13, 0, 0, 0} // 13 seconds
params, err := cs.handleSHLOMessage(shloMap)
Expect(err).ToNot(HaveOccurred())
Expect(params.IdleTimeout).To(Equal(13 * time.Second))
})
It("closes the handshakeEvent chan when receiving an SHLO", func() {
HandshakeMessage{Tag: TagSHLO, Data: shloMap}.Write(&stream.dataToRead)
done := make(chan struct{})
go func() {
defer GinkgoRecover()
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.HandshakeFailed, errMockStreamClosing.Error())))
close(done)
}()
Eventually(handshakeEvent).Should(Receive())
Eventually(handshakeEvent).Should(BeClosed())
// make the go routine return
stream.close()
Eventually(done).Should(BeClosed())
})
It("passes the transport parameters on the channel", func() {
shloMap[TagSFCW] = []byte{0x0d, 0x00, 0xdf, 0xba}
HandshakeMessage{Tag: TagSHLO, Data: shloMap}.Write(&stream.dataToRead)
done := make(chan struct{})
go func() {
defer GinkgoRecover()
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.HandshakeFailed, errMockStreamClosing.Error())))
close(done)
}()
var params TransportParameters
Eventually(paramsChan).Should(Receive(&params))
Expect(params.StreamFlowControlWindow).To(Equal(protocol.ByteCount(0xbadf000d)))
// make the go routine return
stream.close()
Eventually(done).Should(BeClosed())
})
It("errors if it can't read a connection parameter", func() {
shloMap[TagICSL] = []byte{3, 0, 0} // 1 byte too short
_, err := cs.handleSHLOMessage(shloMap)
Expect(err).To(MatchError(qerr.InvalidCryptoMessageParameter))
})
})
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.MinClientHelloSize))
})
It("doesn't overflow the packet with padding", func() {
tagMap := make(map[Tag][]byte)
tagMap[TagSCID] = bytes.Repeat([]byte{0}, protocol.MinClientHelloSize*6/10)
cs.addPadding(tagMap)
Expect(len(tagMap[TagPAD])).To(BeNumerically("<", protocol.MinClientHelloSize/2))
})
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() {
cs.version = cs.initialVersion - 1
cs.hostname = "sni-hostname"
certManager.commonCertificateHashes = []byte("common certs")
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
Expect(string(tags[TagSNI])).To(Equal(cs.hostname))
Expect(tags[TagPDMD]).To(Equal([]byte("X509")))
Expect(tags[TagVER]).To(Equal([]byte("Q039")))
Expect(tags[TagCCS]).To(Equal(certManager.commonCertificateHashes))
Expect(tags).ToNot(HaveKey(TagTCID))
})
It("requests to omit the connection ID", func() {
cs.params.OmitConnectionID = true
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
Expect(tags).To(HaveKeyWithValue(TagTCID, []byte{0, 0, 0, 0}))
})
It("adds the tags returned from the connectionParametersManager to the CHLO", func() {
pnTags := cs.params.getHelloMap()
Expect(pnTags).ToNot(BeEmpty())
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
for t := range pnTags {
Expect(tags).To(HaveKey(t))
}
})
It("doesn't send a CCS if there are no common certificate sets available", func() {
certManager.commonCertificateHashes = nil
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
Expect(tags).ToNot(HaveKey(TagCCS))
})
It("includes the server config id, if available", func() {
id := []byte("foobar")
cs.serverConfig = &serverConfigClient{ID: id}
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
Expect(tags[TagSCID]).To(Equal(id))
})
It("includes the source address token, if available", func() {
cs.stk = []byte("sourceaddresstoken")
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
Expect(tags[TagSTK]).To(Equal(cs.stk))
})
It("includes the server nonce, if available", func() {
cs.sno = []byte("foobar")
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
Expect(tags[TagSNO]).To(Equal(cs.sno))
})
It("doesn't include optional values, if not available", func() {
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
Expect(tags).ToNot(HaveKey(TagSCID))
Expect(tags).ToNot(HaveKey(TagSNO))
Expect(tags).ToNot(HaveKey(TagSTK))
})
It("doesn't change any values after reading the certificate, if the server config is missing", func() {
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
certManager.leafCert = []byte("leafcert")
Expect(cs.getTags()).To(Equal(tags))
})
It("sends a the values needed for a full CHLO after reading the certificate and the server config", func() {
certManager.leafCert = []byte("leafcert")
cs.nonc = []byte("client-nonce")
kex, err := crypto.NewCurve25519KEX()
Expect(err).ToNot(HaveOccurred())
cs.serverConfig = &serverConfigClient{kex: kex}
xlct := []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}
certManager.leafCertHash = binary.LittleEndian.Uint64(xlct)
tags, err := cs.getTags()
Expect(err).ToNot(HaveOccurred())
Expect(tags[TagNONC]).To(Equal(cs.nonc))
Expect(tags[TagPUBS]).To(Equal(kex.PublicKey()))
Expect(tags[TagXLCT]).To(Equal(xlct))
Expect(tags[TagKEXS]).To(Equal([]byte("C255")))
Expect(tags[TagAEAD]).To(Equal([]byte("AESG")))
})
It("doesn't send more than MaxClientHellos CHLOs", func() {
Expect(cs.clientHelloCounter).To(BeZero())
for i := 1; i <= protocol.MaxClientHellos; i++ {
err := cs.sendCHLO()
Expect(err).ToNot(HaveOccurred())
Expect(cs.clientHelloCounter).To(Equal(i))
}
err := cs.sendCHLO()
Expect(err).To(MatchError(qerr.Error(qerr.CryptoTooManyRejects, fmt.Sprintf("More than %d rejects", protocol.MaxClientHellos))))
})
})
Context("escalating crypto", func() {
doCompleteREJ := func() {
cs.serverVerified = true
err := cs.maybeUpgradeCrypto()
Expect(err).ToNot(HaveOccurred())
Expect(cs.secureAEAD).ToNot(BeNil())
}
doSHLO := func() {
cs.receivedSecurePacket = true
_, err := cs.handleSHLOMessage(shloMap)
Expect(err).ToNot(HaveOccurred())
}
// sets all values necessary for escalating to secureAEAD
BeforeEach(func() {
kex, err := crypto.NewCurve25519KEX()
Expect(err).ToNot(HaveOccurred())
cs.serverConfig = &serverConfigClient{
kex: kex,
obit: []byte("obit"),
sharedSecret: []byte("sharedSecret"),
raw: []byte("rawserverconfig"),
}
cs.lastSentCHLO = []byte("lastSentCHLO")
cs.nonc = []byte("nonc")
cs.diversificationNonce = []byte("divnonce")
certManager.leafCert = []byte("leafCert")
})
It("creates a secureAEAD once it has all necessary values", func() {
cs.serverVerified = true
err := cs.maybeUpgradeCrypto()
Expect(err).ToNot(HaveOccurred())
Expect(cs.secureAEAD).ToNot(BeNil())
Expect(keyDerivationCalledWith.forwardSecure).To(BeFalse())
Expect(keyDerivationCalledWith.sharedSecret).To(Equal(cs.serverConfig.sharedSecret))
Expect(keyDerivationCalledWith.nonces).To(Equal(cs.nonc))
Expect(keyDerivationCalledWith.connID).To(Equal(cs.connID))
Expect(keyDerivationCalledWith.chlo).To(Equal(cs.lastSentCHLO))
Expect(keyDerivationCalledWith.scfg).To(Equal(cs.serverConfig.Get()))
Expect(keyDerivationCalledWith.cert).To(Equal(certManager.leafCert))
Expect(keyDerivationCalledWith.divNonce).To(Equal(cs.diversificationNonce))
Expect(keyDerivationCalledWith.pers).To(Equal(protocol.PerspectiveClient))
Expect(handshakeEvent).To(Receive())
Expect(handshakeEvent).ToNot(Receive())
Expect(handshakeEvent).ToNot(BeClosed())
})
It("uses the server nonce, if the server sent one", func() {
cs.serverVerified = true
cs.sno = []byte("server nonce")
err := cs.maybeUpgradeCrypto()
Expect(err).ToNot(HaveOccurred())
Expect(cs.secureAEAD).ToNot(BeNil())
Expect(keyDerivationCalledWith.nonces).To(Equal(append(cs.nonc, cs.sno...)))
Expect(handshakeEvent).To(Receive())
Expect(handshakeEvent).ToNot(Receive())
Expect(handshakeEvent).ToNot(BeClosed())
})
It("doesn't create a secureAEAD if the certificate is not yet verified, even if it has all necessary values", func() {
err := cs.maybeUpgradeCrypto()
Expect(err).ToNot(HaveOccurred())
Expect(cs.secureAEAD).To(BeNil())
Expect(handshakeEvent).ToNot(Receive())
cs.serverVerified = true
// make sure we really had all necessary values before, and only serverVerified was missing
err = cs.maybeUpgradeCrypto()
Expect(err).ToNot(HaveOccurred())
Expect(cs.secureAEAD).ToNot(BeNil())
Expect(handshakeEvent).To(Receive())
Expect(handshakeEvent).ToNot(Receive())
Expect(handshakeEvent).ToNot(BeClosed())
})
It("tries to escalate before reading a handshake message", func() {
Expect(cs.secureAEAD).To(BeNil())
cs.serverVerified = true
done := make(chan struct{})
go func() {
defer GinkgoRecover()
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.HandshakeFailed, errMockStreamClosing.Error())))
close(done)
}()
Eventually(handshakeEvent).Should(Receive())
Expect(cs.secureAEAD).ToNot(BeNil())
Expect(handshakeEvent).ToNot(Receive())
Expect(handshakeEvent).ToNot(BeClosed())
// make the go routine return
stream.close()
Eventually(done).Should(BeClosed())
})
It("tries to escalate the crypto after receiving a diversification nonce", func() {
done := make(chan struct{})
go func() {
defer GinkgoRecover()
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.HandshakeFailed, errMockStreamClosing.Error())))
close(done)
}()
cs.diversificationNonce = nil
cs.serverVerified = true
Expect(cs.secureAEAD).To(BeNil())
cs.SetDiversificationNonce([]byte("div"))
Eventually(handshakeEvent).Should(Receive())
Expect(cs.secureAEAD).ToNot(BeNil())
Expect(handshakeEvent).ToNot(Receive())
Expect(handshakeEvent).ToNot(BeClosed())
// make the go routine return
stream.close()
Eventually(done).Should(BeClosed())
})
Context("null encryption", func() {
It("is used initially", func() {
cs.nullAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(10), []byte{}).Return([]byte("foobar unencrypted"))
enc, sealer := cs.GetSealer()
Expect(enc).To(Equal(protocol.EncryptionUnencrypted))
d := sealer.Seal(nil, []byte("foobar"), 10, []byte{})
Expect(d).To(Equal([]byte("foobar unencrypted")))
})
It("is used for the crypto stream", func() {
cs.nullAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(1), []byte{}).Return([]byte("foobar unencrypted"))
enc, sealer := cs.GetSealerForCryptoStream()
Expect(enc).To(Equal(protocol.EncryptionUnencrypted))
d := sealer.Seal(nil, []byte("foobar"), 1, []byte{})
Expect(d).To(Equal([]byte("foobar unencrypted")))
})
It("is accepted initially", func() {
cs.nullAEAD.(*mockcrypto.MockAEAD).EXPECT().Open(nil, []byte("unencrypted"), protocol.PacketNumber(1), []byte{}).Return([]byte("decrypted"), nil)
d, enc, err := cs.Open(nil, []byte("unencrypted"), 1, []byte{})
Expect(err).ToNot(HaveOccurred())
Expect(d).To(Equal([]byte("decrypted")))
Expect(enc).To(Equal(protocol.EncryptionUnencrypted))
})
It("is accepted before the server sent an encrypted packet", func() {
doCompleteREJ()
cs.secureAEAD.(*mockcrypto.MockAEAD).EXPECT().Open(nil, []byte("unencrypted"), protocol.PacketNumber(1), []byte{}).Return(nil, errors.New("authentication failed"))
cs.nullAEAD.(*mockcrypto.MockAEAD).EXPECT().Open(nil, []byte("unencrypted"), protocol.PacketNumber(1), []byte{}).Return([]byte("decrypted"), nil)
cs.receivedSecurePacket = false
Expect(cs.secureAEAD).ToNot(BeNil())
d, enc, err := cs.Open(nil, []byte("unencrypted"), 1, []byte{})
Expect(err).ToNot(HaveOccurred())
Expect(d).To(Equal([]byte("decrypted")))
Expect(enc).To(Equal(protocol.EncryptionUnencrypted))
})
It("is not accepted after the server sent an encrypted packet", func() {
doCompleteREJ()
cs.secureAEAD.(*mockcrypto.MockAEAD).EXPECT().Open(nil, []byte("unencrypted"), protocol.PacketNumber(3), []byte{}).Return(nil, errors.New("authentication failed"))
cs.receivedSecurePacket = true
_, enc, err := cs.Open(nil, []byte("unencrypted"), 3, []byte{})
Expect(err).To(MatchError("authentication failed"))
Expect(enc).To(Equal(protocol.EncryptionUnspecified))
})
It("errors if the has the wrong hash", func() {
cs.nullAEAD.(*mockcrypto.MockAEAD).EXPECT().Open(nil, []byte("not unencrypted"), protocol.PacketNumber(3), []byte{}).Return(nil, errors.New("authentication failed"))
_, enc, err := cs.Open(nil, []byte("not unencrypted"), 3, []byte{})
Expect(err).To(MatchError("authentication failed"))
Expect(enc).To(Equal(protocol.EncryptionUnspecified))
})
})
Context("initial encryption", func() {
It("is used immediately when available", func() {
doCompleteREJ()
cs.secureAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(1), []byte{}).Return([]byte("foobar secure"))
cs.receivedSecurePacket = false
enc, sealer := cs.GetSealer()
Expect(enc).To(Equal(protocol.EncryptionSecure))
d := sealer.Seal(nil, []byte("foobar"), 1, []byte{})
Expect(d).To(Equal([]byte("foobar secure")))
})
It("is accepted", func() {
doCompleteREJ()
cs.secureAEAD.(*mockcrypto.MockAEAD).EXPECT().Open(nil, []byte("encrypted"), protocol.PacketNumber(3), []byte{}).Return([]byte("decrypted"), nil)
d, enc, err := cs.Open(nil, []byte("encrypted"), 3, []byte{})
Expect(err).ToNot(HaveOccurred())
Expect(d).To(Equal([]byte("decrypted")))
Expect(enc).To(Equal(protocol.EncryptionSecure))
Expect(cs.receivedSecurePacket).To(BeTrue())
})
It("is not used after receiving the SHLO", func() {
doSHLO()
cs.forwardSecureAEAD.(*mockcrypto.MockAEAD).EXPECT().Open(nil, []byte("encrypted"), protocol.PacketNumber(30), []byte{}).Return(nil, errors.New("authentication failed"))
_, enc, err := cs.Open(nil, []byte("encrypted"), 30, []byte{})
Expect(err).To(MatchError("authentication failed"))
Expect(enc).To(Equal(protocol.EncryptionUnspecified))
})
It("is not used for the crypto stream", func() {
doCompleteREJ()
cs.nullAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(3), []byte{}).Return([]byte("foobar unencrypted"))
enc, sealer := cs.GetSealerForCryptoStream()
Expect(enc).To(Equal(protocol.EncryptionUnencrypted))
d := sealer.Seal(nil, []byte("foobar"), 3, []byte{})
Expect(d).To(Equal([]byte("foobar unencrypted")))
})
})
Context("forward-secure encryption", func() {
It("is used after receiving the SHLO", func() {
doSHLO()
cs.forwardSecureAEAD.(*mockcrypto.MockAEAD).EXPECT().Open(nil, []byte("shlo"), protocol.PacketNumber(4), []byte{})
cs.forwardSecureAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(10), []byte{}).Return([]byte("foobar forward sec"))
_, enc, err := cs.Open(nil, []byte("shlo"), 4, []byte{})
Expect(err).ToNot(HaveOccurred())
Expect(enc).To(Equal(protocol.EncryptionForwardSecure))
enc, sealer := cs.GetSealer()
Expect(enc).To(Equal(protocol.EncryptionForwardSecure))
d := sealer.Seal(nil, []byte("foobar"), 10, []byte{})
Expect(d).To(Equal([]byte("foobar forward sec")))
})
It("is not used for the crypto stream", func() {
doSHLO()
cs.nullAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(3), []byte{}).Return([]byte("foobar unencrypted"))
enc, sealer := cs.GetSealerForCryptoStream()
Expect(enc).To(Equal(protocol.EncryptionUnencrypted))
d := sealer.Seal(nil, []byte("foobar"), 3, []byte{})
Expect(d).To(Equal([]byte("foobar unencrypted")))
})
})
Context("reporting the connection state", func() {
It("reports the connection state before the handshake completes", func() {
chain := []*x509.Certificate{testdata.GetCertificate().Leaf}
certManager.chain = chain
state := cs.ConnectionState()
Expect(state.HandshakeComplete).To(BeFalse())
Expect(state.PeerCertificates).To(Equal(chain))
})
It("reports the connection state after the handshake completes", func() {
doSHLO()
state := cs.ConnectionState()
Expect(state.HandshakeComplete).To(BeTrue())
})
})
Context("forcing encryption levels", func() {
It("forces null encryption", func() {
cs.nullAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(4), []byte{}).Return([]byte("foobar unencrypted"))
sealer, err := cs.GetSealerWithEncryptionLevel(protocol.EncryptionUnencrypted)
Expect(err).ToNot(HaveOccurred())
d := sealer.Seal(nil, []byte("foobar"), 4, []byte{})
Expect(d).To(Equal([]byte("foobar unencrypted")))
})
It("forces initial encryption", func() {
doCompleteREJ()
cs.secureAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(3), []byte{}).Return([]byte("foobar secure"))
sealer, err := cs.GetSealerWithEncryptionLevel(protocol.EncryptionSecure)
Expect(err).ToNot(HaveOccurred())
d := sealer.Seal(nil, []byte("foobar"), 3, []byte{})
Expect(d).To(Equal([]byte("foobar secure")))
})
It("errors of no AEAD for initial encryption is available", func() {
sealer, err := cs.GetSealerWithEncryptionLevel(protocol.EncryptionSecure)
Expect(err).To(MatchError("CryptoSetupClient: no secureAEAD"))
Expect(sealer).To(BeNil())
})
It("forces forward-secure encryption", func() {
doSHLO()
cs.forwardSecureAEAD.(*mockcrypto.MockAEAD).EXPECT().Seal(nil, []byte("foobar"), protocol.PacketNumber(4), []byte{}).Return([]byte("foobar forward sec"))
sealer, err := cs.GetSealerWithEncryptionLevel(protocol.EncryptionForwardSecure)
Expect(err).ToNot(HaveOccurred())
d := sealer.Seal(nil, []byte("foobar"), 4, []byte{})
Expect(d).To(Equal([]byte("foobar forward sec")))
})
It("errors of no AEAD for forward-secure encryption is available", func() {
sealer, err := cs.GetSealerWithEncryptionLevel(protocol.EncryptionForwardSecure)
Expect(err).To(MatchError("CryptoSetupClient: no forwardSecureAEAD"))
Expect(sealer).To(BeNil())
})
It("errors if no encryption level is specified", func() {
sealer, err := cs.GetSealerWithEncryptionLevel(protocol.EncryptionUnspecified)
Expect(err).To(MatchError("CryptoSetupClient: no encryption level specified"))
Expect(sealer).To(BeNil())
})
})
})
Context("Diversification Nonces", func() {
It("sets a diversification nonce", func() {
done := make(chan struct{})
go func() {
defer GinkgoRecover()
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.HandshakeFailed, errMockStreamClosing.Error())))
close(done)
}()
nonce := []byte("foobar")
cs.SetDiversificationNonce(nonce)
Eventually(func() []byte { return cs.diversificationNonce }).Should(Equal(nonce))
// make the go routine return
stream.close()
Eventually(done).Should(BeClosed())
})
It("doesn't do anything when called multiple times with the same nonce", func() {
done := make(chan struct{})
go func() {
defer GinkgoRecover()
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(qerr.Error(qerr.HandshakeFailed, errMockStreamClosing.Error())))
close(done)
}()
nonce := []byte("foobar")
cs.SetDiversificationNonce(nonce)
cs.SetDiversificationNonce(nonce)
Eventually(func() []byte { return cs.diversificationNonce }).Should(Equal(nonce))
// make the go routine return
stream.close()
Eventually(done).Should(BeClosed())
})
It("rejects a different diversification nonce", func() {
done := make(chan struct{})
go func() {
defer GinkgoRecover()
err := cs.HandleCryptoStream()
Expect(err).To(MatchError(errConflictingDiversificationNonces))
close(done)
}()
nonce1 := []byte("foobar")
nonce2 := []byte("raboof")
cs.SetDiversificationNonce(nonce1)
cs.SetDiversificationNonce(nonce2)
Eventually(done).Should(BeClosed())
})
})
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))
})
})
})