Merge pull request #1667 from lucas-clemente/refactor-initial-aead

refactor initialization of the initial AEAD
This commit is contained in:
Marten Seemann
2018-12-14 16:41:45 +06:30
committed by GitHub
11 changed files with 85 additions and 306 deletions

View File

@@ -1,10 +0,0 @@
package crypto
import "github.com/lucas-clemente/quic-go/internal/protocol"
// An AEAD implements QUIC's authenticated encryption and associated data
type AEAD interface {
Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error)
Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte
Overhead() int
}

View File

@@ -1,74 +0,0 @@
package crypto
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"errors"
"github.com/lucas-clemente/quic-go/internal/protocol"
)
type aeadAESGCM struct {
otherIV []byte
myIV []byte
encrypter cipher.AEAD
decrypter cipher.AEAD
}
var _ AEAD = &aeadAESGCM{}
const ivLen = 12
// NewAEADAESGCM creates a AEAD using AES-GCM
func NewAEADAESGCM(otherKey []byte, myKey []byte, otherIV []byte, myIV []byte) (AEAD, error) {
// the IVs need to be at least 8 bytes long, otherwise we can't compute the nonce
if len(otherIV) != ivLen || len(myIV) != ivLen {
return nil, errors.New("AES-GCM: expected 12 byte IVs")
}
encrypterCipher, err := aes.NewCipher(myKey)
if err != nil {
return nil, err
}
encrypter, err := cipher.NewGCM(encrypterCipher)
if err != nil {
return nil, err
}
decrypterCipher, err := aes.NewCipher(otherKey)
if err != nil {
return nil, err
}
decrypter, err := cipher.NewGCM(decrypterCipher)
if err != nil {
return nil, err
}
return &aeadAESGCM{
otherIV: otherIV,
myIV: myIV,
encrypter: encrypter,
decrypter: decrypter,
}, nil
}
func (aead *aeadAESGCM) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) {
return aead.decrypter.Open(dst, aead.makeNonce(aead.otherIV, packetNumber), src, associatedData)
}
func (aead *aeadAESGCM) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte {
return aead.encrypter.Seal(dst, aead.makeNonce(aead.myIV, packetNumber), src, associatedData)
}
func (aead *aeadAESGCM) makeNonce(iv []byte, packetNumber protocol.PacketNumber) []byte {
nonce := make([]byte, ivLen)
binary.BigEndian.PutUint64(nonce[ivLen-8:], uint64(packetNumber))
for i := 0; i < ivLen; i++ {
nonce[i] ^= iv[i]
}
return nonce
}
func (aead *aeadAESGCM) Overhead() int {
return aead.encrypter.Overhead()
}

View File

@@ -1,84 +0,0 @@
package crypto
import (
"crypto/rand"
"fmt"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("AES-GCM", func() {
var (
alice, bob AEAD
keyAlice, keyBob, ivAlice, ivBob []byte
)
BeforeEach(func() {
ivAlice = make([]byte, 12)
ivBob = make([]byte, 12)
})
// 16 bytes for TLS_AES_128_GCM_SHA256
// 32 bytes for TLS_AES_256_GCM_SHA384
for _, ks := range []int{16, 32} {
keySize := ks
Context(fmt.Sprintf("with %d byte keys", keySize), func() {
BeforeEach(func() {
keyAlice = make([]byte, keySize)
keyBob = make([]byte, keySize)
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(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(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(nil, []byte("foobar"), 42, []byte("aad"))
Expect(b).To(HaveLen(6 + bob.Overhead()))
})
It("fails with wrong aad", func() {
b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad"))
_, err := bob.Open(nil, b, 42, []byte("aad2"))
Expect(err).To(HaveOccurred())
})
It("rejects wrong key and iv sizes", func() {
e := "AES-GCM: expected 12 byte IVs"
var err error
_, err = NewAEADAESGCM(keyBob, keyAlice, ivBob[1:], ivAlice)
Expect(err).To(MatchError(e))
_, err = NewAEADAESGCM(keyBob, keyAlice, ivBob, ivAlice[1:])
Expect(err).To(MatchError(e))
})
})
}
It("errors when an invalid key size is used", func() {
keyAlice = make([]byte, 17)
keyBob = make([]byte, 17)
_, err := NewAEADAESGCM(keyBob, keyAlice, ivBob, ivAlice)
Expect(err).To(MatchError("crypto/aes: invalid key size 17"))
})
})

View File

@@ -6,8 +6,9 @@ import (
"encoding/binary"
)
// HkdfExtract generates a pseudorandom key for use with Expand from an input secret and an optional independent salt.
// copied from https://github.com/cloudflare/tls-tris/blob/master/hkdf.go
func hkdfExtract(hash crypto.Hash, secret, salt []byte) []byte {
func HkdfExtract(hash crypto.Hash, secret, salt []byte) []byte {
if salt == nil {
salt = make([]byte, hash.Size())
}
@@ -47,7 +48,7 @@ func hkdfExpand(hash crypto.Hash, prk, info []byte, l int) []byte {
return res
}
// hkdfExpandLabel HKDF expands a label
// HkdfExpandLabel HKDF expands a label
func HkdfExpandLabel(hash crypto.Hash, secret []byte, label string, length int) []byte {
const prefix = "quic "
qlabel := make([]byte, 2 /* length */ +1 /* length of label */ +len(prefix)+len(label)+1 /* length of context (empty) */)

View File

@@ -57,11 +57,11 @@ var _ = Describe("HKDF", func() {
t := testcases[i]
It(fmt.Sprintf("case %s: extracts", t.name), func() {
Expect(hkdfExtract(t.hash, t.secret, t.salt)).To(Equal(t.extracted))
Expect(HkdfExtract(t.hash, t.secret, t.salt)).To(Equal(t.extracted))
})
It(fmt.Sprintf("case %s: expands", t.name), func() {
prk := hkdfExtract(t.hash, t.secret, t.salt)
prk := HkdfExtract(t.hash, t.secret, t.salt)
Expect(hkdfExpand(t.hash, prk, t.info, len(t.expanded))).To(Equal(t.expanded))
})
}

View File

@@ -1,41 +0,0 @@
package crypto
import (
"crypto"
"github.com/lucas-clemente/quic-go/internal/protocol"
)
var quicVersion1Salt = []byte{0x9c, 0x10, 0x8f, 0x98, 0x52, 0x0a, 0x5c, 0x5c, 0x32, 0x96, 0x8e, 0x95, 0x0e, 0x8a, 0x2c, 0x5f, 0xe0, 0x6d, 0x6c, 0x38}
// NewNullAEAD creates a NullAEAD
func NewNullAEAD(connectionID protocol.ConnectionID, pers protocol.Perspective) (AEAD, error) {
clientSecret, serverSecret := computeSecrets(connectionID)
var mySecret, otherSecret []byte
if pers == protocol.PerspectiveClient {
mySecret = clientSecret
otherSecret = serverSecret
} else {
mySecret = serverSecret
otherSecret = clientSecret
}
myKey, myIV := computeNullAEADKeyAndIV(mySecret)
otherKey, otherIV := computeNullAEADKeyAndIV(otherSecret)
return NewAEADAESGCM(otherKey, myKey, otherIV, myIV)
}
func computeSecrets(connID protocol.ConnectionID) (clientSecret, serverSecret []byte) {
initialSecret := hkdfExtract(crypto.SHA256, connID, quicVersion1Salt)
clientSecret = HkdfExpandLabel(crypto.SHA256, initialSecret, "client in", crypto.SHA256.Size())
serverSecret = HkdfExpandLabel(crypto.SHA256, initialSecret, "server in", crypto.SHA256.Size())
return
}
func computeNullAEADKeyAndIV(secret []byte) (key, iv []byte) {
key = HkdfExpandLabel(crypto.SHA256, secret, "key", 16)
iv = HkdfExpandLabel(crypto.SHA256, secret, "iv", 12)
return
}

View File

@@ -74,7 +74,8 @@ type cryptoSetup struct {
clientHelloWrittenChan chan struct{}
initialStream io.Writer
initialAEAD crypto.AEAD
initialOpener Opener
initialSealer Sealer
handshakeStream io.Writer
handshakeOpener Opener
@@ -175,13 +176,14 @@ func newCryptoSetup(
logger utils.Logger,
perspective protocol.Perspective,
) (CryptoSetup, <-chan struct{} /* ClientHello written */, error) {
initialAEAD, err := crypto.NewNullAEAD(connID, perspective)
initialSealer, initialOpener, err := newInitialAEAD(connID, perspective)
if err != nil {
return nil, nil, err
}
cs := &cryptoSetup{
initialStream: initialStream,
initialAEAD: initialAEAD,
initialSealer: initialSealer,
initialOpener: initialOpener,
handshakeStream: handshakeStream,
readEncLevel: protocol.EncryptionInitial,
writeEncLevel: protocol.EncryptionInitial,
@@ -467,7 +469,7 @@ func (h *cryptoSetup) GetSealer() (protocol.EncryptionLevel, Sealer) {
if h.handshakeSealer != nil {
return protocol.EncryptionHandshake, h.handshakeSealer
}
return protocol.EncryptionInitial, h.initialAEAD
return protocol.EncryptionInitial, h.initialSealer
}
func (h *cryptoSetup) GetSealerWithEncryptionLevel(level protocol.EncryptionLevel) (Sealer, error) {
@@ -475,7 +477,7 @@ func (h *cryptoSetup) GetSealerWithEncryptionLevel(level protocol.EncryptionLeve
switch level {
case protocol.EncryptionInitial:
return h.initialAEAD, nil
return h.initialSealer, nil
case protocol.EncryptionHandshake:
if h.handshakeSealer == nil {
return nil, errNoSealer
@@ -492,7 +494,7 @@ func (h *cryptoSetup) GetSealerWithEncryptionLevel(level protocol.EncryptionLeve
}
func (h *cryptoSetup) OpenInitial(dst, src []byte, pn protocol.PacketNumber, ad []byte) ([]byte, error) {
return h.initialAEAD.Open(dst, src, pn, ad)
return h.initialOpener.Open(dst, src, pn, ad)
}
func (h *cryptoSetup) OpenHandshake(dst, src []byte, pn protocol.PacketNumber, ad []byte) ([]byte, error) {

View File

@@ -0,0 +1,57 @@
package handshake
import (
gocrypto "crypto"
"crypto/aes"
"crypto/cipher"
"github.com/lucas-clemente/quic-go/internal/crypto"
"github.com/lucas-clemente/quic-go/internal/protocol"
)
var quicVersion1Salt = []byte{0x9c, 0x10, 0x8f, 0x98, 0x52, 0x0a, 0x5c, 0x5c, 0x32, 0x96, 0x8e, 0x95, 0x0e, 0x8a, 0x2c, 0x5f, 0xe0, 0x6d, 0x6c, 0x38}
func newInitialAEAD(connID protocol.ConnectionID, pers protocol.Perspective) (Sealer, Opener, error) {
clientSecret, serverSecret := computeSecrets(connID)
var mySecret, otherSecret []byte
if pers == protocol.PerspectiveClient {
mySecret = clientSecret
otherSecret = serverSecret
} else {
mySecret = serverSecret
otherSecret = clientSecret
}
myKey, myIV := computeInitialKeyAndIV(mySecret)
otherKey, otherIV := computeInitialKeyAndIV(otherSecret)
encrypterCipher, err := aes.NewCipher(myKey)
if err != nil {
return nil, nil, err
}
encrypter, err := cipher.NewGCM(encrypterCipher)
if err != nil {
return nil, nil, err
}
decrypterCipher, err := aes.NewCipher(otherKey)
if err != nil {
return nil, nil, err
}
decrypter, err := cipher.NewGCM(decrypterCipher)
if err != nil {
return nil, nil, err
}
return newSealer(encrypter, myIV), newOpener(decrypter, otherIV), nil
}
func computeSecrets(connID protocol.ConnectionID) (clientSecret, serverSecret []byte) {
initialSecret := crypto.HkdfExtract(gocrypto.SHA256, connID, quicVersion1Salt)
clientSecret = crypto.HkdfExpandLabel(gocrypto.SHA256, initialSecret, "client in", gocrypto.SHA256.Size())
serverSecret = crypto.HkdfExpandLabel(gocrypto.SHA256, initialSecret, "server in", gocrypto.SHA256.Size())
return
}
func computeInitialKeyAndIV(secret []byte) (key, iv []byte) {
key = crypto.HkdfExpandLabel(gocrypto.SHA256, secret, "key", 16)
iv = crypto.HkdfExpandLabel(gocrypto.SHA256, secret, "iv", 12)
return
}

View File

@@ -1,12 +1,13 @@
package crypto
package handshake
import (
"github.com/lucas-clemente/quic-go/internal/protocol"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("NullAEAD using AES-GCM", func() {
var _ = Describe("Initial AEAD using AES-GCM", func() {
// values taken from https://github.com/quicwg/base-drafts/wiki/Test-Vector-for-the-Clear-Text-AEAD-key-derivation
Context("using the test vector from the QUIC WG Wiki", func() {
connID := protocol.ConnectionID([]byte{0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08})
@@ -29,7 +30,7 @@ var _ = Describe("NullAEAD using AES-GCM", func() {
It("computes the client key and IV", func() {
clientSecret, _ := computeSecrets(connID)
key, iv := computeNullAEADKeyAndIV(clientSecret)
key, iv := computeInitialKeyAndIV(clientSecret)
Expect(key).To(Equal([]byte{
0xf2, 0x92, 0x8f, 0x26, 0x14, 0xad, 0x6c, 0x20,
0xb9, 0xbd, 0x00, 0x8e, 0x9c, 0x89, 0x63, 0x1c,
@@ -42,7 +43,7 @@ var _ = Describe("NullAEAD using AES-GCM", func() {
It("computes the server key and IV", func() {
_, serverSecret := computeSecrets(connID)
key, iv := computeNullAEADKeyAndIV(serverSecret)
key, iv := computeInitialKeyAndIV(serverSecret)
Expect(key).To(Equal([]byte{
0xf5, 0x68, 0x17, 0xd0, 0xfc, 0x59, 0x5c, 0xfc,
0x0a, 0x2b, 0x0b, 0xcf, 0xb1, 0x87, 0x35, 0xec,
@@ -56,17 +57,17 @@ var _ = Describe("NullAEAD using AES-GCM", func() {
It("seals and opens", func() {
connectionID := protocol.ConnectionID([]byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef})
clientAEAD, err := NewNullAEAD(connectionID, protocol.PerspectiveClient)
clientSealer, clientOpener, err := newInitialAEAD(connectionID, protocol.PerspectiveClient)
Expect(err).ToNot(HaveOccurred())
serverAEAD, err := NewNullAEAD(connectionID, protocol.PerspectiveServer)
serverSealer, serverOpener, err := newInitialAEAD(connectionID, protocol.PerspectiveServer)
Expect(err).ToNot(HaveOccurred())
clientMessage := clientAEAD.Seal(nil, []byte("foobar"), 42, []byte("aad"))
m, err := serverAEAD.Open(nil, clientMessage, 42, []byte("aad"))
clientMessage := clientSealer.Seal(nil, []byte("foobar"), 42, []byte("aad"))
m, err := serverOpener.Open(nil, clientMessage, 42, []byte("aad"))
Expect(err).ToNot(HaveOccurred())
Expect(m).To(Equal([]byte("foobar")))
serverMessage := serverAEAD.Seal(nil, []byte("raboof"), 99, []byte("daa"))
m, err = clientAEAD.Open(nil, serverMessage, 99, []byte("daa"))
serverMessage := serverSealer.Seal(nil, []byte("raboof"), 99, []byte("daa"))
m, err = clientOpener.Open(nil, serverMessage, 99, []byte("daa"))
Expect(err).ToNot(HaveOccurred())
Expect(m).To(Equal([]byte("raboof")))
})
@@ -74,13 +75,13 @@ var _ = Describe("NullAEAD using AES-GCM", func() {
It("doesn't work if initialized with different connection IDs", func() {
c1 := protocol.ConnectionID([]byte{0, 0, 0, 0, 0, 0, 0, 1})
c2 := protocol.ConnectionID([]byte{0, 0, 0, 0, 0, 0, 0, 2})
clientAEAD, err := NewNullAEAD(c1, protocol.PerspectiveClient)
clientSealer, _, err := newInitialAEAD(c1, protocol.PerspectiveClient)
Expect(err).ToNot(HaveOccurred())
serverAEAD, err := NewNullAEAD(c2, protocol.PerspectiveServer)
_, serverOpener, err := newInitialAEAD(c2, protocol.PerspectiveServer)
Expect(err).ToNot(HaveOccurred())
clientMessage := clientAEAD.Seal(nil, []byte("foobar"), 42, []byte("aad"))
_, err = serverAEAD.Open(nil, clientMessage, 42, []byte("aad"))
clientMessage := clientSealer.Seal(nil, []byte("foobar"), 42, []byte("aad"))
_, err = serverOpener.Open(nil, clientMessage, 42, []byte("aad"))
Expect(err).To(MatchError("cipher: message authentication failed"))
})
})

View File

@@ -1,72 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/lucas-clemente/quic-go/internal/crypto (interfaces: AEAD)
// Package mockcrypto is a generated GoMock package.
package mockcrypto
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
protocol "github.com/lucas-clemente/quic-go/internal/protocol"
)
// MockAEAD is a mock of AEAD interface
type MockAEAD struct {
ctrl *gomock.Controller
recorder *MockAEADMockRecorder
}
// MockAEADMockRecorder is the mock recorder for MockAEAD
type MockAEADMockRecorder struct {
mock *MockAEAD
}
// NewMockAEAD creates a new mock instance
func NewMockAEAD(ctrl *gomock.Controller) *MockAEAD {
mock := &MockAEAD{ctrl: ctrl}
mock.recorder = &MockAEADMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockAEAD) EXPECT() *MockAEADMockRecorder {
return m.recorder
}
// Open mocks base method
func (m *MockAEAD) Open(arg0, arg1 []byte, arg2 protocol.PacketNumber, arg3 []byte) ([]byte, error) {
ret := m.ctrl.Call(m, "Open", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Open indicates an expected call of Open
func (mr *MockAEADMockRecorder) Open(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockAEAD)(nil).Open), arg0, arg1, arg2, arg3)
}
// Overhead mocks base method
func (m *MockAEAD) Overhead() int {
ret := m.ctrl.Call(m, "Overhead")
ret0, _ := ret[0].(int)
return ret0
}
// Overhead indicates an expected call of Overhead
func (mr *MockAEADMockRecorder) Overhead() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Overhead", reflect.TypeOf((*MockAEAD)(nil).Overhead))
}
// Seal mocks base method
func (m *MockAEAD) Seal(arg0, arg1 []byte, arg2 protocol.PacketNumber, arg3 []byte) []byte {
ret := m.ctrl.Call(m, "Seal", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].([]byte)
return ret0
}
// Seal indicates an expected call of Seal
func (mr *MockAEADMockRecorder) Seal(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Seal", reflect.TypeOf((*MockAEAD)(nil).Seal), arg0, arg1, arg2, arg3)
}

View File

@@ -7,4 +7,3 @@ package mocks
//go:generate sh -c "../mockgen_internal.sh mockackhandler ackhandler/received_packet_handler.go github.com/lucas-clemente/quic-go/internal/ackhandler ReceivedPacketHandler"
//go:generate sh -c "../mockgen_internal.sh mocks congestion.go github.com/lucas-clemente/quic-go/internal/congestion SendAlgorithm"
//go:generate sh -c "../mockgen_internal.sh mocks connection_flow_controller.go github.com/lucas-clemente/quic-go/internal/flowcontrol ConnectionFlowController"
//go:generate sh -c "../mockgen_internal.sh mockcrypto crypto/aead.go github.com/lucas-clemente/quic-go/internal/crypto AEAD"