diff options
Diffstat (limited to 'crypto/crypto.go')
| -rw-r--r-- | crypto/crypto.go | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/crypto/crypto.go b/crypto/crypto.go new file mode 100644 index 0000000..5eeff50 --- /dev/null +++ b/crypto/crypto.go @@ -0,0 +1,168 @@ +/* + * 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: +// - Key management (key.go) +// - Securely holding keys in memory +// - Inserting keys into the keyring +// - Randomness (rand.go) +// - Cryptographic algorithms (crypto.go) +// - encryption (AES256-CTR) +// - authentication (SHA256-based HMAC) +// - key stretching (SHA256-based HKDF) +// - key wrapping/unwrapping (Encrypt then MAC) +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/sha256" + "fmt" + "io" + + "golang.org/x/crypto/hkdf" + "golang.org/x/sys/unix" + + "fscrypt/metadata" + "fscrypt/util" +) + +// Lengths for our keys and buffers used for crypto. +const ( + // We always use 256-bit keys internally (compared to 512-bit policy keys). + InternalKeyLen = 32 + IVLen = 16 + SaltLen = 16 + // PolicyKeyLen is the length of all keys passed directly to the Keyring + PolicyKeyLen = unix.FS_MAX_KEY_SIZE +) + +// "name" has invalid length if expected != actual +func checkInputLength(name string, expected, actual int) { + if expected != actual { + util.NeverError(util.InvalidLengthError(name, expected, actual)) + } +} + +// stretchKey stretches a key of length KeyLen using unsalted HKDF to make two +// keys of length KeyLen. +func stretchKey(key *Key) (encKey, authKey *Key) { + checkInputLength("hkdf key", 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, InternalKeyLen) + util.NeverError(err) + authKey, err = NewFixedLengthKeyFromReader(hkdf, InternalKeyLen) + util.NeverError(err) + + return +} + +// 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) { + checkInputLength("aesCTR key", InternalKeyLen, key.Len()) + checkInputLength("aesCTR iv", IVLen, len(iv)) + checkInputLength("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) +} + +// Get a HMAC (with a SHA256-based hash) of some data using the provided key. +func getHMAC(key *Key, data ...[]byte) []byte { + checkInputLength("hmac key", 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 wrappingKey.Len() != InternalKeyLen { + return nil, util.InvalidLengthError("wrapping key", InternalKeyLen, wrappingKey.Len()) + } + + data := &metadata.WrappedKeyData{ + IV: make([]byte, IVLen), + EncryptedKey: make([]byte, secretKey.Len()), + } + + // Get random IV + if _, err := io.ReadFull(RandReader, data.IV); 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 KeyLen, 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 wrappingKey.Len() != InternalKeyLen { + return nil, util.InvalidLengthError("wrapping key", InternalKeyLen, wrappingKey.Len()) + } + + // 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, fmt.Errorf("key authentication check failed") + } + + secretKey, err := newBlankKey(len(data.EncryptedKey)) + if err != nil { + return nil, err + } + aesCTR(encKey, data.IV, data.EncryptedKey, secretKey.data) + + return secretKey, nil +} |