mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-15 02:40:23 +00:00
3073409695
In older versions of fscrypt there is a race condition when multiple encrypted cephfs instances are deployed simultaneously. Signed-off-by: NymanRobin <robin.nyman@est.tech>
229 lines
7.8 KiB
Go
229 lines
7.8 KiB
Go
/*
|
|
* crypto.go - Cryptographic algorithms used by the rest of fscrypt.
|
|
*
|
|
* Copyright 2017 Google Inc.
|
|
* Author: Joe Richey (joerichey@google.com)
|
|
*
|
|
* 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 crypto manages all the cryptography for fscrypt. This includes:
|
|
// 1. Key management (key.go)
|
|
// - Securely holding keys in memory
|
|
// - Making recovery keys
|
|
// 2. Randomness (rand.go)
|
|
// 3. Cryptographic algorithms (crypto.go)
|
|
// - encryption (AES256-CTR)
|
|
// - authentication (SHA256-based HMAC)
|
|
// - key stretching (SHA256-based HKDF)
|
|
// - key wrapping/unwrapping (Encrypt then MAC)
|
|
// - passphrase-based key derivation (Argon2id)
|
|
// - key descriptor computation (double SHA512, or HKDF-SHA512)
|
|
package crypto
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"io"
|
|
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/crypto/argon2"
|
|
"golang.org/x/crypto/hkdf"
|
|
|
|
"github.com/google/fscrypt/metadata"
|
|
"github.com/google/fscrypt/util"
|
|
)
|
|
|
|
// Crypto error values
|
|
var (
|
|
ErrBadAuth = errors.New("key authentication check failed")
|
|
ErrRecoveryCode = errors.New("invalid recovery code")
|
|
ErrMlockUlimit = errors.New("could not lock key in memory")
|
|
)
|
|
|
|
// panicInputLength panics if "name" has invalid length (expected != actual)
|
|
func panicInputLength(name string, expected, actual int) {
|
|
if err := util.CheckValidLength(expected, actual); err != nil {
|
|
panic(errors.Wrap(err, name))
|
|
}
|
|
}
|
|
|
|
// checkWrappingKey returns an error if the wrapping key has the wrong length
|
|
func checkWrappingKey(wrappingKey *Key) error {
|
|
err := util.CheckValidLength(metadata.InternalKeyLen, wrappingKey.Len())
|
|
return errors.Wrap(err, "wrapping key")
|
|
}
|
|
|
|
// stretchKey stretches a key of length InternalKeyLen using unsalted HKDF to
|
|
// make two keys of length InternalKeyLen.
|
|
func stretchKey(key *Key) (encKey, authKey *Key) {
|
|
panicInputLength("hkdf key", metadata.InternalKeyLen, key.Len())
|
|
|
|
// The new hkdf function uses the hash and key to create a reader that
|
|
// can be used to securely initialize multiple keys. This means that
|
|
// reads on the hkdf give independent cryptographic keys. The hkdf will
|
|
// also always have enough entropy to read two keys.
|
|
hkdf := hkdf.New(sha256.New, key.data, nil, nil)
|
|
|
|
encKey, err := NewFixedLengthKeyFromReader(hkdf, metadata.InternalKeyLen)
|
|
util.NeverError(err)
|
|
authKey, err = NewFixedLengthKeyFromReader(hkdf, metadata.InternalKeyLen)
|
|
util.NeverError(err)
|
|
|
|
return
|
|
}
|
|
|
|
// aesCTR runs AES256-CTR on the input using the provided key and iv. This
|
|
// function can be used to either encrypt or decrypt input of any size. Note
|
|
// that input and output must be the same size.
|
|
func aesCTR(key *Key, iv, input, output []byte) {
|
|
panicInputLength("aesCTR key", metadata.InternalKeyLen, key.Len())
|
|
panicInputLength("aesCTR iv", metadata.IVLen, len(iv))
|
|
panicInputLength("aesCTR output", len(input), len(output))
|
|
|
|
blockCipher, err := aes.NewCipher(key.data)
|
|
util.NeverError(err) // Key is checked to have correct length
|
|
|
|
stream := cipher.NewCTR(blockCipher, iv)
|
|
stream.XORKeyStream(output, input)
|
|
}
|
|
|
|
// getHMAC returns the SHA256-based HMAC of some data using the provided key.
|
|
func getHMAC(key *Key, data ...[]byte) []byte {
|
|
panicInputLength("hmac key", metadata.InternalKeyLen, key.Len())
|
|
|
|
mac := hmac.New(sha256.New, key.data)
|
|
for _, buffer := range data {
|
|
// SHA256 HMAC should never be unable to write the data
|
|
_, err := mac.Write(buffer)
|
|
util.NeverError(err)
|
|
}
|
|
|
|
return mac.Sum(nil)
|
|
}
|
|
|
|
// Wrap takes a wrapping Key of length InternalKeyLen, and uses it to wrap a
|
|
// secret Key of any length. This wrapping uses a random IV, the encrypted data,
|
|
// and an HMAC to verify the wrapping key was correct. All of this is included
|
|
// in the returned WrappedKeyData structure.
|
|
func Wrap(wrappingKey, secretKey *Key) (*metadata.WrappedKeyData, error) {
|
|
if err := checkWrappingKey(wrappingKey); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data := &metadata.WrappedKeyData{EncryptedKey: make([]byte, secretKey.Len())}
|
|
|
|
// Get random IV
|
|
var err error
|
|
if data.IV, err = NewRandomBuffer(metadata.IVLen); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stretch key for encryption and authentication (unsalted).
|
|
encKey, authKey := stretchKey(wrappingKey)
|
|
defer encKey.Wipe()
|
|
defer authKey.Wipe()
|
|
|
|
// Encrypt the secret and include the HMAC of the output ("Encrypt-then-MAC").
|
|
aesCTR(encKey, data.IV, secretKey.data, data.EncryptedKey)
|
|
|
|
data.Hmac = getHMAC(authKey, data.IV, data.EncryptedKey)
|
|
return data, nil
|
|
}
|
|
|
|
// Unwrap takes a wrapping Key of length InternalKeyLen, and uses it to unwrap
|
|
// the WrappedKeyData to get the unwrapped secret Key. The Wrapped Key data
|
|
// includes an authentication check, so an error will be returned if that check
|
|
// fails.
|
|
func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) {
|
|
if err := checkWrappingKey(wrappingKey); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stretch key for encryption and authentication (unsalted).
|
|
encKey, authKey := stretchKey(wrappingKey)
|
|
defer encKey.Wipe()
|
|
defer authKey.Wipe()
|
|
|
|
// Check validity of the HMAC
|
|
if !hmac.Equal(getHMAC(authKey, data.IV, data.EncryptedKey), data.Hmac) {
|
|
return nil, ErrBadAuth
|
|
}
|
|
|
|
secretKey, err := NewBlankKey(len(data.EncryptedKey))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
aesCTR(encKey, data.IV, data.EncryptedKey, secretKey.data)
|
|
|
|
return secretKey, nil
|
|
}
|
|
|
|
func computeKeyDescriptorV1(key *Key) string {
|
|
h1 := sha512.Sum512(key.data)
|
|
h2 := sha512.Sum512(h1[:])
|
|
length := hex.DecodedLen(metadata.PolicyDescriptorLenV1)
|
|
return hex.EncodeToString(h2[:length])
|
|
}
|
|
|
|
func computeKeyDescriptorV2(key *Key) (string, error) {
|
|
// This algorithm is specified by the kernel. It uses unsalted
|
|
// HKDF-SHA512, where the application-information string is the prefix
|
|
// "fscrypt\0" followed by the HKDF_CONTEXT_KEY_IDENTIFIER byte.
|
|
hkdf := hkdf.New(sha512.New, key.data, nil, []byte("fscrypt\x00\x01"))
|
|
h := make([]byte, hex.DecodedLen(metadata.PolicyDescriptorLenV2))
|
|
if _, err := io.ReadFull(hkdf, h); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(h), nil
|
|
}
|
|
|
|
// ComputeKeyDescriptor computes the descriptor for a given cryptographic key.
|
|
// If policyVersion=1, it uses the first 8 bytes of the double application of
|
|
// SHA512 on the key. Use this for protectors and v1 policy keys.
|
|
// If policyVersion=2, it uses HKDF-SHA512 to compute a key identifier that's
|
|
// compatible with the kernel's key identifiers for v2 policy keys.
|
|
// In both cases, the resulting bytes are formatted as hex.
|
|
func ComputeKeyDescriptor(key *Key, policyVersion int64) (string, error) {
|
|
switch policyVersion {
|
|
case 1:
|
|
return computeKeyDescriptorV1(key), nil
|
|
case 2:
|
|
return computeKeyDescriptorV2(key)
|
|
default:
|
|
return "", errors.Errorf("policy version of %d is invalid", policyVersion)
|
|
}
|
|
}
|
|
|
|
// PassphraseHash uses Argon2id to produce a Key given the passphrase, salt, and
|
|
// hashing costs. This method is designed to take a long time and consume
|
|
// considerable memory. For more information, see the documentation at
|
|
// https://godoc.org/golang.org/x/crypto/argon2.
|
|
func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) (*Key, error) {
|
|
t := uint32(costs.Time)
|
|
m := uint32(costs.Memory)
|
|
p := uint8(costs.Parallelism)
|
|
key := argon2.IDKey(passphrase.data, salt, t, m, p, metadata.InternalKeyLen)
|
|
|
|
hash, err := NewBlankKey(metadata.InternalKeyLen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
copy(hash.data, key)
|
|
return hash, nil
|
|
}
|