diff --git a/crypto/null_aead.go b/crypto/null_aead.go index 5aa198ce..ed856633 100644 --- a/crypto/null_aead.go +++ b/crypto/null_aead.go @@ -8,13 +8,24 @@ import ( "github.com/lucas-clemente/quic-go/protocol" ) -// NullAEAD handles not-yet encrypted packets -type NullAEAD struct{} +// nullAEAD handles not-yet encrypted packets +type nullAEAD struct { + perspective protocol.Perspective + version protocol.VersionNumber +} -var _ AEAD = &NullAEAD{} +var _ AEAD = &nullAEAD{} + +// NewNullAEAD creates a NullAEAD +func NewNullAEAD(p protocol.Perspective, v protocol.VersionNumber) AEAD { + return &nullAEAD{ + perspective: p, + version: v, + } +} // Open and verify the ciphertext -func (NullAEAD) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { +func (n *nullAEAD) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { if len(src) < 12 { return nil, errors.New("NullAEAD: ciphertext cannot be less than 12 bytes long") } @@ -22,6 +33,13 @@ func (NullAEAD) Open(dst, src []byte, packetNumber protocol.PacketNumber, associ hash := fnv128a.New() hash.Write(associatedData) hash.Write(src[12:]) + if n.version >= protocol.Version37 { + if n.perspective == protocol.PerspectiveServer { + hash.Write([]byte("Client")) + } else { + hash.Write([]byte("Server")) + } + } testHigh, testLow := hash.Sum128() low := binary.LittleEndian.Uint64(src) @@ -34,7 +52,7 @@ func (NullAEAD) Open(dst, src []byte, packetNumber protocol.PacketNumber, associ } // Seal writes hash and ciphertext to the buffer -func (NullAEAD) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { +func (n *nullAEAD) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { if cap(dst) < 12+len(src) { dst = make([]byte, 12+len(src)) } else { @@ -44,6 +62,15 @@ func (NullAEAD) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associ hash := fnv128a.New() hash.Write(associatedData) hash.Write(src) + + if n.version >= protocol.Version37 { + if n.perspective == protocol.PerspectiveServer { + hash.Write([]byte("Server")) + } else { + hash.Write([]byte("Client")) + } + } + high, low := hash.Sum128() copy(dst[12:], src) diff --git a/crypto/null_aead_test.go b/crypto/null_aead_test.go index 14be9f4f..144715a6 100644 --- a/crypto/null_aead_test.go +++ b/crypto/null_aead_test.go @@ -1,46 +1,37 @@ package crypto import ( + "github.com/lucas-clemente/quic-go/protocol" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Crypto/NullAEAD", func() { + aad := []byte("All human beings are born free and equal in dignity and rights.") + plainText := []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.") + hash36 := []byte{0x98, 0x9b, 0x33, 0x3f, 0xe8, 0xde, 0x32, 0x5c, 0xa6, 0x7f, 0x9c, 0xf7} + It("opens", func() { - aad := []byte("All human beings are born free and equal in dignity and rights.") - plainText := []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.") - hash := []byte{0x98, 0x9b, 0x33, 0x3f, 0xe8, 0xde, 0x32, 0x5c, 0xa6, 0x7f, 0x9c, 0xf7} - cipherText := append(hash, plainText...) - aead := &NullAEAD{} + cipherText := append(hash36, plainText...) + aead := NewNullAEAD(protocol.PerspectiveServer, protocol.Version36) res, err := aead.Open(nil, cipherText, 0, aad) Expect(err).ToNot(HaveOccurred()) Expect(res).To(Equal([]byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."))) }) - It("fails", func() { - aad := []byte("All human beings are born free and equal in dignity and rights..") - plainText := []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.") - hash := []byte{0x98, 0x9b, 0x33, 0x3f, 0xe8, 0xde, 0x32, 0x5c, 0xa6, 0x7f, 0x9c, 0xf7} - cipherText := append(hash, plainText...) - aead := &NullAEAD{} - _, err := aead.Open(nil, cipherText, 0, aad) - Expect(err).To(HaveOccurred()) - }) - It("seals", func() { - aad := []byte("All human beings are born free and equal in dignity and rights.") - plainText := []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.") - aead := &NullAEAD{} - Expect(aead.Seal(nil, plainText, 0, aad)).To(Equal(append([]byte{0x98, 0x9b, 0x33, 0x3f, 0xe8, 0xde, 0x32, 0x5c, 0xa6, 0x7f, 0x9c, 0xf7}, []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.")...))) + aead := NewNullAEAD(protocol.PerspectiveServer, protocol.Version36) + Expect(aead.Seal(nil, plainText, 0, aad)).To(Equal(append(hash36, []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.")...))) }) It("rejects short ciphertexts", func() { - _, err := NullAEAD{}.Open(nil, nil, 0, nil) + aead := NewNullAEAD(protocol.PerspectiveServer, protocol.Version36) + _, err := aead.Open(nil, nil, 0, nil) Expect(err).To(MatchError("NullAEAD: ciphertext cannot be less than 12 bytes long")) }) It("seals in-place", func() { - aead := &NullAEAD{} + aead := NewNullAEAD(protocol.PerspectiveServer, protocol.Version36) buf := make([]byte, 6, 12+6) copy(buf, []byte("foobar")) res := aead.Seal(buf[0:0], buf, 0, nil) @@ -48,4 +39,35 @@ var _ = Describe("Crypto/NullAEAD", func() { Expect(buf[12:]).To(Equal([]byte("foobar"))) Expect(res[12:]).To(Equal([]byte("foobar"))) }) + + It("fails", func() { + cipherText := append(append(hash36, plainText...), byte(0x42)) + aead := NewNullAEAD(protocol.PerspectiveServer, protocol.Version36) + _, err := aead.Open(nil, cipherText, 0, aad) + Expect(err).To(HaveOccurred()) + }) + + Context("including the perspective, for QUIC >= 37", func() { + var aeadServer AEAD + var aeadClient AEAD + + BeforeEach(func() { + aeadServer = NewNullAEAD(protocol.PerspectiveServer, protocol.Version37) + aeadClient = NewNullAEAD(protocol.PerspectiveClient, protocol.Version37) + }) + + It("opens, for QUIC version >= 37, as a server", func() { + cipherText := aeadClient.Seal(nil, plainText, 0, aad) + res, err := aeadServer.Open(nil, cipherText, 0, aad) + Expect(err).ToNot(HaveOccurred()) + Expect(res).To(Equal([]byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."))) + }) + + It("opens, for QUIC version >= 37, as a client", func() { + cipherText := aeadServer.Seal(nil, plainText, 0, aad) + res, err := aeadClient.Open(nil, cipherText, 0, aad) + Expect(err).ToNot(HaveOccurred()) + Expect(res).To(Equal([]byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."))) + }) + }) }) diff --git a/handshake/crypto_setup_client.go b/handshake/crypto_setup_client.go index 6aaeb5f8..c81fe997 100644 --- a/handshake/crypto_setup_client.go +++ b/handshake/crypto_setup_client.go @@ -44,6 +44,7 @@ type cryptoSetupClient struct { keyExchange KeyExchangeFunction receivedSecurePacket bool + nullAEAD crypto.AEAD secureAEAD crypto.AEAD forwardSecureAEAD crypto.AEAD aeadChanged chan protocol.EncryptionLevel @@ -79,6 +80,7 @@ func NewCryptoSetupClient( connectionParameters: connectionParameters, keyDerivation: crypto.DeriveKeysAESGCM, keyExchange: getEphermalKEX, + nullAEAD: crypto.NewNullAEAD(protocol.PerspectiveClient, version), aeadChanged: aeadChanged, negotiatedVersions: negotiatedVersions, }, nil @@ -300,8 +302,7 @@ func (h *cryptoSetupClient) Open(dst, src []byte, packetNumber protocol.PacketNu return nil, protocol.EncryptionUnspecified, err } } - nullAEAD := &crypto.NullAEAD{} - res, err := nullAEAD.Open(dst, src, packetNumber, associatedData) + res, err := h.nullAEAD.Open(dst, src, packetNumber, associatedData) if err != nil { return nil, protocol.EncryptionUnspecified, err } @@ -340,7 +341,7 @@ func (h *cryptoSetupClient) GetSealerWithEncryptionLevel(encLevel protocol.Encry } func (h *cryptoSetupClient) sealUnencrypted(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return (&crypto.NullAEAD{}).Seal(dst, src, packetNumber, associatedData) + return h.nullAEAD.Seal(dst, src, packetNumber, associatedData) } func (h *cryptoSetupClient) sealSecure(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { diff --git a/handshake/crypto_setup_server.go b/handshake/crypto_setup_server.go index 4740b09c..ada88053 100644 --- a/handshake/crypto_setup_server.go +++ b/handshake/crypto_setup_server.go @@ -28,6 +28,7 @@ type cryptoSetupServer struct { scfg *ServerConfig diversificationNonce []byte + nullAEAD crypto.AEAD secureAEAD crypto.AEAD forwardSecureAEAD crypto.AEAD receivedForwardSecurePacket bool @@ -69,6 +70,7 @@ func NewCryptoSetup( scfg: scfg, keyDerivation: crypto.DeriveKeysAESGCM, keyExchange: getEphermalKEX, + nullAEAD: crypto.NewNullAEAD(protocol.PerspectiveServer, version), cryptoStream: cryptoStream, connectionParameters: connectionParametersManager, aeadChanged: aeadChanged, @@ -184,8 +186,7 @@ func (h *cryptoSetupServer) Open(dst, src []byte, packetNumber protocol.PacketNu return nil, protocol.EncryptionUnspecified, err } } - nullAEAD := &crypto.NullAEAD{} - res, err := nullAEAD.Open(dst, src, packetNumber, associatedData) + res, err := h.nullAEAD.Open(dst, src, packetNumber, associatedData) if err != nil { return res, protocol.EncryptionUnspecified, err } @@ -225,7 +226,7 @@ func (h *cryptoSetupServer) GetSealerWithEncryptionLevel(encLevel protocol.Encry } func (h *cryptoSetupServer) sealUnencrypted(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return (&crypto.NullAEAD{}).Seal(dst, src, packetNumber, associatedData) + return h.nullAEAD.Seal(dst, src, packetNumber, associatedData) } func (h *cryptoSetupServer) sealSecure(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { diff --git a/packet_unpacker_test.go b/packet_unpacker_test.go index 90d4d083..b56ba2f4 100644 --- a/packet_unpacker_test.go +++ b/packet_unpacker_test.go @@ -17,11 +17,13 @@ type mockAEAD struct { } func (m *mockAEAD) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) { - res, err := (&crypto.NullAEAD{}).Open(dst, src, packetNumber, associatedData) + nullAEAD := crypto.NewNullAEAD(protocol.PerspectiveServer, protocol.VersionWhatever) + res, err := nullAEAD.Open(dst, src, packetNumber, associatedData) return res, m.encLevelOpen, err } func (m *mockAEAD) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel) { - return (&crypto.NullAEAD{}).Seal(dst, src, packetNumber, associatedData), protocol.EncryptionUnspecified + nullAEAD := crypto.NewNullAEAD(protocol.PerspectiveServer, protocol.VersionWhatever) + return nullAEAD.Seal(dst, src, packetNumber, associatedData), protocol.EncryptionUnspecified } var _ quicAEAD = &mockAEAD{} diff --git a/protocol/version.go b/protocol/version.go index d5c0dacf..424b5dd3 100644 --- a/protocol/version.go +++ b/protocol/version.go @@ -13,6 +13,7 @@ type VersionNumber int const ( Version35 VersionNumber = 35 + iota Version36 + Version37 VersionWhatever = 0 // for when the version doesn't matter VersionUnsupported = -1 ) diff --git a/server_test.go b/server_test.go index 5a575edd..06512d8d 100644 --- a/server_test.go +++ b/server_test.go @@ -169,7 +169,8 @@ var _ = Describe("Server", func() { It("closes and deletes sessions", func() { serv.deleteClosedSessionsAfter = time.Second // make sure that the nil value for the closed session doesn't get deleted in this test - err := serv.handlePacket(nil, nil, append(firstPacket, (&crypto.NullAEAD{}).Seal(nil, nil, 0, firstPacket)...)) + nullAEAD := crypto.NewNullAEAD(protocol.PerspectiveServer, protocol.VersionWhatever) + err := serv.handlePacket(nil, nil, append(firstPacket, nullAEAD.Seal(nil, nil, 0, firstPacket)...)) Expect(err).ToNot(HaveOccurred()) Expect(serv.sessions).To(HaveLen(1)) Expect(serv.sessions[connID]).ToNot(BeNil()) @@ -181,7 +182,8 @@ var _ = Describe("Server", func() { It("deletes nil session entries after a wait time", func() { serv.deleteClosedSessionsAfter = 25 * time.Millisecond - err := serv.handlePacket(nil, nil, append(firstPacket, (&crypto.NullAEAD{}).Seal(nil, nil, 0, firstPacket)...)) + nullAEAD := crypto.NewNullAEAD(protocol.PerspectiveServer, protocol.VersionWhatever) + err := serv.handlePacket(nil, nil, append(firstPacket, nullAEAD.Seal(nil, nil, 0, firstPacket)...)) Expect(err).ToNot(HaveOccurred()) Expect(serv.sessions).To(HaveLen(1)) serv.closeCallback(connID) diff --git a/session_test.go b/session_test.go index 8aff5cd5..419d825b 100644 --- a/session_test.go +++ b/session_test.go @@ -1343,7 +1343,7 @@ var _ = Describe("Session", func() { It("uses ICSL after handshake", func(done Done) { // sess.lastNetworkActivityTime = time.Now().Add(-time.Minute) *(*bool)(unsafe.Pointer(reflect.ValueOf(sess.cryptoSetup).Elem().FieldByName("receivedForwardSecurePacket").UnsafeAddr())) = true - *(*crypto.AEAD)(unsafe.Pointer(reflect.ValueOf(sess.cryptoSetup).Elem().FieldByName("forwardSecureAEAD").UnsafeAddr())) = &crypto.NullAEAD{} + *(*crypto.AEAD)(unsafe.Pointer(reflect.ValueOf(sess.cryptoSetup).Elem().FieldByName("forwardSecureAEAD").UnsafeAddr())) = crypto.NewNullAEAD(protocol.PerspectiveServer, protocol.VersionWhatever) cpm.idleTime = 0 * time.Millisecond sess.packer.connectionParameters = sess.connectionParameters sess.run() // Would normally not return