diff options
Diffstat (limited to 'metadata')
| -rw-r--r-- | metadata/checks.go | 71 | ||||
| -rw-r--r-- | metadata/config.go | 54 | ||||
| -rw-r--r-- | metadata/config_test.go | 76 | ||||
| -rw-r--r-- | metadata/constants.go | 20 | ||||
| -rw-r--r-- | metadata/metadata.pb.go | 870 | ||||
| -rw-r--r-- | metadata/metadata.proto | 20 | ||||
| -rw-r--r-- | metadata/policy.go | 325 | ||||
| -rw-r--r-- | metadata/policy_test.go | 66 |
8 files changed, 1101 insertions, 401 deletions
diff --git a/metadata/checks.go b/metadata/checks.go index 4fe4531..d7dea41 100644 --- a/metadata/checks.go +++ b/metadata/checks.go @@ -20,8 +20,11 @@ package metadata import ( - "github.com/golang/protobuf/proto" + "log" + "math" + "github.com/pkg/errors" + "google.golang.org/protobuf/proto" "github.com/google/fscrypt/util" ) @@ -57,20 +60,37 @@ func (s SourceType) CheckValidity() error { return nil } +// MaxParallelism is the maximum allowed value for HashingCosts.Parallelism. +const MaxParallelism = math.MaxUint8 + // CheckValidity ensures the hash costs will be accepted by Argon2. func (h *HashingCosts) CheckValidity() error { if h == nil { return errNotInitialized } - if h.Time <= 0 { - return errors.Errorf("time=%d is not positive", h.Time) + + minP := int64(1) + p := uint8(h.Parallelism) + if h.Parallelism < minP || h.Parallelism > MaxParallelism { + if h.TruncationFixed || p == 0 { + return errors.Errorf("parallelism cost %d is not in range [%d, %d]", + h.Parallelism, minP, MaxParallelism) + } + // Previously we unconditionally casted costs.Parallelism to a uint8, + // so we replicate this behavior for backwards compatibility. + log.Printf("WARNING: Truncating parallelism cost of %d to %d", h.Parallelism, p) } - if h.Parallelism <= 0 { - return errors.Errorf("parallelism=%d is not positive", h.Parallelism) + + minT := int64(1) + maxT := int64(math.MaxUint32) + if h.Time < minT || h.Time > maxT { + return errors.Errorf("time cost %d is not in range [%d, %d]", h.Time, minT, maxT) } - minMemory := 8 * h.Parallelism - if h.Memory < minMemory { - return errors.Errorf("memory=%d is less than minimum (%d)", h.Memory, minMemory) + + minM := 8 * int64(p) + maxM := int64(math.MaxUint32) + if h.Memory < minM || h.Memory > maxM { + return errors.Errorf("memory cost %d KiB is not in range [%d, %d]", h.Memory, minM, maxM) } return nil } @@ -119,7 +139,7 @@ func (p *ProtectorData) CheckValidity() error { if err := p.WrappedKey.CheckValidity(); err != nil { return errors.Wrap(err, "wrapped protector key") } - if err := util.CheckValidLength(DescriptorLen, len(p.ProtectorDescriptor)); err != nil { + if err := util.CheckValidLength(ProtectorDescriptorLen, len(p.ProtectorDescriptor)); err != nil { return errors.Wrap(err, "protector descriptor") } @@ -138,7 +158,17 @@ func (e *EncryptionOptions) CheckValidity() error { if err := e.Contents.CheckValidity(); err != nil { return errors.Wrap(err, "contents encryption mode") } - return errors.Wrap(e.Filenames.CheckValidity(), "filenames encryption mode") + if err := e.Filenames.CheckValidity(); err != nil { + return errors.Wrap(err, "filenames encryption mode") + } + // If PolicyVersion is unset, treat it as 1. + if e.PolicyVersion == 0 { + e.PolicyVersion = 1 + } + if e.PolicyVersion != 1 && e.PolicyVersion != 2 { + return errors.Errorf("policy version of %d is invalid", e.PolicyVersion) + } + return nil } // CheckValidity ensures the fields are valid and have the correct lengths. @@ -152,7 +182,7 @@ func (w *WrappedPolicyKey) CheckValidity() error { if err := util.CheckValidLength(PolicyKeyLen, len(w.WrappedKey.EncryptedKey)); err != nil { return errors.Wrap(err, "encrypted key") } - err := util.CheckValidLength(DescriptorLen, len(w.ProtectorDescriptor)) + err := util.CheckValidLength(ProtectorDescriptorLen, len(w.ProtectorDescriptor)) return errors.Wrap(err, "wrapping protector descriptor") } @@ -167,11 +197,26 @@ func (p *PolicyData) CheckValidity() error { return errors.Wrapf(err, "policy key slot %d", i) } } - if err := util.CheckValidLength(DescriptorLen, len(p.KeyDescriptor)); err != nil { + + if err := p.Options.CheckValidity(); err != nil { + return errors.Wrap(err, "policy options") + } + + var expectedLen int + switch p.Options.PolicyVersion { + case 1: + expectedLen = PolicyDescriptorLenV1 + case 2: + expectedLen = PolicyDescriptorLenV2 + default: + return errors.Errorf("policy version of %d is invalid", p.Options.PolicyVersion) + } + + if err := util.CheckValidLength(expectedLen, len(p.KeyDescriptor)); err != nil { return errors.Wrap(err, "policy key descriptor") } - return errors.Wrap(p.Options.CheckValidity(), "policy options") + return nil } // CheckValidity ensures the Config has all the necessary info for its Source. diff --git a/metadata/config.go b/metadata/config.go index 0f95fbe..65fd7b5 100644 --- a/metadata/config.go +++ b/metadata/config.go @@ -21,53 +21,47 @@ // Package metadata contains all of the on disk structures. // These structures are defined in metadata.proto. The package also // contains functions for manipulating these structures, specifically: -// * Reading and Writing the Config file to disk -// * Getting and Setting Policies for directories -// * Reasonable defaults for a Policy's EncryptionOptions +// - Reading and Writing the Config file to disk +// - Getting and Setting Policies for directories +// - Reasonable defaults for a Policy's EncryptionOptions package metadata import ( "io" - "strings" - "github.com/golang/protobuf/jsonpb" + "google.golang.org/protobuf/encoding/protojson" ) // WriteConfig outputs the Config data as nicely formatted JSON func WriteConfig(config *Config, out io.Writer) error { - m := jsonpb.Marshaler{ - EmitDefaults: true, - EnumsAsInts: false, - Indent: "\t", - OrigName: true, + m := protojson.MarshalOptions{ + Multiline: true, + Indent: "\t", + UseProtoNames: true, + UseEnumNumbers: false, + EmitUnpopulated: true, } - if err := m.Marshal(out, config); err != nil { + bytes, err := m.Marshal(config) + if err != nil { return err } - - _, err := out.Write([]byte{'\n'}) + if _, err = out.Write(bytes); err != nil { + return err + } + _, err = out.Write([]byte{'\n'}) return err } // ReadConfig writes the JSON data into the config structure func ReadConfig(in io.Reader) (*Config, error) { - config := new(Config) - // Allow (and ignore) unknown fields for forwards compatibility. - u := jsonpb.Unmarshaler{ - AllowUnknownFields: true, + bytes, err := io.ReadAll(in) + if err != nil { + return nil, err } - return config, u.Unmarshal(in, config) -} - -// HasCompatibilityOption returns true if the specified string is in the list of -// compatibility options. This assumes the compatibility options are in a comma -// separated string. -func (c *Config) HasCompatibilityOption(option string) bool { - options := strings.Split(c.Compatibility, ",") - for _, o := range options { - if o == option { - return true - } + config := new(Config) + // Discard unknown fields for forwards compatibility. + u := protojson.UnmarshalOptions{ + DiscardUnknown: true, } - return false + return config, u.Unmarshal(bytes, config) } diff --git a/metadata/config_test.go b/metadata/config_test.go index 95afa04..3048757 100644 --- a/metadata/config_test.go +++ b/metadata/config_test.go @@ -21,19 +21,21 @@ package metadata import ( "bytes" - "reflect" + "encoding/json" "testing" + + "google.golang.org/protobuf/proto" ) var testConfig = &Config{ Source: SourceType_custom_passphrase, HashCosts: &HashingCosts{ - Time: 10, - Memory: 1 << 12, - Parallelism: 8, + Time: 10, + Memory: 1 << 12, + Parallelism: 8, + TruncationFixed: true, }, - Compatibility: "", - Options: DefaultOptions, + Options: DefaultOptions, } var testConfigString = `{ @@ -41,17 +43,29 @@ var testConfigString = `{ "hash_costs": { "time": "10", "memory": "4096", - "parallelism": "8" + "parallelism": "8", + "truncation_fixed": true }, - "compatibility": "", "options": { "padding": "32", "contents": "AES_256_XTS", - "filenames": "AES_256_CTS" - } + "filenames": "AES_256_CTS", + "policy_version": "1" + }, + "use_fs_keyring_for_v1_policies": false, + "allow_cross_user_metadata": false } ` +// Used for JSON string comparison while ignoring whitespace +func compact(t testing.TB, s string) string { + var b bytes.Buffer + if err := json.Compact(&b, []byte(s)); err != nil { + t.Fatalf("failed to compact json: %v", err) + } + return b.String() +} + // Makes sure that writing a config and reading it back gives the same thing. func TestWrite(t *testing.T) { var b bytes.Buffer @@ -60,7 +74,7 @@ func TestWrite(t *testing.T) { t.Fatal(err) } t.Logf("json encoded config:\n%s", b.String()) - if b.String() != testConfigString { + if compact(t, b.String()) != compact(t, testConfigString) { t.Errorf("did not match: %s", testConfigString) } } @@ -72,7 +86,45 @@ func TestRead(t *testing.T) { t.Fatal(err) } t.Logf("decoded config:\n%s", cfg) - if !reflect.DeepEqual(cfg, testConfig) { + if !proto.Equal(cfg, testConfig) { t.Errorf("did not match: %s", testConfig) } } + +// Makes sure we can parse a legacy config file that doesn't have the fields +// that were added later and that has the removed "compatibility" field. +func TestOptionalFields(t *testing.T) { + contents := `{ + "source": "custom_passphrase", + "hash_costs": { + "time": "10", + "memory": "4096", + "parallelism": "8" + }, + "compatibility": "legacy", + "options": { + "padding": "32", + "contents": "AES_256_XTS", + "filenames": "AES_256_CTS" + } + } + ` + buf := bytes.NewBufferString(contents) + cfg, err := ReadConfig(buf) + if err != nil { + t.Fatal(err) + } + if cfg.GetUseFsKeyringForV1Policies() { + t.Error("use_fs_keyring_for_v1_policies should be false, but was true") + } + if cfg.Options.PolicyVersion != 0 { + t.Errorf("policy version should be 0, but was %d", cfg.Options.PolicyVersion) + } + if err = cfg.CheckValidity(); err != nil { + t.Error(err) + } + // CheckValidity() should change an unset policy version to 1. + if cfg.Options.PolicyVersion != 1 { + t.Errorf("policy version should be 1 now, but was %d", cfg.Options.PolicyVersion) + } +} diff --git a/metadata/constants.go b/metadata/constants.go index 344f955..fa6b8a7 100644 --- a/metadata/constants.go +++ b/metadata/constants.go @@ -27,8 +27,12 @@ import ( // Lengths for our keys, buffers, and strings used in fscrypt. const ( - // DescriptorLen is the length of all Protector and Policy descriptors. - DescriptorLen = 2 * unix.FS_KEY_DESCRIPTOR_SIZE + // Length of policy descriptor (in hex chars) for v1 encryption policies + PolicyDescriptorLenV1 = 2 * unix.FSCRYPT_KEY_DESCRIPTOR_SIZE + // Length of protector descriptor (in hex chars) + ProtectorDescriptorLen = PolicyDescriptorLenV1 + // Length of policy descriptor (in hex chars) for v2 encryption policies + PolicyDescriptorLenV2 = 2 * unix.FSCRYPT_KEY_IDENTIFIER_SIZE // We always use 256-bit keys internally (compared to 512-bit policy keys). InternalKeyLen = 32 IVLen = 16 @@ -36,15 +40,17 @@ const ( // We use SHA256 for the HMAC, and len(HMAC) == len(hash size). HMACLen = sha256.Size // PolicyKeyLen is the length of all keys passed directly to the Keyring - PolicyKeyLen = unix.FS_MAX_KEY_SIZE + PolicyKeyLen = unix.FSCRYPT_MAX_KEY_SIZE ) var ( - // DefaultOptions use the supported encryption modes and max padding. + // DefaultOptions use the supported encryption modes, max padding, and + // policy version 1. DefaultOptions = &EncryptionOptions{ - Padding: 32, - Contents: EncryptionOptions_AES_256_XTS, - Filenames: EncryptionOptions_AES_256_CTS, + Padding: 32, + Contents: EncryptionOptions_AES_256_XTS, + Filenames: EncryptionOptions_AES_256_CTS, + PolicyVersion: 1, } // DefaultSource is the source we use if none is specified. DefaultSource = SourceType_custom_passphrase diff --git a/metadata/metadata.pb.go b/metadata/metadata.pb.go index 558e8f2..8b030f8 100644 --- a/metadata/metadata.pb.go +++ b/metadata/metadata.pb.go @@ -1,37 +1,48 @@ +// +// metadata.proto - File which contains all of the metadata structures which we +// write to metadata files. Must be compiled with protoc to use the library. +// Compilation can be invoked with go generate. +// +// 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. + +// If the *.proto file is modified, be sure to run "make gen" (at the project +// root) to recreate the *.pb.go file. + // Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc v3.6.1 // source: metadata/metadata.proto -/* -Package metadata is a generated protocol buffer package. - -It is generated from these files: - metadata/metadata.proto - -It has these top-level messages: - HashingCosts - WrappedKeyData - ProtectorData - EncryptionOptions - WrappedPolicyKey - PolicyData - Config -*/ package metadata -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) // Specifies the method in which an outside secret is obtained for a Protector type SourceType int32 @@ -43,379 +54,728 @@ const ( SourceType_raw_key SourceType = 3 ) -var SourceType_name = map[int32]string{ - 0: "default", - 1: "pam_passphrase", - 2: "custom_passphrase", - 3: "raw_key", -} -var SourceType_value = map[string]int32{ - "default": 0, - "pam_passphrase": 1, - "custom_passphrase": 2, - "raw_key": 3, +// Enum value maps for SourceType. +var ( + SourceType_name = map[int32]string{ + 0: "default", + 1: "pam_passphrase", + 2: "custom_passphrase", + 3: "raw_key", + } + SourceType_value = map[string]int32{ + "default": 0, + "pam_passphrase": 1, + "custom_passphrase": 2, + "raw_key": 3, + } +) + +func (x SourceType) Enum() *SourceType { + p := new(SourceType) + *p = x + return p } func (x SourceType) String() string { - return proto.EnumName(SourceType_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SourceType) Descriptor() protoreflect.EnumDescriptor { + return file_metadata_metadata_proto_enumTypes[0].Descriptor() +} + +func (SourceType) Type() protoreflect.EnumType { + return &file_metadata_metadata_proto_enumTypes[0] } -func (SourceType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } -// Type of encryption; should match declarations of unix.FS_ENCRYPTION_MODE +func (x SourceType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SourceType.Descriptor instead. +func (SourceType) EnumDescriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{0} +} + +// Type of encryption; should match declarations of unix.FSCRYPT_MODE type EncryptionOptions_Mode int32 const ( - EncryptionOptions_default EncryptionOptions_Mode = 0 - EncryptionOptions_AES_256_XTS EncryptionOptions_Mode = 1 - EncryptionOptions_AES_256_GCM EncryptionOptions_Mode = 2 - EncryptionOptions_AES_256_CBC EncryptionOptions_Mode = 3 - EncryptionOptions_AES_256_CTS EncryptionOptions_Mode = 4 - EncryptionOptions_AES_128_CBC EncryptionOptions_Mode = 5 - EncryptionOptions_AES_128_CTS EncryptionOptions_Mode = 6 + EncryptionOptions_default EncryptionOptions_Mode = 0 + EncryptionOptions_AES_256_XTS EncryptionOptions_Mode = 1 + EncryptionOptions_AES_256_GCM EncryptionOptions_Mode = 2 + EncryptionOptions_AES_256_CBC EncryptionOptions_Mode = 3 + EncryptionOptions_AES_256_CTS EncryptionOptions_Mode = 4 + EncryptionOptions_AES_128_CBC EncryptionOptions_Mode = 5 + EncryptionOptions_AES_128_CTS EncryptionOptions_Mode = 6 + EncryptionOptions_Adiantum EncryptionOptions_Mode = 9 + EncryptionOptions_AES_256_HCTR2 EncryptionOptions_Mode = 10 ) -var EncryptionOptions_Mode_name = map[int32]string{ - 0: "default", - 1: "AES_256_XTS", - 2: "AES_256_GCM", - 3: "AES_256_CBC", - 4: "AES_256_CTS", - 5: "AES_128_CBC", - 6: "AES_128_CTS", -} -var EncryptionOptions_Mode_value = map[string]int32{ - "default": 0, - "AES_256_XTS": 1, - "AES_256_GCM": 2, - "AES_256_CBC": 3, - "AES_256_CTS": 4, - "AES_128_CBC": 5, - "AES_128_CTS": 6, +// Enum value maps for EncryptionOptions_Mode. +var ( + EncryptionOptions_Mode_name = map[int32]string{ + 0: "default", + 1: "AES_256_XTS", + 2: "AES_256_GCM", + 3: "AES_256_CBC", + 4: "AES_256_CTS", + 5: "AES_128_CBC", + 6: "AES_128_CTS", + 9: "Adiantum", + 10: "AES_256_HCTR2", + } + EncryptionOptions_Mode_value = map[string]int32{ + "default": 0, + "AES_256_XTS": 1, + "AES_256_GCM": 2, + "AES_256_CBC": 3, + "AES_256_CTS": 4, + "AES_128_CBC": 5, + "AES_128_CTS": 6, + "Adiantum": 9, + "AES_256_HCTR2": 10, + } +) + +func (x EncryptionOptions_Mode) Enum() *EncryptionOptions_Mode { + p := new(EncryptionOptions_Mode) + *p = x + return p } func (x EncryptionOptions_Mode) String() string { - return proto.EnumName(EncryptionOptions_Mode_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EncryptionOptions_Mode) Descriptor() protoreflect.EnumDescriptor { + return file_metadata_metadata_proto_enumTypes[1].Descriptor() +} + +func (EncryptionOptions_Mode) Type() protoreflect.EnumType { + return &file_metadata_metadata_proto_enumTypes[1] +} + +func (x EncryptionOptions_Mode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EncryptionOptions_Mode.Descriptor instead. +func (EncryptionOptions_Mode) EnumDescriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{3, 0} } -func (EncryptionOptions_Mode) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{3, 0} } // Cost parameters to be used in our hashing functions. type HashingCosts struct { - Time int64 `protobuf:"varint,2,opt,name=time" json:"time,omitempty"` - Memory int64 `protobuf:"varint,3,opt,name=memory" json:"memory,omitempty"` - Parallelism int64 `protobuf:"varint,4,opt,name=parallelism" json:"parallelism,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` + Memory int64 `protobuf:"varint,3,opt,name=memory,proto3" json:"memory,omitempty"` + Parallelism int64 `protobuf:"varint,4,opt,name=parallelism,proto3" json:"parallelism,omitempty"` + // If true, parallelism should no longer be truncated to 8 bits. + TruncationFixed bool `protobuf:"varint,5,opt,name=truncation_fixed,json=truncationFixed,proto3" json:"truncation_fixed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HashingCosts) Reset() { + *x = HashingCosts{} + mi := &file_metadata_metadata_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HashingCosts) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *HashingCosts) Reset() { *m = HashingCosts{} } -func (m *HashingCosts) String() string { return proto.CompactTextString(m) } -func (*HashingCosts) ProtoMessage() {} -func (*HashingCosts) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (*HashingCosts) ProtoMessage() {} -func (m *HashingCosts) GetTime() int64 { - if m != nil { - return m.Time +func (x *HashingCosts) ProtoReflect() protoreflect.Message { + mi := &file_metadata_metadata_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HashingCosts.ProtoReflect.Descriptor instead. +func (*HashingCosts) Descriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{0} +} + +func (x *HashingCosts) GetTime() int64 { + if x != nil { + return x.Time } return 0 } -func (m *HashingCosts) GetMemory() int64 { - if m != nil { - return m.Memory +func (x *HashingCosts) GetMemory() int64 { + if x != nil { + return x.Memory } return 0 } -func (m *HashingCosts) GetParallelism() int64 { - if m != nil { - return m.Parallelism +func (x *HashingCosts) GetParallelism() int64 { + if x != nil { + return x.Parallelism } return 0 } +func (x *HashingCosts) GetTruncationFixed() bool { + if x != nil { + return x.TruncationFixed + } + return false +} + // This structure is used for our authenticated wrapping/unwrapping of keys. type WrappedKeyData struct { - IV []byte `protobuf:"bytes,1,opt,name=IV,json=iV,proto3" json:"IV,omitempty"` - EncryptedKey []byte `protobuf:"bytes,2,opt,name=encrypted_key,json=encryptedKey,proto3" json:"encrypted_key,omitempty"` - Hmac []byte `protobuf:"bytes,3,opt,name=hmac,proto3" json:"hmac,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + IV []byte `protobuf:"bytes,1,opt,name=IV,proto3" json:"IV,omitempty"` + EncryptedKey []byte `protobuf:"bytes,2,opt,name=encrypted_key,json=encryptedKey,proto3" json:"encrypted_key,omitempty"` + Hmac []byte `protobuf:"bytes,3,opt,name=hmac,proto3" json:"hmac,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WrappedKeyData) Reset() { + *x = WrappedKeyData{} + mi := &file_metadata_metadata_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (m *WrappedKeyData) Reset() { *m = WrappedKeyData{} } -func (m *WrappedKeyData) String() string { return proto.CompactTextString(m) } -func (*WrappedKeyData) ProtoMessage() {} -func (*WrappedKeyData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +func (x *WrappedKeyData) String() string { + return protoimpl.X.MessageStringOf(x) +} -func (m *WrappedKeyData) GetIV() []byte { - if m != nil { - return m.IV +func (*WrappedKeyData) ProtoMessage() {} + +func (x *WrappedKeyData) ProtoReflect() protoreflect.Message { + mi := &file_metadata_metadata_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WrappedKeyData.ProtoReflect.Descriptor instead. +func (*WrappedKeyData) Descriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{1} +} + +func (x *WrappedKeyData) GetIV() []byte { + if x != nil { + return x.IV } return nil } -func (m *WrappedKeyData) GetEncryptedKey() []byte { - if m != nil { - return m.EncryptedKey +func (x *WrappedKeyData) GetEncryptedKey() []byte { + if x != nil { + return x.EncryptedKey } return nil } -func (m *WrappedKeyData) GetHmac() []byte { - if m != nil { - return m.Hmac +func (x *WrappedKeyData) GetHmac() []byte { + if x != nil { + return x.Hmac } return nil } // The associated data for each protector type ProtectorData struct { - ProtectorDescriptor string `protobuf:"bytes,1,opt,name=protector_descriptor,json=protectorDescriptor" json:"protector_descriptor,omitempty"` - Source SourceType `protobuf:"varint,2,opt,name=source,enum=metadata.SourceType" json:"source,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ProtectorDescriptor string `protobuf:"bytes,1,opt,name=protector_descriptor,json=protectorDescriptor,proto3" json:"protector_descriptor,omitempty"` + Source SourceType `protobuf:"varint,2,opt,name=source,proto3,enum=metadata.SourceType" json:"source,omitempty"` // These are only used by some of the protector types - Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"` - Costs *HashingCosts `protobuf:"bytes,4,opt,name=costs" json:"costs,omitempty"` - Salt []byte `protobuf:"bytes,5,opt,name=salt,proto3" json:"salt,omitempty"` - Uid int64 `protobuf:"varint,6,opt,name=uid" json:"uid,omitempty"` - WrappedKey *WrappedKeyData `protobuf:"bytes,7,opt,name=wrapped_key,json=wrappedKey" json:"wrapped_key,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Costs *HashingCosts `protobuf:"bytes,4,opt,name=costs,proto3" json:"costs,omitempty"` + Salt []byte `protobuf:"bytes,5,opt,name=salt,proto3" json:"salt,omitempty"` + Uid int64 `protobuf:"varint,6,opt,name=uid,proto3" json:"uid,omitempty"` + WrappedKey *WrappedKeyData `protobuf:"bytes,7,opt,name=wrapped_key,json=wrappedKey,proto3" json:"wrapped_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (m *ProtectorData) Reset() { *m = ProtectorData{} } -func (m *ProtectorData) String() string { return proto.CompactTextString(m) } -func (*ProtectorData) ProtoMessage() {} -func (*ProtectorData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } +func (x *ProtectorData) Reset() { + *x = ProtectorData{} + mi := &file_metadata_metadata_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProtectorData) String() string { + return protoimpl.X.MessageStringOf(x) +} -func (m *ProtectorData) GetProtectorDescriptor() string { - if m != nil { - return m.ProtectorDescriptor +func (*ProtectorData) ProtoMessage() {} + +func (x *ProtectorData) ProtoReflect() protoreflect.Message { + mi := &file_metadata_metadata_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProtectorData.ProtoReflect.Descriptor instead. +func (*ProtectorData) Descriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{2} +} + +func (x *ProtectorData) GetProtectorDescriptor() string { + if x != nil { + return x.ProtectorDescriptor } return "" } -func (m *ProtectorData) GetSource() SourceType { - if m != nil { - return m.Source +func (x *ProtectorData) GetSource() SourceType { + if x != nil { + return x.Source } return SourceType_default } -func (m *ProtectorData) GetName() string { - if m != nil { - return m.Name +func (x *ProtectorData) GetName() string { + if x != nil { + return x.Name } return "" } -func (m *ProtectorData) GetCosts() *HashingCosts { - if m != nil { - return m.Costs +func (x *ProtectorData) GetCosts() *HashingCosts { + if x != nil { + return x.Costs } return nil } -func (m *ProtectorData) GetSalt() []byte { - if m != nil { - return m.Salt +func (x *ProtectorData) GetSalt() []byte { + if x != nil { + return x.Salt } return nil } -func (m *ProtectorData) GetUid() int64 { - if m != nil { - return m.Uid +func (x *ProtectorData) GetUid() int64 { + if x != nil { + return x.Uid } return 0 } -func (m *ProtectorData) GetWrappedKey() *WrappedKeyData { - if m != nil { - return m.WrappedKey +func (x *ProtectorData) GetWrappedKey() *WrappedKeyData { + if x != nil { + return x.WrappedKey } return nil } // Encryption policy specifics, corresponds to the fscrypt_policy struct type EncryptionOptions struct { - Padding int64 `protobuf:"varint,1,opt,name=padding" json:"padding,omitempty"` - Contents EncryptionOptions_Mode `protobuf:"varint,2,opt,name=contents,enum=metadata.EncryptionOptions_Mode" json:"contents,omitempty"` - Filenames EncryptionOptions_Mode `protobuf:"varint,3,opt,name=filenames,enum=metadata.EncryptionOptions_Mode" json:"filenames,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Padding int64 `protobuf:"varint,1,opt,name=padding,proto3" json:"padding,omitempty"` + Contents EncryptionOptions_Mode `protobuf:"varint,2,opt,name=contents,proto3,enum=metadata.EncryptionOptions_Mode" json:"contents,omitempty"` + Filenames EncryptionOptions_Mode `protobuf:"varint,3,opt,name=filenames,proto3,enum=metadata.EncryptionOptions_Mode" json:"filenames,omitempty"` + PolicyVersion int64 `protobuf:"varint,4,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EncryptionOptions) Reset() { + *x = EncryptionOptions{} + mi := &file_metadata_metadata_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EncryptionOptions) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *EncryptionOptions) Reset() { *m = EncryptionOptions{} } -func (m *EncryptionOptions) String() string { return proto.CompactTextString(m) } -func (*EncryptionOptions) ProtoMessage() {} -func (*EncryptionOptions) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } +func (*EncryptionOptions) ProtoMessage() {} -func (m *EncryptionOptions) GetPadding() int64 { - if m != nil { - return m.Padding +func (x *EncryptionOptions) ProtoReflect() protoreflect.Message { + mi := &file_metadata_metadata_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EncryptionOptions.ProtoReflect.Descriptor instead. +func (*EncryptionOptions) Descriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{3} +} + +func (x *EncryptionOptions) GetPadding() int64 { + if x != nil { + return x.Padding } return 0 } -func (m *EncryptionOptions) GetContents() EncryptionOptions_Mode { - if m != nil { - return m.Contents +func (x *EncryptionOptions) GetContents() EncryptionOptions_Mode { + if x != nil { + return x.Contents } return EncryptionOptions_default } -func (m *EncryptionOptions) GetFilenames() EncryptionOptions_Mode { - if m != nil { - return m.Filenames +func (x *EncryptionOptions) GetFilenames() EncryptionOptions_Mode { + if x != nil { + return x.Filenames } return EncryptionOptions_default } +func (x *EncryptionOptions) GetPolicyVersion() int64 { + if x != nil { + return x.PolicyVersion + } + return 0 +} + type WrappedPolicyKey struct { - ProtectorDescriptor string `protobuf:"bytes,1,opt,name=protector_descriptor,json=protectorDescriptor" json:"protector_descriptor,omitempty"` - WrappedKey *WrappedKeyData `protobuf:"bytes,2,opt,name=wrapped_key,json=wrappedKey" json:"wrapped_key,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ProtectorDescriptor string `protobuf:"bytes,1,opt,name=protector_descriptor,json=protectorDescriptor,proto3" json:"protector_descriptor,omitempty"` + WrappedKey *WrappedKeyData `protobuf:"bytes,2,opt,name=wrapped_key,json=wrappedKey,proto3" json:"wrapped_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (m *WrappedPolicyKey) Reset() { *m = WrappedPolicyKey{} } -func (m *WrappedPolicyKey) String() string { return proto.CompactTextString(m) } -func (*WrappedPolicyKey) ProtoMessage() {} -func (*WrappedPolicyKey) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } +func (x *WrappedPolicyKey) Reset() { + *x = WrappedPolicyKey{} + mi := &file_metadata_metadata_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -func (m *WrappedPolicyKey) GetProtectorDescriptor() string { - if m != nil { - return m.ProtectorDescriptor +func (x *WrappedPolicyKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WrappedPolicyKey) ProtoMessage() {} + +func (x *WrappedPolicyKey) ProtoReflect() protoreflect.Message { + mi := &file_metadata_metadata_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WrappedPolicyKey.ProtoReflect.Descriptor instead. +func (*WrappedPolicyKey) Descriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{4} +} + +func (x *WrappedPolicyKey) GetProtectorDescriptor() string { + if x != nil { + return x.ProtectorDescriptor } return "" } -func (m *WrappedPolicyKey) GetWrappedKey() *WrappedKeyData { - if m != nil { - return m.WrappedKey +func (x *WrappedPolicyKey) GetWrappedKey() *WrappedKeyData { + if x != nil { + return x.WrappedKey } return nil } // The associated data for each policy type PolicyData struct { - KeyDescriptor string `protobuf:"bytes,1,opt,name=key_descriptor,json=keyDescriptor" json:"key_descriptor,omitempty"` - Options *EncryptionOptions `protobuf:"bytes,2,opt,name=options" json:"options,omitempty"` - WrappedPolicyKeys []*WrappedPolicyKey `protobuf:"bytes,3,rep,name=wrapped_policy_keys,json=wrappedPolicyKeys" json:"wrapped_policy_keys,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + KeyDescriptor string `protobuf:"bytes,1,opt,name=key_descriptor,json=keyDescriptor,proto3" json:"key_descriptor,omitempty"` + Options *EncryptionOptions `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"` + WrappedPolicyKeys []*WrappedPolicyKey `protobuf:"bytes,3,rep,name=wrapped_policy_keys,json=wrappedPolicyKeys,proto3" json:"wrapped_policy_keys,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyData) Reset() { + *x = PolicyData{} + mi := &file_metadata_metadata_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (m *PolicyData) Reset() { *m = PolicyData{} } -func (m *PolicyData) String() string { return proto.CompactTextString(m) } -func (*PolicyData) ProtoMessage() {} -func (*PolicyData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } +func (x *PolicyData) String() string { + return protoimpl.X.MessageStringOf(x) +} -func (m *PolicyData) GetKeyDescriptor() string { - if m != nil { - return m.KeyDescriptor +func (*PolicyData) ProtoMessage() {} + +func (x *PolicyData) ProtoReflect() protoreflect.Message { + mi := &file_metadata_metadata_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyData.ProtoReflect.Descriptor instead. +func (*PolicyData) Descriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{5} +} + +func (x *PolicyData) GetKeyDescriptor() string { + if x != nil { + return x.KeyDescriptor } return "" } -func (m *PolicyData) GetOptions() *EncryptionOptions { - if m != nil { - return m.Options +func (x *PolicyData) GetOptions() *EncryptionOptions { + if x != nil { + return x.Options } return nil } -func (m *PolicyData) GetWrappedPolicyKeys() []*WrappedPolicyKey { - if m != nil { - return m.WrappedPolicyKeys +func (x *PolicyData) GetWrappedPolicyKeys() []*WrappedPolicyKey { + if x != nil { + return x.WrappedPolicyKeys } return nil } // Data stored in the config file type Config struct { - Source SourceType `protobuf:"varint,1,opt,name=source,enum=metadata.SourceType" json:"source,omitempty"` - HashCosts *HashingCosts `protobuf:"bytes,2,opt,name=hash_costs,json=hashCosts" json:"hash_costs,omitempty"` - Compatibility string `protobuf:"bytes,3,opt,name=compatibility" json:"compatibility,omitempty"` - Options *EncryptionOptions `protobuf:"bytes,4,opt,name=options" json:"options,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Source SourceType `protobuf:"varint,1,opt,name=source,proto3,enum=metadata.SourceType" json:"source,omitempty"` + HashCosts *HashingCosts `protobuf:"bytes,2,opt,name=hash_costs,json=hashCosts,proto3" json:"hash_costs,omitempty"` + Options *EncryptionOptions `protobuf:"bytes,4,opt,name=options,proto3" json:"options,omitempty"` + UseFsKeyringForV1Policies bool `protobuf:"varint,5,opt,name=use_fs_keyring_for_v1_policies,json=useFsKeyringForV1Policies,proto3" json:"use_fs_keyring_for_v1_policies,omitempty"` + AllowCrossUserMetadata bool `protobuf:"varint,6,opt,name=allow_cross_user_metadata,json=allowCrossUserMetadata,proto3" json:"allow_cross_user_metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_metadata_metadata_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Config) Reset() { *m = Config{} } -func (m *Config) String() string { return proto.CompactTextString(m) } -func (*Config) ProtoMessage() {} -func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } +func (*Config) ProtoMessage() {} -func (m *Config) GetSource() SourceType { - if m != nil { - return m.Source +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_metadata_metadata_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_metadata_metadata_proto_rawDescGZIP(), []int{6} +} + +func (x *Config) GetSource() SourceType { + if x != nil { + return x.Source } return SourceType_default } -func (m *Config) GetHashCosts() *HashingCosts { - if m != nil { - return m.HashCosts +func (x *Config) GetHashCosts() *HashingCosts { + if x != nil { + return x.HashCosts } return nil } -func (m *Config) GetCompatibility() string { - if m != nil { - return m.Compatibility +func (x *Config) GetOptions() *EncryptionOptions { + if x != nil { + return x.Options } - return "" + return nil } -func (m *Config) GetOptions() *EncryptionOptions { - if m != nil { - return m.Options +func (x *Config) GetUseFsKeyringForV1Policies() bool { + if x != nil { + return x.UseFsKeyringForV1Policies } - return nil + return false } -func init() { - proto.RegisterType((*HashingCosts)(nil), "metadata.HashingCosts") - proto.RegisterType((*WrappedKeyData)(nil), "metadata.WrappedKeyData") - proto.RegisterType((*ProtectorData)(nil), "metadata.ProtectorData") - proto.RegisterType((*EncryptionOptions)(nil), "metadata.EncryptionOptions") - proto.RegisterType((*WrappedPolicyKey)(nil), "metadata.WrappedPolicyKey") - proto.RegisterType((*PolicyData)(nil), "metadata.PolicyData") - proto.RegisterType((*Config)(nil), "metadata.Config") - proto.RegisterEnum("metadata.SourceType", SourceType_name, SourceType_value) - proto.RegisterEnum("metadata.EncryptionOptions_Mode", EncryptionOptions_Mode_name, EncryptionOptions_Mode_value) -} - -func init() { proto.RegisterFile("metadata/metadata.proto", fileDescriptor0) } - -var fileDescriptor0 = []byte{ - // 642 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xed, 0x6a, 0xdb, 0x3c, - 0x14, 0xc7, 0x1f, 0xdb, 0x69, 0xd2, 0x9c, 0xbc, 0x3c, 0xae, 0xda, 0x75, 0x66, 0xfb, 0x12, 0xbc, - 0x0d, 0xca, 0x28, 0x1d, 0xcd, 0xe8, 0xd8, 0x60, 0x0c, 0xb6, 0xb4, 0xec, 0xa5, 0x94, 0x75, 0x4a, - 0xe8, 0x36, 0x18, 0x04, 0xd5, 0x56, 0x1b, 0x51, 0xdb, 0x12, 0x96, 0x42, 0xf0, 0xb7, 0xdd, 0xc3, - 0xee, 0x61, 0x97, 0xb0, 0x8b, 0xd8, 0x55, 0x0d, 0xc9, 0xb1, 0xe3, 0xb4, 0x50, 0xb2, 0x7d, 0x31, - 0x47, 0x7f, 0x49, 0xe7, 0x7f, 0xf4, 0x93, 0x8e, 0xe1, 0x6e, 0x4c, 0x15, 0x09, 0x89, 0x22, 0x4f, - 0x8a, 0x60, 0x4f, 0xa4, 0x5c, 0x71, 0xb4, 0x5e, 0x8c, 0xfd, 0x6f, 0xd0, 0x7e, 0x47, 0xe4, 0x84, - 0x25, 0x97, 0x03, 0x2e, 0x95, 0x44, 0x08, 0x6a, 0x8a, 0xc5, 0xd4, 0xb3, 0x7b, 0xd6, 0x8e, 0x83, - 0x4d, 0x8c, 0xb6, 0xa1, 0x1e, 0xd3, 0x98, 0xa7, 0x99, 0xe7, 0x18, 0x75, 0x3e, 0x42, 0x3d, 0x68, - 0x09, 0x92, 0x92, 0x28, 0xa2, 0x11, 0x93, 0xb1, 0x57, 0x33, 0x93, 0x55, 0xc9, 0xff, 0x0a, 0xdd, - 0xcf, 0x29, 0x11, 0x82, 0x86, 0xc7, 0x34, 0x3b, 0x24, 0x8a, 0xa0, 0x2e, 0xd8, 0xef, 0xcf, 0x3c, - 0xab, 0x67, 0xed, 0xb4, 0xb1, 0xcd, 0xce, 0xd0, 0x03, 0xe8, 0xd0, 0x24, 0x48, 0x33, 0xa1, 0x68, - 0x38, 0xbe, 0xa2, 0x99, 0x31, 0x6e, 0xe3, 0x76, 0x29, 0x1e, 0xd3, 0x4c, 0x17, 0x35, 0x89, 0x49, - 0x60, 0xec, 0xdb, 0xd8, 0xc4, 0xfe, 0x0f, 0x1b, 0x3a, 0xa7, 0x29, 0x57, 0x34, 0x50, 0x3c, 0x35, - 0xa9, 0xf7, 0x61, 0x4b, 0x14, 0xc2, 0x38, 0xa4, 0x32, 0x48, 0x99, 0x50, 0x3c, 0x35, 0x66, 0x4d, - 0xbc, 0x59, 0xce, 0x1d, 0x96, 0x53, 0x68, 0x17, 0xea, 0x92, 0x4f, 0xd3, 0x20, 0x3f, 0x6f, 0xb7, - 0xbf, 0xb5, 0x57, 0x82, 0x1a, 0x1a, 0x7d, 0x94, 0x09, 0x8a, 0xe7, 0x6b, 0x74, 0x19, 0x09, 0x89, - 0xa9, 0x29, 0xa3, 0x89, 0x4d, 0x8c, 0x76, 0x61, 0x2d, 0xd0, 0xe0, 0xcc, 0xe9, 0x5b, 0xfd, 0xed, - 0x45, 0x82, 0x2a, 0x56, 0x9c, 0x2f, 0xd2, 0x19, 0x24, 0x89, 0x94, 0xb7, 0x96, 0x1f, 0x44, 0xc7, - 0xc8, 0x05, 0x67, 0xca, 0x42, 0xaf, 0x6e, 0xe8, 0xe9, 0x10, 0xbd, 0x80, 0xd6, 0x2c, 0xa7, 0x66, - 0x88, 0x34, 0x4c, 0x66, 0x6f, 0x91, 0x79, 0x19, 0x29, 0x86, 0x59, 0x39, 0xf6, 0x7f, 0xda, 0xb0, - 0x71, 0x94, 0xa3, 0x63, 0x3c, 0xf9, 0x68, 0xbe, 0x12, 0x79, 0xd0, 0x10, 0x24, 0x0c, 0x59, 0x72, - 0x69, 0x60, 0x38, 0xb8, 0x18, 0xa2, 0x97, 0xb0, 0x1e, 0xf0, 0x44, 0xd1, 0x44, 0xc9, 0x39, 0x82, - 0xde, 0xc2, 0xe7, 0x46, 0xa2, 0xbd, 0x13, 0x1e, 0x52, 0x5c, 0xee, 0x40, 0xaf, 0xa0, 0x79, 0xc1, - 0x22, 0xaa, 0x41, 0x48, 0x43, 0x65, 0x95, 0xed, 0x8b, 0x2d, 0x7e, 0x06, 0x35, 0x2d, 0xa1, 0x16, - 0x34, 0x42, 0x7a, 0x41, 0xa6, 0x91, 0x72, 0xff, 0x43, 0xff, 0x43, 0xeb, 0xf5, 0xd1, 0x70, 0xdc, - 0x3f, 0x78, 0x36, 0xfe, 0x32, 0x1a, 0xba, 0x56, 0x55, 0x78, 0x3b, 0x38, 0x71, 0xed, 0xaa, 0x30, - 0x78, 0x33, 0x70, 0x9d, 0x25, 0x61, 0x34, 0x74, 0x6b, 0x85, 0xb0, 0xdf, 0x7f, 0x6e, 0x56, 0xac, - 0x2d, 0x09, 0xa3, 0xa1, 0x5b, 0xf7, 0xbf, 0x5b, 0xe0, 0xce, 0x39, 0x9e, 0xf2, 0x88, 0x05, 0x99, - 0x7e, 0x67, 0xff, 0xf0, 0x82, 0xae, 0xdd, 0x95, 0xfd, 0x17, 0x77, 0xf5, 0xcb, 0x02, 0xc8, 0xbd, - 0xcd, 0xf3, 0x7d, 0x04, 0xdd, 0x2b, 0x9a, 0xdd, 0xb4, 0xed, 0x5c, 0xd1, 0xac, 0x62, 0x78, 0x00, - 0x0d, 0x9e, 0xe3, 0x9c, 0x9b, 0xdd, 0xbf, 0x85, 0x38, 0x2e, 0xd6, 0xa2, 0x0f, 0xb0, 0x59, 0xd4, - 0x29, 0x8c, 0xa7, 0x2e, 0x57, 0x5f, 0x9a, 0xb3, 0xd3, 0xea, 0xdf, 0xbb, 0x51, 0x6f, 0xc9, 0x04, - 0x6f, 0xcc, 0xae, 0x29, 0xd2, 0xff, 0x6d, 0x41, 0x7d, 0xc0, 0x93, 0x0b, 0x76, 0x59, 0x69, 0x20, - 0x6b, 0x85, 0x06, 0x3a, 0x00, 0x98, 0x10, 0x39, 0x19, 0xe7, 0x1d, 0x63, 0xdf, 0xda, 0x31, 0x4d, - 0xbd, 0x32, 0xff, 0x27, 0x3d, 0x84, 0x4e, 0xc0, 0x63, 0x41, 0x14, 0x3b, 0x67, 0x11, 0x53, 0xd9, - 0xbc, 0x01, 0x97, 0xc5, 0x2a, 0x98, 0xda, 0xea, 0x60, 0x1e, 0x7f, 0x02, 0x58, 0x54, 0xba, 0xfc, - 0x12, 0x11, 0x74, 0x05, 0x89, 0xc7, 0x82, 0x48, 0x29, 0x26, 0x29, 0x91, 0xd4, 0xb5, 0xd0, 0x1d, - 0xd8, 0x08, 0xa6, 0x52, 0xf1, 0x25, 0xd9, 0xd6, 0xfb, 0x52, 0x32, 0xd3, 0x4c, 0x5d, 0xe7, 0xbc, - 0x6e, 0x7e, 0xb2, 0x4f, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x99, 0x65, 0x0d, 0x45, 0x7f, 0x05, - 0x00, 0x00, +func (x *Config) GetAllowCrossUserMetadata() bool { + if x != nil { + return x.AllowCrossUserMetadata + } + return false +} + +var File_metadata_metadata_proto protoreflect.FileDescriptor + +const file_metadata_metadata_proto_rawDesc = "" + + "\n" + + "\x17metadata/metadata.proto\x12\bmetadata\"\x87\x01\n" + + "\fHashingCosts\x12\x12\n" + + "\x04time\x18\x02 \x01(\x03R\x04time\x12\x16\n" + + "\x06memory\x18\x03 \x01(\x03R\x06memory\x12 \n" + + "\vparallelism\x18\x04 \x01(\x03R\vparallelism\x12)\n" + + "\x10truncation_fixed\x18\x05 \x01(\bR\x0ftruncationFixed\"Y\n" + + "\x0eWrappedKeyData\x12\x0e\n" + + "\x02IV\x18\x01 \x01(\fR\x02IV\x12#\n" + + "\rencrypted_key\x18\x02 \x01(\fR\fencryptedKey\x12\x12\n" + + "\x04hmac\x18\x03 \x01(\fR\x04hmac\"\x93\x02\n" + + "\rProtectorData\x121\n" + + "\x14protector_descriptor\x18\x01 \x01(\tR\x13protectorDescriptor\x12,\n" + + "\x06source\x18\x02 \x01(\x0e2\x14.metadata.SourceTypeR\x06source\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12,\n" + + "\x05costs\x18\x04 \x01(\v2\x16.metadata.HashingCostsR\x05costs\x12\x12\n" + + "\x04salt\x18\x05 \x01(\fR\x04salt\x12\x10\n" + + "\x03uid\x18\x06 \x01(\x03R\x03uid\x129\n" + + "\vwrapped_key\x18\a \x01(\v2\x18.metadata.WrappedKeyDataR\n" + + "wrappedKey\"\xef\x02\n" + + "\x11EncryptionOptions\x12\x18\n" + + "\apadding\x18\x01 \x01(\x03R\apadding\x12<\n" + + "\bcontents\x18\x02 \x01(\x0e2 .metadata.EncryptionOptions.ModeR\bcontents\x12>\n" + + "\tfilenames\x18\x03 \x01(\x0e2 .metadata.EncryptionOptions.ModeR\tfilenames\x12%\n" + + "\x0epolicy_version\x18\x04 \x01(\x03R\rpolicyVersion\"\x9a\x01\n" + + "\x04Mode\x12\v\n" + + "\adefault\x10\x00\x12\x0f\n" + + "\vAES_256_XTS\x10\x01\x12\x0f\n" + + "\vAES_256_GCM\x10\x02\x12\x0f\n" + + "\vAES_256_CBC\x10\x03\x12\x0f\n" + + "\vAES_256_CTS\x10\x04\x12\x0f\n" + + "\vAES_128_CBC\x10\x05\x12\x0f\n" + + "\vAES_128_CTS\x10\x06\x12\f\n" + + "\bAdiantum\x10\t\x12\x11\n" + + "\rAES_256_HCTR2\x10\n" + + "\"\x80\x01\n" + + "\x10WrappedPolicyKey\x121\n" + + "\x14protector_descriptor\x18\x01 \x01(\tR\x13protectorDescriptor\x129\n" + + "\vwrapped_key\x18\x02 \x01(\v2\x18.metadata.WrappedKeyDataR\n" + + "wrappedKey\"\xb6\x01\n" + + "\n" + + "PolicyData\x12%\n" + + "\x0ekey_descriptor\x18\x01 \x01(\tR\rkeyDescriptor\x125\n" + + "\aoptions\x18\x02 \x01(\v2\x1b.metadata.EncryptionOptionsR\aoptions\x12J\n" + + "\x13wrapped_policy_keys\x18\x03 \x03(\v2\x1a.metadata.WrappedPolicyKeyR\x11wrappedPolicyKeys\"\xb7\x02\n" + + "\x06Config\x12,\n" + + "\x06source\x18\x01 \x01(\x0e2\x14.metadata.SourceTypeR\x06source\x125\n" + + "\n" + + "hash_costs\x18\x02 \x01(\v2\x16.metadata.HashingCostsR\thashCosts\x125\n" + + "\aoptions\x18\x04 \x01(\v2\x1b.metadata.EncryptionOptionsR\aoptions\x12A\n" + + "\x1euse_fs_keyring_for_v1_policies\x18\x05 \x01(\bR\x19useFsKeyringForV1Policies\x129\n" + + "\x19allow_cross_user_metadata\x18\x06 \x01(\bR\x16allowCrossUserMetadataJ\x04\b\x03\x10\x04R\rcompatibility*Q\n" + + "\n" + + "SourceType\x12\v\n" + + "\adefault\x10\x00\x12\x12\n" + + "\x0epam_passphrase\x10\x01\x12\x15\n" + + "\x11custom_passphrase\x10\x02\x12\v\n" + + "\araw_key\x10\x03B$Z\"github.com/google/fscrypt/metadatab\x06proto3" + +var ( + file_metadata_metadata_proto_rawDescOnce sync.Once + file_metadata_metadata_proto_rawDescData []byte +) + +func file_metadata_metadata_proto_rawDescGZIP() []byte { + file_metadata_metadata_proto_rawDescOnce.Do(func() { + file_metadata_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_metadata_metadata_proto_rawDesc), len(file_metadata_metadata_proto_rawDesc))) + }) + return file_metadata_metadata_proto_rawDescData +} + +var file_metadata_metadata_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_metadata_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_metadata_metadata_proto_goTypes = []any{ + (SourceType)(0), // 0: metadata.SourceType + (EncryptionOptions_Mode)(0), // 1: metadata.EncryptionOptions.Mode + (*HashingCosts)(nil), // 2: metadata.HashingCosts + (*WrappedKeyData)(nil), // 3: metadata.WrappedKeyData + (*ProtectorData)(nil), // 4: metadata.ProtectorData + (*EncryptionOptions)(nil), // 5: metadata.EncryptionOptions + (*WrappedPolicyKey)(nil), // 6: metadata.WrappedPolicyKey + (*PolicyData)(nil), // 7: metadata.PolicyData + (*Config)(nil), // 8: metadata.Config +} +var file_metadata_metadata_proto_depIdxs = []int32{ + 0, // 0: metadata.ProtectorData.source:type_name -> metadata.SourceType + 2, // 1: metadata.ProtectorData.costs:type_name -> metadata.HashingCosts + 3, // 2: metadata.ProtectorData.wrapped_key:type_name -> metadata.WrappedKeyData + 1, // 3: metadata.EncryptionOptions.contents:type_name -> metadata.EncryptionOptions.Mode + 1, // 4: metadata.EncryptionOptions.filenames:type_name -> metadata.EncryptionOptions.Mode + 3, // 5: metadata.WrappedPolicyKey.wrapped_key:type_name -> metadata.WrappedKeyData + 5, // 6: metadata.PolicyData.options:type_name -> metadata.EncryptionOptions + 6, // 7: metadata.PolicyData.wrapped_policy_keys:type_name -> metadata.WrappedPolicyKey + 0, // 8: metadata.Config.source:type_name -> metadata.SourceType + 2, // 9: metadata.Config.hash_costs:type_name -> metadata.HashingCosts + 5, // 10: metadata.Config.options:type_name -> metadata.EncryptionOptions + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_metadata_metadata_proto_init() } +func file_metadata_metadata_proto_init() { + if File_metadata_metadata_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_metadata_metadata_proto_rawDesc), len(file_metadata_metadata_proto_rawDesc)), + NumEnums: 2, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_metadata_metadata_proto_goTypes, + DependencyIndexes: file_metadata_metadata_proto_depIdxs, + EnumInfos: file_metadata_metadata_proto_enumTypes, + MessageInfos: file_metadata_metadata_proto_msgTypes, + }.Build() + File_metadata_metadata_proto = out.File + file_metadata_metadata_proto_goTypes = nil + file_metadata_metadata_proto_depIdxs = nil } diff --git a/metadata/metadata.proto b/metadata/metadata.proto index 5e1b9dd..f2dd78f 100644 --- a/metadata/metadata.proto +++ b/metadata/metadata.proto @@ -19,15 +19,20 @@ * the License. */ -// If you modify this file, be sure to run "go generate" on this package. +// If the *.proto file is modified, be sure to run "make gen" (at the project +// root) to recreate the *.pb.go file. syntax = "proto3"; package metadata; +option go_package = "github.com/google/fscrypt/metadata"; + // Cost parameters to be used in our hashing functions. message HashingCosts { int64 time = 2; int64 memory = 3; int64 parallelism = 4; + // If true, parallelism should no longer be truncated to 8 bits. + bool truncation_fixed = 5; } // This structure is used for our authenticated wrapping/unwrapping of keys. @@ -63,7 +68,7 @@ message ProtectorData { message EncryptionOptions { int64 padding = 1; - // Type of encryption; should match declarations of unix.FS_ENCRYPTION_MODE + // Type of encryption; should match declarations of unix.FSCRYPT_MODE enum Mode { default = 0; AES_256_XTS = 1; @@ -72,10 +77,14 @@ message EncryptionOptions { AES_256_CTS = 4; AES_128_CBC = 5; AES_128_CTS = 6; + Adiantum = 9; + AES_256_HCTR2 = 10; } Mode contents = 2; Mode filenames = 3; + + int64 policy_version = 4; } message WrappedPolicyKey { @@ -94,6 +103,11 @@ message PolicyData { message Config { SourceType source = 1; HashingCosts hash_costs = 2; - string compatibility = 3; EncryptionOptions options = 4; + bool use_fs_keyring_for_v1_policies = 5; + bool allow_cross_user_metadata = 6; + + // reserve the removed field 'string compatibility = 3;' + reserved 3; + reserved "compatibility"; } diff --git a/metadata/policy.go b/metadata/policy.go index 533d48a..fe6c38f 100644 --- a/metadata/policy.go +++ b/metadata/policy.go @@ -22,9 +22,13 @@ package metadata import ( "encoding/hex" + "fmt" "log" "math" "os" + "os/user" + "strconv" + "syscall" "unsafe" "github.com/pkg/errors" @@ -33,80 +37,248 @@ import ( "github.com/google/fscrypt/util" ) -// Encryption specific errors var ( + // ErrEncryptionNotSupported indicates that encryption is not supported + // on the given filesystem, and there is no way to enable it. ErrEncryptionNotSupported = errors.New("encryption not supported") - ErrEncryptionNotEnabled = errors.New("encryption not enabled") - ErrNotEncrypted = errors.New("file or directory not encrypted") - ErrEncrypted = errors.New("file or directory already encrypted") - ErrBadEncryptionOptions = util.SystemError("invalid encryption options provided") + + // ErrEncryptionNotEnabled indicates that encryption is not supported on + // the given filesystem, but there is a way to enable it. + ErrEncryptionNotEnabled = errors.New("encryption not enabled") ) -// policyIoctl is a wrapper for the ioctl syscall. It 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(file *os.File, request uintptr, policy *unix.FscryptPolicy) error { - // 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: +// ErrAlreadyEncrypted indicates that the path is already encrypted. +type ErrAlreadyEncrypted struct { + Path string +} + +func (err *ErrAlreadyEncrypted) Error() string { + return fmt.Sprintf("file or directory %q is already encrypted", err.Path) +} + +// ErrBadEncryptionOptions indicates that unsupported encryption options were given. +type ErrBadEncryptionOptions struct { + Path string + Options *EncryptionOptions +} + +func (err *ErrBadEncryptionOptions) Error() string { + return fmt.Sprintf(`cannot encrypt %q because the kernel doesn't support the requested encryption options. + + The options are %s`, err.Path, err.Options) +} + +// ErrDirectoryNotOwned indicates a directory can't be encrypted because it's +// owned by another user. +type ErrDirectoryNotOwned struct { + Path string + Owner uint32 +} + +func (err *ErrDirectoryNotOwned) Error() string { + owner := strconv.Itoa(int(err.Owner)) + if u, e := user.LookupId(owner); e == nil && u.Username != "" { + owner = u.Username + } + return fmt.Sprintf(`cannot encrypt %q because it's owned by another user (%s). + + Encryption can only be enabled on a directory you own, even if you have + write access to the directory.`, err.Path, owner) +} + +// ErrLockedRegularFile indicates that the path is a locked regular file. +type ErrLockedRegularFile struct { + Path string +} + +func (err *ErrLockedRegularFile) Error() string { + return fmt.Sprintf("cannot operate on locked regular file %q", err.Path) +} + +// ErrNotEncrypted indicates that the path is not encrypted. +type ErrNotEncrypted struct { + Path string +} + +func (err *ErrNotEncrypted) Error() string { + return fmt.Sprintf("file or directory %q is not encrypted", err.Path) +} + +func getPolicyIoctl(file *os.File, request uintptr, arg unsafe.Pointer) error { + _, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), request, uintptr(arg)) + if errno == 0 { return nil - case unix.ENOTTY: - return ErrEncryptionNotSupported - case unix.EOPNOTSUPP: - return ErrEncryptionNotEnabled - 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 +} + +func setPolicy(file *os.File, arg unsafe.Pointer) error { + _, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), unix.FS_IOC_SET_ENCRYPTION_POLICY, uintptr(arg)) + if errno != 0 { return errno } + + if err := file.Sync(); err != nil { + return err + } + + return nil } -// Maps EncryptionOptions.Padding <-> FscryptPolicy.Flags +// Maps EncryptionOptions.Padding <-> FSCRYPT_POLICY_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} + flagsArray = []int64{unix.FSCRYPT_POLICY_FLAGS_PAD_4, unix.FSCRYPT_POLICY_FLAGS_PAD_8, + unix.FSCRYPT_POLICY_FLAGS_PAD_16, unix.FSCRYPT_POLICY_FLAGS_PAD_32} ) +// flagsToPadding returns the amount of padding specified in the policy flags. +func flagsToPadding(flags uint8) int64 { + paddingFlag := int64(flags & unix.FS_POLICY_FLAGS_PAD_MASK) + + // This lookup should always succeed + padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray) + if !ok { + log.Panicf("padding flag of %x not found", paddingFlag) + } + return padding +} + +func buildV1PolicyData(policy *unix.FscryptPolicyV1) *PolicyData { + return &PolicyData{ + KeyDescriptor: hex.EncodeToString(policy.Master_key_descriptor[:]), + Options: &EncryptionOptions{ + Padding: flagsToPadding(policy.Flags), + Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), + Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), + PolicyVersion: 1, + }, + } +} + +func buildV2PolicyData(policy *unix.FscryptPolicyV2) *PolicyData { + return &PolicyData{ + KeyDescriptor: hex.EncodeToString(policy.Master_key_identifier[:]), + Options: &EncryptionOptions{ + Padding: flagsToPadding(policy.Flags), + Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), + Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), + PolicyVersion: 2, + }, + } +} + // 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) { file, err := os.Open(path) if err != nil { + if err.(*os.PathError).Err == syscall.ENOKEY { + return nil, &ErrLockedRegularFile{path} + } return nil, err } defer file.Close() - var policy unix.FscryptPolicy - if err := policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY, &policy); err != nil { - return nil, errors.Wrapf(err, "get encryption policy %s", path) + // First try the new version of the ioctl. This works for both v1 and v2 policies. + var arg unix.FscryptGetPolicyExArg + arg.Size = uint64(unsafe.Sizeof(arg.Policy)) + policyPtr := util.Ptr(arg.Policy[:]) + err = getPolicyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY_EX, unsafe.Pointer(&arg)) + if err == unix.ENOTTY { + // Fall back to the old version of the ioctl. This works for v1 policies only. + err = getPolicyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY, policyPtr) + arg.Size = uint64(unsafe.Sizeof(unix.FscryptPolicyV1{})) } + switch err { + case nil: + break + case unix.ENOTTY: + return nil, ErrEncryptionNotSupported + case unix.EOPNOTSUPP: + return nil, ErrEncryptionNotEnabled + case unix.ENODATA, unix.ENOENT: + // ENOENT was returned instead of ENODATA on some filesystems before v4.11. + return nil, &ErrNotEncrypted{path} + default: + return nil, errors.Wrapf(err, "failed to get encryption policy of %q", path) + } + switch arg.Policy[0] { // arg.policy.version + case unix.FSCRYPT_POLICY_V1: + if arg.Size != uint64(unsafe.Sizeof(unix.FscryptPolicyV1{})) { + // should never happen + return nil, errors.New("unexpected size for v1 policy") + } + return buildV1PolicyData((*unix.FscryptPolicyV1)(policyPtr)), nil + case unix.FSCRYPT_POLICY_V2: + if arg.Size != uint64(unsafe.Sizeof(unix.FscryptPolicyV2{})) { + // should never happen + return nil, errors.New("unexpected size for v2 policy") + } + return buildV2PolicyData((*unix.FscryptPolicyV2)(policyPtr)), nil + default: + return nil, errors.Errorf("unsupported encryption policy version [%d]", + arg.Policy[0]) + } +} - // Convert the padding flag into an amount of padding - paddingFlag := int64(policy.Flags & unix.FS_POLICY_FLAGS_PAD_MASK) +// For improved performance, use the DIRECT_KEY flag when using ciphers that +// support it, e.g. Adiantum. It is safe because fscrypt won't reuse the key +// for any other policy. (Multiple directories with same policy are okay.) +func shouldUseDirectKeyFlag(options *EncryptionOptions) bool { + // Contents and filenames encryption modes must be the same + if options.Contents != options.Filenames { + return false + } + // Currently only Adiantum supports DIRECT_KEY. + return options.Contents == EncryptionOptions_Adiantum +} - // This lookup should always succeed - padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray) +func buildPolicyFlags(options *EncryptionOptions) uint8 { + // This lookup should always succeed (as policy is valid) + flags, ok := util.Lookup(options.Padding, paddingArray, flagsArray) if !ok { - log.Panicf("padding flag of %x not found", paddingFlag) + log.Panicf("padding of %d was not found", options.Padding) + } + if shouldUseDirectKeyFlag(options) { + flags |= unix.FSCRYPT_POLICY_FLAG_DIRECT_KEY } + return uint8(flags) +} - return &PolicyData{ - KeyDescriptor: hex.EncodeToString(policy.Master_key_descriptor[:]), - Options: &EncryptionOptions{ - Padding: padding, - Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), - Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), - }, - }, nil +func setV1Policy(file *os.File, options *EncryptionOptions, descriptorBytes []byte) error { + policy := unix.FscryptPolicyV1{ + Version: unix.FSCRYPT_POLICY_V1, + Contents_encryption_mode: uint8(options.Contents), + Filenames_encryption_mode: uint8(options.Filenames), + Flags: uint8(buildPolicyFlags(options)), + } + + // The descriptor should always be the correct length (as policy is valid) + if len(descriptorBytes) != unix.FSCRYPT_KEY_DESCRIPTOR_SIZE { + log.Panic("wrong descriptor size for v1 policy") + } + copy(policy.Master_key_descriptor[:], descriptorBytes) + + return setPolicy(file, unsafe.Pointer(&policy)) +} + +func setV2Policy(file *os.File, options *EncryptionOptions, descriptorBytes []byte) error { + policy := unix.FscryptPolicyV2{ + Version: unix.FSCRYPT_POLICY_V2, + Contents_encryption_mode: uint8(options.Contents), + Filenames_encryption_mode: uint8(options.Filenames), + Flags: uint8(buildPolicyFlags(options)), + } + + // The descriptor should always be the correct length (as policy is valid) + if len(descriptorBytes) != unix.FSCRYPT_KEY_IDENTIFIER_SIZE { + log.Panic("wrong descriptor size for v2 policy") + } + copy(policy.Master_key_identifier[:], descriptorBytes) + + return setPolicy(file, unsafe.Pointer(&policy)) } // SetPolicy sets up the specified directory to be encrypted with the specified @@ -119,30 +291,24 @@ func SetPolicy(path string, data *PolicyData) error { } defer file.Close() - if err := data.CheckValidity(); err != nil { + if err = data.CheckValidity(); err != nil { return errors.Wrap(err, "invalid policy") } - // This lookup should always succeed (as policy is valid) - paddingFlag, ok := util.Lookup(data.Options.Padding, paddingArray, flagsArray) - if !ok { - log.Panicf("padding of %d was not found", data.Options.Padding) - } - descriptorBytes, err := hex.DecodeString(data.KeyDescriptor) if err != nil { - return errors.New("invalid descriptor: " + data.KeyDescriptor) + return errors.New("invalid key descriptor: " + data.KeyDescriptor) } - policy := unix.FscryptPolicy{ - Version: 0, // Version must always be zero - Contents_encryption_mode: uint8(data.Options.Contents), - Filenames_encryption_mode: uint8(data.Options.Filenames), - Flags: uint8(paddingFlag), + switch data.Options.PolicyVersion { + case 1: + err = setV1Policy(file, data.Options, descriptorBytes) + case 2: + err = setV2Policy(file, data.Options, descriptorBytes) + default: + err = errors.Errorf("policy version of %d is invalid", data.Options.PolicyVersion) } - copy(policy.Master_key_descriptor[:], descriptorBytes) - - if err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &policy); err == unix.EINVAL { + if err == unix.EINVAL { // 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. @@ -151,14 +317,27 @@ func SetPolicy(path string, data *PolicyData) error { err = unix.ENOTDIR } else if _, policyErr := GetPolicy(path); policyErr == nil { // Checking if a policy is already set on this directory - err = ErrEncrypted - } else { - // Default to generic "bad options". - err = ErrBadEncryptionOptions + err = unix.EEXIST } } - - return errors.Wrapf(err, "set encryption policy %s", path) + switch err { + case nil: + return nil + case unix.EACCES: + var stat unix.Stat_t + if statErr := unix.Stat(path, &stat); statErr == nil && stat.Uid != uint32(os.Geteuid()) { + return &ErrDirectoryNotOwned{path, stat.Uid} + } + case unix.EEXIST: + return &ErrAlreadyEncrypted{path} + case unix.EINVAL: + return &ErrBadEncryptionOptions{path, data.Options} + case unix.ENOTTY: + return ErrEncryptionNotSupported + case unix.EOPNOTSUPP: + return ErrEncryptionNotEnabled + } + return errors.Wrapf(err, "failed to set encryption policy on %q", path) } // CheckSupport returns an error if the filesystem containing path does not @@ -172,20 +351,24 @@ func CheckSupport(path string) error { defer file.Close() // On supported directories, giving a bad policy will return EINVAL - badPolicy := unix.FscryptPolicy{ + badPolicy := unix.FscryptPolicyV1{ Version: math.MaxUint8, Contents_encryption_mode: math.MaxUint8, Filenames_encryption_mode: math.MaxUint8, - Flags: math.MaxUint8, + Flags: math.MaxUint8, } - err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &badPolicy) + err = setPolicy(file, unsafe.Pointer(&badPolicy)) switch err { case nil: log.Panicf(`FS_IOC_SET_ENCRYPTION_POLICY succeeded when it should have failed. Please open an issue, filesystem %q may be corrupted.`, path) case unix.EINVAL, unix.EACCES: return nil + case unix.ENOTTY: + return ErrEncryptionNotSupported + case unix.EOPNOTSUPP: + return ErrEncryptionNotEnabled } - return err + return errors.Wrapf(err, "unexpected error checking for encryption support on filesystem %q", path) } diff --git a/metadata/policy_test.go b/metadata/policy_test.go index 783a784..7856ed3 100644 --- a/metadata/policy_test.go +++ b/metadata/policy_test.go @@ -23,19 +23,33 @@ import ( "fmt" "os" "path/filepath" - "reflect" "testing" + "golang.org/x/sys/unix" + "google.golang.org/protobuf/proto" + "github.com/google/fscrypt/util" ) -const goodDescriptor = "0123456789abcdef" +const goodV1Descriptor = "0123456789abcdef" -var goodPolicy = &PolicyData{ - KeyDescriptor: goodDescriptor, +var goodV1Policy = &PolicyData{ + KeyDescriptor: goodV1Descriptor, Options: DefaultOptions, } +var goodV2EncryptionOptions = &EncryptionOptions{ + Padding: 32, + Contents: EncryptionOptions_AES_256_XTS, + Filenames: EncryptionOptions_AES_256_CTS, + PolicyVersion: 2, +} + +var goodV2Policy = &PolicyData{ + KeyDescriptor: "0123456789abcdef0123456789abcdef", + Options: goodV2EncryptionOptions, +} + // Creates a temporary directory for testing. func createTestDirectory(t *testing.T) (directory string, err error) { baseDirectory, err := util.TestRoot() @@ -82,7 +96,7 @@ func TestSetPolicyEmptyDirectory(t *testing.T) { } defer os.RemoveAll(directory) - if err = SetPolicy(directory, goodPolicy); err != nil { + if err = SetPolicy(directory, goodV1Policy); err != nil { t.Error(err) } } @@ -95,7 +109,7 @@ func TestSetPolicyNonemptyDirectory(t *testing.T) { } defer os.RemoveAll(directory) - if err = SetPolicy(directory, goodPolicy); err == nil { + if err = SetPolicy(directory, goodV1Policy); err == nil { t.Error("should have failed to set policy on a nonempty directory") } } @@ -108,7 +122,7 @@ func TestSetPolicyFile(t *testing.T) { } defer os.RemoveAll(directory) - if err = SetPolicy(file, goodPolicy); err == nil { + if err = SetPolicy(file, goodV1Policy); err == nil { t.Error("should have failed to set policy on a file") } } @@ -140,15 +154,15 @@ func TestGetPolicyEmptyDirectory(t *testing.T) { defer os.RemoveAll(directory) var actualPolicy *PolicyData - if err = SetPolicy(directory, goodPolicy); err != nil { + if err = SetPolicy(directory, goodV1Policy); err != nil { t.Fatal(err) } if actualPolicy, err = GetPolicy(directory); err != nil { t.Fatal(err) } - if !reflect.DeepEqual(actualPolicy, goodPolicy) { - t.Errorf("policy %+v does not equal expected policy %+v", actualPolicy, goodPolicy) + if !proto.Equal(actualPolicy, goodV1Policy) { + t.Errorf("policy %+v does not equal expected policy %+v", actualPolicy, goodV1Policy) } } @@ -164,3 +178,35 @@ func TestGetPolicyUnencrypted(t *testing.T) { t.Error("should have failed to set policy on a file") } } + +func requireV2PolicySupport(t *testing.T, directory string) { + file, err := os.Open(directory) + if err != nil { + t.Fatal(err) + } + defer file.Close() + + err = getPolicyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY_EX, nil) + if err == ErrEncryptionNotSupported { + t.Skip("No support for v2 encryption policies, skipping test") + } +} + +// Tests that a non-root user cannot set a v2 encryption policy unless the key +// has been added. +func TestSetV2PolicyNoKey(t *testing.T) { + if util.IsUserRoot() { + t.Skip("This test cannot be run as root") + } + directory, err := createTestDirectory(t) + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(directory) + requireV2PolicySupport(t, directory) + + err = SetPolicy(directory, goodV2Policy) + if err == nil { + t.Error("shouldn't have been able to set v2 policy without key added") + } +} |