aboutsummaryrefslogtreecommitdiff
path: root/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'crypto')
-rw-r--r--crypto/crypto.go168
-rw-r--r--crypto/crypto_test.go207
-rw-r--r--crypto/key.go82
-rw-r--r--crypto/rand.go44
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")
}