diff --git a/crypto/aesgcm_aead.go b/crypto/aesgcm_aead.go new file mode 100644 index 00000000..40f9a938 --- /dev/null +++ b/crypto/aesgcm_aead.go @@ -0,0 +1,58 @@ +package crypto + +import ( + "crypto/cipher" + "errors" + + "github.com/lucas-clemente/aes12" + + "github.com/lucas-clemente/quic-go/protocol" +) + +type aeadAESGCM struct { + otherIV []byte + myIV []byte + encrypter cipher.AEAD + decrypter cipher.AEAD +} + +// NewAEADAESGCM creates a AEAD using AES-GCM with 12 bytes tag size +// +// AES-GCM support is a bit hacky, since the go stdlib does not support 12 byte +// tag size, and couples the cipher and aes packages closely. +// See https://github.com/lucas-clemente/aes12. +func NewAEADAESGCM(otherKey []byte, myKey []byte, otherIV []byte, myIV []byte) (AEAD, error) { + if len(myKey) != 16 || len(otherKey) != 16 || len(myIV) != 4 || len(otherIV) != 4 { + return nil, errors.New("AES-GCM: expected 16-byte keys and 4-byte IVs") + } + encrypterCipher, err := aes12.NewCipher(myKey) + if err != nil { + return nil, err + } + encrypter, err := aes12.NewGCM(encrypterCipher) + if err != nil { + return nil, err + } + decrypterCipher, err := aes12.NewCipher(otherKey) + if err != nil { + return nil, err + } + decrypter, err := aes12.NewGCM(decrypterCipher) + if err != nil { + return nil, err + } + return &aeadAESGCM{ + otherIV: otherIV, + myIV: myIV, + encrypter: encrypter, + decrypter: decrypter, + }, nil +} + +func (aead *aeadAESGCM) Open(packetNumber protocol.PacketNumber, associatedData []byte, ciphertext []byte) ([]byte, error) { + return aead.decrypter.Open(nil, makeNonce(aead.otherIV, packetNumber), ciphertext, associatedData) +} + +func (aead *aeadAESGCM) Seal(packetNumber protocol.PacketNumber, associatedData []byte, plaintext []byte) []byte { + return aead.encrypter.Seal(nil, makeNonce(aead.myIV, packetNumber), plaintext, associatedData) +} diff --git a/crypto/aesgcm_aead_test.go b/crypto/aesgcm_aead_test.go new file mode 100644 index 00000000..d41235e1 --- /dev/null +++ b/crypto/aesgcm_aead_test.go @@ -0,0 +1,69 @@ +package crypto + +import ( + "crypto/rand" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("AES-GCM", func() { + var ( + alice, bob AEAD + keyAlice, keyBob, ivAlice, ivBob []byte + ) + + BeforeEach(func() { + keyAlice = make([]byte, 16) + keyBob = make([]byte, 16) + ivAlice = make([]byte, 4) + ivBob = make([]byte, 4) + rand.Reader.Read(keyAlice) + rand.Reader.Read(keyBob) + rand.Reader.Read(ivAlice) + rand.Reader.Read(ivBob) + var err error + alice, err = NewAEADAESGCM(keyBob, keyAlice, ivBob, ivAlice) + Expect(err).ToNot(HaveOccurred()) + bob, err = NewAEADAESGCM(keyAlice, keyBob, ivAlice, ivBob) + Expect(err).ToNot(HaveOccurred()) + }) + + It("seals and opens", func() { + b := alice.Seal(42, []byte("aad"), []byte("foobar")) + text, err := bob.Open(42, []byte("aad"), b) + Expect(err).ToNot(HaveOccurred()) + Expect(text).To(Equal([]byte("foobar"))) + }) + + It("seals and opens reverse", func() { + b := bob.Seal(42, []byte("aad"), []byte("foobar")) + text, err := alice.Open(42, []byte("aad"), b) + Expect(err).ToNot(HaveOccurred()) + Expect(text).To(Equal([]byte("foobar"))) + }) + + It("has the proper length", func() { + b := bob.Seal(42, []byte("aad"), []byte("foobar")) + Expect(b).To(HaveLen(6 + 12)) + }) + + It("fails with wrong aad", func() { + b := alice.Seal(42, []byte("aad"), []byte("foobar")) + _, err := bob.Open(42, []byte("aad2"), b) + Expect(err).To(HaveOccurred()) + }) + + It("rejects wrong key and iv sizes", func() { + var err error + e := "AES-GCM: expected 16-byte keys and 4-byte IVs" + _, err = NewAEADAESGCM(keyBob[1:], keyAlice, ivBob, ivAlice) + Expect(err).To(MatchError(e)) + _, err = NewAEADAESGCM(keyBob, keyAlice[1:], ivBob, ivAlice) + Expect(err).To(MatchError(e)) + _, err = NewAEADAESGCM(keyBob, keyAlice, ivBob[1:], ivAlice) + Expect(err).To(MatchError(e)) + _, err = NewAEADAESGCM(keyBob, keyAlice, ivBob, ivAlice[1:]) + Expect(err).To(MatchError(e)) + }) +}) diff --git a/crypto/chacha20poly1305_aead.go b/crypto/chacha20poly1305_aead.go index b27617d1..dd2106e7 100644 --- a/crypto/chacha20poly1305_aead.go +++ b/crypto/chacha20poly1305_aead.go @@ -39,11 +39,7 @@ func NewAEADChacha20Poly1305(otherKey []byte, myKey []byte, otherIV []byte, myIV } func (aead *aeadChacha20Poly1305) Open(packetNumber protocol.PacketNumber, associatedData []byte, ciphertext []byte) ([]byte, error) { - plaintext, err := aead.decrypter.Open(nil, makeNonce(aead.otherIV, packetNumber), ciphertext, associatedData) - if err != nil { - return nil, err - } - return plaintext, nil + return aead.decrypter.Open(nil, makeNonce(aead.otherIV, packetNumber), ciphertext, associatedData) } func (aead *aeadChacha20Poly1305) Seal(packetNumber protocol.PacketNumber, associatedData []byte, plaintext []byte) []byte { diff --git a/crypto/chacha20poly1305_aead_test.go b/crypto/chacha20poly1305_aead_test.go index 709ff1a5..96bb39c9 100644 --- a/crypto/chacha20poly1305_aead_test.go +++ b/crypto/chacha20poly1305_aead_test.go @@ -43,6 +43,11 @@ var _ = Describe("Chacha20poly1305", func() { Expect(text).To(Equal([]byte("foobar"))) }) + It("has the proper length", func() { + b := bob.Seal(42, []byte("aad"), []byte("foobar")) + Expect(b).To(HaveLen(6 + 12)) + }) + It("fails with wrong aad", func() { b := alice.Seal(42, []byte("aad"), []byte("foobar")) _, err := bob.Open(42, []byte("aad2"), b) diff --git a/crypto/key_derivation.go b/crypto/key_derivation.go index 6c649405..3c3973de 100644 --- a/crypto/key_derivation.go +++ b/crypto/key_derivation.go @@ -11,8 +11,25 @@ import ( "golang.org/x/crypto/hkdf" ) -// DeriveKeysChacha20 derives the client and server keys and creates a matching chacha20poly1305 instance +// DeriveKeysChacha20 derives the client and server keys and creates a matching chacha20poly1305 AEAD instance func DeriveKeysChacha20(version protocol.VersionNumber, forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo []byte, scfg []byte, cert []byte, divNonce []byte) (AEAD, error) { + otherKey, myKey, otherIV, myIV, err := deriveKeys(version, forwardSecure, sharedSecret, nonces, connID, chlo, scfg, cert, divNonce, 32) + if err != nil { + return nil, err + } + return NewAEADChacha20Poly1305(otherKey, myKey, otherIV, myIV) +} + +// DeriveKeysAESGCM derives the client and server keys and creates a matching AES-GCM AEAD instance +func DeriveKeysAESGCM(version protocol.VersionNumber, forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo []byte, scfg []byte, cert []byte, divNonce []byte) (AEAD, error) { + otherKey, myKey, otherIV, myIV, err := deriveKeys(version, forwardSecure, sharedSecret, nonces, connID, chlo, scfg, cert, divNonce, 16) + if err != nil { + return nil, err + } + return NewAEADAESGCM(otherKey, myKey, otherIV, myIV) +} + +func deriveKeys(version protocol.VersionNumber, forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo, scfg, cert, divNonce []byte, keyLen int) ([]byte, []byte, []byte, []byte, error) { var info bytes.Buffer if forwardSecure { info.Write([]byte("QUIC forward secure key expansion\x00")) @@ -26,31 +43,31 @@ func DeriveKeysChacha20(version protocol.VersionNumber, forwardSecure bool, shar r := hkdf.New(sha256.New, sharedSecret, nonces, info.Bytes()) - otherKey := make([]byte, 32) - myKey := make([]byte, 32) + otherKey := make([]byte, keyLen) + myKey := make([]byte, keyLen) otherIV := make([]byte, 4) myIV := make([]byte, 4) if _, err := io.ReadFull(r, otherKey); err != nil { - return nil, err + return nil, nil, nil, nil, err } if _, err := io.ReadFull(r, myKey); err != nil { - return nil, err + return nil, nil, nil, nil, err } if _, err := io.ReadFull(r, otherIV); err != nil { - return nil, err + return nil, nil, nil, nil, err } if _, err := io.ReadFull(r, myIV); err != nil { - return nil, err + return nil, nil, nil, nil, err } if !forwardSecure && version >= protocol.Version33 { if err := diversify(myKey, myIV, divNonce); err != nil { - return nil, err + return nil, nil, nil, nil, err } } - return NewAEADChacha20Poly1305(otherKey, myKey, otherIV, myIV) + return otherKey, myKey, otherIV, myIV, nil } func diversify(key, iv, divNonce []byte) error { diff --git a/crypto/key_derivation_test.go b/crypto/key_derivation_test.go index 043dfb16..96ae8537 100644 --- a/crypto/key_derivation_test.go +++ b/crypto/key_derivation_test.go @@ -8,79 +8,159 @@ import ( ) var _ = Describe("KeyDerivation", func() { - It("derives non-fs keys", func() { - aead, err := DeriveKeysChacha20( - protocol.Version32, - false, - []byte("0123456789012345678901"), - []byte("nonce"), - protocol.ConnectionID(42), - []byte("chlo"), - []byte("scfg"), - []byte("cert"), - nil, - ) - Expect(err).ToNot(HaveOccurred()) - chacha := aead.(*aeadChacha20Poly1305) - // If the IVs match, the keys will match too, since the keys are read earlier - Expect(chacha.myIV).To(Equal([]byte{0xf0, 0xf5, 0x4c, 0xa8})) - Expect(chacha.otherIV).To(Equal([]byte{0x75, 0xd8, 0xa2, 0x8d})) + Context("chacha20poly1305", func() { + It("derives non-fs keys", func() { + aead, err := DeriveKeysChacha20( + protocol.Version32, + false, + []byte("0123456789012345678901"), + []byte("nonce"), + protocol.ConnectionID(42), + []byte("chlo"), + []byte("scfg"), + []byte("cert"), + nil, + ) + Expect(err).ToNot(HaveOccurred()) + chacha := aead.(*aeadChacha20Poly1305) + // If the IVs match, the keys will match too, since the keys are read earlier + Expect(chacha.myIV).To(Equal([]byte{0xf0, 0xf5, 0x4c, 0xa8})) + Expect(chacha.otherIV).To(Equal([]byte{0x75, 0xd8, 0xa2, 0x8d})) + }) + + It("derives fs keys", func() { + aead, err := DeriveKeysChacha20( + protocol.Version32, + true, + []byte("0123456789012345678901"), + []byte("nonce"), + protocol.ConnectionID(42), + []byte("chlo"), + []byte("scfg"), + []byte("cert"), + nil, + ) + Expect(err).ToNot(HaveOccurred()) + chacha := aead.(*aeadChacha20Poly1305) + // If the IVs match, the keys will match too, since the keys are read earlier + Expect(chacha.myIV).To(Equal([]byte{0xf5, 0x73, 0x11, 0x79})) + Expect(chacha.otherIV).To(Equal([]byte{0xf7, 0x26, 0x4d, 0x2c})) + }) + + It("does not use diversification nonces in FS key derivation", func() { + aead, err := DeriveKeysChacha20( + protocol.Version33, + true, + []byte("0123456789012345678901"), + []byte("nonce"), + protocol.ConnectionID(42), + []byte("chlo"), + []byte("scfg"), + []byte("cert"), + []byte("divnonce"), + ) + Expect(err).ToNot(HaveOccurred()) + chacha := aead.(*aeadChacha20Poly1305) + // If the IVs match, the keys will match too, since the keys are read earlier + Expect(chacha.myIV).To(Equal([]byte{0xf5, 0x73, 0x11, 0x79})) + Expect(chacha.otherIV).To(Equal([]byte{0xf7, 0x26, 0x4d, 0x2c})) + }) + + It("uses diversification nonces in initial key derivation", func() { + aead, err := DeriveKeysChacha20( + protocol.Version33, + false, + []byte("0123456789012345678901"), + []byte("nonce"), + protocol.ConnectionID(42), + []byte("chlo"), + []byte("scfg"), + []byte("cert"), + []byte("divnonce"), + ) + Expect(err).ToNot(HaveOccurred()) + chacha := aead.(*aeadChacha20Poly1305) + // If the IVs match, the keys will match too, since the keys are read earlier + Expect(chacha.myIV).To(Equal([]byte{0xc4, 0x12, 0x25, 0x64})) + Expect(chacha.otherIV).To(Equal([]byte{0x75, 0xd8, 0xa2, 0x8d})) + }) }) - It("derives fs keys", func() { - aead, err := DeriveKeysChacha20( - protocol.Version32, - true, - []byte("0123456789012345678901"), - []byte("nonce"), - protocol.ConnectionID(42), - []byte("chlo"), - []byte("scfg"), - []byte("cert"), - nil, - ) - Expect(err).ToNot(HaveOccurred()) - chacha := aead.(*aeadChacha20Poly1305) - // If the IVs match, the keys will match too, since the keys are read earlier - Expect(chacha.myIV).To(Equal([]byte{0xf5, 0x73, 0x11, 0x79})) - Expect(chacha.otherIV).To(Equal([]byte{0xf7, 0x26, 0x4d, 0x2c})) - }) + Context("AES-GCM", func() { + It("derives non-fs keys", func() { + aead, err := DeriveKeysAESGCM( + protocol.Version32, + false, + []byte("0123456789012345678901"), + []byte("nonce"), + protocol.ConnectionID(42), + []byte("chlo"), + []byte("scfg"), + []byte("cert"), + nil, + ) + Expect(err).ToNot(HaveOccurred()) + chacha := aead.(*aeadAESGCM) + // If the IVs match, the keys will match too, since the keys are read earlier + Expect(chacha.myIV).To(Equal([]byte{0x28, 0x71, 0x71, 0x16})) + Expect(chacha.otherIV).To(Equal([]byte{0x64, 0xef, 0x3c, 0x9})) + }) - It("does not use diversification nonces in FS key derivation", func() { - aead, err := DeriveKeysChacha20( - protocol.Version33, - true, - []byte("0123456789012345678901"), - []byte("nonce"), - protocol.ConnectionID(42), - []byte("chlo"), - []byte("scfg"), - []byte("cert"), - []byte("divnonce"), - ) - Expect(err).ToNot(HaveOccurred()) - chacha := aead.(*aeadChacha20Poly1305) - // If the IVs match, the keys will match too, since the keys are read earlier - Expect(chacha.myIV).To(Equal([]byte{0xf5, 0x73, 0x11, 0x79})) - Expect(chacha.otherIV).To(Equal([]byte{0xf7, 0x26, 0x4d, 0x2c})) - }) + It("derives fs keys", func() { + aead, err := DeriveKeysAESGCM( + protocol.Version32, + true, + []byte("0123456789012345678901"), + []byte("nonce"), + protocol.ConnectionID(42), + []byte("chlo"), + []byte("scfg"), + []byte("cert"), + nil, + ) + Expect(err).ToNot(HaveOccurred()) + chacha := aead.(*aeadAESGCM) + // If the IVs match, the keys will match too, since the keys are read earlier + Expect(chacha.myIV).To(Equal([]byte{0x7, 0xad, 0xab, 0xb8})) + Expect(chacha.otherIV).To(Equal([]byte{0xf2, 0x7a, 0xcc, 0x42})) + }) - It("uses diversification nonces in initial key derivation", func() { - aead, err := DeriveKeysChacha20( - protocol.Version33, - false, - []byte("0123456789012345678901"), - []byte("nonce"), - protocol.ConnectionID(42), - []byte("chlo"), - []byte("scfg"), - []byte("cert"), - []byte("divnonce"), - ) - Expect(err).ToNot(HaveOccurred()) - chacha := aead.(*aeadChacha20Poly1305) - // If the IVs match, the keys will match too, since the keys are read earlier - Expect(chacha.myIV).To(Equal([]byte{0xc4, 0x12, 0x25, 0x64})) - Expect(chacha.otherIV).To(Equal([]byte{0x75, 0xd8, 0xa2, 0x8d})) + It("does not use diversification nonces in FS key derivation", func() { + aead, err := DeriveKeysAESGCM( + protocol.Version33, + true, + []byte("0123456789012345678901"), + []byte("nonce"), + protocol.ConnectionID(42), + []byte("chlo"), + []byte("scfg"), + []byte("cert"), + []byte("divnonce"), + ) + Expect(err).ToNot(HaveOccurred()) + chacha := aead.(*aeadAESGCM) + // If the IVs match, the keys will match too, since the keys are read earlier + Expect(chacha.myIV).To(Equal([]byte{0x7, 0xad, 0xab, 0xb8})) + Expect(chacha.otherIV).To(Equal([]byte{0xf2, 0x7a, 0xcc, 0x42})) + }) + + It("uses diversification nonces in initial key derivation", func() { + aead, err := DeriveKeysAESGCM( + protocol.Version33, + false, + []byte("0123456789012345678901"), + []byte("nonce"), + protocol.ConnectionID(42), + []byte("chlo"), + []byte("scfg"), + []byte("cert"), + []byte("divnonce"), + ) + Expect(err).ToNot(HaveOccurred()) + chacha := aead.(*aeadAESGCM) + // If the IVs match, the keys will match too, since the keys are read earlier + Expect(chacha.myIV).To(Equal([]byte{0x1c, 0xec, 0xac, 0x9b})) + Expect(chacha.otherIV).To(Equal([]byte{0x64, 0xef, 0x3c, 0x9})) + }) }) }) diff --git a/handshake/crypto_setup.go b/handshake/crypto_setup.go index be7f67e7..0705376f 100644 --- a/handshake/crypto_setup.go +++ b/handshake/crypto_setup.go @@ -71,7 +71,7 @@ func NewCryptoSetup( scfg: scfg, nonce: nonce, diversificationNonce: diversificationNonce, - keyDerivation: crypto.DeriveKeysChacha20, + keyDerivation: crypto.DeriveKeysAESGCM, keyExchange: crypto.NewCurve25519KEX, cryptoStream: cryptoStream, connectionParametersManager: connectionParametersManager, diff --git a/handshake/server_config.go b/handshake/server_config.go index c3de7bd1..56caba5a 100644 --- a/handshake/server_config.go +++ b/handshake/server_config.go @@ -47,7 +47,7 @@ func (s *ServerConfig) Get() []byte { WriteHandshakeMessage(&serverConfig, TagSCFG, map[Tag][]byte{ TagSCID: s.ID, TagKEXS: []byte("C255"), - TagAEAD: []byte("CC20"), + TagAEAD: []byte("AESG"), TagPUBS: append([]byte{0x20, 0x00, 0x00}, s.kex.PublicKey()...), TagOBIT: {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, TagEXPY: {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, diff --git a/handshake/server_config_test.go b/handshake/server_config_test.go index 0e4c100e..35bbda28 100644 --- a/handshake/server_config_test.go +++ b/handshake/server_config_test.go @@ -24,7 +24,7 @@ var _ = Describe("ServerConfig", func() { }) It("gets the proper binary representation", func() { - expected := bytes.NewBuffer([]byte{0x53, 0x43, 0x46, 0x47, 0x7, 0x0, 0x0, 0x0, 0x56, 0x45, 0x52, 0x0, 0x4, 0x0, 0x0, 0x0, 0x41, 0x45, 0x41, 0x44, 0x8, 0x0, 0x0, 0x0, 0x53, 0x43, 0x49, 0x44, 0x18, 0x0, 0x0, 0x0, 0x50, 0x55, 0x42, 0x53, 0x3b, 0x0, 0x0, 0x0, 0x4b, 0x45, 0x58, 0x53, 0x3f, 0x0, 0x0, 0x0, 0x4f, 0x42, 0x49, 0x54, 0x47, 0x0, 0x0, 0x0, 0x45, 0x58, 0x50, 0x59, 0x4f, 0x0, 0x0, 0x0, 0x51, 0x30, 0x33, 0x32, 0x43, 0x43, 0x32, 0x30}) + expected := bytes.NewBuffer([]byte{0x53, 0x43, 0x46, 0x47, 0x7, 0x0, 0x0, 0x0, 0x56, 0x45, 0x52, 0x0, 0x4, 0x0, 0x0, 0x0, 0x41, 0x45, 0x41, 0x44, 0x8, 0x0, 0x0, 0x0, 0x53, 0x43, 0x49, 0x44, 0x18, 0x0, 0x0, 0x0, 0x50, 0x55, 0x42, 0x53, 0x3b, 0x0, 0x0, 0x0, 0x4b, 0x45, 0x58, 0x53, 0x3f, 0x0, 0x0, 0x0, 0x4f, 0x42, 0x49, 0x54, 0x47, 0x0, 0x0, 0x0, 0x45, 0x58, 0x50, 0x59, 0x4f, 0x0, 0x0, 0x0, 0x51, 0x30, 0x33, 0x32, 'A', 'E', 'S', 'G'}) expected.Write(scfg.ID) expected.Write([]byte{0x20, 0x0, 0x0}) expected.Write(kex.PublicKey())