]> git.apps.os.sepia.ceph.com Git - fscrypt.git/commitdiff
crypto: tests, errors, and descriptor computation
authorJoe Richey joerichey@google.com <joerichey@google.com>
Wed, 24 May 2017 01:41:36 +0000 (18:41 -0700)
committerJoe Richey joerichey@google.com <joerichey@google.com>
Wed, 31 May 2017 19:37:35 +0000 (12:37 -0700)
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

crypto/crypto.go
crypto/crypto_test.go
crypto/key.go
crypto/rand.go
crypto/recovery_test.go

index d11dce286af14fd401f0da7b2c42b3830b4dfd4a..a226f2635f1dee976a69443bf68bd7248e89cdd6 100644 (file)
@@ -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
index 471d3ed4c75176171f7541038e278197da6d3fe1..674baeb2a8f39a65c96076ad6563dd5fb02d8ab1 100644 (file)
@@ -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 {
index 611b453b97bd22f7e7e313f9022654591393d795..bc5ec0f2ccd0f1a7fc245b9cd28cf3bb386e47bf 100644 (file)
@@ -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)
 }
index d9d4cffb9ef759cb0980a51f2670fe39eebb07eb..d2948d0c220ca4ebbf4d087a7df080b4cb437725 100644 (file)
@@ -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)
-}
index 2ee18f0883093cab7b3d4b75bd30bdc19a8bbabe..3e3a50fe742f290e4300aecdf7672e3d8463069c 100644 (file)
@@ -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)