diff options
| author | Eric Biggers <ebiggers@google.com> | 2019-12-15 19:31:39 -0800 |
|---|---|---|
| committer | Eric Biggers <ebiggers@google.com> | 2020-01-05 10:02:13 -0800 |
| commit | 2b25de6d445faefc28629603dd754aec9f744e60 (patch) | |
| tree | c2e4dd53a2ed370be5b0699ede59538d508d347d /crypto | |
| parent | d0ac36dcea341ff000aca983dd80e7bef9fc30ec (diff) | |
Metadata support for v2 encryption policies
Linux v5.4 and later supports v2 encryption policies. These have
several advantages over v1 encryption policies:
- Their encryption keys can be added/removed to/from the filesystem by
non-root users, thus gaining the benefits of the filesystem keyring
while also retaining support for non-root use.
- They use a more standard, secure, and flexible key derivation
function. Because of this, some future kernel-level fscrypt features
will be implemented for v2 policies only.
- They prevent a denial-of-service attack where a user could associate
the wrong key with another user's encrypted files.
Prepare the fscrypt tool to support v2 encryption policies by:
- Adding a policy_version field to the EncryptionOptions, i.e. to the
config file and to the policy metadata files.
- Using the kernel-specified algorithm to compute the key descriptor for
v2 policies.
- Handling setting and getting v2 policies.
Actually adding/removing the keys for v2 policies to/from the kernel is
left for the next patch.
Diffstat (limited to 'crypto')
| -rw-r--r-- | crypto/crypto.go | 39 | ||||
| -rw-r--r-- | crypto/crypto_test.go | 27 |
2 files changed, 60 insertions, 6 deletions
diff --git a/crypto/crypto.go b/crypto/crypto.go index ec961b6..9a138d0 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -28,7 +28,7 @@ // - key stretching (SHA256-based HKDF) // - key wrapping/unwrapping (Encrypt then MAC) // - passphrase-based key derivation (Argon2id) -// - descriptor computation (double SHA512) +// - key descriptor computation (double SHA512, or HKDF-SHA512) package crypto import ( @@ -38,6 +38,7 @@ import ( "crypto/sha256" "crypto/sha512" "encoding/hex" + "io" "github.com/pkg/errors" "golang.org/x/crypto/argon2" @@ -176,16 +177,42 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) { return secretKey, nil } -// 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]) } +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 +} + +// 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) + } +} + // 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 diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index d0cef82..6eb0b02 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -464,6 +464,33 @@ func TestUnwrapWrongData(t *testing.T) { } } +func TestComputeKeyDescriptorV1(t *testing.T) { + descriptor, err := ComputeKeyDescriptor(fakeValidPolicyKey, 1) + if err != nil { + t.Fatal(err) + } + if descriptor != "8290608a029c5aae" { + t.Errorf("wrong v1 descriptor: %s", descriptor) + } +} + +func TestComputeKeyDescriptorV2(t *testing.T) { + descriptor, err := ComputeKeyDescriptor(fakeValidPolicyKey, 2) + if err != nil { + t.Fatal(err) + } + if descriptor != "2139f52bf8386ee99845818ac7e91c4a" { + t.Errorf("wrong v2 descriptor: %s", descriptor) + } +} + +func TestComputeKeyDescriptorBadVersion(t *testing.T) { + _, err := ComputeKeyDescriptor(fakeValidPolicyKey, 0) + if err == nil { + t.Error("computing key descriptor with bad version should fail") + } +} + // Run our test cases for passphrase hashing func TestPassphraseHashing(t *testing.T) { for i, testCase := range hashTestCases { |