aboutsummaryrefslogtreecommitdiff
path: root/actions/policy.go
blob: 678bcdc9a952786f5166bc25aaa2ec5763d623b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/*
 * protector.go - functions for dealing with policies
 *
 * 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"
	"log"
	"reflect"

	"fscrypt/crypto"
	"fscrypt/filesystem"
	"fscrypt/metadata"
	"fscrypt/util"
)

// Errors relating to Policies
var (
	ErrMissingPolicyMetadata  = util.SystemError("policy for directory has no filesystem metadata; metadata may be corrupted")
	ErrPolicyMetadataMismatch = util.SystemError("policy metadata is inconsistent; metadata may be corrupted")
	ErrPathWrongFilesystem    = errors.New("provided path for policy is on the wrong filesystem")
	ErrDifferentFilesystem    = errors.New("policies may only protect files on the same filesystem")
	ErrOnlyProtector          = errors.New("cannot remove the only protector for a policy")
	ErrAlreadyProtected       = errors.New("this policy is already protected by this protector")
	ErrNotProtected           = errors.New("this policy is not protected by this protector")
)

// PolicyDescriptorForPath returns the policy descriptor for a file on the
// filesystem. An error is returned if the metadata is inconsistent, the path is
// for the wrong filesystem, or the path is not encrypted.
func PolicyDescriptorForPath(ctx *Context, path string) (string, error) {
	if err := ctx.checkContext(); err != nil {
		return "", err
	}
	// Policies and their paths will always be on the same filesystem
	if pathMount, err := filesystem.FindMount(path); err != nil {
		return "", err
	} else if pathMount != ctx.Mount {
		return "", ErrPathWrongFilesystem
	}
	log.Printf("%q is on mountpoint %q", path, ctx.Mount.Path)

	// We double check that the options agree for both the data we get from
	// the path, and the data we get from the mountpoint.
	pathData, err := metadata.GetPolicy(path)
	if err != nil {
		return "", err
	}
	descriptor := pathData.KeyDescriptor
	log.Printf("found policy %s for %q", descriptor, path)

	mountData, err := ctx.Mount.GetPolicy(descriptor)
	if err != nil {
		log.Printf("getting metadata for policy %s: %v", descriptor, err)
		return "", ErrMissingPolicyMetadata
	}
	log.Printf("found data for policy %s on %q", descriptor, ctx.Mount.Path)

	if !reflect.DeepEqual(pathData.Options, mountData.Options) {
		log.Printf("options from path: %+v", pathData.Options)
		log.Printf("options from mount: %+v", mountData.Options)
		return "", ErrPolicyMetadataMismatch
	}
	log.Print("data from filesystem and path agree")

	return descriptor, nil
}

// IsPolicyUnlocked returns a boolean indicating if the corresponding policy for
// this filesystem has its key in the keyring, meaning files and directories
// using this policy can be read and written.
func IsPolicyUnlocked(ctx *Context, policyDescriptor string) bool {
	_, err := crypto.FindPolicyKey(policyDescriptor, getService(ctx))
	return err == nil
}

// LockPolicy removes a policy key from the keyring. This means after unmounting
// and remounting the directory, files and directories using this policy will be
// inaccessible.
func LockPolicy(ctx *Context, policyDescriptor string) error {
	if err := ctx.checkContext(); err != nil {
		return err
	}
	return crypto.RemovePolicyKey(policyDescriptor, getService(ctx))
}

// PurgeAllPolicies removes all policy keys on the filesystem from the kernel
// keyring. In order for this removal to have an effect, the filesystem should
// also be unmounted.
func PurgeAllPolicies(ctx *Context) error {
	if err := ctx.checkContext(); err != nil {
		return err
	}
	policies, err := ctx.Mount.ListPolicies()
	if err != nil {
		return err
	}

	for _, policy := range policies {
		if err := LockPolicy(ctx, policy); err == crypto.ErrKeyringDelete {
			// This means a policy key was present but we could not
			// delete it. The other errors just indicate that the
			// policy key was not present.
			return err
		}
	}
	return nil
}

// getService returns the keyring service for this context. We use the presence
// of the LegacyConfig flag to determine if we should use the legacy services
// (which are necessary for kernels before v4.8).
func getService(ctx *Context) string {
	if ctx.Config.HasCompatibilityOption(LegacyConfig) {
		switch ctx.Mount.Filesystem {
		case "ext4", "f2fs":
			return ctx.Mount.Filesystem + ":"
		}
	}
	return crypto.DefaultService
}

// getPolicyData creates a partially constructed policy by looking up
// the descriptor on the appropriate filesystem. The policy returned will not
// have its key initialized.
func getPolicyData(ctx *Context, descriptor string) (*Policy, error) {
	if err := ctx.checkContext(); err != nil {
		return nil, err
	}
	data, err := ctx.Mount.GetPolicy(descriptor)
	if err != nil {
		return nil, err
	}
	log.Printf("got data for %s from %q", descriptor, ctx.Mount.Path)

	return &Policy{Context: ctx, data: data}, nil
}

// Policy represents an unlocked policy, so it contains the PolicyData as well
// as the actual protector key. These unlocked Polices can then be applied to a
// directory, or have their key material inserted into the keyring (which will
// allow encrypted files to be accessed). As with the key struct, a Policy
// should be wiped after use.
type Policy struct {
	Context *Context
	data    *metadata.PolicyData
	key     *crypto.Key
}

// CreatePolicy creates a Policy protected by given Protector and stores the
// appropriate data on the filesystem. On error, no data is changed on the
// filesystem.
func CreatePolicy(ctx *Context, protector *Protector) (*Policy, error) {
	if err := ctx.checkContext(); err != nil {
		return nil, err
	}
	// Randomly create the underlying policy key (and wipe if we fail)
	key, err := crypto.NewRandomKey(metadata.PolicyKeyLen)
	if err != nil {
		return nil, err
	}

	policy := &Policy{
		Context: ctx,
		data: &metadata.PolicyData{
			Options:       ctx.Config.Options,
			KeyDescriptor: crypto.ComputeDescriptor(key),
		},
		key: key,
	}

	if err = policy.AddProtector(protector); err != nil {
		policy.Wipe()
		return nil, err
	}

	return policy, nil
}

// GetPolicy retrieves a policy with a specific descriptor. As a Protector is
// needed to unlock the policy, callbacks to select the policy and get the key
// are needed. This method will retry the keyFn as necessary to get the correct
// key for the selected protector.
func GetPolicy(ctx *Context, descriptor string, optionFn OptionFunc, keyFn KeyFunc) (*Policy, error) {
	policy, err := getPolicyData(ctx, descriptor)
	if err != nil {
		return nil, err
	}
	return policy, policy.unwrapPolicy(optionFn, keyFn)
}

// listOptions creates a slice of ProtectorOptions for the protectors protecting
// this policy.
func (policy *Policy) listOptions() []*ProtectorOption {
	options := make([]*ProtectorOption, len(policy.data.WrappedPolicyKeys))
	for i, wrappedPolicyKey := range policy.data.WrappedPolicyKeys {
		options[i] = policy.Context.GetProtectorOption(wrappedPolicyKey.ProtectorDescriptor)
	}
	return options
}

// unwrapPolicy initializes the policy key using the provided callbacks.
func (policy *Policy) unwrapPolicy(optionFn OptionFunc, keyFn KeyFunc) error {
	// Create a list of the ProtectorOptions and a list of the wrapped keys.
	options := policy.listOptions()
	wrappedKeys := make([]*metadata.WrappedKeyData, len(policy.data.WrappedPolicyKeys))

	for i, wrappedPolicyKey := range policy.data.WrappedPolicyKeys {
		wrappedKeys[i] = wrappedPolicyKey.WrappedKey
	}

	// The OptionFunc indicates which option and wrapped key we should use.
	idx, err := optionFn(policy.data.KeyDescriptor, options)
	if err != nil {
		return err
	}
	option := options[idx]
	if option.LoadError != nil {
		return option.LoadError
	}

	wrappedPolicyKey := wrappedKeys[idx]
	log.Printf("protector %s selected in callback", option.Descriptor())

	protectorKey, err := unwrapProtectorKey(option.ProtectorInfo, keyFn)
	if err != nil {
		return err
	}
	defer protectorKey.Wipe()

	log.Printf("unwrapping policy %s with protector", policy.data.KeyDescriptor)
	policy.key, err = crypto.Unwrap(protectorKey, wrappedPolicyKey)
	return err
}

// AddProtector updates the data that is wrapping the Policy Key so that the
// provided Protector is now protecting the specified Policy. If an error is
// returned, no data has been changed. If the policy and protector are on
// different filesystems, a link will be created between them.
func (policy *Policy) AddProtector(protector *Protector) error {
	_, err := policy.findWrappedKeyIndex(protector.data.ProtectorDescriptor)
	if err == nil {
		return ErrAlreadyProtected
	}

	// If the protector is on a different filesystem, we need to add a link
	// to it on the policy's filesystem.
	if policy.Context.Mount != protector.Context.Mount {
		err = policy.Context.Mount.AddLinkedProtector(
			protector.data.ProtectorDescriptor, protector.Context.Mount)
		if err != nil {
			return err
		}
	}

	// Create the wrapped policy key
	wrappedPolicyKey := &metadata.WrappedPolicyKey{
		ProtectorDescriptor: protector.data.ProtectorDescriptor,
	}
	if wrappedPolicyKey.WrappedKey, err = crypto.Wrap(protector.key, policy.key); err != nil {
		return err
	}

	// Append the wrapped key to the data
	policy.addKey(wrappedPolicyKey)

	if err = policy.commitData(); err != nil {
		// revert the addition on failure
		policy.removeKey(len(policy.data.WrappedPolicyKeys) - 1)
		return err
	}
	return nil
}

// RemoveProtector updates the data that is wrapping the Policy Key so that the
// provided Protector is no longer protecting the specified Policy. If an error
// is returned, no data has been changed. Note that w do not attempt to remove
// any links (for the case where the protector and policy are on different
// filesystems). This is because one protector may protect many polices.
func (policy *Policy) RemoveProtector(protectorDescriptor string) error {
	idx, err := policy.findWrappedKeyIndex(protectorDescriptor)
	if err != nil {
		return err
	}

	if len(policy.data.WrappedPolicyKeys) == 1 {
		return ErrOnlyProtector
	}

	// Remove the wrapped key from the data
	toRemove := policy.removeKey(idx)

	if err = policy.commitData(); err != nil {
		// revert the removal on failure (order is irrelevant)
		policy.addKey(toRemove)
		return err
	}
	return nil
}

// Apply sets the Policy on a specified directory. Currently we impose the
// additional constraint that policies and the directories they are applied to
// must reside on the same filesystem.
func (policy *Policy) Apply(path string) error {
	if pathMount, err := filesystem.FindMount(path); err != nil {
		return err
	} else if pathMount != policy.Context.Mount {
		return ErrDifferentFilesystem
	}

	return metadata.SetPolicy(path, policy.data)
}

// Unlock provisions the Policy key into the kernel keyring. This allows reading
// and writing of files encrypted with this directory.
func (policy *Policy) Unlock() error {
	return crypto.InsertPolicyKey(policy.key, policy.data.KeyDescriptor, getService(policy.Context))
}

// Wipe wipes a Policy's internal Key. It should always be called after using a
// Policy. This is often done with a defer statement.
func (policy *Policy) Wipe() error {
	return policy.key.Wipe()
}

// Destroy removes a policy from the filesystem. The internal key should still
// be wiped with Wipe().
func (policy *Policy) Destroy() error {
	return policy.Context.Mount.RemovePolicy(policy.data.KeyDescriptor)
}

// commitData writes the Policy's current data to the filesystem
func (policy *Policy) commitData() error {
	return policy.Context.Mount.AddPolicy(policy.data)
}

// findWrappedPolicyKey returns the index of the wrapped policy key
// corresponding to this policy and protector. An error is returned if no
// wrapped policy key corresponds to the specified protector.
func (policy *Policy) findWrappedKeyIndex(protectorDescriptor string) (int, error) {
	for idx, wrappedPolicyKey := range policy.data.WrappedPolicyKeys {
		if wrappedPolicyKey.ProtectorDescriptor == protectorDescriptor {
			return idx, nil
		}
	}

	return 0, ErrNotProtected
}

// addKey adds the wrapped policy key to end of the wrapped key data.
func (policy *Policy) addKey(toAdd *metadata.WrappedPolicyKey) {
	policy.data.WrappedPolicyKeys = append(policy.data.WrappedPolicyKeys, toAdd)
}

// remove removes the wrapped policy key at the specified index. This
// does not preserve the order of the wrapped policy key array. If no index is
// specified the last key is removed.
func (policy *Policy) removeKey(index int) *metadata.WrappedPolicyKey {
	lastIdx := len(policy.data.WrappedPolicyKeys) - 1
	toRemove := policy.data.WrappedPolicyKeys[index]

	// See https://github.com/golang/go/wiki/SliceTricks
	policy.data.WrappedPolicyKeys[index] = policy.data.WrappedPolicyKeys[lastIdx]
	policy.data.WrappedPolicyKeys[lastIdx] = nil
	policy.data.WrappedPolicyKeys = policy.data.WrappedPolicyKeys[:lastIdx]

	return toRemove
}