diff options
Diffstat (limited to 'crypto')
| -rw-r--r-- | crypto/crypto.go | 168 | ||||
| -rw-r--r-- | crypto/crypto_test.go | 209 | ||||
| -rw-r--r-- | crypto/key.go | 3 |
3 files changed, 377 insertions, 3 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 +} diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 7447e40..6f5c8f0 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -22,6 +22,11 @@ package crypto import ( "bytes" "compress/zlib" + "crypto/aes" + "crypto/sha256" + "fmt" + "fscrypt/metadata" + "fscrypt/util" "os" "testing" ) @@ -46,6 +51,15 @@ var fakeInvalidDescriptor = "123456789abcdef" var fakeValidPolicyKey, _ = makeKey(42, PolicyKeyLen) var fakeInvalidPolicyKey, _ = makeKey(42, PolicyKeyLen-1) +var fakeWrappingKey, _ = makeKey(17, InternalKeyLen) + +// Checks that len(array) == expected +func lengthCheck(name string, array []byte, expected int) error { + if len(array) != expected { + return util.InvalidLengthError(name, expected, len(array)) + } + return nil +} // Tests the two ways of making keys func TestMakeKeys(t *testing.T) { @@ -170,3 +184,198 @@ func didCompress(input []byte) bool { return err == nil && len(input) > output.Len() } + +// Checks that the input arrays are all distinct +func buffersDistinct(buffers ...[]byte) bool { + for i := 0; i < len(buffers); i++ { + for j := i + 1; j < len(buffers); j++ { + if bytes.Equal(buffers[i], buffers[j]) { + // Different entry, but equal arrays + return false + } + } + } + return true +} + +// Checks that our cryptographic operations all produce distinct data +func TestKeysAndOutputsDistinct(t *testing.T) { + data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) + if err != nil { + t.Fatal(err) + } + + encKey, authKey := stretchKey(fakeWrappingKey) + + if !buffersDistinct(fakeWrappingKey.data, fakeValidPolicyKey.data, + encKey.data, authKey.data, data.IV, data.EncryptedKey, data.Hmac) { + t.Error("Key wrapping produced duplicate data") + } +} + +// Check that Wrap() works with fixed keys +func TestWrapSucceeds(t *testing.T) { + data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) + if err != nil { + t.Fatal(err) + } + + if err = lengthCheck("IV", data.IV, aes.BlockSize); err != nil { + t.Error(err) + } + if err = lengthCheck("Encrypted Key", data.EncryptedKey, PolicyKeyLen); err != nil { + t.Error(err) + } + if err = lengthCheck("HMAC", data.Hmac, sha256.Size); err != nil { + t.Error(err) + } +} + +// Checks that applying Wrap then Unwrap gives the original data +func testWrapUnwrapEqual(wrappingKey *Key, secretKey *Key) error { + data, err := Wrap(wrappingKey, secretKey) + if err != nil { + return err + } + + secret, err := Unwrap(wrappingKey, data) + if err != nil { + return err + } + defer secret.Wipe() + + if !bytes.Equal(secretKey.data, secret.data) { + return fmt.Errorf("Got %x after wrap/unwrap with w=%x and s=%x", + secret.data, wrappingKey.data, secretKey.data) + } + return nil +} + +// Check that Unwrap(Wrap(x)) == x with fixed keys +func TestWrapUnwrapEqual(t *testing.T) { + if err := testWrapUnwrapEqual(fakeWrappingKey, fakeValidPolicyKey); err != nil { + t.Error(err) + } +} + +// Check that Unwrap(Wrap(x)) == x with random keys +func TestRandomWrapUnwrapEqual(t *testing.T) { + for i := 0; i < 10; i++ { + wk, err := NewRandomKey(InternalKeyLen) + if err != nil { + t.Fatal(err) + } + sk, err := NewRandomKey(InternalKeyLen) + if err != nil { + t.Fatal(err) + } + if err = testWrapUnwrapEqual(wk, sk); err != nil { + t.Error(err) + } + wk.Wipe() + sk.Wipe() + } +} + +// Check that Unwrap(Wrap(x)) == x with differing lengths of secret key +func TestDifferentLengthSecretKey(t *testing.T) { + wk, err := makeKey(1, InternalKeyLen) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 100; i++ { + sk, err := makeKey(2, i) + if err != nil { + t.Fatal(err) + } + if err = testWrapUnwrapEqual(wk, sk); err != nil { + t.Error(err) + } + sk.Wipe() + } +} + +// Wrong length of wrapping key should fail +func TestWrongWrappingKeyLength(t *testing.T) { + _, err := Wrap(fakeValidPolicyKey, fakeWrappingKey) + if err == nil { + t.Fatal("using a policy key for wrapping should fail") + } +} + +// Wraping twice with the same keys should give different components +func TestWrapTwiceDistinct(t *testing.T) { + data1, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) + if err != nil { + t.Fatal(err) + } + data2, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) + if err != nil { + t.Fatal(err) + } + if !buffersDistinct(data1.IV, data1.EncryptedKey, data1.Hmac, + data2.IV, data2.EncryptedKey, data2.Hmac) { + t.Error("Wrapping same keys twice should give distinct results") + } +} + +// Attempts to Unwrap data with key after altering tweek, should fail +func testFailWithTweek(key *Key, data *metadata.WrappedKeyData, tweek []byte) error { + tweek[0]++ + _, err := Unwrap(key, data) + tweek[0]-- + return err +} + +// Wrapping then unwrapping with different components altered +func TestUnwrapWrongKey(t *testing.T) { + data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) + if err != nil { + t.Fatal(err) + } + if testFailWithTweek(fakeWrappingKey, data, fakeWrappingKey.data) == nil { + t.Error("using a different wrapping key should make unwrap fail") + } +} + +func TestUnwrapWrongData(t *testing.T) { + data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) + if err != nil { + t.Fatal(err) + } + if testFailWithTweek(fakeWrappingKey, data, data.EncryptedKey) == nil { + t.Error("changing encryption key should make unwrap fail") + } + if testFailWithTweek(fakeWrappingKey, data, data.IV) == nil { + t.Error("changing IV should make unwrap fail") + } + if testFailWithTweek(fakeWrappingKey, data, data.Hmac) == nil { + t.Error("changing HMAC should make unwrap fail") + } +} + +func BenchmarkWrap(b *testing.B) { + for n := 0; n < b.N; n++ { + Wrap(fakeWrappingKey, fakeValidPolicyKey) + } +} + +func BenchmarkUnwrap(b *testing.B) { + data, _ := Wrap(fakeWrappingKey, fakeValidPolicyKey) + + for n := 0; n < b.N; n++ { + Unwrap(fakeWrappingKey, data) + } +} + +func BenchmarkRandomWrapUnwrap(b *testing.B) { + for n := 0; n < b.N; n++ { + wk, _ := NewRandomKey(InternalKeyLen) + sk, _ := NewRandomKey(InternalKeyLen) + + testWrapUnwrapEqual(wk, sk) + // Must manually call wipe here, or test will use too much memory. + wk.Wipe() + sk.Wipe() + } +} diff --git a/crypto/key.go b/crypto/key.go index 31d4667..428e89f 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -43,9 +43,6 @@ const ( ServiceF2FS = "f2fs:" ) -// PolicyKeyLen is the length of all keys passed directly to the Keyring -const PolicyKeyLen = unix.FS_MAX_KEY_SIZE - /* UseMlock determines whether we should use the mlock/munlock syscalls to prevent sensitive data like keys and passphrases from being paged to disk. |