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