blob: 43726e877b8b3cff824daa1c419820b3be216bea [file] [log] [blame]
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"fmt"
"strconv"
)
// rekeyAEAD holds the necessary information for an AEAD based on
// AES-GCM that performs nonce-based key derivation and XORs the
// nonce with a random mask.
type rekeyAEAD struct {
kdfKey []byte
kdfCounter []byte
nonceMask []byte
nonceBuf []byte
gcmAEAD cipher.AEAD
}
// KeySizeError signals that the given key does not have the correct size.
type KeySizeError int
func (k KeySizeError) Error() string {
return "alts/conn: invalid key size " + strconv.Itoa(int(k))
}
// newRekeyAEAD creates a new instance of aes128gcm with rekeying.
// The key argument should be 44 bytes, the first 32 bytes are used as a key
// for HKDF-expand and the remainining 12 bytes are used as a random mask for
// the counter.
func newRekeyAEAD(key []byte) (*rekeyAEAD, error) {
k := len(key)
if k != kdfKeyLen+nonceLen {
return nil, KeySizeError(k)
}
return &rekeyAEAD{
kdfKey: key[:kdfKeyLen],
kdfCounter: make([]byte, kdfCounterLen),
nonceMask: key[kdfKeyLen:],
nonceBuf: make([]byte, nonceLen),
gcmAEAD: nil,
}, nil
}
// Seal rekeys if nonce[2:8] is different than in the last call, masks the nonce,
// and calls Seal for aes128gcm.
func (s *rekeyAEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if err := s.rekeyIfRequired(nonce); err != nil {
panic(fmt.Sprintf("Rekeying failed with: %s", err.Error()))
}
maskNonce(s.nonceBuf, nonce, s.nonceMask)
return s.gcmAEAD.Seal(dst, s.nonceBuf, plaintext, additionalData)
}
// Open rekeys if nonce[2:8] is different than in the last call, masks the nonce,
// and calls Open for aes128gcm.
func (s *rekeyAEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if err := s.rekeyIfRequired(nonce); err != nil {
return nil, err
}
maskNonce(s.nonceBuf, nonce, s.nonceMask)
return s.gcmAEAD.Open(dst, s.nonceBuf, ciphertext, additionalData)
}
// rekeyIfRequired creates a new aes128gcm AEAD if the existing AEAD is nil
// or cannot be used with given nonce.
func (s *rekeyAEAD) rekeyIfRequired(nonce []byte) error {
newKdfCounter := nonce[kdfCounterOffset : kdfCounterOffset+kdfCounterLen]
if s.gcmAEAD != nil && bytes.Equal(newKdfCounter, s.kdfCounter) {
return nil
}
copy(s.kdfCounter, newKdfCounter)
a, err := aes.NewCipher(hkdfExpand(s.kdfKey, s.kdfCounter))
if err != nil {
return err
}
s.gcmAEAD, err = cipher.NewGCM(a)
return err
}
// maskNonce XORs the given nonce with the mask and stores the result in dst.
func maskNonce(dst, nonce, mask []byte) {
nonce1 := binary.LittleEndian.Uint64(nonce[:sizeUint64])
nonce2 := binary.LittleEndian.Uint32(nonce[sizeUint64:])
mask1 := binary.LittleEndian.Uint64(mask[:sizeUint64])
mask2 := binary.LittleEndian.Uint32(mask[sizeUint64:])
binary.LittleEndian.PutUint64(dst[:sizeUint64], nonce1^mask1)
binary.LittleEndian.PutUint32(dst[sizeUint64:], nonce2^mask2)
}
// NonceSize returns the required nonce size.
func (s *rekeyAEAD) NonceSize() int {
return s.gcmAEAD.NonceSize()
}
// Overhead returns the ciphertext overhead.
func (s *rekeyAEAD) Overhead() int {
return s.gcmAEAD.Overhead()
}
// hkdfExpand computes the first 16 bytes of the HKDF-expand function
// defined in RFC5869.
func hkdfExpand(key, info []byte) []byte {
mac := hmac.New(sha256.New, key)
mac.Write(info)
mac.Write([]byte{0x01}[:])
return mac.Sum(nil)[:aeadKeyLen]
}