From: Joe Richey joerichey@google.com Date: Wed, 24 May 2017 01:41:36 +0000 (-0700) Subject: crypto: tests, errors, and descriptor computation X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=bc66b8a56ee7ae4f703cf30502aff8b7d68953d0;p=fscrypt.git 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 --- 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 diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 471d3ed..674baeb 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -26,6 +26,7 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "io" "os" "testing" @@ -106,6 +107,7 @@ func TestMakeKeys(t *testing.T) { if err != nil { t.Fatal(err) } + defer key1.Wipe() if !bytes.Equal(data, key1.data) { t.Error("Key from reader contained incorrect data") } @@ -114,6 +116,7 @@ func TestMakeKeys(t *testing.T) { if err != nil { t.Fatal(err) } + defer key2.Wipe() if !bytes.Equal([]byte("1234\n6"), key2.data) { t.Error("Fixed length key from reader contained incorrect data") } @@ -132,8 +135,9 @@ func TestWipe(t *testing.T) { // Making keys with negative length should fail func TestInvalidLength(t *testing.T) { - _, err := NewFixedLengthKeyFromReader(bytes.NewReader([]byte{1, 2, 3, 4}), -1) + key, err := NewFixedLengthKeyFromReader(ConstReader(1), -1) if err == nil { + key.Wipe() t.Error("Negative lengths should cause failure") } } @@ -144,6 +148,7 @@ func TestZeroLength(t *testing.T) { if err != nil { t.Fatal(err) } + defer key1.Wipe() if key1.data != nil { t.Error("FIxed length key from reader contained data") } @@ -152,21 +157,80 @@ func TestZeroLength(t *testing.T) { if err != nil { t.Fatal(err) } + defer key2.Wipe() if key2.data != nil { t.Error("Key from empty reader contained data") } } +// Test that enabling the disabling memory locking succeeds even if a key is +// active when the variable changes. +func TestEnableDisableMemoryLocking(t *testing.T) { + // Mlock on for creation, off for wiping + key, err := NewRandomKey(InternalKeyLen) + UseMlock = false + defer func() { + UseMlock = true + }() + + if err != nil { + t.Fatal(err) + } + if err := key.Wipe(); err != nil { + t.Error(err) + } +} + +// Test that disabling then enabling memory locking succeeds even if a key is +// active when the variable changes. +func TestDisableEnableMemoryLocking(t *testing.T) { + // Mlock off for creation, on for wiping + UseMlock = false + key2, err := NewRandomKey(InternalKeyLen) + UseMlock = true + + if err != nil { + t.Fatal(err) + } + if err := key2.Wipe(); err != nil { + t.Error(err) + } +} + // Test making keys long enough that the keys will have to resize -func TestLongLength(t *testing.T) { - // Key will have to resize 3 times - data := bytes.Repeat([]byte{1}, os.Getpagesize()*5) - key, err := NewKeyFromReader(bytes.NewReader(data)) +func TestKeyResize(t *testing.T) { + // Key will have to resize once + r := io.LimitReader(ConstReader(1), int64(os.Getpagesize())+1) + key, err := NewKeyFromReader(r) + if err != nil { + t.Fatal(err) + } + defer key.Wipe() + for i, b := range key.data { + if b != 1 { + t.Fatalf("Byte %d contained invalid data %q", i, b) + } + } +} + +// Test making keys so long that many resizes are necessary +func TestKeyLargeResize(t *testing.T) { + // Key will have to resize 7 times + r := io.LimitReader(ConstReader(1), int64(os.Getpagesize())*65) + + // Turn off Mlocking as the key will exceed the limit on some systems. + UseMlock = false + key, err := NewKeyFromReader(r) + UseMlock = true + if err != nil { t.Fatal(err) } - if !bytes.Equal(data, key.data) { - t.Error("Key contained incorrect data") + defer key.Wipe() + for i, b := range key.data { + if b != 1 { + t.Fatalf("Byte %d contained invalid data %q", i, b) + } } } @@ -243,6 +307,8 @@ func TestKeysAndOutputsDistinct(t *testing.T) { } encKey, authKey := stretchKey(fakeWrappingKey) + defer encKey.Wipe() + defer authKey.Wipe() if !buffersDistinct(fakeWrappingKey.data, fakeValidPolicyKey.data, encKey.data, authKey.data, data.IV, data.EncryptedKey, data.Hmac) { @@ -320,6 +386,7 @@ func TestDifferentLengthSecretKey(t *testing.T) { if err != nil { t.Fatal(err) } + defer wk.Wipe() for i := 0; i < 100; i++ { sk, err := makeKey(2, i) if err != nil { @@ -359,7 +426,10 @@ func TestWrapTwiceDistinct(t *testing.T) { // Attempts to Unwrap data with key after altering tweek, should fail func testFailWithTweek(key *Key, data *WrappedKeyData, tweek []byte) error { tweek[0]++ - _, err := Unwrap(key, data) + key, err := Unwrap(key, data) + if err == nil { + key.Wipe() + } tweek[0]-- return err } @@ -419,6 +489,8 @@ func TestBadTime(t *testing.T) { if err != nil { t.Fatal(err) } + defer pk.Wipe() + costs := *hashTestCases[0].costs costs.Time = 0 _, err = PassphraseHash(pk, fakeSalt, &costs) @@ -432,6 +504,8 @@ func TestBadMemory(t *testing.T) { if err != nil { t.Fatal(err) } + defer pk.Wipe() + costs := *hashTestCases[0].costs costs.Memory = 7 _, err = PassphraseHash(pk, fakeSalt, &costs) @@ -445,6 +519,8 @@ func TestBadParallelism(t *testing.T) { if err != nil { t.Fatal(err) } + defer pk.Wipe() + costs := *hashTestCases[0].costs costs.Parallelism = 1 << 24 costs.Memory = 1 << 27 // Running n threads requires at least 8*n memory @@ -461,22 +537,36 @@ func BenchmarkWrap(b *testing.B) { } func BenchmarkUnwrap(b *testing.B) { + b.StopTimer() + data, _ := Wrap(fakeWrappingKey, fakeValidPolicyKey) + b.StartTimer() for n := 0; n < b.N; n++ { - Unwrap(fakeWrappingKey, data) + key, err := Unwrap(fakeWrappingKey, data) + if err != nil { + b.Fatal(err) + } + key.Wipe() } } func BenchmarkUnwrapNolock(b *testing.B) { + b.StopTimer() + UseMlock = false defer func() { UseMlock = true }() data, _ := Wrap(fakeWrappingKey, fakeValidPolicyKey) + b.StartTimer() for n := 0; n < b.N; n++ { - _, _ = Unwrap(fakeWrappingKey, data) + key, err := Unwrap(fakeWrappingKey, data) + if err != nil { + b.Fatal(err) + } + key.Wipe() } } @@ -493,12 +583,16 @@ func BenchmarkRandomWrapUnwrap(b *testing.B) { } func benchmarkPassphraseHashing(b *testing.B, costs *HashingCosts) { + b.StopTimer() + + pk, err := fakePassphraseKey() + if err != nil { + b.Fatal(err) + } + defer pk.Wipe() + + b.StartTimer() for n := 0; n < b.N; n++ { - pk, err := fakePassphraseKey() - if err != nil { - b.Fatal(err) - } - defer pk.Wipe() hash, err := PassphraseHash(pk, fakeSalt, costs) hash.Wipe() if err != nil { diff --git a/crypto/key.go b/crypto/key.go index 611b453..bc5ec0f 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -22,9 +22,10 @@ package crypto import ( "bytes" + "crypto/subtle" "encoding/base32" - "fmt" "io" + "log" "os" "runtime" @@ -101,7 +102,8 @@ func newBlankKey(length int) (*Key, error) { if length == 0 { return &Key{data: nil}, nil } else if length < 0 { - return nil, util.InvalidInputF("requested key length %d is negative", length) + log.Printf("key length of %d is invalid", length) + return nil, ErrNegitiveLength } flags := keyMmapFlags @@ -112,7 +114,8 @@ func newBlankKey(length int) (*Key, error) { // See MAP_ANONYMOUS in http://man7.org/linux/man-pages/man2/mmap.2.html data, err := unix.Mmap(-1, 0, length, keyProtection, flags) if err != nil { - return nil, util.SystemErrorF("could not mmap() buffer: %v", err) + log.Printf("unix.Mmap() with length=%d failed: %v", length, err) + return nil, ErrKeyAlloc } key := &Key{data: data} @@ -135,7 +138,8 @@ func (key *Key) Wipe() error { } if err := unix.Munmap(data); err != nil { - return util.SystemErrorF("could not munmap() buffer: %v", err) + log.Printf("unix.Munmap() failed: %v", err) + return ErrKeyFree } } return nil @@ -153,6 +157,12 @@ func (key *Key) UnsafeData() []byte { return key.data } +// Equals compares the contents of two keys, returning true if they have the same +// key data. This function runs in constant time. +func (key *Key) Equals(key2 *Key) bool { + return subtle.ConstantTimeCompare(key.data, key2.data) == 1 +} + // resize returns a new key with size requestedSize and the appropriate data // copied over. The original data is wiped. This method does nothing and returns // itself if the key's length equals requestedSize. @@ -219,8 +229,8 @@ func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) { } // addPayloadToSessionKeyring adds the payload to the current session keyring as -// type logon, returning the key's new ID. -func addPayloadToSessionKeyring(payload []byte, description string) (int, error) { +// type logon, returning an error on failure. +func addPayloadToSessionKeyring(payload []byte, description string) error { // We cannot add directly to KEY_SPEC_SESSION_KEYRING, as that will make // a new session keyring if one does not exist, which will be garbage // collected when the process terminates. Instead, we first get the ID @@ -228,18 +238,25 @@ func addPayloadToSessionKeyring(payload []byte, description string) (int, error) // keyring if a session keyring does not exist. keyringID, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, 0) if err != nil { - return 0, err + log.Printf("unix.KeyctlGetKeyringID failed: %v", err) + log.Print("could not get keyring ID of KEY_SPEC_SESSION_KEYRING") + return ErrKeyringLocate } - return unix.AddKey("logon", description, payload, keyringID) + if _, err = unix.AddKey("logon", description, payload, keyringID); err != nil { + log.Printf("unix.AddKey failed: %v", err) + log.Printf("could not insert %q into keyring (ID = %d)", description, keyringID) + return ErrKeyringInsert + } + return nil } // InsertPolicyKey puts the provided policy key into the kernel keyring with the // provided descriptor, provided service prefix, and type logon. The key and // descriptor must have the appropriate lengths. func InsertPolicyKey(key *Key, descriptor string, service string) error { - if key.Len() != PolicyKeyLen { - return util.InvalidLengthError("Policy Key", PolicyKeyLen, key.Len()) + if key.Len() != metadata.PolicyKeyLen { + return util.InvalidLengthError("Policy Key", metadata.PolicyKeyLen, key.Len()) } if len(descriptor) != metadata.DescriptorLen { @@ -257,11 +274,11 @@ func InsertPolicyKey(key *Key, descriptor string, service string) error { fscryptKey := (*unix.FscryptKey)(util.Ptr(payload.data)) // Mode is ignored by the kernel fscryptKey.Mode = 0 - fscryptKey.Size = PolicyKeyLen + fscryptKey.Size = metadata.PolicyKeyLen copy(fscryptKey.Raw[:], key.data) - if _, err := addPayloadToSessionKeyring(payload.data, service+descriptor); err != nil { - return util.SystemErrorF("inserting key - %s: %v", descriptor, err) + if err := addPayloadToSessionKeyring(payload.data, service+descriptor); err != nil { + return err } return nil @@ -272,7 +289,7 @@ var ( encoding = base32.StdEncoding blockSize = 8 separator = []byte("-") - encodedLength = encoding.EncodedLen(InternalKeyLen) + encodedLength = encoding.EncodedLen(metadata.PolicyKeyLen) decodedLength = encoding.DecodedLen(encodedLength) // RecoveryCodeLength is the number of bytes in every recovery code RecoveryCodeLength = (encodedLength/blockSize)*(blockSize+len(separator)) - len(separator) @@ -282,8 +299,8 @@ var ( // WARNING: This recovery key is enough to derive the original key, so it must // be given the same level of protection as a raw cryptographic key. func WriteRecoveryCode(key *Key, writer io.Writer) error { - if key.Len() != InternalKeyLen { - return util.InvalidLengthError("key", InternalKeyLen, key.Len()) + if key.Len() != metadata.PolicyKeyLen { + return util.InvalidLengthError("key", metadata.PolicyKeyLen, key.Len()) } // We store the base32 encoded data (without separators) in a temp key @@ -330,7 +347,8 @@ func ReadRecoveryCode(reader io.Reader) (*Key, error) { for blockStart := blockSize; blockStart < encodedLength; blockStart += blockSize { r.Read(inputSeparator) if r.Err() == nil && !bytes.Equal(separator, inputSeparator) { - return nil, fmt.Errorf("invalid separator: %q", inputSeparator) + log.Printf("separator of %q is invalid", inputSeparator) + return nil, ErrRecoveryCode } blockEnd := util.MinInt(blockStart+blockSize, encodedLength) @@ -339,7 +357,8 @@ func ReadRecoveryCode(reader io.Reader) (*Key, error) { // If any reads have failed, return the error if r.Err() != nil { - return nil, r.Err() + log.Printf("error while reading recovery code: %v", r.Err()) + return nil, ErrRecoveryCode } // Now we decode the key, resizing if necessary @@ -349,7 +368,8 @@ func ReadRecoveryCode(reader io.Reader) (*Key, error) { } if _, err = encoding.Decode(decodedKey.data, encodedKey.data); err != nil { decodedKey.Wipe() - return nil, err + log.Printf("error decoding recovery code: %v", err) + return nil, ErrRecoveryCode } - return decodedKey.resize(InternalKeyLen) + return decodedKey.resize(metadata.PolicyKeyLen) } diff --git a/crypto/rand.go b/crypto/rand.go index d9d4cff..d2948d0 100644 --- a/crypto/rand.go +++ b/crypto/rand.go @@ -21,25 +21,35 @@ package crypto import ( "io" + "log" "golang.org/x/sys/unix" - - "fscrypt/util" ) -/* -RandReader uses the Linux Getrandom() syscall to read random bytes. If the -operating system has insufficient randomness, the read will fail. This is an -improvement over Go's built-in crypto/rand which will still return bytes if the -system has insufficiency entropy (https://github.com/golang/go/issues/19274). +// NewRandomBuffer uses the Linux Getrandom() syscall to create random bytes. If +// the operating system has insufficient randomness, the buffer creation will +// fail. This is an improvement over Go's built-in crypto/rand which will still +// return bytes if the system has insufficiency entropy. +// See: https://github.com/golang/go/issues/19274 +// +// While this syscall was only introduced in Kernel v3.17, it predates the +// introduction of filesystem encryption, so it introduces no additional +// compatibility issues. +func NewRandomBuffer(length int) ([]byte, error) { + buffer := make([]byte, length) + if _, err := io.ReadFull(randReader{}, buffer); err != nil { + return nil, err + } + return buffer, nil +} -While this syscall was only introduced in Kernel v3.17, it predates the -introduction of filesystem encryption, so it introduces no additional -compatibility issues. -*/ -var RandReader io.Reader = randReader{} +// NewRandomKey creates a random key of the specified length. This function uses +// the same random number generation process a NewRandomBuffer. +func NewRandomKey(length int) (*Key, error) { + return NewFixedLengthKeyFromReader(randReader{}, length) +} -// As we just call into Getrandom, no internal data is needed. +// randReader just calls into Getrandom, so no internal data is needed. type randReader struct{} func (r randReader) Read(buffer []byte) (int, error) { @@ -48,15 +58,11 @@ func (r randReader) Read(buffer []byte) (int, error) { case nil: return n, nil case unix.EAGAIN: - return 0, util.SystemErrorF("entropy pool not yet initialized") + return 0, ErrLowEntropy case unix.ENOSYS: - return 0, util.SystemErrorF("getrandom not implemented; kernel must be v3.17 or later") + return 0, ErrRandNotSupported default: - return 0, util.SystemErrorF("cannot get randomness: %v", err) + log.Printf("unix.Getrandom failed: %v", err) + return 0, ErrRandFailed } } - -// NewRandomKey creates a random key (from RandReader) of the specified length. -func NewRandomKey(length int) (*Key, error) { - return NewFixedLengthKeyFromReader(RandReader, length) -} diff --git a/crypto/recovery_test.go b/crypto/recovery_test.go index 2ee18f0..3e3a50f 100644 --- a/crypto/recovery_test.go +++ b/crypto/recovery_test.go @@ -23,12 +23,13 @@ package crypto import ( "bytes" "fmt" + "fscrypt/metadata" "testing" ) -const fakeSecretRecoveryCode = "EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTA====" +const fakeSecretRecoveryCode = "EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJQ=" -var fakeSecretKey, _ = makeKey(38, InternalKeyLen) +var fakeSecretKey, _ = makeKey(38, metadata.PolicyKeyLen) // Note that this function is INSECURE. FOR TESTING ONLY func getRecoveryCodeFromKey(key *Key) ([]byte, error) { @@ -40,10 +41,11 @@ func getRecoveryCodeFromKey(key *Key) ([]byte, error) { } func getRandomRecoveryCodeBuffer() ([]byte, error) { - key, err := NewRandomKey(InternalKeyLen) + key, err := NewRandomKey(metadata.PolicyKeyLen) if err != nil { return nil, err } + defer key.Wipe() return getRecoveryCodeFromKey(key) } @@ -63,6 +65,7 @@ func testKeyEncodeDecode(key *Key) error { if err != nil { return err } + defer key2.Wipe() if !bytes.Equal(key.data, key2.data) { return fmt.Errorf("encoding then decoding %x didn't yield the same key", key.data) @@ -77,6 +80,7 @@ func testRecoveryDecodeEncode(buf []byte) error { if err != nil { return err } + defer key.Wipe() buf2, err := getRecoveryCodeFromKey(key) if err != nil { @@ -112,10 +116,11 @@ func TestFakeSecretKey(t *testing.T) { } func TestEncodeDecode(t *testing.T) { - key, err := NewRandomKey(InternalKeyLen) + key, err := NewRandomKey(metadata.PolicyKeyLen) if err != nil { t.Fatal(err) } + defer key.Wipe() if err = testKeyEncodeDecode(key); err != nil { t.Error(err) @@ -134,10 +139,11 @@ func TestDecodeEncode(t *testing.T) { } func TestWrongLengthError(t *testing.T) { - key, err := NewRandomKey(InternalKeyLen - 1) + key, err := NewRandomKey(metadata.PolicyKeyLen - 1) if err != nil { t.Fatal(err) } + defer key.Wipe() if _, err = getRecoveryCodeFromKey(key); err == nil { t.Error("key with wrong length should have failed to encode") @@ -146,28 +152,40 @@ func TestWrongLengthError(t *testing.T) { func TestBadCharacterError(t *testing.T) { buf, err := getRandomRecoveryCodeBuffer() + if err != nil { + t.Fatal(err) + } // Lowercase letters not allowed buf[3] = 'k' - if _, err = getKeyFromRecoveryCode(buf); err == nil { + if key, err := getKeyFromRecoveryCode(buf); err == nil { + key.Wipe() t.Error("lowercase letters should make decoding fail") } } func TestBadEndCharacterError(t *testing.T) { buf, err := getRandomRecoveryCodeBuffer() + if err != nil { + t.Fatal(err) + } // Separator must be '-' buf[blockSize] = '_' - if _, err = getKeyFromRecoveryCode(buf); err == nil { + if key, err := getKeyFromRecoveryCode(buf); err == nil { + key.Wipe() t.Error("any separator that isn't '-' should make decoding fail") } } func BenchmarkEncode(b *testing.B) { - key, err := NewRandomKey(InternalKeyLen) + b.StopTimer() + + key, err := NewRandomKey(metadata.PolicyKeyLen) if err != nil { b.Fatal(err) } + defer key.Wipe() + b.StartTimer() for n := 0; n < b.N; n++ { if _, err = getRecoveryCodeFromKey(key); err != nil { b.Fatal(err) @@ -176,24 +194,33 @@ func BenchmarkEncode(b *testing.B) { } func BenchmarkDecode(b *testing.B) { + b.StopTimer() + buf, err := getRandomRecoveryCodeBuffer() if err != nil { b.Fatal(err) } + b.StartTimer() for n := 0; n < b.N; n++ { - if _, err = getKeyFromRecoveryCode(buf); err != nil { + key, err := getKeyFromRecoveryCode(buf) + if err != nil { b.Fatal(err) } + key.Wipe() } } func BenchmarkEncodeDecode(b *testing.B) { - key, err := NewRandomKey(InternalKeyLen) + b.StopTimer() + + key, err := NewRandomKey(metadata.PolicyKeyLen) if err != nil { b.Fatal(err) } + defer key.Wipe() + b.StartTimer() for n := 0; n < b.N; n++ { if err = testKeyEncodeDecode(key); err != nil { b.Fatal(err) @@ -202,11 +229,14 @@ func BenchmarkEncodeDecode(b *testing.B) { } func BenchmarkDecodeEncode(b *testing.B) { + b.StopTimer() + buf, err := getRandomRecoveryCodeBuffer() if err != nil { b.Fatal(err) } + b.StartTimer() for n := 0; n < b.N; n++ { if err = testRecoveryDecodeEncode(buf); err != nil { b.Fatal(err)