From 70ccdd078e71b36178acf87a88b6ebadf4011266 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Tue, 23 May 2017 18:59:39 -0700 Subject: actions: creating and unlocking protectors This commit adds in the Protector struct to the actions package. This struct represents an unlocked Protector. They can be created from a context or they can be unlocked using some provided data. In either case, the data is provided via a callback mechanism. Change-Id: I066e965b8e8e0feeba61d9c0e4472dd08965cafb --- actions/callback.go | 101 +++++++++++++++++++++ actions/protector.go | 221 ++++++++++++++++++++++++++++++++++++++++++++++ actions/protector_test.go | 74 ++++++++++++++++ 3 files changed, 396 insertions(+) create mode 100644 actions/callback.go create mode 100644 actions/protector.go create mode 100644 actions/protector_test.go (limited to 'actions') 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") + } +} -- cgit v1.2.3