/* * * 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] }