From a972c7a21e85581e9259061b1fc96d6242d47388 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 24 Feb 2017 13:01:17 +0700 Subject: [PATCH] return the encryption level of a packet when decrypting it --- h2quic/client.go | 7 ++++--- h2quic/client_test.go | 2 +- handshake/crypto_setup_client.go | 19 +++++++++++-------- handshake/crypto_setup_client_test.go | 18 ++++++++++++------ handshake/crypto_setup_interface.go | 2 +- handshake/crypto_setup_server.go | 19 ++++++++++++------- handshake/crypto_setup_server_test.go | 25 ++++++++++++++++--------- packet_packer_test.go | 4 ++-- packet_unpacker.go | 10 +++++++--- packet_unpacker_test.go | 18 ++++++++++++++---- protocol/encryption_level.go | 6 ++++-- session_test.go | 2 +- 12 files changed, 85 insertions(+), 47 deletions(-) diff --git a/h2quic/client.go b/h2quic/client.go index f6174b4b..fd7eb85e 100644 --- a/h2quic/client.go +++ b/h2quic/client.go @@ -45,9 +45,10 @@ var _ h2quicClient = &Client{} // NewClient creates a new client func NewClient(t *QuicRoundTripper, tlsConfig *tls.Config, hostname string) *Client { c := &Client{ - t: t, - hostname: authorityAddr("https", hostname), - responses: make(map[protocol.StreamID]chan *http.Response), + t: t, + hostname: authorityAddr("https", hostname), + responses: make(map[protocol.StreamID]chan *http.Response), + encryptionLevel: protocol.EncryptionUnencrypted, } c.cryptoChangedCond = sync.Cond{L: &c.mutex} c.config = &quic.Config{ diff --git a/h2quic/client_test.go b/h2quic/client_test.go index 0546125b..24b82669 100644 --- a/h2quic/client_test.go +++ b/h2quic/client_test.go @@ -90,7 +90,7 @@ var _ = Describe("Client", func() { }) It("sets the correct crypto level", func() { - Expect(client.encryptionLevel).To(Equal(protocol.Unencrypted)) + Expect(client.encryptionLevel).To(Equal(protocol.EncryptionUnencrypted)) client.config.ConnState(session, quic.ConnStateSecure) Expect(client.encryptionLevel).To(Equal(protocol.EncryptionSecure)) client.config.ConnState(session, quic.ConnStateForwardSecure) diff --git a/handshake/crypto_setup_client.go b/handshake/crypto_setup_client.go index c1fd9951..9bbc4e11 100644 --- a/handshake/crypto_setup_client.go +++ b/handshake/crypto_setup_client.go @@ -51,7 +51,6 @@ type cryptoSetupClient struct { connectionParameters ConnectionParametersManager } -var _ crypto.AEAD = &cryptoSetupClient{} var _ CryptoSetup = &cryptoSetupClient{} var ( @@ -276,27 +275,31 @@ func (h *cryptoSetupClient) validateVersionList(verTags []byte) bool { return true } -func (h *cryptoSetupClient) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { +func (h *cryptoSetupClient) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) { if h.forwardSecureAEAD != nil { data, err := h.forwardSecureAEAD.Open(dst, src, packetNumber, associatedData) if err == nil { - return data, nil + return data, protocol.EncryptionForwardSecure, nil } - return nil, err + return nil, protocol.EncryptionUnspecified, err } if h.secureAEAD != nil { data, err := h.secureAEAD.Open(dst, src, packetNumber, associatedData) if err == nil { h.receivedSecurePacket = true - return data, nil + return data, protocol.EncryptionSecure, nil } if h.receivedSecurePacket { - return nil, err + return nil, protocol.EncryptionUnspecified, err } } - - return (&crypto.NullAEAD{}).Open(dst, src, packetNumber, associatedData) + nullAEAD := &crypto.NullAEAD{} + res, err := nullAEAD.Open(dst, src, packetNumber, associatedData) + if err != nil { + return nil, protocol.EncryptionUnspecified, err + } + return res, protocol.EncryptionUnencrypted, nil } func (h *cryptoSetupClient) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { diff --git a/handshake/crypto_setup_client_test.go b/handshake/crypto_setup_client_test.go index 4544d833..4c19d649 100644 --- a/handshake/crypto_setup_client_test.go +++ b/handshake/crypto_setup_client_test.go @@ -680,25 +680,28 @@ var _ = Describe("Crypto setup", func() { }) It("is accepted initially", func() { - d, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) + d, enc, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal([]byte("foobar"))) + Expect(enc).To(Equal(protocol.EncryptionUnencrypted)) }) It("is accepted before the server sent an encrypted packet", func() { doCompleteREJ() cs.receivedSecurePacket = false Expect(cs.secureAEAD).ToNot(BeNil()) - d, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) + d, enc, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal([]byte("foobar"))) + Expect(enc).To(Equal(protocol.EncryptionUnencrypted)) }) It("is not accepted after the server sent an encrypted packet", func() { doCompleteREJ() cs.receivedSecurePacket = true - _, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) + _, enc, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) Expect(err).To(MatchError("authentication failed")) + Expect(enc).To(Equal(protocol.EncryptionUnspecified)) }) }) @@ -712,24 +715,27 @@ var _ = Describe("Crypto setup", func() { It("is accepted", func() { doCompleteREJ() - d, err := cs.Open(nil, []byte("encrypted"), 0, []byte{}) + d, enc, err := cs.Open(nil, []byte("encrypted"), 0, []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() - _, err := cs.Open(nil, []byte("encrypted"), 0, []byte{}) + _, enc, err := cs.Open(nil, []byte("encrypted"), 0, []byte{}) Expect(err).To(MatchError("authentication failed")) + Expect(enc).To(Equal(protocol.EncryptionUnspecified)) }) }) Context("forward-secure encryption", func() { It("is used after receiving the SHLO", func() { doSHLO() - _, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{}) + _, enc, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{}) Expect(err).ToNot(HaveOccurred()) + Expect(enc).To(Equal(protocol.EncryptionForwardSecure)) d := cs.Seal(nil, []byte("foobar"), 0, []byte{}) Expect(d).To(Equal([]byte("foobar forward sec"))) }) diff --git a/handshake/crypto_setup_interface.go b/handshake/crypto_setup_interface.go index 4822cfb8..91a234d0 100644 --- a/handshake/crypto_setup_interface.go +++ b/handshake/crypto_setup_interface.go @@ -5,7 +5,7 @@ import "github.com/lucas-clemente/quic-go/protocol" // CryptoSetup is a crypto setup type CryptoSetup interface { HandleCryptoStream() error - Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) + Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte LockForSealing() UnlockForSealing() diff --git a/handshake/crypto_setup_server.go b/handshake/crypto_setup_server.go index 0b7024eb..593fe115 100644 --- a/handshake/crypto_setup_server.go +++ b/handshake/crypto_setup_server.go @@ -43,7 +43,7 @@ type cryptoSetupServer struct { mutex sync.RWMutex } -var _ crypto.AEAD = &cryptoSetupServer{} +var _ CryptoSetup = &cryptoSetupServer{} // NewCryptoSetup creates a new CryptoSetup instance for a server func NewCryptoSetup( @@ -152,7 +152,7 @@ func (h *cryptoSetupServer) handleMessage(chloData []byte, cryptoData map[Tag][] } // Open a message -func (h *cryptoSetupServer) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { +func (h *cryptoSetupServer) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) { h.mutex.RLock() defer h.mutex.RUnlock() @@ -160,23 +160,28 @@ func (h *cryptoSetupServer) Open(dst, src []byte, packetNumber protocol.PacketNu res, err := h.forwardSecureAEAD.Open(dst, src, packetNumber, associatedData) if err == nil { h.receivedForwardSecurePacket = true - return res, nil + return res, protocol.EncryptionForwardSecure, nil } if h.receivedForwardSecurePacket { - return nil, err + return nil, protocol.EncryptionUnspecified, err } } if h.secureAEAD != nil { res, err := h.secureAEAD.Open(dst, src, packetNumber, associatedData) if err == nil { h.receivedSecurePacket = true - return res, nil + return res, protocol.EncryptionSecure, nil } if h.receivedSecurePacket { - return nil, err + return nil, protocol.EncryptionUnspecified, err } } - return (&crypto.NullAEAD{}).Open(dst, src, packetNumber, associatedData) + nullAEAD := &crypto.NullAEAD{} + res, err := nullAEAD.Open(dst, src, packetNumber, associatedData) + if err != nil { + return res, protocol.EncryptionUnspecified, err + } + return res, protocol.EncryptionUnencrypted, err } // Seal a message, call LockForSealing() before! diff --git a/handshake/crypto_setup_server_test.go b/handshake/crypto_setup_server_test.go index faf80e24..1902b6aa 100644 --- a/handshake/crypto_setup_server_test.go +++ b/handshake/crypto_setup_server_test.go @@ -577,26 +577,30 @@ var _ = Describe("Crypto setup", func() { }) It("is accepted initially", func() { - d, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) + d, enc, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal([]byte("foobar"))) + Expect(enc).To(Equal(protocol.EncryptionUnencrypted)) }) It("is still accepted after CHLO", func() { doCHLO() Expect(cs.secureAEAD).ToNot(BeNil()) - _, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) + _, enc, err := cs.Open(nil, foobarFNVSigned, 0, []byte{}) Expect(err).ToNot(HaveOccurred()) + Expect(enc).To(Equal(protocol.EncryptionUnencrypted)) }) It("is not accepted after receiving secure packet", func() { doCHLO() Expect(cs.secureAEAD).ToNot(BeNil()) - d, err := cs.Open(nil, []byte("encrypted"), 0, []byte{}) + d, enc, err := cs.Open(nil, []byte("encrypted"), 0, []byte{}) + Expect(enc).To(Equal(protocol.EncryptionSecure)) Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal([]byte("decrypted"))) - _, err = cs.Open(nil, foobarFNVSigned, 0, []byte{}) + _, enc, err = cs.Open(nil, foobarFNVSigned, 0, []byte{}) Expect(err).To(MatchError("authentication failed")) + Expect(enc).To(Equal(protocol.EncryptionUnspecified)) }) It("is not used after CHLO", func() { @@ -615,14 +619,15 @@ var _ = Describe("Crypto setup", func() { It("is accepted after CHLO", func() { doCHLO() - d, err := cs.Open(nil, []byte("encrypted"), 0, []byte{}) + d, enc, err := cs.Open(nil, []byte("encrypted"), 0, []byte{}) + Expect(enc).To(Equal(protocol.EncryptionSecure)) Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal([]byte("decrypted"))) }) It("is not used after receiving forward secure packet", func() { doCHLO() - _, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{}) + _, _, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{}) Expect(err).ToNot(HaveOccurred()) d := cs.Seal(nil, []byte("foobar"), 0, []byte{}) Expect(d).To(Equal([]byte("foobar forward sec"))) @@ -630,17 +635,19 @@ var _ = Describe("Crypto setup", func() { It("is not accepted after receiving forward secure packet", func() { doCHLO() - _, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{}) + _, _, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{}) Expect(err).ToNot(HaveOccurred()) - _, err = cs.Open(nil, []byte("encrypted"), 0, []byte{}) + _, enc, err := cs.Open(nil, []byte("encrypted"), 0, []byte{}) Expect(err).To(MatchError("authentication failed")) + Expect(enc).To(Equal(protocol.EncryptionUnspecified)) }) }) Context("forward secure encryption", func() { It("is used after receiving forward secure packet", func() { doCHLO() - _, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{}) + _, enc, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{}) + Expect(enc).To(Equal(protocol.EncryptionForwardSecure)) Expect(err).ToNot(HaveOccurred()) d := cs.Seal(nil, []byte("foobar"), 0, []byte{}) Expect(d).To(Equal([]byte("foobar forward sec"))) diff --git a/packet_packer_test.go b/packet_packer_test.go index 39bf8708..15b2a078 100644 --- a/packet_packer_test.go +++ b/packet_packer_test.go @@ -16,8 +16,8 @@ type mockCryptoSetup struct { func (m *mockCryptoSetup) HandleCryptoStream() error { return nil } -func (m *mockCryptoSetup) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { - return nil, nil +func (m *mockCryptoSetup) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) { + return nil, protocol.EncryptionUnspecified, nil } func (m *mockCryptoSetup) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { return append(src, bytes.Repeat([]byte{0}, 12)...) diff --git a/packet_unpacker.go b/packet_unpacker.go index f6fcc2cb..44eee48a 100644 --- a/packet_unpacker.go +++ b/packet_unpacker.go @@ -5,21 +5,25 @@ import ( "errors" "fmt" - "github.com/lucas-clemente/quic-go/crypto" "github.com/lucas-clemente/quic-go/frames" "github.com/lucas-clemente/quic-go/protocol" "github.com/lucas-clemente/quic-go/qerr" ) +type quicAEAD interface { + Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) + Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte +} + type packetUnpacker struct { version protocol.VersionNumber - aead crypto.AEAD + aead quicAEAD } func (u *packetUnpacker) Unpack(publicHeaderBinary []byte, hdr *PublicHeader, data []byte) (*unpackedPacket, error) { buf := getPacketBuffer() defer putPacketBuffer(buf) - decrypted, err := u.aead.Open(buf, data, hdr.PacketNumber, publicHeaderBinary) + decrypted, _, err := u.aead.Open(buf, data, hdr.PacketNumber, publicHeaderBinary) if err != nil { // Wrap err in quicError so that public reset is sent by session return nil, qerr.Error(qerr.DecryptionFailure, err.Error()) diff --git a/packet_unpacker_test.go b/packet_unpacker_test.go index c0735bde..2c7609fa 100644 --- a/packet_unpacker_test.go +++ b/packet_unpacker_test.go @@ -12,30 +12,40 @@ import ( . "github.com/onsi/gomega" ) +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) + return res, protocol.EncryptionUnspecified, err +} +func (m *mockAEAD) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { + return (&crypto.NullAEAD{}).Seal(dst, src, packetNumber, associatedData) +} + +var _ quicAEAD = &mockAEAD{} + var _ = Describe("Packet unpacker", func() { var ( unpacker *packetUnpacker hdr *PublicHeader hdrBin []byte - aead crypto.AEAD data []byte buf *bytes.Buffer ) BeforeEach(func() { - aead = &crypto.NullAEAD{} hdr = &PublicHeader{ PacketNumber: 10, PacketNumberLen: 1, } hdrBin = []byte{0x04, 0x4c, 0x01} - unpacker = &packetUnpacker{aead: aead} + unpacker = &packetUnpacker{aead: &mockAEAD{}} data = nil buf = &bytes.Buffer{} }) setData := func(p []byte) { - data = aead.Seal(nil, p, 0, hdrBin) + data = unpacker.aead.Seal(nil, p, 0, hdrBin) } It("does not read read a private flag for QUIC Version >= 34", func() { diff --git a/protocol/encryption_level.go b/protocol/encryption_level.go index 3622c9fb..088a8d24 100644 --- a/protocol/encryption_level.go +++ b/protocol/encryption_level.go @@ -5,8 +5,10 @@ package protocol type EncryptionLevel int const ( - // Unencrypted is not encrypted - Unencrypted EncryptionLevel = iota + // EncryptionUnspecified is a not specified encryption level + EncryptionUnspecified EncryptionLevel = iota + // EncryptionUnencrypted is not encrypted + EncryptionUnencrypted // EncryptionSecure is encrypted, but not forward secure EncryptionSecure // EncryptionForwardSecure is forward secure diff --git a/session_test.go b/session_test.go index 0fc82349..96f6c195 100644 --- a/session_test.go +++ b/session_test.go @@ -742,7 +742,7 @@ var _ = Describe("Session", func() { sess.conn.(*mockConnection).remoteAddr = remoteIP // use the real packetUnpacker here, to make sure this test fails if the error code for failed decryption changes sess.unpacker = &packetUnpacker{} - sess.unpacker.(*packetUnpacker).aead = &crypto.NullAEAD{} + sess.unpacker.(*packetUnpacker).aead = &mockAEAD{} p := receivedPacket{ remoteAddr: attackerIP, publicHeader: &PublicHeader{PacketNumber: 1337},