diff options
Diffstat (limited to 'crypto')
| -rw-r--r-- | crypto/crypto.go | 168 | ||||
| -rw-r--r-- | crypto/crypto_test.go | 207 | ||||
| -rw-r--r-- | crypto/key.go | 82 | ||||
| -rw-r--r-- | crypto/rand.go | 44 |
4 files changed, 227 insertions, 274 deletions
diff --git a/crypto/crypto.go b/crypto/crypto.go index a85d345..6a719dd 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -18,26 +18,19 @@ */ // Package crypto manages all the cryptography for fscrypt. This includes: -// - Key management (key.go) -// - Securely holding keys in memory -// - Making recovery keys -// - Randomness (rand.go) -// - Cryptographic algorithms (crypto.go) -// - encryption (AES256-CTR) -// - authentication (SHA256-based HMAC) -// - key stretching (SHA256-based HKDF) -// - key wrapping/unwrapping (Encrypt then MAC) -// - passphrase-based key derivation (Argon2id) -// - descriptor computation (double SHA512) +// 1. Key management (key.go) +// - Securely holding keys in memory +// - Making recovery keys +// 2. Randomness (rand.go) +// 3. Cryptographic algorithms (crypto.go) +// - encryption (AES256-CTR) +// - authentication (SHA256-based HMAC) +// - key stretching (SHA256-based HKDF) +// - key wrapping/unwrapping (Encrypt then MAC) +// - passphrase-based key derivation (Argon2id) +// - key descriptor computation (double SHA512, or HKDF-SHA512) package crypto -/* -#cgo LDFLAGS: -largon2 -#include <stdlib.h> // malloc(), free() -#include <argon2.h> -*/ -import "C" - import ( "crypto/aes" "crypto/cipher" @@ -45,9 +38,10 @@ import ( "crypto/sha256" "crypto/sha512" "encoding/hex" - "unsafe" + "io" "github.com/pkg/errors" + "golang.org/x/crypto/argon2" "golang.org/x/crypto/hkdf" "github.com/google/fscrypt/metadata" @@ -56,13 +50,9 @@ import ( // Crypto error values var ( - ErrBadAuth = errors.New("key authentication check failed") - ErrNegitiveLength = errors.New("keys cannot have negative lengths") - ErrRecoveryCode = errors.New("invalid recovery code") - ErrGetrandomFail = util.SystemError("getrandom() failed") - ErrKeyAlloc = util.SystemError("could not allocate memory for key") - ErrKeyFree = util.SystemError("could not free memory of key") - ErrKeyLock = errors.New("could not lock key in memory") + ErrBadAuth = errors.New("key authentication check failed") + ErrRecoveryCode = errors.New("invalid recovery code") + ErrMlockUlimit = errors.New("could not lock key in memory") ) // panicInputLength panics if "name" has invalid length (expected != actual) @@ -78,8 +68,8 @@ func checkWrappingKey(wrappingKey *Key) error { return errors.Wrap(err, "wrapping key") } -// stretchKey stretches a key of length KeyLen using unsalted HKDF to make two -// keys of length KeyLen. +// stretchKey stretches a key of length InternalKeyLen using unsalted HKDF to +// make two keys of length InternalKeyLen. func stretchKey(key *Key) (encKey, authKey *Key) { panicInputLength("hkdf key", metadata.InternalKeyLen, key.Len()) @@ -155,9 +145,10 @@ func Wrap(wrappingKey, secretKey *Key) (*metadata.WrappedKeyData, error) { return data, nil } -// Unwrap takes a wrapping Key of length KeyLen, and uses it to unwrap the -// WrappedKeyData to get the unwrapped secret Key. The Wrapped Key data includes -// an authentication check, so an error will be returned if that check fails. +// Unwrap takes a wrapping Key of length InternalKeyLen, and uses it to unwrap +// the WrappedKeyData to get the unwrapped secret Key. The Wrapped Key data +// includes an authentication check, so an error will be returned if that check +// fails. func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) { if err := checkWrappingKey(wrappingKey); err != nil { return nil, err @@ -173,7 +164,7 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) { return nil, ErrBadAuth } - secretKey, err := newBlankKey(len(data.EncryptedKey)) + secretKey, err := NewBlankKey(len(data.EncryptedKey)) if err != nil { return nil, err } @@ -182,89 +173,56 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) { return secretKey, nil } -// newArgon2Context creates an argon2_context C struct given the hash and -// passphrase keys, salt and costs. The structure must be freed by the caller. -func newArgon2Context(hash, passphrase *Key, - salt []byte, costs *metadata.HashingCosts) *C.argon2_context { - - ctx := (*C.argon2_context)(C.malloc(C.sizeof_argon2_context)) - - ctx.out = (*C.uint8_t)(util.Ptr(hash.data)) - ctx.outlen = C.uint32_t(hash.Len()) - - ctx.pwd = (*C.uint8_t)(util.Ptr(passphrase.data)) - ctx.pwdlen = C.uint32_t(passphrase.Len()) - - ctx.salt = (*C.uint8_t)(util.Ptr(salt)) - ctx.saltlen = C.uint32_t(len(salt)) - - ctx.secret = nil // We don't use the secret field. - ctx.secretlen = 0 - ctx.ad = nil // We don't use the associated data field. - ctx.adlen = 0 - - ctx.t_cost = C.uint32_t(costs.Time) - ctx.m_cost = C.uint32_t(costs.Memory) - ctx.lanes = C.uint32_t(costs.Parallelism) - - ctx.threads = ctx.lanes - ctx.version = C.ARGON2_VERSION_13 - - // We use the built in malloc/free for memory. - ctx.allocate_cbk = nil - ctx.free_cbk = nil - ctx.flags = C.ARGON2_FLAG_CLEAR_PASSWORD - - return ctx -} - -// ComputeDescriptor computes the descriptor for a given cryptographic key. In -// keeping with the process used in e4crypt, this uses the initial bytes -// (formatted as hexadecimal) of the double application of SHA512 on the key. -func ComputeDescriptor(key *Key) string { +func computeKeyDescriptorV1(key *Key) string { h1 := sha512.Sum512(key.data) h2 := sha512.Sum512(h1[:]) - length := hex.DecodedLen(metadata.DescriptorLen) + length := hex.DecodedLen(metadata.PolicyDescriptorLenV1) return hex.EncodeToString(h2[:length]) } -/* -PassphraseHash uses Argon2id to produce a Key given the passphrase, salt, and -hashing costs. This method is designed to take a long time and consume -considerable memory. On success, passphrase will no longer have valid data. -However, the caller should still call passphrase.Wipe(). - -Argon2 is the winning algorithm of the Password Hashing Competition -(see: https://password-hashing.net). It is designed to be "memory hard" -in that a large amount of memory is required to compute the hash value. -This makes it hard to use specialized hardware like GPUs and ASICs. We -use it in "id" mode to provide extra protection against side-channel -attacks. For more info see: https://github.com/P-H-C/phc-winner-argon2 -*/ -func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) (*Key, error) { - if err := util.CheckValidLength(metadata.SaltLen, len(salt)); err != nil { - return nil, errors.Wrap(err, "passphrase hashing salt") - } - if err := costs.CheckValidity(); err != nil { - return nil, errors.Wrap(err, "passphrase hashing costs") +func computeKeyDescriptorV2(key *Key) (string, error) { + // This algorithm is specified by the kernel. It uses unsalted + // HKDF-SHA512, where the application-information string is the prefix + // "fscrypt\0" followed by the HKDF_CONTEXT_KEY_IDENTIFIER byte. + hkdf := hkdf.New(sha512.New, key.data, nil, []byte("fscrypt\x00\x01")) + h := make([]byte, hex.DecodedLen(metadata.PolicyDescriptorLenV2)) + if _, err := io.ReadFull(hkdf, h); err != nil { + return "", err } + return hex.EncodeToString(h), nil +} - // This key will hold the hashing output - hash, err := newBlankKey(metadata.InternalKeyLen) - if err != nil { - return nil, err +// ComputeKeyDescriptor computes the descriptor for a given cryptographic key. +// If policyVersion=1, it uses the first 8 bytes of the double application of +// SHA512 on the key. Use this for protectors and v1 policy keys. +// If policyVersion=2, it uses HKDF-SHA512 to compute a key identifier that's +// compatible with the kernel's key identifiers for v2 policy keys. +// In both cases, the resulting bytes are formatted as hex. +func ComputeKeyDescriptor(key *Key, policyVersion int64) (string, error) { + switch policyVersion { + case 1: + return computeKeyDescriptorV1(key), nil + case 2: + return computeKeyDescriptorV2(key) + default: + return "", errors.Errorf("policy version of %d is invalid", policyVersion) } +} - ctx := newArgon2Context(hash, passphrase, salt, costs) - defer C.free(unsafe.Pointer(ctx)) +// PassphraseHash uses Argon2id to produce a Key given the passphrase, salt, and +// hashing costs. This method is designed to take a long time and consume +// considerable memory. For more information, see the documentation at +// https://godoc.org/golang.org/x/crypto/argon2. +func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) (*Key, error) { + t := uint32(costs.Time) + m := uint32(costs.Memory) + p := uint8(costs.Parallelism) + key := argon2.IDKey(passphrase.data, salt, t, m, p, metadata.InternalKeyLen) - // Run the hashing function (translating the error if there is one) - returnCode := C.argon2id_ctx(ctx) - if returnCode != C.ARGON2_OK { - hash.Wipe() - errorString := C.GoString(C.argon2_error_message(returnCode)) - return nil, util.SystemError("argon2: " + errorString) + hash, err := NewBlankKey(metadata.InternalKeyLen) + if err != nil { + return nil, err } - + copy(hash.data, key) return hash, nil } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 444f847..1fa5a0c 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -30,11 +30,7 @@ import ( "os" "testing" - "golang.org/x/sys/unix" - "github.com/google/fscrypt/metadata" - "github.com/google/fscrypt/security" - "github.com/google/fscrypt/util" ) // Reader that always returns the same byte @@ -53,19 +49,14 @@ func makeKey(b byte, n int) (*Key, error) { } var ( - fakeValidDescriptor = "0123456789abcdef" - fakeSalt = bytes.Repeat([]byte{'a'}, metadata.SaltLen) - fakePassword = []byte("password") - defaultService = unix.FS_KEY_DESC_PREFIX - - fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) - fakeInvalidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen-1) - fakeWrappingKey, _ = makeKey(17, metadata.InternalKeyLen) + fakeSalt = bytes.Repeat([]byte{'a'}, metadata.SaltLen) + fakePassword = []byte("password") - testUser, _ = util.EffectiveUser() + fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) + fakeWrappingKey, _ = makeKey(17, metadata.InternalKeyLen) ) -// As the passpharase hashing function clears the passphrase, we need to make +// As the passphrase hashing function clears the passphrase, we need to make // a new passphrase key for each test func fakePassphraseKey() (*Key, error) { return NewFixedLengthKeyFromReader(bytes.NewReader(fakePassword), len(fakePassword)) @@ -73,7 +64,9 @@ func fakePassphraseKey() (*Key, error) { // Values for test cases pulled from argon2 command line tool. // To generate run: -// echo "password" | argon2 "aaaaaaaaaaaaaaaa" -id -t <t> -m <m> -p <p> -l 32 +// +// echo "password" | argon2 "aaaaaaaaaaaaaaaa" -id -t <t> -m <m> -p <p> -l 32 +// // where costs.Time = <t>, costs.Memory = 2^<m>, and costs.Parallelism = <p>. type hashTestCase struct { costs *metadata.HashingCosts @@ -85,6 +78,12 @@ var hashTestCases = []hashTestCase{ costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 1}, hexHash: "a66f5398e33761bf161fdf1273e99b148f07d88d12d85b7673fddd723f95ec34", }, + // Make sure we maintain our backwards compatible behavior, where + // Parallelism is truncated to 8-bits unless TruncationFixed is true. + { + costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 257}, + hexHash: "a66f5398e33761bf161fdf1273e99b148f07d88d12d85b7673fddd723f95ec34", + }, { costs: &metadata.HashingCosts{Time: 10, Memory: 1 << 10, Parallelism: 1}, hexHash: "5fa2cb89db1f7413ba1776258b7c8ee8c377d122078d28fe1fd645c353787f50", @@ -97,6 +96,15 @@ var hashTestCases = []hashTestCase{ costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 10}, hexHash: "b7c3d7a0be222680b5ea3af3fb1a0b7b02b92cbd7007821dc8b84800c86c7783", }, + { + costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 11, Parallelism: 255}, + hexHash: "d51af3775bbdd0cba31d96fd6d921d9de27f521ceffe667618cd7624f6643071", + }, + // Adding TruncationFixed shouldn't matter if Parallelism < 256. + { + costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 11, Parallelism: 255, TruncationFixed: true}, + hexHash: "d51af3775bbdd0cba31d96fd6d921d9de27f521ceffe667618cd7624f6643071", + }, } // Checks that len(array) == expected @@ -158,7 +166,7 @@ func TestZeroLength(t *testing.T) { } defer key1.Wipe() if key1.data != nil { - t.Error("FIxed length key from reader contained data") + t.Error("Fixed length key from reader contained data") } key2, err := NewKeyFromReader(bytes.NewReader(nil)) @@ -171,7 +179,7 @@ func TestZeroLength(t *testing.T) { } } -// Test that enabling the disabling memory locking succeeds even if a key is +// Test that enabling then 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 @@ -242,47 +250,10 @@ func TestKeyLargeResize(t *testing.T) { } } -// Adds and removes a key with various services. -func TestAddRemoveKeys(t *testing.T) { - for _, service := range []string{defaultService, "ext4:", "f2fs:"} { - validDescription := service + fakeValidDescriptor - if err := InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser); err != nil { - t.Error(err) - } - if err := security.RemoveKey(validDescription, testUser); err != nil { - t.Error(err) - } - } -} - -// Adds a key twice (both should succeed) -func TestAddTwice(t *testing.T) { - validDescription := defaultService + fakeValidDescriptor - InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser) - if InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser) != nil { - t.Error("InsertPolicyKey should not fail if key already exists") - } - security.RemoveKey(validDescription, testUser) -} - -// Makes sure a key fails with bad policy or service -func TestBadAddKeys(t *testing.T) { - validDescription := defaultService + fakeValidDescriptor - if InsertPolicyKey(fakeInvalidPolicyKey, validDescription, testUser) == nil { - security.RemoveKey(validDescription, testUser) - t.Error("InsertPolicyKey should fail with bad policy key") - } - invalidDescription := "ext4" + fakeValidDescriptor - if InsertPolicyKey(fakeValidPolicyKey, invalidDescription, testUser) == nil { - security.RemoveKey(invalidDescription, testUser) - t.Error("InsertPolicyKey should fail with bad service") - } -} - // Check that we can create random keys. All this test does to test the // "randomness" is generate a page of random bytes and attempts compression. // If the data can be compressed it is probably not very random. This isn't -// indented to be a sufficient test for randomness (which is impossible), but a +// intended to be a sufficient test for randomness (which is impossible), but a // way to catch simple regressions (key is all zeros or contains a repeating // pattern). func TestRandomKeyGen(t *testing.T) { @@ -303,7 +274,7 @@ func TestBigKeyGen(t *testing.T) { case nil: key.Wipe() return - case ErrKeyLock: + case ErrMlockUlimit: // Don't fail just because "ulimit -l" is too low. return default: @@ -456,7 +427,7 @@ func TestWrongUnwrappingKeyLength(t *testing.T) { } } -// Wraping twice with the same keys should give different components +// Wrapping twice with the same keys should give different components func TestWrapTwiceDistinct(t *testing.T) { data1, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) if err != nil { @@ -472,14 +443,14 @@ func TestWrapTwiceDistinct(t *testing.T) { } } -// Attempts to Unwrap data with key after altering tweek, should fail -func testFailWithTweek(key *Key, data *metadata.WrappedKeyData, tweek []byte) error { - tweek[0]++ +// Attempts to Unwrap data with key after altering tweak, should fail +func testFailWithTweak(key *Key, data *metadata.WrappedKeyData, tweak []byte) error { + tweak[0]++ key, err := Unwrap(key, data) if err == nil { key.Wipe() } - tweek[0]-- + tweak[0]-- return err } @@ -489,7 +460,7 @@ func TestUnwrapWrongKey(t *testing.T) { if err != nil { t.Fatal(err) } - if testFailWithTweek(fakeWrappingKey, data, fakeWrappingKey.data) == nil { + if testFailWithTweak(fakeWrappingKey, data, fakeWrappingKey.data) == nil { t.Error("using a different wrapping key should make unwrap fail") } } @@ -499,96 +470,92 @@ func TestUnwrapWrongData(t *testing.T) { if err != nil { t.Fatal(err) } - if testFailWithTweek(fakeWrappingKey, data, data.EncryptedKey) == nil { + if testFailWithTweak(fakeWrappingKey, data, data.EncryptedKey) == nil { t.Error("changing encryption key should make unwrap fail") } - if testFailWithTweek(fakeWrappingKey, data, data.IV) == nil { + if testFailWithTweak(fakeWrappingKey, data, data.IV) == nil { t.Error("changing IV should make unwrap fail") } - if testFailWithTweek(fakeWrappingKey, data, data.Hmac) == nil { + if testFailWithTweak(fakeWrappingKey, data, data.Hmac) == nil { t.Error("changing HMAC should make unwrap fail") } } -// Run our test cases for passphrase hashing -func TestPassphraseHashing(t *testing.T) { - for i, testCase := range hashTestCases { - pk, err := fakePassphraseKey() - if err != nil { - t.Fatal(err) - } - defer pk.Wipe() - - hash, err := PassphraseHash(pk, fakeSalt, testCase.costs) - if err != nil { - t.Fatal(err) - } - defer hash.Wipe() - - actual := hex.EncodeToString(hash.data) - if actual != testCase.hexHash { - t.Errorf("Hash test %d: for costs=%+v expected hash of %q got %q", - i, testCase.costs, testCase.hexHash, actual) - } - } -} - -func TestBadTime(t *testing.T) { - pk, err := fakePassphraseKey() +func TestComputeKeyDescriptorV1(t *testing.T) { + descriptor, err := ComputeKeyDescriptor(fakeValidPolicyKey, 1) if err != nil { t.Fatal(err) } - defer pk.Wipe() - - costs := *hashTestCases[0].costs - costs.Time = 0 - _, err = PassphraseHash(pk, fakeSalt, &costs) - if err == nil { - t.Errorf("time cost of %d should be invalid", costs.Time) + if descriptor != "8290608a029c5aae" { + t.Errorf("wrong v1 descriptor: %s", descriptor) } } -func TestBadMemory(t *testing.T) { - pk, err := fakePassphraseKey() +func TestComputeKeyDescriptorV2(t *testing.T) { + descriptor, err := ComputeKeyDescriptor(fakeValidPolicyKey, 2) if err != nil { t.Fatal(err) } - defer pk.Wipe() + if descriptor != "2139f52bf8386ee99845818ac7e91c4a" { + t.Errorf("wrong v2 descriptor: %s", descriptor) + } +} - costs := *hashTestCases[0].costs - costs.Memory = 7 - _, err = PassphraseHash(pk, fakeSalt, &costs) +func TestComputeKeyDescriptorBadVersion(t *testing.T) { + _, err := ComputeKeyDescriptor(fakeValidPolicyKey, 0) if err == nil { - t.Errorf("memory cost of %d should be invalid", costs.Memory) + t.Error("computing key descriptor with bad version should fail") } } -func TestBadParallelism(t *testing.T) { +// Run our test cases for passphrase hashing +func TestPassphraseHashing(t *testing.T) { pk, err := fakePassphraseKey() 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 - _, err = PassphraseHash(pk, fakeSalt, &costs) - if err == nil { - t.Errorf("parallelism cost of %d should be invalid", costs.Parallelism) + for i, testCase := range hashTestCases { + if err := testCase.costs.CheckValidity(); err != nil { + t.Errorf("Hash test %d: for costs=%+v hashing failed: %v", i, testCase.costs, err) + continue + } + hash, err := PassphraseHash(pk, fakeSalt, testCase.costs) + if err != nil { + t.Errorf("Hash test %d: for costs=%+v hashing failed: %v", i, testCase.costs, err) + continue + } + defer hash.Wipe() + + actual := hex.EncodeToString(hash.data) + if actual != testCase.hexHash { + t.Errorf("Hash test %d: for costs=%+v expected hash of %q got %q", + i, testCase.costs, testCase.hexHash, actual) + } } } -func TestBadSalt(t *testing.T) { - pk, err := fakePassphraseKey() - if err != nil { - t.Fatal(err) - } - defer pk.Wipe() +var badCosts = []*metadata.HashingCosts{ + // Bad Time costs + {Time: 0, Memory: 1 << 11, Parallelism: 1}, + {Time: 1 << 33, Memory: 1 << 11, Parallelism: 1}, + // Bad Memory costs + {Time: 1, Memory: 5, Parallelism: 1}, + {Time: 1, Memory: 1 << 33, Parallelism: 1}, + // Bad Parallelism costs + {Time: 1, Memory: 1 << 11, Parallelism: 0, TruncationFixed: false}, + {Time: 1, Memory: 1 << 11, Parallelism: 0, TruncationFixed: true}, + {Time: 1, Memory: 1 << 11, Parallelism: 256, TruncationFixed: false}, + {Time: 1, Memory: 1 << 11, Parallelism: 256, TruncationFixed: true}, + {Time: 1, Memory: 1 << 11, Parallelism: 257, TruncationFixed: true}, +} - _, err = PassphraseHash(pk, []byte{1, 2, 3, 4}, hashTestCases[0].costs) - if err == nil { - t.Error("too short of salt should be invalid") +func TestBadParameters(t *testing.T) { + for i, costs := range badCosts { + if costs.CheckValidity() == nil { + t.Errorf("Hash test %d: expected error for costs=%+v", i, costs) + } } } diff --git a/crypto/key.go b/crypto/key.go index 9bf9098..2e57443 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -33,7 +33,6 @@ import ( "io" "log" "os" - "os/user" "runtime" "unsafe" @@ -41,7 +40,6 @@ import ( "golang.org/x/sys/unix" "github.com/google/fscrypt/metadata" - "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) @@ -94,13 +92,13 @@ type Key struct { data []byte } -// newBlankKey constructs a blank key of a specified length and returns an error +// NewBlankKey constructs a blank key of a specified length and returns an error // if we are unable to allocate or lock the necessary memory. -func newBlankKey(length int) (*Key, error) { +func NewBlankKey(length int) (*Key, error) { if length == 0 { return &Key{data: nil}, nil } else if length < 0 { - return nil, errors.Wrapf(ErrNegitiveLength, "length of %d requested", length) + return nil, errors.Errorf("requested key length %d is negative", length) } flags := keyMmapFlags @@ -111,11 +109,11 @@ 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 == unix.EAGAIN { - return nil, ErrKeyLock + return nil, ErrMlockUlimit } if err != nil { - log.Printf("unix.Mmap() with length=%d failed: %v", length, err) - return nil, ErrKeyAlloc + return nil, errors.Wrapf(err, + "failed to allocate (mmap) key buffer of length %d", length) } key := &Key{data: data} @@ -141,7 +139,7 @@ func (key *Key) Wipe() error { if err := unix.Munmap(data); err != nil { log.Printf("unix.Munmap() failed: %v", err) - return ErrKeyFree + return errors.Wrapf(err, "failed to free (munmap) key buffer") } } return nil @@ -167,7 +165,7 @@ func (key *Key) resize(requestedSize int) (*Key, error) { } defer key.Wipe() - resizedKey, err := newBlankKey(requestedSize) + resizedKey, err := NewBlankKey(requestedSize) if err != nil { return nil, err } @@ -175,6 +173,18 @@ func (key *Key) resize(requestedSize int) (*Key, error) { return resizedKey, nil } +// Data returns a slice of the key's underlying data. Note that this may become +// outdated if the key is resized. +func (key *Key) Data() []byte { + return key.data +} + +// UnsafePtr returns an unsafe pointer to the key's underlying data. Note that +// this will only be valid as long as the key is not resized. +func (key *Key) UnsafePtr() unsafe.Pointer { + return util.Ptr(key.data) +} + // UnsafeToCString makes a copy of the string's data into a null-terminated C // string allocated by C. Note that this method is unsafe as this C copy has no // locking or wiping functionality. The key shouldn't contain any `\0` bytes. @@ -185,12 +195,22 @@ func (key *Key) UnsafeToCString() unsafe.Pointer { return data } +// Clone creates a key as a copy of another one. +func (key *Key) Clone() (*Key, error) { + newKey, err := NewBlankKey(key.Len()) + if err != nil { + return nil, err + } + copy(newKey.data, key.data) + return newKey, nil +} + // NewKeyFromCString creates of a copy of some C string's data in a key. Note // that the original C string is not modified at all, so steps must be taken to // ensure that this original copy is secured. func NewKeyFromCString(str unsafe.Pointer) (*Key, error) { size := C.strlen((*C.char)(str)) - key, err := newBlankKey(int(size)) + key, err := NewBlankKey(int(size)) if err != nil { return nil, err } @@ -198,12 +218,12 @@ func NewKeyFromCString(str unsafe.Pointer) (*Key, error) { return key, nil } -// NewKeyFromReader constructs a key of abritary length by reading from reader +// NewKeyFromReader constructs a key of arbitrary length by reading from reader // until hitting EOF. func NewKeyFromReader(reader io.Reader) (*Key, error) { // Use an initial key size of a page. As Mmap allocates a page anyway, // there isn't much additional overhead from starting with a whole page. - key, err := newBlankKey(os.Getpagesize()) + key, err := NewBlankKey(os.Getpagesize()) if err != nil { return nil, err } @@ -235,7 +255,7 @@ func NewKeyFromReader(reader io.Reader) (*Key, error) { // NewFixedLengthKeyFromReader constructs a key with a specified length by // reading exactly length bytes from reader. func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) { - key, err := newBlankKey(length) + key, err := NewBlankKey(length) if err != nil { return nil, err } @@ -246,30 +266,6 @@ func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) { return key, nil } -// InsertPolicyKey puts the provided policy key into the kernel keyring with the -// provided description, and type logon. The key must be a policy key. -func InsertPolicyKey(key *Key, description string, target *user.User) error { - if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil { - return errors.Wrap(err, "policy key") - } - - // Create our payload (containing an FscryptKey) - payload, err := newBlankKey(int(unsafe.Sizeof(unix.FscryptKey{}))) - if err != nil { - return err - } - defer payload.Wipe() - - // Cast the payload to an FscryptKey so we can initialize the fields. - fscryptKey := (*unix.FscryptKey)(util.Ptr(payload.data)) - // Mode is ignored by the kernel - fscryptKey.Mode = 0 - fscryptKey.Size = metadata.PolicyKeyLen - copy(fscryptKey.Raw[:], key.data) - - return security.InsertKey(payload.data, description, target) -} - var ( // The recovery code is base32 with a dash between each block of 8 characters. encoding = base32.StdEncoding @@ -290,7 +286,7 @@ func WriteRecoveryCode(key *Key, writer io.Writer) error { } // We store the base32 encoded data (without separators) in a temp key - encodedKey, err := newBlankKey(encodedLength) + encodedKey, err := NewBlankKey(encodedLength) if err != nil { return err } @@ -312,13 +308,13 @@ func WriteRecoveryCode(key *Key, writer io.Writer) error { return w.Err() } -// ReadRecoveryCode gets the recovery code from the provided writer and returns +// ReadRecoveryCode gets the recovery code from the provided reader and returns // the corresponding cryptographic key. // 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 ReadRecoveryCode(reader io.Reader) (*Key, error) { // We store the base32 encoded data (without separators) in a temp key - encodedKey, err := newBlankKey(encodedLength) + encodedKey, err := NewBlankKey(encodedLength) if err != nil { return nil, err } @@ -333,7 +329,7 @@ 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) { - err := errors.Wrapf(ErrRecoveryCode, "invalid separator %q", inputSeparator) + err = errors.Wrapf(ErrRecoveryCode, "invalid separator %q", inputSeparator) return nil, err } @@ -347,7 +343,7 @@ func ReadRecoveryCode(reader io.Reader) (*Key, error) { } // Now we decode the key, resizing if necessary - decodedKey, err := newBlankKey(decodedLength) + decodedKey, err := NewBlankKey(decodedLength) if err != nil { return nil, err } diff --git a/crypto/rand.go b/crypto/rand.go index 0778ebd..527f841 100644 --- a/crypto/rand.go +++ b/crypto/rand.go @@ -30,7 +30,8 @@ import ( // 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 +// +// 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 @@ -44,11 +45,43 @@ func NewRandomBuffer(length int) ([]byte, error) { } // NewRandomKey creates a random key of the specified length. This function uses -// the same random number generation process a NewRandomBuffer. +// the same random number generation process as NewRandomBuffer. func NewRandomKey(length int) (*Key, error) { return NewFixedLengthKeyFromReader(randReader{}, length) } +// NewRandomPassphrase creates a random passphrase of the specified length +// containing random alphabetic characters. +func NewRandomPassphrase(length int) (*Key, error) { + chars := []byte("abcdefghijklmnopqrstuvwxyz") + passphrase, err := NewBlankKey(length) + if err != nil { + return nil, err + } + for i := 0; i < length; { + // Get some random bytes. + raw, err := NewRandomKey((length - i) * 2) + if err != nil { + return nil, err + } + // Translate the random bytes into random characters. + for _, b := range raw.data { + if int(b) >= 256-(256%len(chars)) { + // Avoid bias towards the first characters in the list. + continue + } + c := chars[int(b)%len(chars)] + passphrase.data[i] = c + i++ + if i == length { + break + } + } + raw.Wipe() + } + return passphrase, nil +} + // randReader just calls into Getrandom, so no internal data is needed. type randReader struct{} @@ -58,10 +91,9 @@ func (r randReader) Read(buffer []byte) (int, error) { case nil: return n, nil case unix.EAGAIN: - return 0, errors.Wrap(ErrGetrandomFail, "insufficient entropy in pool") + err = errors.New("insufficient entropy in pool") case unix.ENOSYS: - return 0, errors.Wrap(ErrGetrandomFail, "kernel must be v3.17 or later") - default: - return 0, errors.Wrap(ErrGetrandomFail, err.Error()) + err = errors.New("kernel must be v3.17 or later") } + return 0, errors.Wrap(err, "getrandom() failed") } |