diff options
Diffstat (limited to 'crypto')
| -rw-r--r-- | crypto/crypto.go | 90 | ||||
| -rw-r--r-- | crypto/crypto_test.go | 124 | ||||
| -rw-r--r-- | crypto/key.go | 60 | ||||
| -rw-r--r-- | crypto/rand.go | 48 | ||||
| -rw-r--r-- | crypto/recovery_test.go | 50 |
5 files changed, 272 insertions, 100 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 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) |