diff options
Diffstat (limited to 'crypto/crypto.go')
| -rw-r--r-- | crypto/crypto.go | 168 |
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 } |