From 8b7e2744da27546406c807479dc21614cf4e6b33 Mon Sep 17 00:00:00 2001 From: Lucas Clemente Date: Thu, 14 Apr 2016 09:56:39 +0200 Subject: [PATCH] implement AES-GCM as AEAD --- crypto/AEAD.go | 4 +-- crypto/NullAEAD.go | 4 +-- crypto/NullAEAD_test.go | 6 ++-- crypto/aes_gcm_aead.go | 71 +++++++++++++++++++++++++++++++++++++ crypto/aes_gcm_aead_test.go | 59 ++++++++++++++++++++++++++++++ example/main.go | 4 +-- 6 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 crypto/aes_gcm_aead.go create mode 100644 crypto/aes_gcm_aead_test.go diff --git a/crypto/AEAD.go b/crypto/AEAD.go index a6275705..c7169792 100644 --- a/crypto/AEAD.go +++ b/crypto/AEAD.go @@ -7,6 +7,6 @@ import ( // An AEAD implements QUIC's authenticated encryption and associated data type AEAD interface { - Open(associatedData []byte, ciphertext io.Reader) (*bytes.Reader, error) - Seal(b *bytes.Buffer, associatedData []byte, plaintext []byte) + Open(packetNumber uint64, associatedData []byte, ciphertext io.Reader) (*bytes.Reader, error) + Seal(packetNumber uint64, b *bytes.Buffer, associatedData []byte, plaintext []byte) } diff --git a/crypto/NullAEAD.go b/crypto/NullAEAD.go index b03d8bd5..92df9494 100644 --- a/crypto/NullAEAD.go +++ b/crypto/NullAEAD.go @@ -16,7 +16,7 @@ type NullAEAD struct{} var _ AEAD = &NullAEAD{} // Open and verify the ciphertext -func (*NullAEAD) Open(associatedData []byte, r io.Reader) (*bytes.Reader, error) { +func (*NullAEAD) Open(packetNumber uint64, associatedData []byte, r io.Reader) (*bytes.Reader, error) { ciphertext, err := ioutil.ReadAll(r) if err != nil { return nil, err @@ -40,7 +40,7 @@ func (*NullAEAD) Open(associatedData []byte, r io.Reader) (*bytes.Reader, error) } // Seal writes hash and ciphertext to the buffer -func (*NullAEAD) Seal(b *bytes.Buffer, associatedData []byte, plaintext []byte) { +func (*NullAEAD) Seal(packetNumber uint64, b *bytes.Buffer, associatedData []byte, plaintext []byte) { hash := New128a() hash.Write(associatedData) hash.Write(plaintext) diff --git a/crypto/NullAEAD_test.go b/crypto/NullAEAD_test.go index 2210b5d5..39ec22d7 100644 --- a/crypto/NullAEAD_test.go +++ b/crypto/NullAEAD_test.go @@ -15,7 +15,7 @@ var _ = Describe("Crypto/NullAEAD", func() { hash := []byte{0x98, 0x9b, 0x33, 0x3f, 0xe8, 0xde, 0x32, 0x5c, 0xa6, 0x7f, 0x9c, 0xf7} cipherText := append(hash, plainText...) aead := &NullAEAD{} - r, err := aead.Open(aad, bytes.NewReader(cipherText)) + r, err := aead.Open(0, aad, bytes.NewReader(cipherText)) Expect(err).ToNot(HaveOccurred()) res, err := ioutil.ReadAll(r) Expect(err).ToNot(HaveOccurred()) @@ -28,7 +28,7 @@ var _ = Describe("Crypto/NullAEAD", func() { hash := []byte{0x98, 0x9b, 0x33, 0x3f, 0xe8, 0xde, 0x32, 0x5c, 0xa6, 0x7f, 0x9c, 0xf7} cipherText := append(hash, plainText...) aead := &NullAEAD{} - _, err := aead.Open(aad, bytes.NewReader(cipherText)) + _, err := aead.Open(0, aad, bytes.NewReader(cipherText)) Expect(err).To(HaveOccurred()) }) @@ -37,7 +37,7 @@ var _ = Describe("Crypto/NullAEAD", func() { plainText := []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.") b := &bytes.Buffer{} aead := &NullAEAD{} - aead.Seal(b, aad, plainText) + aead.Seal(0, b, aad, plainText) Expect(b.Bytes()).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.")...))) }) }) diff --git a/crypto/aes_gcm_aead.go b/crypto/aes_gcm_aead.go new file mode 100644 index 00000000..1d5973bb --- /dev/null +++ b/crypto/aes_gcm_aead.go @@ -0,0 +1,71 @@ +package crypto + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/binary" + "errors" + "io" + "io/ioutil" +) + +type aeadAESGCM struct { + otherIV []byte + myIV []byte + encrypter cipher.AEAD + decrypter cipher.AEAD +} + +// NewAEADAESGCM creates a AEAD using AES-GCM +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") + } + encCipher, err := aes.NewCipher(myKey) + if err != nil { + return nil, err + } + encrypter, err := cipher.NewGCM(encCipher) + if err != nil { + return nil, err + } + decCipher, err := aes.NewCipher(otherKey) + if err != nil { + return nil, err + } + decrypter, err := cipher.NewGCM(decCipher) + if err != nil { + return nil, err + } + return &aeadAESGCM{ + otherIV: otherIV, + myIV: myIV, + encrypter: encrypter, + decrypter: decrypter, + }, nil +} + +func (aead *aeadAESGCM) Open(packetNumber uint64, associatedData []byte, r io.Reader) (*bytes.Reader, error) { + ciphertext, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + plaintext, err := aead.decrypter.Open(nil, makeNonce(aead.otherIV, packetNumber), ciphertext, associatedData) + if err != nil { + return nil, err + } + return bytes.NewReader(plaintext), nil +} + +func (aead *aeadAESGCM) Seal(packetNumber uint64, b *bytes.Buffer, associatedData []byte, plaintext []byte) { + ciphertext := aead.encrypter.Seal(nil, makeNonce(aead.myIV, packetNumber), plaintext, associatedData) + b.Write(ciphertext) +} + +func makeNonce(iv []byte, packetNumber uint64) []byte { + res := make([]byte, 12) + copy(res[0:4], iv) + binary.LittleEndian.PutUint64(res[4:12], packetNumber) + return res +} diff --git a/crypto/aes_gcm_aead_test.go b/crypto/aes_gcm_aead_test.go new file mode 100644 index 00000000..c41e2ddf --- /dev/null +++ b/crypto/aes_gcm_aead_test.go @@ -0,0 +1,59 @@ +package crypto + +import ( + "bytes" + "crypto/rand" + "io/ioutil" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("AES-GCM AEAD", func() { + var ( + alice, bob AEAD + ) + + 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 := &bytes.Buffer{} + alice.Seal(42, b, []byte("aad"), []byte("foobar")) + r, err := bob.Open(42, []byte("aad"), b) + Expect(err).ToNot(HaveOccurred()) + text, err := ioutil.ReadAll(r) + Expect(err).ToNot(HaveOccurred()) + Expect(text).To(Equal([]byte("foobar"))) + }) + + It("seals and opens reverse", func() { + b := &bytes.Buffer{} + bob.Seal(42, b, []byte("aad"), []byte("foobar")) + r, err := alice.Open(42, []byte("aad"), b) + Expect(err).ToNot(HaveOccurred()) + text, err := ioutil.ReadAll(r) + Expect(err).ToNot(HaveOccurred()) + Expect(text).To(Equal([]byte("foobar"))) + }) + + It("fails with wrong aad", func() { + b := &bytes.Buffer{} + alice.Seal(42, b, []byte("aad"), []byte("foobar")) + _, err := bob.Open(42, []byte("aad2"), b) + Expect(err).To(MatchError("cipher: message authentication failed")) + }) +}) diff --git a/example/main.go b/example/main.go index 51685b35..9b54a073 100644 --- a/example/main.go +++ b/example/main.go @@ -64,7 +64,7 @@ func main() { } nullAEAD := &crypto.NullAEAD{} - r, err = nullAEAD.Open(data[0:int(r.Size())-r.Len()], r) + r, err = nullAEAD.Open(0, data[0:int(r.Size())-r.Len()], r) if err != nil { panic(err) } @@ -136,7 +136,7 @@ func main() { panic(err) } - nullAEAD.Seal(fullReply, fullReply.Bytes(), replyFrame.Bytes()) + nullAEAD.Seal(0, fullReply, fullReply.Bytes(), replyFrame.Bytes()) conn.WriteToUDP(fullReply.Bytes(), remoteAddr)