update AEADs to allow in-place encryption and decryption

ref #217
This commit is contained in:
Lucas Clemente 2016-07-26 14:51:19 +02:00
parent eb9c23096d
commit d5255a4075
13 changed files with 99 additions and 78 deletions

View file

@ -4,6 +4,6 @@ import "github.com/lucas-clemente/quic-go/protocol"
// An AEAD implements QUIC's authenticated encryption and associated data
type AEAD interface {
Open(packetNumber protocol.PacketNumber, associatedData []byte, ciphertext []byte) ([]byte, error)
Seal(packetNumber protocol.PacketNumber, associatedData []byte, plaintext []byte) []byte
Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error)
Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte
}

View file

@ -49,10 +49,10 @@ func NewAEADAESGCM(otherKey []byte, myKey []byte, otherIV []byte, myIV []byte) (
}, 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) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) {
return aead.decrypter.Open(dst, makeNonce(aead.otherIV, packetNumber), src, associatedData)
}
func (aead *aeadAESGCM) Seal(packetNumber protocol.PacketNumber, associatedData []byte, plaintext []byte) []byte {
return aead.encrypter.Seal(nil, makeNonce(aead.myIV, packetNumber), plaintext, associatedData)
func (aead *aeadAESGCM) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte {
return aead.encrypter.Seal(dst, makeNonce(aead.myIV, packetNumber), src, associatedData)
}

View file

@ -30,27 +30,27 @@ var _ = Describe("AES-GCM", func() {
})
It("seals and opens", func() {
b := alice.Seal(42, []byte("aad"), []byte("foobar"))
text, err := bob.Open(42, []byte("aad"), b)
b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad"))
text, err := bob.Open(nil, b, 42, []byte("aad"))
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)
b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad"))
text, err := alice.Open(nil, b, 42, []byte("aad"))
Expect(err).ToNot(HaveOccurred())
Expect(text).To(Equal([]byte("foobar")))
})
It("has the proper length", func() {
b := bob.Seal(42, []byte("aad"), []byte("foobar"))
b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad"))
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)
b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad"))
_, err := bob.Open(nil, b, 42, []byte("aad2"))
Expect(err).To(HaveOccurred())
})

View file

@ -43,12 +43,12 @@ func NewAEADChacha20Poly1305(otherKey []byte, myKey []byte, otherIV []byte, myIV
}, nil
}
func (aead *aeadChacha20Poly1305) Open(packetNumber protocol.PacketNumber, associatedData []byte, ciphertext []byte) ([]byte, error) {
return aead.decrypter.Open(nil, makeNonce(aead.otherIV, packetNumber), ciphertext, associatedData)
func (aead *aeadChacha20Poly1305) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) {
return aead.decrypter.Open(dst, makeNonce(aead.otherIV, packetNumber), src, associatedData)
}
func (aead *aeadChacha20Poly1305) Seal(packetNumber protocol.PacketNumber, associatedData []byte, plaintext []byte) []byte {
return aead.encrypter.Seal(nil, makeNonce(aead.myIV, packetNumber), plaintext, associatedData)
func (aead *aeadChacha20Poly1305) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte {
return aead.encrypter.Seal(dst, makeNonce(aead.myIV, packetNumber), src, associatedData)
}
func makeNonce(iv []byte, packetNumber protocol.PacketNumber) []byte {

View file

@ -30,27 +30,27 @@ var _ = Describe("Chacha20poly1305", func() {
})
It("seals and opens", func() {
b := alice.Seal(42, []byte("aad"), []byte("foobar"))
text, err := bob.Open(42, []byte("aad"), b)
b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad"))
text, err := bob.Open(nil, b, 42, []byte("aad"))
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)
b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad"))
text, err := alice.Open(nil, b, 42, []byte("aad"))
Expect(err).ToNot(HaveOccurred())
Expect(text).To(Equal([]byte("foobar")))
})
It("has the proper length", func() {
b := bob.Seal(42, []byte("aad"), []byte("foobar"))
b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad"))
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)
b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad"))
_, err := bob.Open(nil, b, 42, []byte("aad2"))
Expect(err).To(HaveOccurred())
})

View file

@ -14,36 +14,40 @@ type NullAEAD struct{}
var _ AEAD = &NullAEAD{}
// Open and verify the ciphertext
func (NullAEAD) Open(packetNumber protocol.PacketNumber, associatedData []byte, ciphertext []byte) ([]byte, error) {
if len(ciphertext) < 12 {
func (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")
}
hash := fnv128a.New()
hash.Write(associatedData)
hash.Write(ciphertext[12:])
hash.Write(src[12:])
testHigh, testLow := hash.Sum128()
low := binary.LittleEndian.Uint64(ciphertext)
high := binary.LittleEndian.Uint32(ciphertext[8:])
low := binary.LittleEndian.Uint64(src)
high := binary.LittleEndian.Uint32(src[8:])
if uint32(testHigh&0xffffffff) != high || testLow != low {
return nil, errors.New("NullAEAD: failed to authenticate received data")
}
return ciphertext[12:], nil
return src[12:], nil
}
// Seal writes hash and ciphertext to the buffer
func (NullAEAD) Seal(packetNumber protocol.PacketNumber, associatedData []byte, plaintext []byte) []byte {
res := make([]byte, 12+len(plaintext))
func (NullAEAD) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte {
if cap(dst) < 12+len(src) {
dst = make([]byte, 12+len(src))
} else {
dst = dst[:12+len(src)]
}
hash := fnv128a.New()
hash.Write(associatedData)
hash.Write(plaintext)
hash.Write(src)
high, low := hash.Sum128()
binary.LittleEndian.PutUint64(res, low)
binary.LittleEndian.PutUint32(res[8:], uint32(high))
copy(res[12:], plaintext)
return res
copy(dst[12:], src)
binary.LittleEndian.PutUint64(dst, low)
binary.LittleEndian.PutUint32(dst[8:], uint32(high))
return dst
}

View file

@ -12,7 +12,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{}
res, err := aead.Open(0, aad, cipherText)
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.")))
})
@ -23,7 +23,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(0, aad, cipherText)
_, err := aead.Open(nil, cipherText, 0, aad)
Expect(err).To(HaveOccurred())
})
@ -31,11 +31,21 @@ 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.")
aead := &NullAEAD{}
Expect(aead.Seal(0, aad, plainText)).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.")...)))
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.")...)))
})
It("rejects short ciphertexts", func() {
_, err := NullAEAD{}.Open(0, nil, nil)
_, err := NullAEAD{}.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{}
buf := make([]byte, 6, 12+6)
copy(buf, []byte("foobar"))
res := aead.Seal(buf[0:0], buf, 0, nil)
buf = buf[:12+6]
Expect(buf[12:]).To(Equal([]byte("foobar")))
Expect(res[12:]).To(Equal([]byte("foobar")))
})
})

View file

@ -142,12 +142,12 @@ func (h *CryptoSetup) handleMessage(chloData []byte, cryptoData map[Tag][]byte)
}
// Open a message
func (h *CryptoSetup) Open(packetNumber protocol.PacketNumber, associatedData []byte, ciphertext []byte) ([]byte, error) {
func (h *CryptoSetup) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) {
h.mutex.RLock()
defer h.mutex.RUnlock()
if h.forwardSecureAEAD != nil {
res, err := h.forwardSecureAEAD.Open(packetNumber, associatedData, ciphertext)
res, err := h.forwardSecureAEAD.Open(dst, src, packetNumber, associatedData)
if err == nil {
h.receivedForwardSecurePacket = true
return res, nil
@ -157,7 +157,7 @@ func (h *CryptoSetup) Open(packetNumber protocol.PacketNumber, associatedData []
}
}
if h.secureAEAD != nil {
res, err := h.secureAEAD.Open(packetNumber, associatedData, ciphertext)
res, err := h.secureAEAD.Open(dst, src, packetNumber, associatedData)
if err == nil {
h.receivedSecurePacket = true
return res, nil
@ -166,17 +166,17 @@ func (h *CryptoSetup) Open(packetNumber protocol.PacketNumber, associatedData []
return nil, err
}
}
return (&crypto.NullAEAD{}).Open(packetNumber, associatedData, ciphertext)
return (&crypto.NullAEAD{}).Open(dst, src, packetNumber, associatedData)
}
// Seal a message, call LockForSealing() before!
func (h *CryptoSetup) Seal(packetNumber protocol.PacketNumber, associatedData []byte, plaintext []byte) []byte {
func (h *CryptoSetup) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte {
if h.receivedForwardSecurePacket {
return h.forwardSecureAEAD.Seal(packetNumber, associatedData, plaintext)
return h.forwardSecureAEAD.Seal(dst, src, packetNumber, associatedData)
} else if h.secureAEAD != nil {
return h.secureAEAD.Seal(packetNumber, associatedData, plaintext)
return h.secureAEAD.Seal(dst, src, packetNumber, associatedData)
} else {
return (&crypto.NullAEAD{}).Seal(packetNumber, associatedData, plaintext)
return (&crypto.NullAEAD{}).Seal(dst, src, packetNumber, associatedData)
}
}

View file

@ -52,17 +52,24 @@ type mockAEAD struct {
sharedSecret []byte
}
func (m *mockAEAD) Seal(packetNumber protocol.PacketNumber, associatedData []byte, plaintext []byte) []byte {
if m.forwardSecure {
return []byte("forward secure encrypted")
func (m *mockAEAD) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte {
if cap(dst) < len(src)+12 {
dst = make([]byte, len(src)+12)
}
return []byte("encrypted")
dst = dst[:len(src)+12]
copy(dst, src)
if !m.forwardSecure {
copy(dst[len(src):], []byte(" normal sec"))
} else {
copy(dst[len(src):], []byte(" forward sec"))
}
return dst
}
func (m *mockAEAD) Open(packetNumber protocol.PacketNumber, associatedData []byte, ciphertext []byte) ([]byte, error) {
if m.forwardSecure && string(ciphertext) == "forward secure encrypted" {
func (m *mockAEAD) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) {
if m.forwardSecure && string(src) == "forward secure encrypted" {
return []byte("decrypted"), nil
} else if !m.forwardSecure && string(ciphertext) == "encrypted" {
} else if !m.forwardSecure && string(src) == "encrypted" {
return []byte("decrypted"), nil
}
return nil, errors.New("authentication failed")
@ -296,11 +303,11 @@ var _ = Describe("Crypto setup", func() {
Context("null encryption", func() {
It("is used initially", func() {
Expect(cs.Seal(0, []byte{}, []byte("foobar"))).To(Equal(foobarFNVSigned))
Expect(cs.Seal(nil, []byte("foobar"), 0, []byte{})).To(Equal(foobarFNVSigned))
})
It("is accepted initially", func() {
d, err := cs.Open(0, []byte{}, foobarFNVSigned)
d, err := cs.Open(nil, foobarFNVSigned, 0, []byte{})
Expect(err).ToNot(HaveOccurred())
Expect(d).To(Equal([]byte("foobar")))
})
@ -308,23 +315,23 @@ var _ = Describe("Crypto setup", func() {
It("is still accepted after CHLO", func() {
doCHLO()
Expect(cs.secureAEAD).ToNot(BeNil())
_, err := cs.Open(0, []byte{}, foobarFNVSigned)
_, err := cs.Open(nil, foobarFNVSigned, 0, []byte{})
Expect(err).ToNot(HaveOccurred())
})
It("is not accepted after receiving secure packet", func() {
doCHLO()
Expect(cs.secureAEAD).ToNot(BeNil())
d, err := cs.Open(0, []byte{}, []byte("encrypted"))
d, err := cs.Open(nil, []byte("encrypted"), 0, []byte{})
Expect(err).ToNot(HaveOccurred())
Expect(d).To(Equal([]byte("decrypted")))
_, err = cs.Open(0, []byte{}, foobarFNVSigned)
_, err = cs.Open(nil, foobarFNVSigned, 0, []byte{})
Expect(err).To(MatchError("authentication failed"))
})
It("is not used after CHLO", func() {
doCHLO()
d := cs.Seal(0, []byte{}, []byte("foobar"))
d := cs.Seal(nil, []byte("foobar"), 0, []byte{})
Expect(d).ToNot(Equal(foobarFNVSigned))
})
})
@ -332,30 +339,30 @@ var _ = Describe("Crypto setup", func() {
Context("initial encryption", func() {
It("is used after CHLO", func() {
doCHLO()
d := cs.Seal(0, []byte{}, []byte("foobar"))
Expect(d).To(Equal([]byte("encrypted")))
d := cs.Seal(nil, []byte("foobar"), 0, []byte{})
Expect(d).To(Equal([]byte("foobar normal sec")))
})
It("is accepted after CHLO", func() {
doCHLO()
d, err := cs.Open(0, []byte{}, []byte("encrypted"))
d, err := cs.Open(nil, []byte("encrypted"), 0, []byte{})
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{}, []byte("forward secure encrypted"))
_, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{})
Expect(err).ToNot(HaveOccurred())
d := cs.Seal(0, []byte{}, []byte("foobar"))
Expect(d).To(Equal([]byte("forward secure encrypted")))
d := cs.Seal(nil, []byte("foobar"), 0, []byte{})
Expect(d).To(Equal([]byte("foobar forward sec")))
})
It("is not accepted after receiving forward secure packet", func() {
doCHLO()
_, err := cs.Open(0, []byte{}, []byte("forward secure encrypted"))
_, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{})
Expect(err).ToNot(HaveOccurred())
_, err = cs.Open(0, []byte{}, []byte("encrypted"))
_, err = cs.Open(nil, []byte("encrypted"), 0, []byte{})
Expect(err).To(MatchError("authentication failed"))
})
})
@ -363,10 +370,10 @@ var _ = Describe("Crypto setup", func() {
Context("forward secure encryption", func() {
It("is used after receiving forward secure packet", func() {
doCHLO()
_, err := cs.Open(0, []byte{}, []byte("forward secure encrypted"))
_, err := cs.Open(nil, []byte("forward secure encrypted"), 0, []byte{})
Expect(err).ToNot(HaveOccurred())
d := cs.Seal(0, []byte{}, []byte("foobar"))
Expect(d).To(Equal([]byte("forward secure encrypted")))
d := cs.Seal(nil, []byte("foobar"), 0, []byte{})
Expect(d).To(Equal([]byte("foobar forward sec")))
})
})
})

View file

@ -132,7 +132,7 @@ func (p *packetPacker) packPacket(stopWaitingFrame *frames.StopWaitingFrame, con
return nil, err
}
ciphertext := p.cryptoSetup.Seal(currentPacketNumber, raw.Bytes(), payload)
ciphertext := p.cryptoSetup.Seal(nil, payload, currentPacketNumber, raw.Bytes())
raw.Write(ciphertext)
if protocol.ByteCount(raw.Len()) > protocol.MaxPacketSize {

View file

@ -24,7 +24,7 @@ type packetUnpacker struct {
func (u *packetUnpacker) Unpack(publicHeaderBinary []byte, hdr *publicHeader, r *bytes.Reader) (*unpackedPacket, error) {
ciphertext, _ := ioutil.ReadAll(r)
plaintext, err := u.aead.Open(hdr.PacketNumber, publicHeaderBinary, ciphertext)
plaintext, err := u.aead.Open(nil, ciphertext, 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())

View file

@ -38,13 +38,13 @@ var _ = Describe("Packet unpacker", func() {
if unpacker.version < protocol.Version34 { // add private flag
data = append([]byte{0x01}, data...)
}
r = bytes.NewReader(aead.Seal(0, hdrBin, data))
r = bytes.NewReader(aead.Seal(nil, data, 0, hdrBin))
}
It("returns an error for empty packets that don't have a private flag, for QUIC Version < 34", func() {
// don't use setReader here, since it adds a private flag
unpacker.version = protocol.Version33
r = bytes.NewReader(aead.Seal(0, hdrBin, []byte{}))
r = bytes.NewReader(aead.Seal(nil, nil, 0, hdrBin))
_, err := unpacker.Unpack(hdrBin, hdr, r)
Expect(err).To(MatchError(qerr.MissingPayload))
})

View file

@ -72,7 +72,7 @@ var _ = Describe("Server", func() {
It("closes and deletes sessions", func() {
pheader := []byte{0x09, 0xf6, 0x19, 0x86, 0x66, 0x9b, 0x9f, 0xfa, 0x4c, 0x51, 0x30, 0x33, 0x32, 0x01}
err := server.handlePacket(nil, nil, append(pheader, (&crypto.NullAEAD{}).Seal(0, pheader, nil)...))
err := server.handlePacket(nil, nil, append(pheader, (&crypto.NullAEAD{}).Seal(nil, nil, 0, pheader)...))
Expect(err).ToNot(HaveOccurred())
Expect(server.sessions).To(HaveLen(1))
server.closeCallback(0x4cfa9f9b668619f6)