From d32a1b8a2b7dfd3eaa33a09a7592988755025a8f Mon Sep 17 00:00:00 2001 From: Lucas Clemente Date: Sun, 17 Apr 2016 17:11:54 +0200 Subject: [PATCH] add tests for crypto escalation --- handshake/crypto_setup.go | 19 +++++--- handshake/crypto_setup_test.go | 86 ++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/handshake/crypto_setup.go b/handshake/crypto_setup.go index da3dd10ef..7a90e61ac 100644 --- a/handshake/crypto_setup.go +++ b/handshake/crypto_setup.go @@ -11,6 +11,9 @@ import ( "github.com/lucas-clemente/quic-go/protocol" ) +// KeyDerivationFunction is used for key derivation +type KeyDerivationFunction func(forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo []byte, scfg []byte, cert []byte) (crypto.AEAD, error) + // The CryptoSetup handles all things crypto for the Session type CryptoSetup struct { connID protocol.ConnectionID @@ -20,8 +23,9 @@ type CryptoSetup struct { secureAEAD crypto.AEAD forwardSecureAEAD crypto.AEAD - receivedSecurePacket bool receivedForwardSecurePacket bool + + keyDerivation KeyDerivationFunction } // NewCryptoSetup creates a new CryptoSetup instance @@ -31,10 +35,11 @@ func NewCryptoSetup(connID protocol.ConnectionID, version protocol.VersionNumber panic(err) } return &CryptoSetup{ - connID: connID, - version: version, - scfg: scfg, - nonce: nonce, + connID: connID, + version: version, + scfg: scfg, + nonce: nonce, + keyDerivation: crypto.DeriveKeysChacha20, } } @@ -122,12 +127,12 @@ func (h *CryptoSetup) handleCHLO(data []byte, cryptoData map[Tag][]byte) ([]byte nonce.Write(cryptoData[TagNONC]) nonce.Write(h.nonce) - h.secureAEAD, err = crypto.DeriveKeysChacha20(false, sharedSecret, nonce.Bytes(), h.connID, data, h.scfg.Get(), h.scfg.signer.GetCertUncompressed()) + h.secureAEAD, err = h.keyDerivation(false, sharedSecret, nonce.Bytes(), h.connID, data, h.scfg.Get(), h.scfg.signer.GetCertUncompressed()) if err != nil { return nil, err } // TODO: Use new curve - h.forwardSecureAEAD, err = crypto.DeriveKeysChacha20(true, sharedSecret, nonce.Bytes(), h.connID, data, h.scfg.Get(), h.scfg.signer.GetCertUncompressed()) + h.forwardSecureAEAD, err = h.keyDerivation(true, sharedSecret, nonce.Bytes(), h.connID, data, h.scfg.Get(), h.scfg.signer.GetCertUncompressed()) if err != nil { return nil, err } diff --git a/handshake/crypto_setup_test.go b/handshake/crypto_setup_test.go index f656b493d..52c31865b 100644 --- a/handshake/crypto_setup_test.go +++ b/handshake/crypto_setup_test.go @@ -2,8 +2,11 @@ package handshake import ( "bytes" + "errors" + "io" "io/ioutil" + "github.com/lucas-clemente/quic-go/crypto" "github.com/lucas-clemente/quic-go/protocol" . "github.com/onsi/ginkgo" @@ -36,6 +39,32 @@ func (*mockSigner) GetCertUncompressed() []byte { return []byte("certuncompressed") } +type mockAEAD struct { + forwardSecure bool +} + +func (m *mockAEAD) Seal(packetNumber protocol.PacketNumber, b *bytes.Buffer, associatedData []byte, plaintext []byte) { + if m.forwardSecure { + b.Write([]byte("forward secure encrypted")) + } else { + b.Write([]byte("encrypted")) + } +} + +func (m *mockAEAD) Open(packetNumber protocol.PacketNumber, associatedData []byte, ciphertext io.Reader) (*bytes.Reader, error) { + data, _ := ioutil.ReadAll(ciphertext) + if m.forwardSecure && string(data) == "forward secure encrypted" { + return bytes.NewReader([]byte("decrypted")), nil + } else if !m.forwardSecure && string(data) == "encrypted" { + return bytes.NewReader([]byte("decrypted")), nil + } + return nil, errors.New("authentication failed") +} + +func mockKeyDerivation(forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo []byte, scfg []byte, cert []byte) (crypto.AEAD, error) { + return &mockAEAD{forwardSecure: forwardSecure}, nil +} + var _ = Describe("Crypto setup", func() { var ( kex *mockKEX @@ -52,6 +81,7 @@ var _ = Describe("Crypto setup", func() { scfg = NewServerConfig(kex, signer) v := protocol.SupportedVersions[len(protocol.SupportedVersions)-1] cs = NewCryptoSetup(protocol.ConnectionID(42), v, scfg) + cs.keyDerivation = mockKeyDerivation }) It("has a nonce", func() { @@ -111,6 +141,11 @@ var _ = Describe("Crypto setup", func() { Context("escalating crypto", func() { foobarFNVSigned := []byte{0x18, 0x6f, 0x44, 0xba, 0x97, 0x35, 0xd, 0x6f, 0xbf, 0x64, 0x3c, 0x79, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72} + doCHLO := func() { + _, err := cs.handleCHLO([]byte("chlo-data"), map[Tag][]byte{TagPUBS: []byte("pubs-c")}) + Expect(err).ToNot(HaveOccurred()) + } + Context("null encryption", func() { It("is used initially", func() { cs.Seal(0, buf, []byte{}, []byte("foobar")) @@ -126,19 +161,60 @@ var _ = Describe("Crypto setup", func() { }) It("is not accepted after CHLO", func() { - _, err := cs.handleCHLO([]byte("chlo-data"), map[Tag][]byte{TagPUBS: []byte("pubs-c")}) - Expect(err).ToNot(HaveOccurred()) + doCHLO() Expect(cs.secureAEAD).ToNot(BeNil()) - _, err = cs.Open(0, []byte{}, bytes.NewReader(foobarFNVSigned)) + _, err := cs.Open(0, []byte{}, bytes.NewReader(foobarFNVSigned)) Expect(err).To(MatchError("authentication failed")) }) It("is not used after CHLO", func() { - _, err := cs.handleCHLO([]byte("chlo-data"), map[Tag][]byte{TagPUBS: []byte("pubs-c")}) - Expect(err).ToNot(HaveOccurred()) + doCHLO() cs.Seal(0, buf, []byte{}, []byte("foobar")) Expect(buf.Bytes()).ToNot(Equal(foobarFNVSigned)) }) }) + + Context("initial encryption", func() { + It("is used after CHLO", func() { + doCHLO() + cs.Seal(0, buf, []byte{}, []byte("foobar")) + Expect(buf.Bytes()).To(Equal([]byte("encrypted"))) + }) + + It("is accepted after CHLO", func() { + doCHLO() + r, err := cs.Open(0, []byte{}, bytes.NewReader([]byte("encrypted"))) + Expect(err).ToNot(HaveOccurred()) + d, err := ioutil.ReadAll(r) + Expect(err).ToNot(HaveOccurred()) + Expect(d).To(Equal([]byte("decrypted"))) + }) + + It("is not used after receiving forward secure packet", func() { + doCHLO() + _, err := cs.Open(0, []byte{}, bytes.NewReader([]byte("forward secure encrypted"))) + Expect(err).ToNot(HaveOccurred()) + cs.Seal(0, buf, []byte{}, []byte("foobar")) + Expect(buf.Bytes()).To(Equal([]byte("forward secure encrypted"))) + }) + + It("is not accepted after receiving forward secure packet", func() { + doCHLO() + _, err := cs.Open(0, []byte{}, bytes.NewReader([]byte("forward secure encrypted"))) + Expect(err).ToNot(HaveOccurred()) + _, err = cs.Open(0, []byte{}, bytes.NewReader([]byte("encrypted"))) + Expect(err).To(MatchError("authentication failed")) + }) + }) + + Context("forward secure encryption", func() { + It("is used after receiving forward secure packet", func() { + doCHLO() + _, err := cs.Open(0, []byte{}, bytes.NewReader([]byte("forward secure encrypted"))) + Expect(err).ToNot(HaveOccurred()) + cs.Seal(0, buf, []byte{}, []byte("foobar")) + Expect(buf.Bytes()).To(Equal([]byte("forward secure encrypted"))) + }) + }) }) })