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, 63 insertions, 105 deletions
diff --git a/crypto/crypto.go b/crypto/crypto.go
index a85d345..6a719dd 100644
--- a/crypto/crypto.go
+++ b/crypto/crypto.go
@@ -18,26 +18,19 @@
*/
// Package crypto manages all the cryptography for fscrypt. This includes:
-// - Key management (key.go)
-// - Securely holding keys in memory
-// - Making recovery keys
-// - 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)
-// - passphrase-based key derivation (Argon2id)
-// - descriptor computation (double SHA512)
+// 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
-/*
-#cgo LDFLAGS: -largon2
-#include <stdlib.h> // malloc(), free()
-#include <argon2.h>
-*/
-import "C"
-
import (
"crypto/aes"
"crypto/cipher"
@@ -45,9 +38,10 @@ import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
- "unsafe"
+ "io"
"github.com/pkg/errors"
+ "golang.org/x/crypto/argon2"
"golang.org/x/crypto/hkdf"
"github.com/google/fscrypt/metadata"
@@ -56,13 +50,9 @@ import (
// Crypto error values
var (
- ErrBadAuth = errors.New("key authentication check failed")
- ErrNegitiveLength = errors.New("keys cannot have negative lengths")
- ErrRecoveryCode = errors.New("invalid recovery code")
- ErrGetrandomFail = util.SystemError("getrandom() failed")
- ErrKeyAlloc = util.SystemError("could not allocate memory for key")
- ErrKeyFree = util.SystemError("could not free memory of key")
- ErrKeyLock = errors.New("could not lock key in memory")
+ 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)
@@ -78,8 +68,8 @@ func checkWrappingKey(wrappingKey *Key) error {
return errors.Wrap(err, "wrapping key")
}
-// stretchKey stretches a key of length KeyLen using unsalted HKDF to make two
-// keys of length KeyLen.
+// 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())
@@ -155,9 +145,10 @@ func Wrap(wrappingKey, secretKey *Key) (*metadata.WrappedKeyData, error) {
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.
+// 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
@@ -173,7 +164,7 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) {
return nil, ErrBadAuth
}
- secretKey, err := newBlankKey(len(data.EncryptedKey))
+ secretKey, err := NewBlankKey(len(data.EncryptedKey))
if err != nil {
return nil, err
}
@@ -182,89 +173,56 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) {
return secretKey, nil
}
-// newArgon2Context creates an argon2_context C struct given the hash and
-// passphrase keys, salt and costs. The structure must be freed by the caller.
-func newArgon2Context(hash, passphrase *Key,
- salt []byte, costs *metadata.HashingCosts) *C.argon2_context {
-
- ctx := (*C.argon2_context)(C.malloc(C.sizeof_argon2_context))
-
- ctx.out = (*C.uint8_t)(util.Ptr(hash.data))
- ctx.outlen = C.uint32_t(hash.Len())
-
- ctx.pwd = (*C.uint8_t)(util.Ptr(passphrase.data))
- ctx.pwdlen = C.uint32_t(passphrase.Len())
-
- ctx.salt = (*C.uint8_t)(util.Ptr(salt))
- ctx.saltlen = C.uint32_t(len(salt))
-
- ctx.secret = nil // We don't use the secret field.
- ctx.secretlen = 0
- ctx.ad = nil // We don't use the associated data field.
- ctx.adlen = 0
-
- ctx.t_cost = C.uint32_t(costs.Time)
- ctx.m_cost = C.uint32_t(costs.Memory)
- ctx.lanes = C.uint32_t(costs.Parallelism)
-
- ctx.threads = ctx.lanes
- ctx.version = C.ARGON2_VERSION_13
-
- // We use the built in malloc/free for memory.
- ctx.allocate_cbk = nil
- ctx.free_cbk = nil
- ctx.flags = C.ARGON2_FLAG_CLEAR_PASSWORD
-
- return ctx
-}
-
-// ComputeDescriptor computes the descriptor for a given cryptographic key. In
-// keeping with the process used in e4crypt, this uses the initial bytes
-// (formatted as hexadecimal) of the double application of SHA512 on the key.
-func ComputeDescriptor(key *Key) string {
+func computeKeyDescriptorV1(key *Key) string {
h1 := sha512.Sum512(key.data)
h2 := sha512.Sum512(h1[:])
- length := hex.DecodedLen(metadata.DescriptorLen)
+ length := hex.DecodedLen(metadata.PolicyDescriptorLenV1)
return hex.EncodeToString(h2[:length])
}
-/*
-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. On success, passphrase will no longer have valid data.
-However, the caller should still call passphrase.Wipe().
-
-Argon2 is the winning algorithm of the Password Hashing Competition
-(see: https://password-hashing.net). It is designed to be "memory hard"
-in that a large amount of memory is required to compute the hash value.
-This makes it hard to use specialized hardware like GPUs and ASICs. We
-use it in "id" mode to provide extra protection against side-channel
-attacks. For more info see: https://github.com/P-H-C/phc-winner-argon2
-*/
-func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) (*Key, error) {
- if err := util.CheckValidLength(metadata.SaltLen, len(salt)); err != nil {
- return nil, errors.Wrap(err, "passphrase hashing salt")
- }
- if err := costs.CheckValidity(); err != nil {
- return nil, errors.Wrap(err, "passphrase hashing costs")
+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
+}
- // This key will hold the hashing output
- hash, err := newBlankKey(metadata.InternalKeyLen)
- if err != nil {
- return nil, err
+// 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)
}
+}
- ctx := newArgon2Context(hash, passphrase, salt, costs)
- defer C.free(unsafe.Pointer(ctx))
+// 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)
- // Run the hashing function (translating the error if there is one)
- returnCode := C.argon2id_ctx(ctx)
- if returnCode != C.ARGON2_OK {
- hash.Wipe()
- errorString := C.GoString(C.argon2_error_message(returnCode))
- return nil, util.SystemError("argon2: " + errorString)
+ hash, err := NewBlankKey(metadata.InternalKeyLen)
+ if err != nil {
+ return nil, err
}
-
+ copy(hash.data, key)
return hash, nil
}