diff options
| author | Joe Richey joerichey@google.com <joerichey@google.com> | 2017-05-23 18:41:36 -0700 |
|---|---|---|
| committer | Joe Richey joerichey@google.com <joerichey@google.com> | 2017-05-31 12:37:35 -0700 |
| commit | bc66b8a56ee7ae4f703cf30502aff8b7d68953d0 (patch) | |
| tree | ed58e8e175e3edb86234df5772858adaffe9cab2 /crypto/crypto.go | |
| parent | 44c2c7aeda3de09a405ed06aadacbc2c0c7f2a67 (diff) | |
crypto: tests, errors, and descriptor computation
This changes the crypto package so it now builds in light of the changes
to the util and metadata package. This commit also improves the error
handling, adds tests, and makes it so recovery keys now correspond to
Policy keys (as they are used to recover a directory in the absence of
any metadata).
The only feature addition here is the ability to compute descriptors.
For backwards compatibility, we keep the same descriptor algorithm used
before (double SHA512).
Change-Id: Ia2b53c6e85ce65c57595e6823d3c4c92219bc8dc
Diffstat (limited to 'crypto/crypto.go')
| -rw-r--r-- | crypto/crypto.go | 90 |
1 files changed, 56 insertions, 34 deletions
diff --git a/crypto/crypto.go b/crypto/crypto.go index d11dce2..a226f26 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -29,6 +29,7 @@ // - key stretching (SHA256-based HKDF) // - key wrapping/unwrapping (Encrypt then MAC) // - passphrase-based key derivation (Argon2id) +// - descriptor computation (double SHA512) package crypto /* @@ -43,38 +44,51 @@ import ( "crypto/cipher" "crypto/hmac" "crypto/sha256" - "fmt" - "io" + "crypto/sha512" + "encoding/hex" + "errors" "unsafe" "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 +// Crypto error values +var ( + ErrBadAuth = errors.New("key authentication check failed") + ErrNegitiveLength = errors.New("negative length requested for key") + ErrKeyAlloc = util.SystemError("could not allocate memory for key") + ErrKeyFree = util.SystemError("could not free memory of key") + ErrKeyringLocate = util.SystemError("could not locate the session keyring") + ErrKeyringInsert = util.SystemError("could not insert key into the session keyring") + ErrRecoveryCode = errors.New("provided recovery code had incorrect format") + ErrLowEntropy = util.SystemError("insufficient entropy in pool to generate random bytes") + ErrRandNotSupported = util.SystemError("getrandom() not implemented; kernel must be v3.17 or later") + ErrRandFailed = util.SystemError("cannot get random bytes") ) -// checkInputLength panics if "name" has invalid length (expected != actual) -func checkInputLength(name string, expected, actual int) { +// panicInputLength panics if "name" has invalid length (expected != actual) +func panicInputLength(name string, expected, actual int) { if expected != actual { util.NeverError(util.InvalidLengthError(name, expected, actual)) } } +// checkWrappingKey returns an error if the wrapping key has the wrong length +func checkWrappingKey(wrappingKey *Key) error { + l := wrappingKey.Len() + if l != metadata.InternalKeyLen { + return util.InvalidLengthError("wrapping key", metadata.InternalKeyLen, l) + } + return nil +} + // 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()) + panicInputLength("hkdf key", metadata.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 @@ -82,9 +96,9 @@ func stretchKey(key *Key) (encKey, authKey *Key) { // also always have enough entropy to read two keys. hkdf := hkdf.New(sha256.New, key.data, nil, nil) - encKey, err := NewFixedLengthKeyFromReader(hkdf, InternalKeyLen) + encKey, err := NewFixedLengthKeyFromReader(hkdf, metadata.InternalKeyLen) util.NeverError(err) - authKey, err = NewFixedLengthKeyFromReader(hkdf, InternalKeyLen) + authKey, err = NewFixedLengthKeyFromReader(hkdf, metadata.InternalKeyLen) util.NeverError(err) return @@ -94,9 +108,9 @@ func stretchKey(key *Key) (encKey, authKey *Key) { // 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)) + panicInputLength("aesCTR key", metadata.InternalKeyLen, key.Len()) + panicInputLength("aesCTR iv", metadata.IVLen, len(iv)) + panicInputLength("aesCTR output", len(input), len(output)) blockCipher, err := aes.NewCipher(key.data) util.NeverError(err) // Key is checked to have correct length @@ -107,7 +121,7 @@ func aesCTR(key *Key, iv, input, output []byte) { // getHMAC returns the SHA256-based HMAC of some data using the provided key. func getHMAC(key *Key, data ...[]byte) []byte { - checkInputLength("hmac key", InternalKeyLen, key.Len()) + panicInputLength("hmac key", metadata.InternalKeyLen, key.Len()) mac := hmac.New(sha256.New, key.data) for _, buffer := range data { @@ -124,17 +138,15 @@ func getHMAC(key *Key, data ...[]byte) []byte { // 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()) + err := checkWrappingKey(wrappingKey) + if err != nil { + return nil, err } - data := &metadata.WrappedKeyData{ - IV: make([]byte, IVLen), - EncryptedKey: make([]byte, secretKey.Len()), - } + data := &metadata.WrappedKeyData{EncryptedKey: make([]byte, secretKey.Len())} // Get random IV - if _, err := io.ReadFull(RandReader, data.IV); err != nil { + if data.IV, err = NewRandomBuffer(metadata.IVLen); err != nil { return nil, err } @@ -154,8 +166,8 @@ func Wrap(wrappingKey, secretKey *Key) (*metadata.WrappedKeyData, error) { // 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()) + if err := checkWrappingKey(wrappingKey); err != nil { + return nil, err } // Stretch key for encryption and authentication (unsalted). @@ -165,7 +177,7 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) { // Check validity of the HMAC if !hmac.Equal(getHMAC(authKey, data.IV, data.EncryptedKey), data.Hmac) { - return nil, fmt.Errorf("key authentication check failed") + return nil, ErrBadAuth } secretKey, err := newBlankKey(len(data.EncryptedKey)) @@ -213,6 +225,16 @@ func newArgon2Context(hash, passphrase *Key, 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 { + h1 := sha512.Sum512(key.data) + h2 := sha512.Sum512(h1[:]) + length := hex.DecodedLen(metadata.DescriptorLen) + 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 @@ -227,12 +249,12 @@ 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 len(salt) != SaltLen { - return nil, util.InvalidLengthError("salt", SaltLen, len(salt)) + if len(salt) != metadata.SaltLen { + return nil, util.InvalidLengthError("salt", metadata.SaltLen, len(salt)) } // This key will hold the hashing output - hash, err := newBlankKey(InternalKeyLen) + hash, err := newBlankKey(metadata.InternalKeyLen) if err != nil { return nil, err } @@ -245,7 +267,7 @@ func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) if returnCode != C.ARGON2_OK { hash.Wipe() errorString := C.GoString(C.argon2_error_message(returnCode)) - return nil, util.SystemErrorF("argon2: %s", errorString) + return nil, util.SystemError("argon2: " + errorString) } return hash, nil |