From 53d15f466a665e4e564af3afdcbcfe9ff1c91331 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 2 Mar 2017 11:47:07 -0800 Subject: [PATCH] crypto: insert key into keyring from go This commit adds in the ability to insert Keys into the kernel keyring from go code. This is done via a patched version of x/sys/unix. We also expose the specific requirements for keys that will be placed in the keyring, namely PolicyKeyLen. The legacy services are also exposed. Change-Id: I177928c9aa676cae13b749042b9a3996e7490f68 --- crypto/crypto_test.go | 28 +++++++++++++++++ crypto/key.go | 69 +++++++++++++++++++++++++++++++++++++++-- metadata/policy.go | 3 +- metadata/policy_test.go | 4 +-- 4 files changed, 99 insertions(+), 5 deletions(-) diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index d76381e..025b5b9 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -40,6 +40,12 @@ func makeKey(b byte, n int) (*Key, error) { return NewFixedLengthKeyFromReader(ConstReader(b), n) } +var fakeValidDescriptor = "0123456789abcdef" +var fakeInvalidDescriptor = "123456789abcdef" + +var fakeValidPolicyKey, _ = makeKey(42, PolicyKeyLen) +var fakeInvalidPolicyKey, _ = makeKey(42, PolicyKeyLen-1) + // Tests the two ways of making keys func TestMakeKeys(t *testing.T) { data := []byte("1234\n6789") @@ -111,3 +117,25 @@ func TestLongLength(t *testing.T) { t.Error("Key contained incorrect data") } } + +// Adds a key with and without legacy (check keyctl to see the key identifiers). +func TestAddKeys(t *testing.T) { + for _, service := range []string{ServiceDefault, ServiceExt4, ServiceF2FS} { + if err := InsertPolicyKey(fakeValidPolicyKey, fakeValidDescriptor, service); err != nil { + t.Error(err) + } + } +} + +// Makes sure a key fails with bad descriptor, policy, or service +func TestBadAddKeys(t *testing.T) { + if InsertPolicyKey(fakeInvalidPolicyKey, fakeValidDescriptor, ServiceDefault) == nil { + t.Error("InsertPolicyKey should fail with bad policy key") + } + if InsertPolicyKey(fakeValidPolicyKey, fakeInvalidDescriptor, ServiceDefault) == nil { + t.Error("InsertPolicyKey should fail with bad descriptor") + } + if InsertPolicyKey(fakeValidPolicyKey, fakeValidDescriptor, "ext4") == nil { + t.Error("InsertPolicyKey should fail with bad service") + } +} diff --git a/crypto/key.go b/crypto/key.go index 88b5a2c..31d4667 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -27,15 +27,31 @@ import ( "golang.org/x/sys/unix" + "fscrypt/metadata" "fscrypt/util" ) +// Service Prefixes for keyring keys. As of kernel v4.8, all filesystems +// supporting encryption will use FS_KEY_DESC_PREFIX to indicate that a key in +// the keyring should be used with filesystem encryption. However, we also +// include the older service prefixes for legacy compatibility. +const ( + ServiceDefault = unix.FS_KEY_DESC_PREFIX + // ServiceExt4 was used before v4.8 for ext4 filesystem encryption. + ServiceExt4 = "ext4:" + // ServiceExt4 was used before v4.6 for F2FS filesystem encryption. + ServiceF2FS = "f2fs:" +) + +// PolicyKeyLen is the length of all keys passed directly to the Keyring +const PolicyKeyLen = unix.FS_MAX_KEY_SIZE + /* UseMlock determines whether we should use the mlock/munlock syscalls to prevent sensitive data like keys and passphrases from being paged to disk. UseMlock defaults to true, but can be set to false if the application calling -into this library has insufficient privileges to lock memory. A package could -also bind this setting to a flag by using: +into this library has insufficient privileges to lock memory. Code using this +package could also bind this setting to a flag by using: flag.BoolVar(&crypto.UseMlock, "lock-memory", true, "lock keys in memory") */ @@ -201,3 +217,52 @@ func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) { } return key, nil } + +// 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) { + // 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 + // of the KEY_SPEC_SESSION_KEYRING, which will return the user session + // keyring if a session keyring does not exist. + keyringID, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, 0) + if err != nil { + return 0, err + } + + return unix.AddKey("logon", description, payload, keyringID) +} + +// 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 len(descriptor) != metadata.DescriptorLen { + return util.InvalidLengthError("Descriptor", metadata.DescriptorLen, len(descriptor)) + } + + // Create our payload (containing an FscryptKey) + payload, err := newBlankKey(unix.SizeofFscryptKey) + 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 = PolicyKeyLen + copy(fscryptKey.Raw[:], key.data) + + if _, err := addPayloadToSessionKeyring(payload.data, service+descriptor); err != nil { + return util.SystemErrorF("inserting key - %s: %v", descriptor, err) + } + + return nil +} diff --git a/metadata/policy.go b/metadata/policy.go index ae8b869..8c67f52 100644 --- a/metadata/policy.go +++ b/metadata/policy.go @@ -120,12 +120,13 @@ func GetPolicy(path string) (*PolicyData, error) { // policy. Returns an error if we cannot set the policy for any reason (not a // directory, invalid options or KeyDescriptor, etc). func SetPolicy(path string, data *PolicyData) error { - // Convert the padding value to a flag and the policyID to a byte array + // Convert the padding value to a flag paddingFlag, ok := util.Lookup(data.Options.Padding, paddingArray, flagsArray) if !ok { return util.InvalidInputF("padding of %d", data.Options.Padding) } + // Convert the policyDescriptor to a byte array if len(data.KeyDescriptor) != DescriptorLen { return util.InvalidLengthError("policy descriptor", DescriptorLen, len(data.KeyDescriptor)) } diff --git a/metadata/policy_test.go b/metadata/policy_test.go index 7f8a48b..593f3da 100644 --- a/metadata/policy_test.go +++ b/metadata/policy_test.go @@ -110,7 +110,7 @@ func TestSetPolicyFile(t *testing.T) { } // Tests that we fail when using bad policies -func TestSetPolicyBadIDs(t *testing.T) { +func TestSetPolicyBadDescriptors(t *testing.T) { // Policies that are too short, have invalid chars, or are too long badDescriptors := []string{"123456789abcde", "xxxxxxxxxxxxxxxx", "0123456789abcdef00"} for _, badDescriptor := range badDescriptors { @@ -121,7 +121,7 @@ func TestSetPolicyBadIDs(t *testing.T) { } if err = SetPolicy(directory, badPolicy); err == nil { - t.Errorf("id %q should have made SetPolicy fail", badDescriptor) + t.Errorf("descriptor %q should have made SetPolicy fail", badDescriptor) } os.RemoveAll(directory) } -- 2.39.5