aboutsummaryrefslogtreecommitdiff
path: root/metadata/policy.go
diff options
context:
space:
mode:
Diffstat (limited to 'metadata/policy.go')
-rw-r--r--metadata/policy.go166
1 files changed, 166 insertions, 0 deletions
diff --git a/metadata/policy.go b/metadata/policy.go
new file mode 100644
index 0000000..ae8b869
--- /dev/null
+++ b/metadata/policy.go
@@ -0,0 +1,166 @@
+/*
+ * policy.go - Functions for getting and setting policies on a specified
+ * directory or file.
+ *
+ * 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 metadata
+
+import (
+ "encoding/hex"
+ "errors"
+ "os"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+
+ "fscrypt/util"
+)
+
+// DescriptorLen is the length of all Protector and Policy descriptors.
+const DescriptorLen = 2 * unix.FS_KEY_DESCRIPTOR_SIZE
+
+// Encryption specific errors
+var (
+ ErrEncryptionNotSupported = errors.New("filesystem encryption is not supported")
+ ErrEncryptionDisabled = errors.New("filesystem encryption has been disabled in the kernel config")
+ ErrNotEncrypted = errors.New("file or directory not encrypted")
+ ErrEncrypted = errors.New("file or directory already encrypted")
+ ErrBadEncryptionOptions = errors.New("invalid encryption options provided")
+)
+
+// policyIoctl is a wrapper for the ioctl syscall. If opens the file at the path
+// and passes the correct pointers and file descriptors to the IOCTL syscall.
+// This function also takes some of the unclear errors returned by the syscall
+// and translates then into more specific error strings.
+func policyIoctl(path string, request uintptr, policy *unix.FscryptPolicy) error {
+ file, err := os.Open(path)
+ if err != nil {
+ // For PathErrors, we just want the underlying error
+ return util.UnderlyingError(err)
+ }
+ defer file.Close()
+
+ // The returned errno value can sometimes give strange errors, so we
+ // return encryption specific errors.
+ _, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), request, uintptr(unsafe.Pointer(policy)))
+ switch errno {
+ case 0:
+ return nil
+ case unix.ENOTTY:
+ return ErrEncryptionNotSupported
+ case unix.EOPNOTSUPP:
+ return ErrEncryptionDisabled
+ case unix.ENODATA, unix.ENOENT:
+ // ENOENT was returned instead of ENODATA on some filesystems before v4.11.
+ return ErrNotEncrypted
+ case unix.EEXIST:
+ // EINVAL was returned instead of EEXIST on some filesystems before v4.11.
+ return ErrEncrypted
+ default:
+ return errno
+ }
+}
+
+// DefaultOptions use the only supported encryption modes and maximum padding.
+var DefaultOptions = &EncryptionOptions{
+ Padding: 32,
+ ContentsMode: EncryptionMode_XTS,
+ FilenamesMode: EncryptionMode_CTS,
+}
+
+// Maps EncryptionOptions.Padding <-> FscryptPolicy.Flags
+var (
+ paddingArray = []int64{4, 8, 16, 32}
+ flagsArray = []int64{unix.FS_POLICY_FLAGS_PAD_4, unix.FS_POLICY_FLAGS_PAD_8,
+ unix.FS_POLICY_FLAGS_PAD_16, unix.FS_POLICY_FLAGS_PAD_32}
+)
+
+// GetPolicy returns the Policy data for the given directory or file (includes
+// the KeyDescriptor and the encryption options). Returns an error if the
+// path is not encrypted or the policy couldn't be retrieved.
+func GetPolicy(path string) (*PolicyData, error) {
+ var policy unix.FscryptPolicy
+ if err := policyIoctl(path, unix.FS_IOC_GET_ENCRYPTION_POLICY, &policy); err != nil {
+ return nil, err
+ }
+
+ // Convert the padding flag into an amount of padding
+ paddingFlag := int64(policy.Flags & unix.FS_POLICY_FLAGS_PAD_MASK)
+ padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray)
+ if !ok {
+ return nil, util.SystemErrorF("invalid padding flag: %x", paddingFlag)
+ }
+
+ return &PolicyData{
+ KeyDescriptor: hex.EncodeToString(policy.Master_key_descriptor[:]),
+ Options: &EncryptionOptions{
+ Padding: padding,
+ ContentsMode: EncryptionMode(policy.Contents_encryption_mode),
+ FilenamesMode: EncryptionMode(policy.Filenames_encryption_mode),
+ },
+ }, nil
+}
+
+// SetPolicy sets up the specified directory to be encrypted with the specified
+// 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
+ paddingFlag, ok := util.Lookup(data.Options.Padding, paddingArray, flagsArray)
+ if !ok {
+ return util.InvalidInputF("padding of %d", data.Options.Padding)
+ }
+
+ if len(data.KeyDescriptor) != DescriptorLen {
+ return util.InvalidLengthError("policy descriptor", DescriptorLen, len(data.KeyDescriptor))
+ }
+
+ descriptorBytes, err := hex.DecodeString(data.KeyDescriptor)
+ if err != nil {
+ return util.InvalidInputF("policy descriptor of %s: %v", data.KeyDescriptor, err)
+ }
+
+ policy := unix.FscryptPolicy{
+ Version: 0, // Version must always be zero
+ Contents_encryption_mode: uint8(data.Options.ContentsMode),
+ Filenames_encryption_mode: uint8(data.Options.FilenamesMode),
+ Flags: uint8(paddingFlag),
+ }
+ copy(policy.Master_key_descriptor[:], descriptorBytes)
+
+ if err = policyIoctl(path, unix.FS_IOC_SET_ENCRYPTION_POLICY, &policy); err != nil {
+ // Before kernel v4.11, many different errors all caused unix.EINVAL to be returned.
+ // We try to disambiguate this error here. This disambiguation will not always give
+ // the correct error due to a potential race condition on path.
+ if err == unix.EINVAL {
+ // Checking if the path is not a directory
+ if info, err := os.Stat(path); err != nil || !info.IsDir() {
+ return unix.ENOTDIR
+ }
+ // Checking if a policy is already set on this directory
+ if _, err := GetPolicy(path); err == nil {
+ return ErrEncrypted
+ }
+ // Could not get a more detailed error, return generic "bad options".
+ return ErrBadEncryptionOptions
+ }
+ return err
+ }
+
+ return nil
+}