aboutsummaryrefslogtreecommitdiff
path: root/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'crypto')
-rw-r--r--crypto/crypto.go168
-rw-r--r--crypto/crypto_test.go209
-rw-r--r--crypto/key.go3
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.