aboutsummaryrefslogtreecommitdiff
path: root/actions
diff options
context:
space:
mode:
Diffstat (limited to 'actions')
-rw-r--r--actions/callback.go101
-rw-r--r--actions/protector.go221
-rw-r--r--actions/protector_test.go74
3 files changed, 396 insertions, 0 deletions
diff --git a/actions/callback.go b/actions/callback.go
new file mode 100644
index 0000000..c1d2c8a
--- /dev/null
+++ b/actions/callback.go
@@ -0,0 +1,101 @@
+/*
+ * callback.go - defines how the caller of an action function passes along a key
+ * to be used in this package.
+ *
+ * Copyright 2017 Google Inc.
+ * Author: Joe Richey (joerichey@google.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package actions
+
+import (
+ "fscrypt/crypto"
+ "fscrypt/metadata"
+ "log"
+)
+
+// ProtectorData is the information a caller will receive about a Protector
+// before they have to return the corresponding key. This is currently a
+// read-only view of metadata.ProtectorData.
+type ProtectorData interface {
+ GetProtectorDescriptor() string
+ GetSource() metadata.SourceType
+ GetName() string
+ GetUid() int64
+}
+
+// KeyCallback is passed to a function that will require a key from the caller.
+// For passphrase sources, the returned key should be a password. For raw
+// sources, the returned key should be a standard cryptographic key. Consumers
+// of the callback will wipe the provided key. If the callback returns an error,
+// the function to which the callback is passed returns that error. Note that
+// when using the key to unwrap a known key, the callback will be executed until
+// the correct key is returned or an error is returned.
+type KeyCallback func(data ProtectorData) (*crypto.Key, error)
+
+// getWrappingKey uses the provided callback to get the wrapping key
+// corresponding to the ProtectorData. This runs the passphrase hash for
+// passphrase sources or just relays the callback for raw sources.
+func getWrappingKey(data *metadata.ProtectorData, callback KeyCallback) (*crypto.Key, error) {
+ // We don't need to go anything for raw keys
+ if data.Source == metadata.SourceType_raw_key {
+ return callback(data)
+ }
+
+ // Run the passphrase hash for other sources.
+ passphrase, err := callback(data)
+ if err != nil {
+ return nil, err
+ }
+ defer passphrase.Wipe()
+
+ log.Printf("running passphrase hash for protector %s", data.ProtectorDescriptor)
+ return crypto.PassphraseHash(passphrase, data.Salt, data.Costs)
+}
+
+// unwrapProtectorKey uses the provided callback and protector data to return
+// the unwrapped protector key. This will repeatedly use the callback to get the
+// wrapping key until the correct key is returned or an error is returned.
+func unwrapProtectorKey(data *metadata.ProtectorData, callback KeyCallback) (*crypto.Key, error) {
+ for {
+ wrappingKey, err := getWrappingKey(data, callback)
+ if err != nil {
+ return nil, err
+ }
+
+ protectorKey, err := crypto.Unwrap(wrappingKey, data.WrappedKey)
+ wrappingKey.Wipe()
+ switch err {
+ case nil:
+ log.Printf("valid wrapping key for protector %s", data.ProtectorDescriptor)
+ return protectorKey, nil
+ case crypto.ErrBadAuth:
+ log.Printf("invalid wrapping key for protector %s", data.ProtectorDescriptor)
+ continue
+ default:
+ return nil, err
+ }
+ }
+}
+
+// PolicyCallback is passed to a function that needs to unlock a policy. The
+// callback is used so that the caller can specify which protector they wish to
+// use to unlock a policy. The descriptor is the KeyDescriptor for the Policy,
+// while for each Protector protecting the policy there is either an entry in
+// protectors (if we were able to read the Protector's data). The PolicyCallback
+// should either return a valid index into protectors corresponding to the
+// desired protector, or an error. If the callback returns an error, the
+// function to which the callback is passed returns that error.
+type PolicyCallback func(descriptor string, protectors []ProtectorData) (int, error)
diff --git a/actions/protector.go b/actions/protector.go
new file mode 100644
index 0000000..c57d016
--- /dev/null
+++ b/actions/protector.go
@@ -0,0 +1,221 @@
+/*
+ * protector.go - functions for dealing with protectors
+ *
+ * Copyright 2017 Google Inc.
+ * Author: Joe Richey (joerichey@google.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package actions
+
+import (
+ "errors"
+ "os"
+
+ "fscrypt/crypto"
+ "fscrypt/metadata"
+)
+
+// Errors relating to Protectors
+var (
+ ErrProtectorName = errors.New("login protectors do not need a name")
+ ErrMissingProtectorName = errors.New("custom protectors must have a name")
+ ErrDuplicateName = errors.New("a protector with this name already exists")
+ ErrDuplicateUID = errors.New("there is already a login protector for this user")
+)
+
+// ListProtectorData creates a slice of all the data for Protectors on the
+// Context's mountpoint.
+func (ctx *Context) ListProtectorData() ([]ProtectorData, error) {
+ descriptors, err := ctx.Mount.ListProtectors()
+ if err != nil {
+ return nil, err
+ }
+
+ data := make([]ProtectorData, len(descriptors))
+ for i, descriptor := range descriptors {
+ data[i], err = ctx.Mount.GetRegularProtector(descriptor)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return data, err
+}
+
+// checkForProtectorWithName returns an error if there is already a protector
+// on the filesystem with a specific name (or if we cannot read the necessary
+// data).
+func (ctx *Context) checkForProtectorWithName(name string) error {
+ protectors, err := ctx.ListProtectorData()
+ if err != nil {
+ return err
+ }
+ for _, protector := range protectors {
+ if protector.GetName() == name {
+ return ErrDuplicateName
+ }
+ }
+ return nil
+}
+
+// checkForProtectorWithUid returns an error if there is already a login
+// protector on the filesystem with a specific UID (or if we cannot read the
+// necessary data).
+func (ctx *Context) checkForProtectorWithUID(uid int64) error {
+ protectors, err := ctx.ListProtectorData()
+ if err != nil {
+ return err
+ }
+ for _, protector := range protectors {
+ if protector.GetSource() == metadata.SourceType_pam_passphrase &&
+ protector.GetUid() == uid {
+ return ErrDuplicateUID
+ }
+ }
+ return nil
+}
+
+// Protector represents an unlocked protector, so it contains the ProtectorData
+// as well as the actual protector key. These unlocked Protectors are necessary
+// to unlock policies and create new polices. As with the key struct, a
+// Protector should be wiped after use.
+type Protector struct {
+ *Context
+ data *metadata.ProtectorData
+ key *crypto.Key
+}
+
+// NewProtector creates a protector with a given name (only for custom and raw
+// protector types) and uses the provided KeyCallback to get the Key. The
+// appropriate data is then stored on the filesystem. On error, nothing is
+// changed on the filesystem.
+func (ctx *Context) NewProtector(name string, callback KeyCallback) (*Protector, error) {
+ if !ctx.Config.IsValid() {
+ return nil, ErrBadConfig
+ }
+
+ // Sanity checks for names
+ if ctx.Config.Source == metadata.SourceType_pam_passphrase {
+ // login protectors don't need a name (we use the username instead)
+ if name != "" {
+ return nil, ErrProtectorName
+ }
+ } else {
+ // non-login protectors need a name (so we can distinguish between them)
+ if name == "" {
+ return nil, ErrMissingProtectorName
+ }
+ // we don't want to duplicate naming
+ if err := ctx.checkForProtectorWithName(name); err != nil {
+ return nil, err
+ }
+ }
+
+ var err error
+ protector := &Protector{
+ Context: ctx,
+ data: &metadata.ProtectorData{
+ Name: name,
+ Source: ctx.Config.Source,
+ },
+ }
+
+ // Extra data is needed for some SourceTypes
+ switch protector.data.Source {
+ case metadata.SourceType_pam_passphrase:
+ // As the pam passphrases are user specific, we also store the
+ // UID for this kind of source.
+ protector.data.Uid = int64(os.Getuid())
+ // Make sure we aren't duplicating protectors
+ if err := ctx.checkForProtectorWithUID(protector.data.Uid); err != nil {
+ return nil, err
+ }
+ fallthrough
+ case metadata.SourceType_custom_passphrase:
+ // Our passphrase sources need costs and a random salt.
+ if protector.data.Salt, err = crypto.NewRandomBuffer(metadata.SaltLen); err != nil {
+ return nil, err
+ }
+
+ protector.data.Costs = ctx.Config.HashCosts
+ }
+
+ // Randomly create the underlying protector key (and wipe if we fail)
+ if protector.key, err = crypto.NewRandomKey(metadata.InternalKeyLen); err != nil {
+ return nil, err
+ }
+ protector.data.ProtectorDescriptor = crypto.ComputeDescriptor(protector.key)
+
+ if err := protector.Rewrap(callback); err != nil {
+ protector.Wipe()
+ return nil, err
+ }
+
+ return protector, nil
+}
+
+// GetProtector retrieves a protector with a specific descriptor. As a key is
+// necessary to unlock this Protector, a KeyCallback must also be provided.
+func (ctx *Context) GetProtector(descriptor string, callback KeyCallback) (*Protector, error) {
+ if !ctx.Config.IsValid() {
+ return nil, ErrBadConfig
+ }
+
+ var err error
+ protector := &Protector{Context: ctx}
+
+ if protector.data, err = ctx.Mount.GetRegularProtector(descriptor); err != nil {
+ return nil, err
+ }
+
+ protector.key, err = unwrapProtectorKey(protector.data, callback)
+ return protector, err
+}
+
+// Rewrap updates the data that is wrapping the Protector Key. This is useful if
+// a user's password has changed, for example. As a key is necessary to rewrap
+// this Protector, a KeyCallback must be provided. If an error is returned, no
+// data has been changed.
+func (protector *Protector) Rewrap(callback KeyCallback) error {
+ wrappingKey, err := getWrappingKey(protector.data, callback)
+ if err != nil {
+ return err
+ }
+
+ // Revert change to wrapped key on failure
+ oldWrappedKey := protector.data.WrappedKey
+ defer func() {
+ wrappingKey.Wipe()
+ if err != nil {
+ protector.data.WrappedKey = oldWrappedKey
+ }
+ }()
+
+ if protector.data.WrappedKey, err = crypto.Wrap(wrappingKey, protector.key); err != nil {
+ return err
+ }
+
+ return protector.Mount.AddProtector(protector.data)
+}
+
+// Wipe wipes a Protector's internal Key
+func (protector *Protector) Wipe() error {
+ return protector.key.Wipe()
+}
+
+// Destroy removes a protector from the filesystem. The internal key should
+// still be wiped with Wipe().
+func (protector *Protector) Destroy() error {
+ return protector.Mount.RemoveProtector(protector.data.ProtectorDescriptor)
+}
diff --git a/actions/protector_test.go b/actions/protector_test.go
new file mode 100644
index 0000000..bb59dba
--- /dev/null
+++ b/actions/protector_test.go
@@ -0,0 +1,74 @@
+/*
+ * protector_test.go - tests for creating protectors
+ *
+ * Copyright 2017 Google Inc.
+ * Author: Joe Richey (joerichey@google.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package actions
+
+import (
+ "bytes"
+ "errors"
+ "testing"
+
+ . "fscrypt/crypto"
+)
+
+const testProtectorName = "my favorite protector"
+const testProtectorName2 = testProtectorName + "2"
+
+var errCallback = errors.New("bad callback")
+
+func goodCallback(data ProtectorData) (*Key, error) {
+ return NewFixedLengthKeyFromReader(bytes.NewReader(timingPassphrase), len(timingPassphrase))
+}
+
+func badCallback(data ProtectorData) (*Key, error) {
+ return nil, errCallback
+}
+
+// Tests that we can create a valid protector.
+func TestNewProtector(t *testing.T) {
+ ctx, err := makeContext()
+ defer cleaupContext()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ p, err := ctx.NewProtector(testProtectorName, goodCallback)
+ if err != nil {
+ t.Error(err)
+ } else {
+ p.Wipe()
+ }
+}
+
+// Tests that a failure in the callback is relayed back to the caller.
+func TestBadCallback(t *testing.T) {
+ ctx, err := makeContext()
+ defer cleaupContext()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ p, err := ctx.NewProtector(testProtectorName, badCallback)
+ if err == nil {
+ p.Wipe()
+ }
+ if err != errCallback {
+ t.Error("callback error was not relayed back to caller")
+ }
+}