1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
/*
* 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
}
|