-p -l 32
// where costs.Time = , costs.Memory = 2^, and costs.Parallelism = .
type hashTestCase struct {
costs *metadata.HashingCosts
hexHash string
}
var hashTestCases = []hashTestCase{
{
costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 1},
hexHash: "a66f5398e33761bf161fdf1273e99b148f07d88d12d85b7673fddd723f95ec34",
},
{
costs: &metadata.HashingCosts{Time: 10, Memory: 1 << 10, Parallelism: 1},
hexHash: "5fa2cb89db1f7413ba1776258b7c8ee8c377d122078d28fe1fd645c353787f50",
},
{
costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 15, Parallelism: 1},
hexHash: "f474a213ed14d16ead619568000939b938ddfbd2ac4a82d253afa81b5ebaef84",
},
{
costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 10},
hexHash: "b7c3d7a0be222680b5ea3af3fb1a0b7b02b92cbd7007821dc8b84800c86c7783",
},
}
// Checks that len(array) == expected
func lengthCheck(name string, array []byte, expected int) error {
if len(array) != expected {
return fmt.Errorf("length of %s should be %d", name, expected)
}
return nil
}
// Tests the two ways of making keys
func TestMakeKeys(t *testing.T) {
data := []byte("1234\n6789")
key1, err := NewKeyFromReader(bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
defer key1.Wipe()
if !bytes.Equal(data, key1.data) {
t.Error("Key from reader contained incorrect data")
}
key2, err := NewFixedLengthKeyFromReader(bytes.NewReader(data), 6)
if err != nil {
t.Fatal(err)
}
defer key2.Wipe()
if !bytes.Equal([]byte("1234\n6"), key2.data) {
t.Error("Fixed length key from reader contained incorrect data")
}
}
// Tests that wipe succeeds
func TestWipe(t *testing.T) {
key, err := makeKey(1, 1000)
if err != nil {
t.Fatal(err)
}
if err := key.Wipe(); err != nil {
t.Error(err)
}
}
// Making keys with negative length should fail
func TestInvalidLength(t *testing.T) {
key, err := NewFixedLengthKeyFromReader(ConstReader(1), -1)
if err == nil {
key.Wipe()
t.Error("Negative lengths should cause failure")
}
}
// Test making keys of zero length
func TestZeroLength(t *testing.T) {
key1, err := NewFixedLengthKeyFromReader(os.Stdin, 0)
if err != nil {
t.Fatal(err)
}
defer key1.Wipe()
if key1.data != nil {
t.Error("Fixed length key from reader contained data")
}
key2, err := NewKeyFromReader(bytes.NewReader(nil))
if err != nil {
t.Fatal(err)
}
defer key2.Wipe()
if key2.data != nil {
t.Error("Key from empty reader contained data")
}
}
// Test that enabling then disabling memory locking succeeds even if a key is
// active when the variable changes.
func TestEnableDisableMemoryLocking(t *testing.T) {
// Mlock on for creation, off for wiping
key, err := NewRandomKey(metadata.InternalKeyLen)
UseMlock = false
defer func() {
UseMlock = true
}()
if err != nil {
t.Fatal(err)
}
if err := key.Wipe(); err != nil {
t.Error(err)
}
}
// Test that disabling then enabling memory locking succeeds even if a key is
// active when the variable changes.
func TestDisableEnableMemoryLocking(t *testing.T) {
// Mlock off for creation, on for wiping
UseMlock = false
key2, err := NewRandomKey(metadata.InternalKeyLen)
UseMlock = true
if err != nil {
t.Fatal(err)
}
if err := key2.Wipe(); err != nil {
t.Error(err)
}
}
// Test making keys long enough that the keys will have to resize
func TestKeyResize(t *testing.T) {
// Key will have to resize once
r := io.LimitReader(ConstReader(1), int64(os.Getpagesize())+1)
key, err := NewKeyFromReader(r)
if err != nil {
t.Fatal(err)
}
defer key.Wipe()
for i, b := range key.data {
if b != 1 {
t.Fatalf("Byte %d contained invalid data %q", i, b)
}
}
}
// Test making keys so long that many resizes are necessary
func TestKeyLargeResize(t *testing.T) {
// Key will have to resize 7 times
r := io.LimitReader(ConstReader(1), int64(os.Getpagesize())*65)
// Turn off Mlocking as the key will exceed the limit on some systems.
UseMlock = false
key, err := NewKeyFromReader(r)
UseMlock = true
if err != nil {
t.Fatal(err)
}
defer key.Wipe()
for i, b := range key.data {
if b != 1 {
t.Fatalf("Byte %d contained invalid data %q", i, b)
}
}
}
// Adds and removes a key with various services.
func TestAddRemoveKeys(t *testing.T) {
for _, service := range []string{defaultService, "ext4:", "f2fs:"} {
validDescription := service + fakeValidDescriptor
if err := InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser); err != nil {
t.Error(err)
}
if err := security.RemoveKey(validDescription, testUser); err != nil {
t.Error(err)
}
}
}
// Adds a key twice (both should succeed)
func TestAddTwice(t *testing.T) {
validDescription := defaultService + fakeValidDescriptor
InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser)
if InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser) != nil {
t.Error("InsertPolicyKey should not fail if key already exists")
}
security.RemoveKey(validDescription, testUser)
}
// Makes sure a key fails with bad policy or service
func TestBadAddKeys(t *testing.T) {
validDescription := defaultService + fakeValidDescriptor
if InsertPolicyKey(fakeInvalidPolicyKey, validDescription, testUser) == nil {
security.RemoveKey(validDescription, testUser)
t.Error("InsertPolicyKey should fail with bad policy key")
}
invalidDescription := "ext4" + fakeValidDescriptor
if InsertPolicyKey(fakeValidPolicyKey, invalidDescription, testUser) == nil {
security.RemoveKey(invalidDescription, testUser)
t.Error("InsertPolicyKey should fail with bad service")
}
}
// Check that we can create random keys. All this test does to test the
// "randomness" is generate a page of random bytes and attempts compression.
// If the data can be compressed it is probably not very random. This isn't
// intended to be a sufficient test for randomness (which is impossible), but a
// way to catch simple regressions (key is all zeros or contains a repeating
// pattern).
func TestRandomKeyGen(t *testing.T) {
key, err := NewRandomKey(os.Getpagesize())
if err != nil {
t.Fatal(err)
}
defer key.Wipe()
if didCompress(key.data) {
t.Errorf("Random key (%d bytes) should not be compressible", key.Len())
}
}
func TestBigKeyGen(t *testing.T) {
key, err := NewRandomKey(4096 * 4096)
switch err {
case nil:
key.Wipe()
return
case ErrKeyLock:
// Don't fail just because "ulimit -l" is too low.
return
default:
t.Fatal(err)
}
}
// didCompress checks if the given data can be compressed. Specifically, it
// returns true if running zlib on the provided input produces a shorter output.
func didCompress(input []byte) bool {
var output bytes.Buffer
w := zlib.NewWriter(&output)
_, err := w.Write(input)
w.Close()
return err == nil && len(input) > output.Len()
}
// Checks that the input arrays are all distinct
func buffersDistinct(buffers ...[]byte) bool {
for i := 0; i < len(buffers); i++ {
for j := i + 1; j < len(buffers); j++ {
if bytes.Equal(buffers[i], buffers[j]) {
// Different entry, but equal arrays
return false
}
}
}
return true
}
// Checks that our cryptographic operations all produce distinct data
func TestKeysAndOutputsDistinct(t *testing.T) {
data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey)
if err != nil {
t.Fatal(err)
}
encKey, authKey := stretchKey(fakeWrappingKey)
defer encKey.Wipe()
defer authKey.Wipe()
if !buffersDistinct(fakeWrappingKey.data, fakeValidPolicyKey.data,
encKey.data, authKey.data, data.IV, data.EncryptedKey, data.Hmac) {
t.Error("Key wrapping produced duplicate data")
}
}
// Check that Wrap() works with fixed keys
func TestWrapSucceeds(t *testing.T) {
data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey)
if err != nil {
t.Fatal(err)
}
if err = lengthCheck("IV", data.IV, aes.BlockSize); err != nil {
t.Error(err)
}
if err = lengthCheck("Encrypted Key", data.EncryptedKey, metadata.PolicyKeyLen); err != nil {
t.Error(err)
}
if err = lengthCheck("HMAC", data.Hmac, sha256.Size); err != nil {
t.Error(err)
}
}
// Checks that applying Wrap then Unwrap gives the original data
func testWrapUnwrapEqual(wrappingKey *Key, secretKey *Key) error {
data, err := Wrap(wrappingKey, secretKey)
if err != nil {
return err
}
secret, err := Unwrap(wrappingKey, data)
if err != nil {
return err
}
defer secret.Wipe()
if !bytes.Equal(secretKey.data, secret.data) {
return fmt.Errorf("Got %x after wrap/unwrap with w=%x and s=%x",
secret.data, wrappingKey.data, secretKey.data)
}
return nil
}
// Check that Unwrap(Wrap(x)) == x with fixed keys
func TestWrapUnwrapEqual(t *testing.T) {
if err := testWrapUnwrapEqual(fakeWrappingKey, fakeValidPolicyKey); err != nil {
t.Error(err)
}
}
// Check that Unwrap(Wrap(x)) == x with random keys
func TestRandomWrapUnwrapEqual(t *testing.T) {
for i := 0; i < 10; i++ {
wk, err := NewRandomKey(metadata.InternalKeyLen)
if err != nil {
t.Fatal(err)
}
sk, err := NewRandomKey(metadata.InternalKeyLen)
if err != nil {
t.Fatal(err)
}
if err = testWrapUnwrapEqual(wk, sk); err != nil {
t.Error(err)
}
wk.Wipe()
sk.Wipe()
}
}
// Check that Unwrap(Wrap(x)) == x with differing lengths of secret key
func TestDifferentLengthSecretKey(t *testing.T) {
wk, err := makeKey(1, metadata.InternalKeyLen)
if err != nil {
t.Fatal(err)
}
defer wk.Wipe()
for i := 0; i < 100; i++ {
sk, err := makeKey(2, i)
if err != nil {
t.Fatal(err)
}
if err = testWrapUnwrapEqual(wk, sk); err != nil {
t.Error(err)
}
sk.Wipe()
}
}
// Wrong length of wrapping key should fail
func TestWrongWrappingKeyLength(t *testing.T) {
_, err := Wrap(fakeValidPolicyKey, fakeWrappingKey)
if err == nil {
t.Fatal("using a policy key for wrapping should fail")
}
}
// Wrong length of unwrapping key should fail
func TestWrongUnwrappingKeyLength(t *testing.T) {
data, err := Wrap(fakeWrappingKey, fakeWrappingKey)
if err != nil {
t.Fatal(err)
}
if k, err := Unwrap(fakeValidPolicyKey, data); err == nil {
k.Wipe()
t.Fatal("using a policy key for unwrapping should fail")
}
}
// Wrapping twice with the same keys should give different components
func TestWrapTwiceDistinct(t *testing.T) {
data1, err := Wrap(fakeWrappingKey, fakeValidPolicyKey)
if err != nil {
t.Fatal(err)
}
data2, err := Wrap(fakeWrappingKey, fakeValidPolicyKey)
if err != nil {
t.Fatal(err)
}
if !buffersDistinct(data1.IV, data1.EncryptedKey, data1.Hmac,
data2.IV, data2.EncryptedKey, data2.Hmac) {
t.Error("Wrapping same keys twice should give distinct results")
}
}
// Attempts to Unwrap data with key after altering tweak, should fail
func testFailWithTweak(key *Key, data *metadata.WrappedKeyData, tweak []byte) error {
tweak[0]++
key, err := Unwrap(key, data)
if err == nil {
key.Wipe()
}
tweak[0]--
return err
}
// Wrapping then unwrapping with different components altered
func TestUnwrapWrongKey(t *testing.T) {
data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey)
if err != nil {
t.Fatal(err)
}
if testFailWithTweak(fakeWrappingKey, data, fakeWrappingKey.data) == nil {
t.Error("using a different wrapping key should make unwrap fail")
}
}
func TestUnwrapWrongData(t *testing.T) {
data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey)
if err != nil {
t.Fatal(err)
}
if testFailWithTweak(fakeWrappingKey, data, data.EncryptedKey) == nil {
t.Error("changing encryption key should make unwrap fail")
}
if testFailWithTweak(fakeWrappingKey, data, data.IV) == nil {
t.Error("changing IV should make unwrap fail")
}
if testFailWithTweak(fakeWrappingKey, data, data.Hmac) == nil {
t.Error("changing HMAC should make unwrap fail")
}
}
// Run our test cases for passphrase hashing
func TestPassphraseHashing(t *testing.T) {
for i, testCase := range hashTestCases {
pk, err := fakePassphraseKey()
if err != nil {
t.Fatal(err)
}
defer pk.Wipe()
hash, err := PassphraseHash(pk, fakeSalt, testCase.costs)
if err != nil {
t.Fatal(err)
}
defer hash.Wipe()
actual := hex.EncodeToString(hash.data)
if actual != testCase.hexHash {
t.Errorf("Hash test %d: for costs=%+v expected hash of %q got %q",
i, testCase.costs, testCase.hexHash, actual)
}
}
}
func BenchmarkWrap(b *testing.B) {
for n := 0; n < b.N; n++ {
Wrap(fakeWrappingKey, fakeValidPolicyKey)
}
}
func BenchmarkUnwrap(b *testing.B) {
b.StopTimer()
data, _ := Wrap(fakeWrappingKey, fakeValidPolicyKey)
b.StartTimer()
for n := 0; n < b.N; n++ {
key, err := Unwrap(fakeWrappingKey, data)
if err != nil {
b.Fatal(err)
}
key.Wipe()
}
}
func BenchmarkUnwrapNolock(b *testing.B) {
b.StopTimer()
UseMlock = false
defer func() {
UseMlock = true
}()
data, _ := Wrap(fakeWrappingKey, fakeValidPolicyKey)
b.StartTimer()
for n := 0; n < b.N; n++ {
key, err := Unwrap(fakeWrappingKey, data)
if err != nil {
b.Fatal(err)
}
key.Wipe()
}
}
func BenchmarkRandomWrapUnwrap(b *testing.B) {
for n := 0; n < b.N; n++ {
wk, _ := NewRandomKey(metadata.InternalKeyLen)
sk, _ := NewRandomKey(metadata.InternalKeyLen)
testWrapUnwrapEqual(wk, sk)
// Must manually call wipe here, or test will use too much memory.
wk.Wipe()
sk.Wipe()
}
}
func benchmarkPassphraseHashing(b *testing.B, costs *metadata.HashingCosts) {
b.StopTimer()
pk, err := fakePassphraseKey()
if err != nil {
b.Fatal(err)
}
defer pk.Wipe()
b.StartTimer()
for n := 0; n < b.N; n++ {
hash, err := PassphraseHash(pk, fakeSalt, costs)
hash.Wipe()
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkPassphraseHashing_1MB_1Thread(b *testing.B) {
benchmarkPassphraseHashing(b,
&metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 1})
}
func BenchmarkPassphraseHashing_1GB_1Thread(b *testing.B) {
benchmarkPassphraseHashing(b,
&metadata.HashingCosts{Time: 1, Memory: 1 << 20, Parallelism: 1})
}
func BenchmarkPassphraseHashing_128MB_1Thread(b *testing.B) {
benchmarkPassphraseHashing(b,
&metadata.HashingCosts{Time: 1, Memory: 1 << 17, Parallelism: 1})
}
func BenchmarkPassphraseHashing_128MB_8Thread(b *testing.B) {
benchmarkPassphraseHashing(b,
&metadata.HashingCosts{Time: 1, Memory: 1 << 17, Parallelism: 8})
}
func BenchmarkPassphraseHashing_128MB_8Pass(b *testing.B) {
benchmarkPassphraseHashing(b,
&metadata.HashingCosts{Time: 8, Memory: 1 << 17, Parallelism: 1})
}
fscrypt-0.2.5/crypto/key.go 0000664 0000000 0000000 00000025577 13570323517 0015675 0 ustar 00root root 0000000 0000000 /*
* key.go - Cryptographic key management for fscrypt. Ensures that sensitive
* material is properly handled throughout the program.
*
* 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 crypto
/*
#include
#include
*/
import "C"
import (
"bytes"
"crypto/subtle"
"encoding/base32"
"io"
"log"
"os"
"os/user"
"runtime"
"unsafe"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/security"
"github.com/google/fscrypt/util"
)
const (
// Keys need to readable and writable, but hidden from other processes.
keyProtection = unix.PROT_READ | unix.PROT_WRITE
keyMmapFlags = unix.MAP_PRIVATE | unix.MAP_ANONYMOUS
)
/*
UseMlock determines whether we should use the mlock/munlock syscalls to
prevent sensitive data like keys and passphrases from being paged to disk.
UseMlock defaults to true, but can be set to false if the application calling
into this library has insufficient privileges to lock memory. Code using this
package could also bind this setting to a flag by using:
flag.BoolVar(&crypto.UseMlock, "lock-memory", true, "lock keys in memory")
*/
var UseMlock = true
/*
Key protects some arbitrary buffer of cryptographic material. Its methods
ensure that the Key's data is locked in memory before being used (if
UseMlock is set to true), and is wiped and unlocked after use (via the Wipe()
method). This data is never accessed outside of the fscrypt/crypto package
(except for the UnsafeData method). If a key is successfully created, the
Wipe() method should be called after it's use. For example:
func UseKeyFromStdin() error {
key, err := NewKeyFromReader(os.Stdin)
if err != nil {
return err
}
defer key.Wipe()
// Do stuff with key
return nil
}
The Wipe() method will also be called when a key is garbage collected; however,
it is best practice to clear the key as soon as possible, so it spends a minimal
amount of time in memory.
Note that Key is not thread safe, as a key could be wiped while another thread
is using it. Also, calling Wipe() from two threads could cause an error as
memory could be freed twice.
*/
type Key struct {
data []byte
}
// newBlankKey constructs a blank key of a specified length and returns an error
// if we are unable to allocate or lock the necessary memory.
func newBlankKey(length int) (*Key, error) {
if length == 0 {
return &Key{data: nil}, nil
} else if length < 0 {
return nil, errors.Wrapf(ErrNegativeLength, "length of %d requested", length)
}
flags := keyMmapFlags
if UseMlock {
flags |= unix.MAP_LOCKED
}
// See MAP_ANONYMOUS in http://man7.org/linux/man-pages/man2/mmap.2.html
data, err := unix.Mmap(-1, 0, length, keyProtection, flags)
if err == unix.EAGAIN {
return nil, ErrKeyLock
}
if err != nil {
log.Printf("unix.Mmap() with length=%d failed: %v", length, err)
return nil, ErrKeyAlloc
}
key := &Key{data: data}
// Backup finalizer in case user forgets to "defer key.Wipe()"
runtime.SetFinalizer(key, (*Key).Wipe)
return key, nil
}
// Wipe destroys a Key by zeroing and freeing the memory. The data is zeroed
// even if Wipe returns an error, which occurs if we are unable to unlock or
// free the key memory. Wipe does nothing if the key is already wiped or is nil.
func (key *Key) Wipe() error {
// We do nothing if key or key.data is nil so that Wipe() is idempotent
// and so Wipe() can be called on keys which have already been cleared.
if key != nil && key.data != nil {
data := key.data
key.data = nil
for i := range data {
data[i] = 0
}
if err := unix.Munmap(data); err != nil {
log.Printf("unix.Munmap() failed: %v", err)
return ErrKeyFree
}
}
return nil
}
// Len is the underlying data buffer's length.
func (key *Key) Len() int {
return len(key.data)
}
// Equals compares the contents of two keys, returning true if they have the same
// key data. This function runs in constant time.
func (key *Key) Equals(key2 *Key) bool {
return subtle.ConstantTimeCompare(key.data, key2.data) == 1
}
// resize returns a new key with size requestedSize and the appropriate data
// copied over. The original data is wiped. This method does nothing and returns
// itself if the key's length equals requestedSize.
func (key *Key) resize(requestedSize int) (*Key, error) {
if key.Len() == requestedSize {
return key, nil
}
defer key.Wipe()
resizedKey, err := newBlankKey(requestedSize)
if err != nil {
return nil, err
}
copy(resizedKey.data, key.data)
return resizedKey, nil
}
// UnsafeToCString makes a copy of the string's data into a null-terminated C
// string allocated by C. Note that this method is unsafe as this C copy has no
// locking or wiping functionality. The key shouldn't contain any `\0` bytes.
func (key *Key) UnsafeToCString() unsafe.Pointer {
size := C.size_t(key.Len())
data := C.calloc(size+1, 1)
C.memcpy(data, util.Ptr(key.data), size)
return data
}
// NewKeyFromCString creates of a copy of some C string's data in a key. Note
// that the original C string is not modified at all, so steps must be taken to
// ensure that this original copy is secured.
func NewKeyFromCString(str unsafe.Pointer) (*Key, error) {
size := C.strlen((*C.char)(str))
key, err := newBlankKey(int(size))
if err != nil {
return nil, err
}
C.memcpy(util.Ptr(key.data), str, size)
return key, nil
}
// NewKeyFromReader constructs a key of arbitrary length by reading from reader
// until hitting EOF.
func NewKeyFromReader(reader io.Reader) (*Key, error) {
// Use an initial key size of a page. As Mmap allocates a page anyway,
// there isn't much additional overhead from starting with a whole page.
key, err := newBlankKey(os.Getpagesize())
if err != nil {
return nil, err
}
totalBytesRead := 0
for {
bytesRead, err := reader.Read(key.data[totalBytesRead:])
totalBytesRead += bytesRead
switch err {
case nil:
// Need to continue reading. Grow key if necessary
if key.Len() == totalBytesRead {
if key, err = key.resize(2 * key.Len()); err != nil {
return nil, err
}
}
case io.EOF:
// Getting the EOF error means we are done
return key.resize(totalBytesRead)
default:
// Fail if Read() has a failure
key.Wipe()
return nil, err
}
}
}
// NewFixedLengthKeyFromReader constructs a key with a specified length by
// reading exactly length bytes from reader.
func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) {
key, err := newBlankKey(length)
if err != nil {
return nil, err
}
if _, err := io.ReadFull(reader, key.data); err != nil {
key.Wipe()
return nil, err
}
return key, nil
}
// InsertPolicyKey puts the provided policy key into the kernel keyring with the
// provided description, and type logon. The key must be a policy key.
func InsertPolicyKey(key *Key, description string, target *user.User) error {
if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil {
return errors.Wrap(err, "policy key")
}
// Create our payload (containing an FscryptKey)
payload, err := newBlankKey(int(unsafe.Sizeof(unix.FscryptKey{})))
if err != nil {
return err
}
defer payload.Wipe()
// Cast the payload to an FscryptKey so we can initialize the fields.
fscryptKey := (*unix.FscryptKey)(util.Ptr(payload.data))
// Mode is ignored by the kernel
fscryptKey.Mode = 0
fscryptKey.Size = metadata.PolicyKeyLen
copy(fscryptKey.Raw[:], key.data)
return security.InsertKey(payload.data, description, target)
}
var (
// The recovery code is base32 with a dash between each block of 8 characters.
encoding = base32.StdEncoding
blockSize = 8
separator = []byte("-")
encodedLength = encoding.EncodedLen(metadata.PolicyKeyLen)
decodedLength = encoding.DecodedLen(encodedLength)
// RecoveryCodeLength is the number of bytes in every recovery code
RecoveryCodeLength = (encodedLength/blockSize)*(blockSize+len(separator)) - len(separator)
)
// WriteRecoveryCode outputs key's recovery code to the provided writer.
// WARNING: This recovery key is enough to derive the original key, so it must
// be given the same level of protection as a raw cryptographic key.
func WriteRecoveryCode(key *Key, writer io.Writer) error {
if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil {
return errors.Wrap(err, "recovery key")
}
// We store the base32 encoded data (without separators) in a temp key
encodedKey, err := newBlankKey(encodedLength)
if err != nil {
return err
}
defer encodedKey.Wipe()
encoding.Encode(encodedKey.data, key.data)
w := util.NewErrWriter(writer)
// Write the blocks with separators between them
w.Write(encodedKey.data[:blockSize])
for blockStart := blockSize; blockStart < encodedLength; blockStart += blockSize {
w.Write(separator)
blockEnd := util.MinInt(blockStart+blockSize, encodedLength)
w.Write(encodedKey.data[blockStart:blockEnd])
}
// If any writes have failed, return the error
return w.Err()
}
// ReadRecoveryCode gets the recovery code from the provided reader and returns
// the corresponding cryptographic key.
// WARNING: This recovery key is enough to derive the original key, so it must
// be given the same level of protection as a raw cryptographic key.
func ReadRecoveryCode(reader io.Reader) (*Key, error) {
// We store the base32 encoded data (without separators) in a temp key
encodedKey, err := newBlankKey(encodedLength)
if err != nil {
return nil, err
}
defer encodedKey.Wipe()
r := util.NewErrReader(reader)
// Read the other blocks, checking the separators between them
r.Read(encodedKey.data[:blockSize])
inputSeparator := make([]byte, len(separator))
for blockStart := blockSize; blockStart < encodedLength; blockStart += blockSize {
r.Read(inputSeparator)
if r.Err() == nil && !bytes.Equal(separator, inputSeparator) {
err = errors.Wrapf(ErrRecoveryCode, "invalid separator %q", inputSeparator)
return nil, err
}
blockEnd := util.MinInt(blockStart+blockSize, encodedLength)
r.Read(encodedKey.data[blockStart:blockEnd])
}
// If any reads have failed, return the error
if r.Err() != nil {
return nil, errors.Wrapf(ErrRecoveryCode, "read error %v", r.Err())
}
// Now we decode the key, resizing if necessary
decodedKey, err := newBlankKey(decodedLength)
if err != nil {
return nil, err
}
if _, err = encoding.Decode(decodedKey.data, encodedKey.data); err != nil {
return nil, errors.Wrap(ErrRecoveryCode, err.Error())
}
return decodedKey.resize(metadata.PolicyKeyLen)
}
fscrypt-0.2.5/crypto/rand.go 0000664 0000000 0000000 00000004334 13570323517 0016015 0 ustar 00root root 0000000 0000000 /*
* rand.go - Reader used to generate secure random data for fscrypt.
*
* 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 crypto
import (
"io"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
// NewRandomBuffer uses the Linux Getrandom() syscall to create random bytes. If
// the operating system has insufficient randomness, the buffer creation will
// fail. This is an improvement over Go's built-in crypto/rand which will still
// return bytes if the system has insufficiency entropy.
// See: https://github.com/golang/go/issues/19274
//
// While this syscall was only introduced in Kernel v3.17, it predates the
// introduction of filesystem encryption, so it introduces no additional
// compatibility issues.
func NewRandomBuffer(length int) ([]byte, error) {
buffer := make([]byte, length)
if _, err := io.ReadFull(randReader{}, buffer); err != nil {
return nil, err
}
return buffer, nil
}
// NewRandomKey creates a random key of the specified length. This function uses
// the same random number generation process as NewRandomBuffer.
func NewRandomKey(length int) (*Key, error) {
return NewFixedLengthKeyFromReader(randReader{}, length)
}
// randReader just calls into Getrandom, so no internal data is needed.
type randReader struct{}
func (r randReader) Read(buffer []byte) (int, error) {
n, err := unix.Getrandom(buffer, unix.GRND_NONBLOCK)
switch err {
case nil:
return n, nil
case unix.EAGAIN:
return 0, errors.Wrap(ErrGetrandomFail, "insufficient entropy in pool")
case unix.ENOSYS:
return 0, errors.Wrap(ErrGetrandomFail, "kernel must be v3.17 or later")
default:
return 0, errors.Wrap(ErrGetrandomFail, err.Error())
}
}
fscrypt-0.2.5/crypto/recovery_test.go 0000664 0000000 0000000 00000012543 13570323517 0017767 0 ustar 00root root 0000000 0000000 /*
* recovery_test.go - tests for recovery codes in the crypto package
* tests key wrapping/unwrapping and key generation
*
* 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 crypto
import (
"bytes"
"fmt"
"testing"
"github.com/google/fscrypt/metadata"
)
const fakeSecretRecoveryCode = "EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJQ="
var fakeSecretKey, _ = makeKey(38, metadata.PolicyKeyLen)
// Note that this function is INSECURE. FOR TESTING ONLY
func getRecoveryCodeFromKey(key *Key) ([]byte, error) {
var buf bytes.Buffer
if err := WriteRecoveryCode(key, &buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func getRandomRecoveryCodeBuffer() ([]byte, error) {
key, err := NewRandomKey(metadata.PolicyKeyLen)
if err != nil {
return nil, err
}
defer key.Wipe()
return getRecoveryCodeFromKey(key)
}
func getKeyFromRecoveryCode(buf []byte) (*Key, error) {
return ReadRecoveryCode(bytes.NewReader(buf))
}
// Given a key, make a recovery code from that key, use that code to rederive
// another key and check if they are the same.
func testKeyEncodeDecode(key *Key) error {
buf, err := getRecoveryCodeFromKey(key)
if err != nil {
return err
}
key2, err := getKeyFromRecoveryCode(buf)
if err != nil {
return err
}
defer key2.Wipe()
if !bytes.Equal(key.data, key2.data) {
return fmt.Errorf("encoding then decoding %x didn't yield the same key", key.data)
}
return nil
}
// Given a recovery code, make a key from that recovery code, use that key to
// rederive another recovery code and check if they are the same.
func testRecoveryDecodeEncode(buf []byte) error {
key, err := getKeyFromRecoveryCode(buf)
if err != nil {
return err
}
defer key.Wipe()
buf2, err := getRecoveryCodeFromKey(key)
if err != nil {
return err
}
if !bytes.Equal(buf, buf2) {
return fmt.Errorf("decoding then encoding %x didn't yield the same key", buf)
}
return nil
}
func TestGetRandomRecoveryString(t *testing.T) {
b, err := getRandomRecoveryCodeBuffer()
if err != nil {
t.Fatal(err)
}
t.Log(string(b))
// t.Fail() // Uncomment to see an example random recovery code
}
func TestFakeSecretKey(t *testing.T) {
buf, err := getRecoveryCodeFromKey(fakeSecretKey)
if err != nil {
t.Fatal(err)
}
recoveryCode := string(buf)
if recoveryCode != fakeSecretRecoveryCode {
t.Errorf("got '%s' instead of '%s'", recoveryCode, fakeSecretRecoveryCode)
}
}
func TestEncodeDecode(t *testing.T) {
key, err := NewRandomKey(metadata.PolicyKeyLen)
if err != nil {
t.Fatal(err)
}
defer key.Wipe()
if err = testKeyEncodeDecode(key); err != nil {
t.Error(err)
}
}
func TestDecodeEncode(t *testing.T) {
buf, err := getRandomRecoveryCodeBuffer()
if err != nil {
t.Fatal(err)
}
if err = testRecoveryDecodeEncode(buf); err != nil {
t.Error(err)
}
}
func TestWrongLengthError(t *testing.T) {
key, err := NewRandomKey(metadata.PolicyKeyLen - 1)
if err != nil {
t.Fatal(err)
}
defer key.Wipe()
if _, err = getRecoveryCodeFromKey(key); err == nil {
t.Error("key with wrong length should have failed to encode")
}
}
func TestBadCharacterError(t *testing.T) {
buf, err := getRandomRecoveryCodeBuffer()
if err != nil {
t.Fatal(err)
}
// Lowercase letters not allowed
buf[3] = 'k'
if key, err := getKeyFromRecoveryCode(buf); err == nil {
key.Wipe()
t.Error("lowercase letters should make decoding fail")
}
}
func TestBadEndCharacterError(t *testing.T) {
buf, err := getRandomRecoveryCodeBuffer()
if err != nil {
t.Fatal(err)
}
// Separator must be '-'
buf[blockSize] = '_'
if key, err := getKeyFromRecoveryCode(buf); err == nil {
key.Wipe()
t.Error("any separator that isn't '-' should make decoding fail")
}
}
func BenchmarkEncode(b *testing.B) {
b.StopTimer()
key, err := NewRandomKey(metadata.PolicyKeyLen)
if err != nil {
b.Fatal(err)
}
defer key.Wipe()
b.StartTimer()
for n := 0; n < b.N; n++ {
if _, err = getRecoveryCodeFromKey(key); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDecode(b *testing.B) {
b.StopTimer()
buf, err := getRandomRecoveryCodeBuffer()
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for n := 0; n < b.N; n++ {
key, err := getKeyFromRecoveryCode(buf)
if err != nil {
b.Fatal(err)
}
key.Wipe()
}
}
func BenchmarkEncodeDecode(b *testing.B) {
b.StopTimer()
key, err := NewRandomKey(metadata.PolicyKeyLen)
if err != nil {
b.Fatal(err)
}
defer key.Wipe()
b.StartTimer()
for n := 0; n < b.N; n++ {
if err = testKeyEncodeDecode(key); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDecodeEncode(b *testing.B) {
b.StopTimer()
buf, err := getRandomRecoveryCodeBuffer()
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for n := 0; n < b.N; n++ {
if err = testRecoveryDecodeEncode(buf); err != nil {
b.Fatal(err)
}
}
}
fscrypt-0.2.5/filesystem/ 0000775 0000000 0000000 00000000000 13570323517 0015402 5 ustar 00root root 0000000 0000000 fscrypt-0.2.5/filesystem/filesystem.go 0000664 0000000 0000000 00000041462 13570323517 0020124 0 ustar 00root root 0000000 0000000 /*
* filesystem.go - Contains the functionality for a specific filesystem. This
* includes the commands to setup the filesystem, apply policies, and locate
* metadata.
*
* 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 filesystem deals with the structure of the files on disk used to
// store the metadata for fscrypt. Specifically, this package includes:
// - mountpoint management (mountpoint.go)
// - querying existing mounted filesystems
// - getting filesystems from a UUID
// - finding the filesystem for a specific path
// - metadata organization (filesystem.go)
// - setting up a mounted filesystem for use with fscrypt
// - adding/querying/deleting metadata
// - making links to other filesystems' metadata
// - following links to get data from other filesystems
package filesystem
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/util"
)
// Filesystem error values
var (
ErrNotAMountpoint = errors.New("not a mountpoint")
ErrAlreadySetup = errors.New("already setup for use with fscrypt")
ErrNotSetup = errors.New("not setup for use with fscrypt")
ErrNoMetadata = errors.New("could not find metadata")
ErrLinkedProtector = errors.New("not a regular protector")
ErrInvalidMetadata = errors.New("provided metadata is invalid")
ErrFollowLink = errors.New("cannot follow filesystem link")
ErrLinkExpired = errors.New("no longer exists on linked filesystem")
ErrMakeLink = util.SystemError("cannot create filesystem link")
ErrGlobalMountInfo = util.SystemError("creating global mountpoint list failed")
ErrCorruptMetadata = util.SystemError("on-disk metadata is corrupt")
)
// Mount contains information for a specific mounted filesystem.
// Path - Absolute path where the directory is mounted
// Filesystem - Name of the mounted filesystem
// Options - List of options used when mounting the filesystem
// Device - Device for filesystem (empty string if we cannot find one)
//
// In order to use a Mount to store fscrypt metadata, some directories must be
// setup first. Specifically, the directories created look like:
//
// └── .fscrypt
// ├── policies
// └── protectors
//
// These "policies" and "protectors" directories will contain files that are
// the corresponding metadata structures for policies and protectors. The public
// interface includes functions for setting up these directories and Adding,
// Getting, and Removing these files.
//
// There is also the ability to reference another filesystem's metadata. This is
// used when a Policy on filesystem A is protected with Protector on filesystem
// B. In this scenario, we store a "link file" in the protectors directory whose
// contents look like "UUID=3a6d9a76-47f0-4f13-81bf-3332fbe984fb".
type Mount struct {
Path string
Filesystem string
Options []string
Device string
}
// PathSorter allows mounts to be sorted by Path.
type PathSorter []*Mount
func (p PathSorter) Len() int { return len(p) }
func (p PathSorter) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p PathSorter) Less(i, j int) bool { return p[i].Path < p[j].Path }
const (
// Names of the various directories used in fscrypt
baseDirName = ".fscrypt"
policyDirName = "policies"
protectorDirName = "protectors"
tempPrefix = ".tmp"
linkFileExtension = ".link"
// The base directory should be read-only (except for the creator)
basePermissions = 0755
// The subdirectories should be writable to everyone, but they have the
// sticky bit set so users cannot delete other users' metadata.
dirPermissions = os.ModeSticky | 0777
// The metadata files are globally visible, but can only be deleted by
// the user that created them
filePermissions = 0644
)
func (m *Mount) String() string {
return fmt.Sprintf(`%s
Filsystem: %s
Options: %v
Device: %s`, m.Path, m.Filesystem, m.Options, m.Device)
}
// BaseDir returns the path of the base fscrypt directory on this filesystem.
func (m *Mount) BaseDir() string {
return filepath.Join(m.Path, baseDirName)
}
// ProtectorDir returns the directory containing the protector metadata.
func (m *Mount) ProtectorDir() string {
return filepath.Join(m.BaseDir(), protectorDirName)
}
// protectorPath returns the full path to a regular protector file with the
// specified descriptor.
func (m *Mount) protectorPath(descriptor string) string {
return filepath.Join(m.ProtectorDir(), descriptor)
}
// linkedProtectorPath returns the full path to a linked protector file with the
// specified descriptor.
func (m *Mount) linkedProtectorPath(descriptor string) string {
return m.protectorPath(descriptor) + linkFileExtension
}
// PolicyDir returns the directory containing the policy metadata.
func (m *Mount) PolicyDir() string {
return filepath.Join(m.BaseDir(), policyDirName)
}
// policyPath returns the full path to a regular policy file with the
// specified descriptor.
func (m *Mount) policyPath(descriptor string) string {
return filepath.Join(m.PolicyDir(), descriptor)
}
// tempMount creates a temporary Mount under the main directory. The path for
// the returned tempMount should be removed by the caller.
func (m *Mount) tempMount() (*Mount, error) {
trashDir, err := ioutil.TempDir(m.Path, tempPrefix)
return &Mount{Path: trashDir}, err
}
// err modifies an error to contain the path of this filesystem.
func (m *Mount) err(err error) error {
return errors.Wrapf(err, "filesystem %s", m.Path)
}
// CheckSupport returns an error if this filesystem does not support filesystem
// encryption.
func (m *Mount) CheckSupport() error {
return m.err(metadata.CheckSupport(m.Path))
}
// CheckSetup returns an error if all the fscrypt metadata directories do not
// exist. Will log any unexpected errors or incorrect permissions.
func (m *Mount) CheckSetup() error {
// Run all the checks so we will always get all the warnings
baseGood := isDirCheckPerm(m.BaseDir(), basePermissions)
policyGood := isDirCheckPerm(m.PolicyDir(), dirPermissions)
protectorGood := isDirCheckPerm(m.ProtectorDir(), dirPermissions)
if baseGood && policyGood && protectorGood {
return nil
}
return m.err(ErrNotSetup)
}
// makeDirectories creates the three metadata directories with the correct
// permissions. Note that this function overrides the umask.
func (m *Mount) makeDirectories() error {
// Zero the umask so we get the permissions we want
oldMask := unix.Umask(0)
defer func() {
unix.Umask(oldMask)
}()
if err := os.Mkdir(m.BaseDir(), basePermissions); err != nil {
return err
}
if err := os.Mkdir(m.PolicyDir(), dirPermissions); err != nil {
return err
}
return os.Mkdir(m.ProtectorDir(), dirPermissions)
}
// Setup sets up the filesystem for use with fscrypt. Note that this merely
// creates the appropriate files on the filesystem. It does not actually modify
// the filesystem's feature flags. This operation is atomic; it either succeeds
// or no files in the baseDir are created.
func (m *Mount) Setup() error {
if m.CheckSetup() == nil {
return m.err(ErrAlreadySetup)
}
// We build the directories under a temp Mount and then move into place.
temp, err := m.tempMount()
if err != nil {
return m.err(err)
}
defer os.RemoveAll(temp.Path)
if err = temp.makeDirectories(); err != nil {
return m.err(err)
}
// Atomically move directory into place.
return m.err(os.Rename(temp.BaseDir(), m.BaseDir()))
}
// RemoveAllMetadata removes all the policy and protector metadata from the
// filesystem. This operation is atomic; it either succeeds or no files in the
// baseDir are removed.
// WARNING: Will cause data loss if the metadata is used to encrypt
// directories (this could include directories on other filesystems).
func (m *Mount) RemoveAllMetadata() error {
if err := m.CheckSetup(); err != nil {
return err
}
// temp will hold the old metadata temporarily
temp, err := m.tempMount()
if err != nil {
return m.err(err)
}
defer os.RemoveAll(temp.Path)
// Move directory into temp (to be destroyed on defer)
return m.err(os.Rename(m.BaseDir(), temp.BaseDir()))
}
func syncDirectory(dirPath string) error {
dirFile, err := os.Open(dirPath)
if err != nil {
return err
}
if err = dirFile.Sync(); err != nil {
dirFile.Close()
return err
}
return dirFile.Close()
}
// writeDataAtomic writes the data to the path such that the data is either
// written to stable storage or an error is returned.
func (m *Mount) writeDataAtomic(path string, data []byte) error {
// Write the data to a temporary file, sync it, then rename into place
// so that the operation will be atomic.
dirPath := filepath.Dir(path)
tempFile, err := ioutil.TempFile(dirPath, tempPrefix)
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
// TempFile() creates the file with mode 0600. Change it to 0644.
if err = tempFile.Chmod(filePermissions); err != nil {
tempFile.Close()
return err
}
if _, err = tempFile.Write(data); err != nil {
tempFile.Close()
return err
}
if err = tempFile.Sync(); err != nil {
tempFile.Close()
return err
}
if err = tempFile.Close(); err != nil {
return err
}
if err = os.Rename(tempFile.Name(), path); err != nil {
return err
}
// Ensure the rename has been persisted before returning success.
return syncDirectory(dirPath)
}
// addMetadata writes the metadata structure to the file with the specified
// path. This will overwrite any existing data. The operation is atomic.
func (m *Mount) addMetadata(path string, md metadata.Metadata) error {
if err := md.CheckValidity(); err != nil {
return errors.Wrap(ErrInvalidMetadata, err.Error())
}
data, err := proto.Marshal(md)
if err != nil {
return err
}
log.Printf("writing metadata to %q", path)
return m.writeDataAtomic(path, data)
}
// getMetadata reads the metadata structure from the file with the specified
// path. Only reads normal metadata files, not linked metadata.
func (m *Mount) getMetadata(path string, md metadata.Metadata) error {
data, err := ioutil.ReadFile(path)
if err != nil {
log.Printf("could not read metadata at %q", path)
if os.IsNotExist(err) {
return errors.Wrapf(ErrNoMetadata, "descriptor %s", filepath.Base(path))
}
return err
}
if err := proto.Unmarshal(data, md); err != nil {
return errors.Wrap(ErrCorruptMetadata, err.Error())
}
if err := md.CheckValidity(); err != nil {
log.Printf("metadata at %q is not valid", path)
return errors.Wrap(ErrCorruptMetadata, err.Error())
}
log.Printf("successfully read metadata from %q", path)
return nil
}
// removeMetadata deletes the metadata struct from the file with the specified
// path. Works with regular or linked metadata.
func (m *Mount) removeMetadata(path string) error {
if err := os.Remove(path); err != nil {
log.Printf("could not remove metadata at %q", path)
if os.IsNotExist(err) {
return errors.Wrapf(ErrNoMetadata, "descriptor %s", filepath.Base(path))
}
return err
}
log.Printf("successfully removed metadata at %q", path)
return nil
}
// AddProtector adds the protector metadata to this filesystem's storage. This
// will overwrite the value of an existing protector with this descriptor. This
// will fail with ErrLinkedProtector if a linked protector with this descriptor
// already exists on the filesystem.
func (m *Mount) AddProtector(data *metadata.ProtectorData) error {
if err := m.CheckSetup(); err != nil {
return err
}
if isRegularFile(m.linkedProtectorPath(data.ProtectorDescriptor)) {
return m.err(ErrLinkedProtector)
}
path := m.protectorPath(data.ProtectorDescriptor)
return m.err(m.addMetadata(path, data))
}
// AddLinkedProtector adds a link in this filesystem to the protector metadata
// in the dest filesystem.
func (m *Mount) AddLinkedProtector(descriptor string, dest *Mount) error {
if err := m.CheckSetup(); err != nil {
return err
}
// Check that the link is good (descriptor exists, filesystem has UUID).
if _, err := dest.GetRegularProtector(descriptor); err != nil {
return err
}
// Right now, we only make links using UUIDs.
link, err := makeLink(dest, "UUID")
if err != nil {
return dest.err(err)
}
path := m.linkedProtectorPath(descriptor)
return m.err(m.writeDataAtomic(path, []byte(link)))
}
// GetRegularProtector looks up the protector metadata by descriptor. This will
// fail with ErrNoMetadata if the descriptor is a linked protector.
func (m *Mount) GetRegularProtector(descriptor string) (*metadata.ProtectorData, error) {
if err := m.CheckSetup(); err != nil {
return nil, err
}
data := new(metadata.ProtectorData)
path := m.protectorPath(descriptor)
return data, m.err(m.getMetadata(path, data))
}
// GetProtector returns the Mount of the filesystem containing the information
// and that protector's data. If the descriptor is a regular (not linked)
// protector, the mount will return itself.
func (m *Mount) GetProtector(descriptor string) (*Mount, *metadata.ProtectorData, error) {
if err := m.CheckSetup(); err != nil {
return nil, nil, err
}
// Get the link data from the link file
link, err := ioutil.ReadFile(m.linkedProtectorPath(descriptor))
if err != nil {
// If the link doesn't exist, try for a regular protector.
if os.IsNotExist(err) {
data, err := m.GetRegularProtector(descriptor)
return m, data, err
}
return nil, nil, m.err(err)
}
// As the link could refer to multiple filesystems, we check each one
// for valid metadata.
mnts, err := getMountsFromLink(string(link))
if err != nil {
return nil, nil, m.err(err)
}
for _, mnt := range mnts {
if data, err := mnt.GetRegularProtector(descriptor); err != nil {
log.Print(err)
} else {
return mnt, data, nil
}
}
return nil, nil, m.err(errors.Wrapf(ErrLinkExpired, "protector %s", descriptor))
}
// RemoveProtector deletes the protector metadata (or a link to another
// filesystem's metadata) from the filesystem storage.
func (m *Mount) RemoveProtector(descriptor string) error {
if err := m.CheckSetup(); err != nil {
return err
}
// We first try to remove the linkedProtector. If that metadata does not
// exist, we try to remove the normal protector.
err := m.removeMetadata(m.linkedProtectorPath(descriptor))
if errors.Cause(err) == ErrNoMetadata {
err = m.removeMetadata(m.protectorPath(descriptor))
}
return m.err(err)
}
// ListProtectors lists the descriptors of all protectors on this filesystem.
// This does not include linked protectors.
func (m *Mount) ListProtectors() ([]string, error) {
if err := m.CheckSetup(); err != nil {
return nil, err
}
protectors, err := m.listDirectory(m.ProtectorDir())
return protectors, m.err(err)
}
// AddPolicy adds the policy metadata to the filesystem storage.
func (m *Mount) AddPolicy(data *metadata.PolicyData) error {
if err := m.CheckSetup(); err != nil {
return err
}
return m.err(m.addMetadata(m.policyPath(data.KeyDescriptor), data))
}
// GetPolicy looks up the policy metadata by descriptor.
func (m *Mount) GetPolicy(descriptor string) (*metadata.PolicyData, error) {
if err := m.CheckSetup(); err != nil {
return nil, err
}
data := new(metadata.PolicyData)
return data, m.err(m.getMetadata(m.policyPath(descriptor), data))
}
// RemovePolicy deletes the policy metadata from the filesystem storage.
func (m *Mount) RemovePolicy(descriptor string) error {
if err := m.CheckSetup(); err != nil {
return err
}
return m.err(m.removeMetadata(m.policyPath(descriptor)))
}
// ListPolicies lists the descriptors of all policies on this filesystem.
func (m *Mount) ListPolicies() ([]string, error) {
if err := m.CheckSetup(); err != nil {
return nil, err
}
policies, err := m.listDirectory(m.PolicyDir())
return policies, m.err(err)
}
// listDirectory returns a list of descriptors for a metadata directory,
// including files which are links to other filesystem's metadata.
func (m *Mount) listDirectory(directoryPath string) ([]string, error) {
log.Printf("listing descriptors in %q", directoryPath)
dir, err := os.Open(directoryPath)
if err != nil {
return nil, err
}
defer dir.Close()
names, err := dir.Readdirnames(-1)
if err != nil {
return nil, err
}
var descriptors []string
for _, name := range names {
// Be sure to include links as well
descriptors = append(descriptors, strings.TrimSuffix(name, linkFileExtension))
}
log.Printf("found %d descriptor(s)", len(descriptors))
return descriptors, nil
}
fscrypt-0.2.5/filesystem/filesystem_test.go 0000664 0000000 0000000 00000017211 13570323517 0021156 0 ustar 00root root 0000000 0000000 /*
* filesystem_test.go - Tests for reading/writing metadata to disk.
*
* 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 filesystem
import (
"os"
"path/filepath"
"testing"
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/util"
)
var (
fakeProtectorKey, _ = crypto.NewRandomKey(metadata.InternalKeyLen)
fakePolicyKey, _ = crypto.NewRandomKey(metadata.PolicyKeyLen)
wrappedProtectorKey, _ = crypto.Wrap(fakeProtectorKey, fakeProtectorKey)
wrappedPolicyKey, _ = crypto.Wrap(fakeProtectorKey, fakePolicyKey)
)
// Gets the mount corresponding to the integration test path.
func getTestMount(t *testing.T) (*Mount, error) {
mountpoint, err := util.TestRoot()
if err != nil {
t.Skip(err)
}
return GetMount(mountpoint)
}
func getFakeProtector() *metadata.ProtectorData {
return &metadata.ProtectorData{
ProtectorDescriptor: "fedcba9876543210",
Name: "goodProtector",
Source: metadata.SourceType_raw_key,
WrappedKey: wrappedProtectorKey,
}
}
func getFakePolicy() *metadata.PolicyData {
return &metadata.PolicyData{
KeyDescriptor: "0123456789abcdef",
Options: metadata.DefaultOptions,
WrappedPolicyKeys: []*metadata.WrappedPolicyKey{
{
ProtectorDescriptor: "fedcba9876543210",
WrappedKey: wrappedPolicyKey,
},
},
}
}
// Gets the mount and sets it up
func getSetupMount(t *testing.T) (*Mount, error) {
mnt, err := getTestMount(t)
if err != nil {
return nil, err
}
return mnt, mnt.Setup()
}
// Tests that the setup works and creates the correct files
func TestSetup(t *testing.T) {
mnt, err := getSetupMount(t)
if err != nil {
t.Fatal(err)
}
if err := mnt.CheckSetup(); err != nil {
t.Error(err)
}
os.RemoveAll(mnt.BaseDir())
}
// Tests that we can remove all of the metadata
func TestRemoveAllMetadata(t *testing.T) {
mnt, err := getSetupMount(t)
if err != nil {
t.Fatal(err)
}
if err = mnt.RemoveAllMetadata(); err != nil {
t.Fatal(err)
}
if isDir(mnt.BaseDir()) {
t.Error("metadata was not removed")
}
}
// Adding a good Protector should succeed, adding a bad one should fail
func TestAddProtector(t *testing.T) {
mnt, err := getSetupMount(t)
if err != nil {
t.Fatal(err)
}
defer mnt.RemoveAllMetadata()
protector := getFakeProtector()
if err = mnt.AddProtector(protector); err != nil {
t.Error(err)
}
// Change the source to bad one, or one that requires hashing costs
protector.Source = metadata.SourceType_default
if mnt.AddProtector(protector) == nil {
t.Error("bad source for a descriptor should make metadata invalid")
}
protector.Source = metadata.SourceType_custom_passphrase
if mnt.AddProtector(protector) == nil {
t.Error("protectors using passphrases should require hashing costs")
}
protector.Source = metadata.SourceType_raw_key
// Use a bad wrapped key
protector.WrappedKey = wrappedPolicyKey
if mnt.AddProtector(protector) == nil {
t.Error("bad length for protector keys should make metadata invalid")
}
protector.WrappedKey = wrappedProtectorKey
// Change the descriptor (to a bad length)
protector.ProtectorDescriptor = "abcde"
if mnt.AddProtector(protector) == nil {
t.Error("bad descriptor length should make metadata invalid")
}
}
// Adding a good Policy should succeed, adding a bad one should fail
func TestAddPolicy(t *testing.T) {
mnt, err := getSetupMount(t)
if err != nil {
t.Fatal(err)
}
defer mnt.RemoveAllMetadata()
policy := getFakePolicy()
if err = mnt.AddPolicy(policy); err != nil {
t.Error(err)
}
// Bad encryption options should make policy invalid
policy.Options.Padding = 7
if mnt.AddPolicy(policy) == nil {
t.Error("padding not a power of 2 should make metadata invalid")
}
policy.Options.Padding = 16
policy.Options.Filenames = metadata.EncryptionOptions_default
if mnt.AddPolicy(policy) == nil {
t.Error("encryption mode not set should make metadata invalid")
}
policy.Options.Filenames = metadata.EncryptionOptions_AES_256_CTS
// Use a bad wrapped key
policy.WrappedPolicyKeys[0].WrappedKey = wrappedProtectorKey
if mnt.AddPolicy(policy) == nil {
t.Error("bad length for policy keys should make metadata invalid")
}
policy.WrappedPolicyKeys[0].WrappedKey = wrappedPolicyKey
// Change the descriptor (to a bad length)
policy.KeyDescriptor = "abcde"
if mnt.AddPolicy(policy) == nil {
t.Error("bad descriptor length should make metadata invalid")
}
}
// Tests that we can set a policy and get it back
func TestSetPolicy(t *testing.T) {
mnt, err := getSetupMount(t)
if err != nil {
t.Fatal(err)
}
defer mnt.RemoveAllMetadata()
policy := getFakePolicy()
if err = mnt.AddPolicy(policy); err != nil {
t.Fatal(err)
}
realPolicy, err := mnt.GetPolicy(policy.KeyDescriptor)
if err != nil {
t.Fatal(err)
}
if !proto.Equal(realPolicy, policy) {
t.Errorf("policy %+v does not equal expected policy %+v", realPolicy, policy)
}
}
// Tests that we can set a normal protector and get it back
func TestSetProtector(t *testing.T) {
mnt, err := getSetupMount(t)
if err != nil {
t.Fatal(err)
}
defer mnt.RemoveAllMetadata()
protector := getFakeProtector()
if err = mnt.AddProtector(protector); err != nil {
t.Fatal(err)
}
realProtector, err := mnt.GetRegularProtector(protector.ProtectorDescriptor)
if err != nil {
t.Fatal(err)
}
if !proto.Equal(realProtector, protector) {
t.Errorf("protector %+v does not equal expected protector %+v", realProtector, protector)
}
}
// Gets a setup mount and a fake second mount
func getTwoSetupMounts(t *testing.T) (realMnt, fakeMnt *Mount, err error) {
if realMnt, err = getSetupMount(t); err != nil {
return
}
// Create and setup a fake filesystem
fakeMountpoint := filepath.Join(realMnt.Path, "fake")
if err = os.MkdirAll(fakeMountpoint, basePermissions); err != nil {
return
}
fakeMnt = &Mount{Path: fakeMountpoint}
err = fakeMnt.Setup()
return
}
// Removes all the data from the fake and real filesystems
func cleanupTwoMounts(realMnt, fakeMnt *Mount) {
realMnt.RemoveAllMetadata()
os.RemoveAll(fakeMnt.Path)
}
// Tests that we can set a linked protector and get it back
func TestLinkedProtector(t *testing.T) {
realMnt, fakeMnt, err := getTwoSetupMounts(t)
if err != nil {
t.Fatal(err)
}
defer cleanupTwoMounts(realMnt, fakeMnt)
// Add the protector to the first filesystem
protector := getFakeProtector()
if err = realMnt.AddProtector(protector); err != nil {
t.Fatal(err)
}
// Add the link to the second filesystem
if err = fakeMnt.AddLinkedProtector(protector.ProtectorDescriptor, realMnt); err != nil {
t.Fatal(err)
}
// Get the protector though the second system
_, err = fakeMnt.GetRegularProtector(protector.ProtectorDescriptor)
if errors.Cause(err) != ErrNoMetadata {
t.Fatal(err)
}
retMnt, retProtector, err := fakeMnt.GetProtector(protector.ProtectorDescriptor)
if err != nil {
t.Fatal(err)
}
if retMnt != realMnt {
t.Error("mount returned was incorrect")
}
if !proto.Equal(retProtector, protector) {
t.Errorf("protector %+v does not equal expected protector %+v", retProtector, protector)
}
}
fscrypt-0.2.5/filesystem/mountpoint.go 0000664 0000000 0000000 00000020572 13570323517 0020153 0 ustar 00root root 0000000 0000000 /*
* mountpoint.go - Contains all the functionality for finding mountpoints and
* using UUIDs to refer to them. Specifically, we can find the mountpoint of a
* path, get info about a mountpoint, and find mountpoints with a specific UUID.
*
* 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 filesystem
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/pkg/errors"
)
/*
#include // setmntent, getmntent, endmntent
// The file containing mountpoints info and how we should read it
const char* mountpoints_filename = "/proc/mounts";
const char* read_mode = "r";
*/
import "C"
var (
// These maps hold data about the state of the system's mountpoints.
mountsByPath map[string]*Mount
mountsByDevice map[string][]*Mount
// Used to make the mount functions thread safe
mountMutex sync.Mutex
// True if the maps have been successfully initialized.
mountsInitialized bool
// Supported tokens for filesystem links
uuidToken = "UUID"
// Location to perform UUID lookup
uuidDirectory = "/dev/disk/by-uuid"
)
// getMountInfo populates the Mount mappings by parsing the filesystem
// description file using the getmntent functions. Returns ErrBadLoad if the
// Mount mappings cannot be populated.
func getMountInfo() error {
if mountsInitialized {
return nil
}
// make new maps
mountsByPath = make(map[string]*Mount)
mountsByDevice = make(map[string][]*Mount)
// Load the mount information from mountpoints_filename
fileHandle := C.setmntent(C.mountpoints_filename, C.read_mode)
if fileHandle == nil {
return errors.Wrapf(ErrGlobalMountInfo, "could not read %q",
C.GoString(C.mountpoints_filename))
}
defer C.endmntent(fileHandle)
for {
entry := C.getmntent(fileHandle)
// When getmntent returns nil, we have read all of the entries.
if entry == nil {
mountsInitialized = true
return nil
}
// Create the Mount structure by converting types.
mnt := Mount{
Path: C.GoString(entry.mnt_dir),
Filesystem: C.GoString(entry.mnt_type),
Options: strings.Split(C.GoString(entry.mnt_opts), ","),
}
// Skip invalid mountpoints
var err error
if mnt.Path, err = canonicalizePath(mnt.Path); err != nil {
log.Printf("getting mnt_dir: %v", err)
continue
}
// We can only use mountpoints that are directories for fscrypt.
if !isDir(mnt.Path) {
log.Printf("mnt_dir %v: not a directory", mnt.Path)
continue
}
// Note this overrides the info if we have seen the mountpoint
// earlier in the file. This is correct behavior because the
// filesystems are listed in mount order.
mountsByPath[mnt.Path] = &mnt
deviceName, err := canonicalizePath(C.GoString(entry.mnt_fsname))
// Only use real valid devices (unlike cgroups, tmpfs, ...)
if err == nil && isDevice(deviceName) {
mnt.Device = deviceName
mountsByDevice[deviceName] = append(mountsByDevice[deviceName], &mnt)
}
}
}
// AllFilesystems lists all the Mounts on the current system ordered by path.
// Use CheckSetup() to see if they are used with fscrypt.
func AllFilesystems() ([]*Mount, error) {
mountMutex.Lock()
defer mountMutex.Unlock()
if err := getMountInfo(); err != nil {
return nil, err
}
mounts := make([]*Mount, 0, len(mountsByPath))
for _, mount := range mountsByPath {
mounts = append(mounts, mount)
}
sort.Sort(PathSorter(mounts))
return mounts, nil
}
// UpdateMountInfo updates the filesystem mountpoint maps with the current state
// of the filesystem mountpoints. Returns error if the initialization fails.
func UpdateMountInfo() error {
mountMutex.Lock()
defer mountMutex.Unlock()
mountsInitialized = false
return getMountInfo()
}
// FindMount returns the corresponding Mount object for some path in a
// filesystem. Note that in the case of a bind mounts there may be two Mount
// objects for the same underlying filesystem. An error is returned if the path
// is invalid or we cannot load the required mount data. If a filesystem has
// been updated since the last call to one of the mount functions, run
// UpdateMountInfo to see changes.
func FindMount(path string) (*Mount, error) {
path, err := canonicalizePath(path)
if err != nil {
return nil, err
}
mountMutex.Lock()
defer mountMutex.Unlock()
if err = getMountInfo(); err != nil {
return nil, err
}
// Traverse up the directory tree until we find a mountpoint
for {
if mnt, ok := mountsByPath[path]; ok {
return mnt, nil
}
// Move to the parent directory unless we have reached the root.
parent := filepath.Dir(path)
if parent == path {
return nil, errors.Wrap(ErrNotAMountpoint, path)
}
path = parent
}
}
// GetMount returns the Mount object with a matching mountpoint. An error is
// returned if the path is invalid or we cannot load the required mount data. If
// a filesystem has been updated since the last call to one of the mount
// functions, run UpdateMountInfo to see changes.
func GetMount(mountpoint string) (*Mount, error) {
mountpoint, err := canonicalizePath(mountpoint)
if err != nil {
return nil, err
}
mountMutex.Lock()
defer mountMutex.Unlock()
if err = getMountInfo(); err != nil {
return nil, err
}
if mnt, ok := mountsByPath[mountpoint]; ok {
return mnt, nil
}
return nil, errors.Wrap(ErrNotAMountpoint, mountpoint)
}
// getMountsFromLink returns the Mount objects which match the provided link.
// This link is formatted as a tag (e.g. =) similar to how they
// appear in "/etc/fstab". Currently, only "UUID" tokens are supported. Note
// that this can match multiple Mounts (due to the existence of bind mounts). An
// error is returned if the link is invalid or we cannot load the required mount
// data. If a filesystem has been updated since the last call to one of the
// mount functions, run UpdateMountInfo to see the change.
func getMountsFromLink(link string) ([]*Mount, error) {
// Parse the link
linkComponents := strings.Split(link, "=")
if len(linkComponents) != 2 {
return nil, errors.Wrapf(ErrFollowLink, "link %q format is invalid", link)
}
token := linkComponents[0]
value := linkComponents[1]
if token != uuidToken {
return nil, errors.Wrapf(ErrFollowLink, "token type %q not supported", token)
}
// See if UUID points to an existing device
searchPath := filepath.Join(uuidDirectory, value)
if filepath.Base(searchPath) != value {
return nil, errors.Wrapf(ErrFollowLink, "value %q is not a UUID", value)
}
devicePath, err := canonicalizePath(searchPath)
if err != nil {
return nil, errors.Wrapf(ErrFollowLink, "no device with UUID %q", value)
}
// Lookup mountpoints for device in global store
mountMutex.Lock()
defer mountMutex.Unlock()
if err := getMountInfo(); err != nil {
return nil, err
}
mnts, ok := mountsByDevice[devicePath]
if !ok {
return nil, errors.Wrapf(ErrFollowLink, "no mounts for device %q", devicePath)
}
return mnts, nil
}
// makeLink returns a link of the form = where value is the tag
// value for the Mount's device. Currently, only "UUID" tokens are supported. An
// error is returned if the mount has no device, or no UUID.
func makeLink(mnt *Mount, token string) (string, error) {
if token != uuidToken {
return "", errors.Wrapf(ErrMakeLink, "token type %q not supported", token)
}
if mnt.Device == "" {
return "", errors.Wrapf(ErrMakeLink, "no device for mount %q", mnt.Path)
}
dirContents, err := ioutil.ReadDir(uuidDirectory)
if err != nil {
return "", errors.Wrap(ErrMakeLink, err.Error())
}
for _, fileInfo := range dirContents {
if fileInfo.Mode()&os.ModeSymlink == 0 {
continue // Only interested in UUID symlinks
}
uuid := fileInfo.Name()
devicePath, err := canonicalizePath(filepath.Join(uuidDirectory, uuid))
if err != nil {
log.Print(err)
continue
}
if mnt.Device == devicePath {
return fmt.Sprintf("%s=%s", uuidToken, uuid), nil
}
}
return "", errors.Wrapf(ErrMakeLink, "device %q has no UUID", mnt.Device)
}
fscrypt-0.2.5/filesystem/mountpoint_test.go 0000664 0000000 0000000 00000002053 13570323517 0021204 0 ustar 00root root 0000000 0000000 /*
* mountpoint_test.go - Tests for reading information about all mountpoints.
*
* 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 filesystem
import (
"testing"
)
func TestLoadMountInfo(t *testing.T) {
if err := UpdateMountInfo(); err != nil {
t.Error(err)
}
}
// Benchmarks how long it takes to update the mountpoint data
func BenchmarkLoadFirst(b *testing.B) {
for n := 0; n < b.N; n++ {
err := UpdateMountInfo()
if err != nil {
b.Fatal(err)
}
}
}
fscrypt-0.2.5/filesystem/path.go 0000664 0000000 0000000 00000005073 13570323517 0016672 0 ustar 00root root 0000000 0000000 /*
* path.go - Utility functions for dealing with filesystem paths
*
* 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 filesystem
import (
"log"
"os"
"path/filepath"
"github.com/pkg/errors"
)
// We only check the unix permissions and the sticky bit
const permMask = os.ModeSticky | os.ModePerm
// canonicalizePath turns path into an absolute path without symlinks.
func canonicalizePath(path string) (string, error) {
path, err := filepath.Abs(path)
if err != nil {
return "", err
}
path, err = filepath.EvalSymlinks(path)
// Get a better error if we have an invalid path
if pathErr, ok := err.(*os.PathError); ok {
err = errors.Wrap(pathErr.Err, pathErr.Path)
}
return path, err
}
// loggedStat runs os.Stat, but it logs the error if stat returns any error
// other than nil or IsNotExist.
func loggedStat(name string) (os.FileInfo, error) {
info, err := os.Stat(name)
if err != nil && !os.IsNotExist(err) {
log.Print(err)
}
return info, err
}
// isDir returns true if the path exists and is that of a directory.
func isDir(path string) bool {
info, err := loggedStat(path)
return err == nil && info.IsDir()
}
// isDevice returns true if the path exists and is that of a device.
func isDevice(path string) bool {
info, err := loggedStat(path)
return err == nil && info.Mode()&os.ModeDevice != 0
}
// isDirCheckPerm returns true if the path exists and is a directory. If the
// specified permissions and sticky bit of mode do not match the path, an error
// is logged.
func isDirCheckPerm(path string, mode os.FileMode) bool {
info, err := loggedStat(path)
// Check if directory
if err != nil || !info.IsDir() {
return false
}
// Check for bad permissions
if info.Mode()&permMask != mode&permMask {
log.Printf("directory %s has incorrect permissions", path)
}
return true
}
// isRegularFile returns true if the path exists and is that of a regular file.
func isRegularFile(path string) bool {
info, err := loggedStat(path)
return err == nil && info.Mode().IsRegular()
}
fscrypt-0.2.5/go.mod 0000664 0000000 0000000 00000000607 13570323517 0014327 0 ustar 00root root 0000000 0000000 module github.com/google/fscrypt
go 1.12
require (
github.com/golang/protobuf v1.2.0
github.com/pkg/errors v0.8.0
github.com/urfave/cli v1.20.0
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b
)
fscrypt-0.2.5/go.sum 0000664 0000000 0000000 00000002463 13570323517 0014356 0 ustar 00root root 0000000 0000000 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b h1:cmOZLU2i7CLArKNViO+ZCQ47wqYFyKEIpbGWp+b6Uoc=
golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
fscrypt-0.2.5/metadata/ 0000775 0000000 0000000 00000000000 13570323517 0014776 5 ustar 00root root 0000000 0000000 fscrypt-0.2.5/metadata/checks.go 0000664 0000000 0000000 00000013156 13570323517 0016573 0 ustar 00root root 0000000 0000000 /*
* checks.go - Some sanity check methods for our metadata structures
*
* 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 (
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
"github.com/google/fscrypt/util"
)
var errNotInitialized = errors.New("not initialized")
// Metadata is the interface to all of the protobuf structures that can be
// checked for validity.
type Metadata interface {
CheckValidity() error
proto.Message
}
// CheckValidity ensures the mode has a name and isn't empty.
func (m EncryptionOptions_Mode) CheckValidity() error {
if m == EncryptionOptions_default {
return errNotInitialized
}
if m.String() == "" {
return errors.Errorf("unknown %d", m)
}
return nil
}
// CheckValidity ensures the source has a name and isn't empty.
func (s SourceType) CheckValidity() error {
if s == SourceType_default {
return errNotInitialized
}
if s.String() == "" {
return errors.Errorf("unknown %d", s)
}
return nil
}
// 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)
}
if h.Parallelism <= 0 {
return errors.Errorf("parallelism=%d is not positive", h.Parallelism)
}
minMemory := 8 * h.Parallelism
if h.Memory < minMemory {
return errors.Errorf("memory=%d is less than minimum (%d)", h.Memory, minMemory)
}
return nil
}
// CheckValidity ensures our buffers are the correct length.
func (w *WrappedKeyData) CheckValidity() error {
if w == nil {
return errNotInitialized
}
if len(w.EncryptedKey) == 0 {
return errors.Wrap(errNotInitialized, "encrypted key")
}
if err := util.CheckValidLength(IVLen, len(w.IV)); err != nil {
return errors.Wrap(err, "IV")
}
return errors.Wrap(util.CheckValidLength(HMACLen, len(w.Hmac)), "HMAC")
}
// CheckValidity ensures our ProtectorData has the correct fields for its source.
func (p *ProtectorData) CheckValidity() error {
if p == nil {
return errNotInitialized
}
if err := p.Source.CheckValidity(); err != nil {
return errors.Wrap(err, "protector source")
}
// Source specific checks
switch p.Source {
case SourceType_pam_passphrase:
if p.Uid < 0 {
return errors.Errorf("UID=%d is negative", p.Uid)
}
fallthrough
case SourceType_custom_passphrase:
if err := p.Costs.CheckValidity(); err != nil {
return errors.Wrap(err, "passphrase hashing costs")
}
if err := util.CheckValidLength(SaltLen, len(p.Salt)); err != nil {
return errors.Wrap(err, "passphrase hashing salt")
}
}
// Generic checks
if err := p.WrappedKey.CheckValidity(); err != nil {
return errors.Wrap(err, "wrapped protector key")
}
if err := util.CheckValidLength(DescriptorLen, len(p.ProtectorDescriptor)); err != nil {
return errors.Wrap(err, "protector descriptor")
}
err := util.CheckValidLength(InternalKeyLen, len(p.WrappedKey.EncryptedKey))
return errors.Wrap(err, "encrypted protector key")
}
// CheckValidity ensures each of the options is valid.
func (e *EncryptionOptions) CheckValidity() error {
if e == nil {
return errNotInitialized
}
if _, ok := util.Index(e.Padding, paddingArray); !ok {
return errors.Errorf("padding of %d is invalid", e.Padding)
}
if err := e.Contents.CheckValidity(); err != nil {
return errors.Wrap(err, "contents encryption mode")
}
return errors.Wrap(e.Filenames.CheckValidity(), "filenames encryption mode")
}
// CheckValidity ensures the fields are valid and have the correct lengths.
func (w *WrappedPolicyKey) CheckValidity() error {
if w == nil {
return errNotInitialized
}
if err := w.WrappedKey.CheckValidity(); err != nil {
return errors.Wrap(err, "wrapped key")
}
if err := util.CheckValidLength(PolicyKeyLen, len(w.WrappedKey.EncryptedKey)); err != nil {
return errors.Wrap(err, "encrypted key")
}
err := util.CheckValidLength(DescriptorLen, len(w.ProtectorDescriptor))
return errors.Wrap(err, "wrapping protector descriptor")
}
// CheckValidity ensures the fields and each wrapped key are valid.
func (p *PolicyData) CheckValidity() error {
if p == nil {
return errNotInitialized
}
// Check each wrapped key
for i, w := range p.WrappedPolicyKeys {
if err := w.CheckValidity(); err != nil {
return errors.Wrapf(err, "policy key slot %d", i)
}
}
if err := util.CheckValidLength(DescriptorLen, len(p.KeyDescriptor)); err != nil {
return errors.Wrap(err, "policy key descriptor")
}
return errors.Wrap(p.Options.CheckValidity(), "policy options")
}
// CheckValidity ensures the Config has all the necessary info for its Source.
func (c *Config) CheckValidity() error {
// General checks
if c == nil {
return errNotInitialized
}
if err := c.Source.CheckValidity(); err != nil {
return errors.Wrap(err, "default config source")
}
// Source specific checks
switch c.Source {
case SourceType_pam_passphrase, SourceType_custom_passphrase:
if err := c.HashCosts.CheckValidity(); err != nil {
return errors.Wrap(err, "config hashing costs")
}
}
return errors.Wrap(c.Options.CheckValidity(), "config options")
}
fscrypt-0.2.5/metadata/config.go 0000664 0000000 0000000 00000004302 13570323517 0016571 0 ustar 00root root 0000000 0000000 /*
* config.go - Parsing for our global config file. The file is simply the JSON
* output of the Config protocol buffer.
*
* 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 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
package metadata
import (
"io"
"strings"
"github.com/golang/protobuf/jsonpb"
)
// 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,
}
if err := m.Marshal(out, config); 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,
}
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
}
}
return false
}
fscrypt-0.2.5/metadata/config_test.go 0000664 0000000 0000000 00000003510 13570323517 0017630 0 ustar 00root root 0000000 0000000 /*
* config_test.go - Tests the processing of the config 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 (
"bytes"
"testing"
"github.com/golang/protobuf/proto"
)
var testConfig = &Config{
Source: SourceType_custom_passphrase,
HashCosts: &HashingCosts{
Time: 10,
Memory: 1 << 12,
Parallelism: 8,
},
Compatibility: "",
Options: DefaultOptions,
}
var testConfigString = `{
"source": "custom_passphrase",
"hash_costs": {
"time": "10",
"memory": "4096",
"parallelism": "8"
},
"compatibility": "",
"options": {
"padding": "32",
"contents": "AES_256_XTS",
"filenames": "AES_256_CTS"
}
}
`
// Makes sure that writing a config and reading it back gives the same thing.
func TestWrite(t *testing.T) {
var b bytes.Buffer
err := WriteConfig(testConfig, &b)
if err != nil {
t.Fatal(err)
}
t.Logf("json encoded config:\n%s", b.String())
if b.String() != testConfigString {
t.Errorf("did not match: %s", testConfigString)
}
}
func TestRead(t *testing.T) {
buf := bytes.NewBufferString(testConfigString)
cfg, err := ReadConfig(buf)
if err != nil {
t.Fatal(err)
}
t.Logf("decoded config:\n%s", cfg)
if !proto.Equal(cfg, testConfig) {
t.Errorf("did not match: %s", testConfig)
}
}
fscrypt-0.2.5/metadata/constants.go 0000664 0000000 0000000 00000003162 13570323517 0017343 0 ustar 00root root 0000000 0000000 /*
* constants.go - Some metadata constants used throughout fscrypt
*
* 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 (
"crypto/sha256"
"golang.org/x/sys/unix"
)
// 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
// We always use 256-bit keys internally (compared to 512-bit policy keys).
InternalKeyLen = 32
IVLen = 16
SaltLen = 16
// 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
)
var (
// DefaultOptions use the supported encryption modes and max padding.
DefaultOptions = &EncryptionOptions{
Padding: 32,
Contents: EncryptionOptions_AES_256_XTS,
Filenames: EncryptionOptions_AES_256_CTS,
}
// DefaultSource is the source we use if none is specified.
DefaultSource = SourceType_custom_passphrase
)
fscrypt-0.2.5/metadata/metadata.pb.go 0000664 0000000 0000000 00000051432 13570323517 0017512 0 ustar 00root root 0000000 0000000 // Code generated by protoc-gen-go. DO NOT EDIT.
// source: metadata/metadata.proto
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
// 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
// Specifies the method in which an outside secret is obtained for a Protector
type SourceType int32
const (
SourceType_default SourceType = 0
SourceType_pam_passphrase SourceType = 1
SourceType_custom_passphrase SourceType = 2
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,
}
func (x SourceType) String() string {
return proto.EnumName(SourceType_name, int32(x))
}
func (SourceType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{0}
}
// Type of encryption; should match declarations of unix.FS_ENCRYPTION_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_Adiantum EncryptionOptions_Mode = 9
)
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",
}
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,
"Adiantum": 9,
}
func (x EncryptionOptions_Mode) String() string {
return proto.EnumName(EncryptionOptions_Mode_name, int32(x))
}
func (EncryptionOptions_Mode) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{3, 0}
}
// Cost parameters to be used in our hashing functions.
type HashingCosts struct {
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"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HashingCosts) Reset() { *m = HashingCosts{} }
func (m *HashingCosts) String() string { return proto.CompactTextString(m) }
func (*HashingCosts) ProtoMessage() {}
func (*HashingCosts) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{0}
}
func (m *HashingCosts) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HashingCosts.Unmarshal(m, b)
}
func (m *HashingCosts) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HashingCosts.Marshal(b, m, deterministic)
}
func (dst *HashingCosts) XXX_Merge(src proto.Message) {
xxx_messageInfo_HashingCosts.Merge(dst, src)
}
func (m *HashingCosts) XXX_Size() int {
return xxx_messageInfo_HashingCosts.Size(m)
}
func (m *HashingCosts) XXX_DiscardUnknown() {
xxx_messageInfo_HashingCosts.DiscardUnknown(m)
}
var xxx_messageInfo_HashingCosts proto.InternalMessageInfo
func (m *HashingCosts) GetTime() int64 {
if m != nil {
return m.Time
}
return 0
}
func (m *HashingCosts) GetMemory() int64 {
if m != nil {
return m.Memory
}
return 0
}
func (m *HashingCosts) GetParallelism() int64 {
if m != nil {
return m.Parallelism
}
return 0
}
// This structure is used for our authenticated wrapping/unwrapping of keys.
type WrappedKeyData struct {
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"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *WrappedKeyData) Reset() { *m = WrappedKeyData{} }
func (m *WrappedKeyData) String() string { return proto.CompactTextString(m) }
func (*WrappedKeyData) ProtoMessage() {}
func (*WrappedKeyData) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{1}
}
func (m *WrappedKeyData) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_WrappedKeyData.Unmarshal(m, b)
}
func (m *WrappedKeyData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_WrappedKeyData.Marshal(b, m, deterministic)
}
func (dst *WrappedKeyData) XXX_Merge(src proto.Message) {
xxx_messageInfo_WrappedKeyData.Merge(dst, src)
}
func (m *WrappedKeyData) XXX_Size() int {
return xxx_messageInfo_WrappedKeyData.Size(m)
}
func (m *WrappedKeyData) XXX_DiscardUnknown() {
xxx_messageInfo_WrappedKeyData.DiscardUnknown(m)
}
var xxx_messageInfo_WrappedKeyData proto.InternalMessageInfo
func (m *WrappedKeyData) GetIV() []byte {
if m != nil {
return m.IV
}
return nil
}
func (m *WrappedKeyData) GetEncryptedKey() []byte {
if m != nil {
return m.EncryptedKey
}
return nil
}
func (m *WrappedKeyData) GetHmac() []byte {
if m != nil {
return m.Hmac
}
return nil
}
// The associated data for each protector
type ProtectorData struct {
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,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"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ProtectorData) Reset() { *m = ProtectorData{} }
func (m *ProtectorData) String() string { return proto.CompactTextString(m) }
func (*ProtectorData) ProtoMessage() {}
func (*ProtectorData) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{2}
}
func (m *ProtectorData) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ProtectorData.Unmarshal(m, b)
}
func (m *ProtectorData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ProtectorData.Marshal(b, m, deterministic)
}
func (dst *ProtectorData) XXX_Merge(src proto.Message) {
xxx_messageInfo_ProtectorData.Merge(dst, src)
}
func (m *ProtectorData) XXX_Size() int {
return xxx_messageInfo_ProtectorData.Size(m)
}
func (m *ProtectorData) XXX_DiscardUnknown() {
xxx_messageInfo_ProtectorData.DiscardUnknown(m)
}
var xxx_messageInfo_ProtectorData proto.InternalMessageInfo
func (m *ProtectorData) GetProtectorDescriptor() string {
if m != nil {
return m.ProtectorDescriptor
}
return ""
}
func (m *ProtectorData) GetSource() SourceType {
if m != nil {
return m.Source
}
return SourceType_default
}
func (m *ProtectorData) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *ProtectorData) GetCosts() *HashingCosts {
if m != nil {
return m.Costs
}
return nil
}
func (m *ProtectorData) GetSalt() []byte {
if m != nil {
return m.Salt
}
return nil
}
func (m *ProtectorData) GetUid() int64 {
if m != nil {
return m.Uid
}
return 0
}
func (m *ProtectorData) GetWrappedKey() *WrappedKeyData {
if m != nil {
return m.WrappedKey
}
return nil
}
// Encryption policy specifics, corresponds to the fscrypt_policy struct
type EncryptionOptions struct {
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"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *EncryptionOptions) Reset() { *m = EncryptionOptions{} }
func (m *EncryptionOptions) String() string { return proto.CompactTextString(m) }
func (*EncryptionOptions) ProtoMessage() {}
func (*EncryptionOptions) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{3}
}
func (m *EncryptionOptions) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_EncryptionOptions.Unmarshal(m, b)
}
func (m *EncryptionOptions) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_EncryptionOptions.Marshal(b, m, deterministic)
}
func (dst *EncryptionOptions) XXX_Merge(src proto.Message) {
xxx_messageInfo_EncryptionOptions.Merge(dst, src)
}
func (m *EncryptionOptions) XXX_Size() int {
return xxx_messageInfo_EncryptionOptions.Size(m)
}
func (m *EncryptionOptions) XXX_DiscardUnknown() {
xxx_messageInfo_EncryptionOptions.DiscardUnknown(m)
}
var xxx_messageInfo_EncryptionOptions proto.InternalMessageInfo
func (m *EncryptionOptions) GetPadding() int64 {
if m != nil {
return m.Padding
}
return 0
}
func (m *EncryptionOptions) GetContents() EncryptionOptions_Mode {
if m != nil {
return m.Contents
}
return EncryptionOptions_default
}
func (m *EncryptionOptions) GetFilenames() EncryptionOptions_Mode {
if m != nil {
return m.Filenames
}
return EncryptionOptions_default
}
type WrappedPolicyKey struct {
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"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *WrappedPolicyKey) Reset() { *m = WrappedPolicyKey{} }
func (m *WrappedPolicyKey) String() string { return proto.CompactTextString(m) }
func (*WrappedPolicyKey) ProtoMessage() {}
func (*WrappedPolicyKey) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{4}
}
func (m *WrappedPolicyKey) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_WrappedPolicyKey.Unmarshal(m, b)
}
func (m *WrappedPolicyKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_WrappedPolicyKey.Marshal(b, m, deterministic)
}
func (dst *WrappedPolicyKey) XXX_Merge(src proto.Message) {
xxx_messageInfo_WrappedPolicyKey.Merge(dst, src)
}
func (m *WrappedPolicyKey) XXX_Size() int {
return xxx_messageInfo_WrappedPolicyKey.Size(m)
}
func (m *WrappedPolicyKey) XXX_DiscardUnknown() {
xxx_messageInfo_WrappedPolicyKey.DiscardUnknown(m)
}
var xxx_messageInfo_WrappedPolicyKey proto.InternalMessageInfo
func (m *WrappedPolicyKey) GetProtectorDescriptor() string {
if m != nil {
return m.ProtectorDescriptor
}
return ""
}
func (m *WrappedPolicyKey) GetWrappedKey() *WrappedKeyData {
if m != nil {
return m.WrappedKey
}
return nil
}
// The associated data for each policy
type PolicyData struct {
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"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *PolicyData) Reset() { *m = PolicyData{} }
func (m *PolicyData) String() string { return proto.CompactTextString(m) }
func (*PolicyData) ProtoMessage() {}
func (*PolicyData) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{5}
}
func (m *PolicyData) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PolicyData.Unmarshal(m, b)
}
func (m *PolicyData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_PolicyData.Marshal(b, m, deterministic)
}
func (dst *PolicyData) XXX_Merge(src proto.Message) {
xxx_messageInfo_PolicyData.Merge(dst, src)
}
func (m *PolicyData) XXX_Size() int {
return xxx_messageInfo_PolicyData.Size(m)
}
func (m *PolicyData) XXX_DiscardUnknown() {
xxx_messageInfo_PolicyData.DiscardUnknown(m)
}
var xxx_messageInfo_PolicyData proto.InternalMessageInfo
func (m *PolicyData) GetKeyDescriptor() string {
if m != nil {
return m.KeyDescriptor
}
return ""
}
func (m *PolicyData) GetOptions() *EncryptionOptions {
if m != nil {
return m.Options
}
return nil
}
func (m *PolicyData) GetWrappedPolicyKeys() []*WrappedPolicyKey {
if m != nil {
return m.WrappedPolicyKeys
}
return nil
}
// Data stored in the config file
type Config struct {
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"`
Compatibility string `protobuf:"bytes,3,opt,name=compatibility,proto3" json:"compatibility,omitempty"`
Options *EncryptionOptions `protobuf:"bytes,4,opt,name=options,proto3" json:"options,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Config) Reset() { *m = Config{} }
func (m *Config) String() string { return proto.CompactTextString(m) }
func (*Config) ProtoMessage() {}
func (*Config) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_5e732a616277e389, []int{6}
}
func (m *Config) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Config.Unmarshal(m, b)
}
func (m *Config) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Config.Marshal(b, m, deterministic)
}
func (dst *Config) XXX_Merge(src proto.Message) {
xxx_messageInfo_Config.Merge(dst, src)
}
func (m *Config) XXX_Size() int {
return xxx_messageInfo_Config.Size(m)
}
func (m *Config) XXX_DiscardUnknown() {
xxx_messageInfo_Config.DiscardUnknown(m)
}
var xxx_messageInfo_Config proto.InternalMessageInfo
func (m *Config) GetSource() SourceType {
if m != nil {
return m.Source
}
return SourceType_default
}
func (m *Config) GetHashCosts() *HashingCosts {
if m != nil {
return m.HashCosts
}
return nil
}
func (m *Config) GetCompatibility() string {
if m != nil {
return m.Compatibility
}
return ""
}
func (m *Config) GetOptions() *EncryptionOptions {
if m != nil {
return m.Options
}
return nil
}
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", fileDescriptor_metadata_5e732a616277e389) }
var fileDescriptor_metadata_5e732a616277e389 = []byte{
// 656 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xef, 0x6a, 0xdb, 0x3c,
0x14, 0xc6, 0x5f, 0xdb, 0x69, 0xd2, 0x9c, 0xfc, 0x79, 0x5d, 0xb5, 0x6f, 0x5f, 0xb3, 0x7d, 0x09,
0xde, 0x06, 0x65, 0x94, 0x8e, 0x66, 0x74, 0x6c, 0x30, 0x06, 0x5d, 0x5a, 0xb6, 0xae, 0x94, 0x75,
0x4a, 0xe8, 0x36, 0x18, 0x04, 0xd5, 0x56, 0x1b, 0x51, 0xdb, 0x12, 0x96, 0x42, 0xf0, 0xb7, 0x7d,
0xdb, 0x05, 0xec, 0x5a, 0xb6, 0x8b, 0xd8, 0x55, 0x0d, 0xc9, 0xb1, 0xe3, 0xb4, 0x50, 0xba, 0x7d,
0x31, 0x47, 0x8f, 0xa4, 0xf3, 0x1c, 0xfd, 0xa4, 0x63, 0xf8, 0x3f, 0xa6, 0x8a, 0x84, 0x44, 0x91,
0x27, 0x45, 0xb0, 0x23, 0x52, 0xae, 0x38, 0x5a, 0x2d, 0xc6, 0xfe, 0x17, 0x68, 0xbf, 0x25, 0x72,
0xc2, 0x92, 0xcb, 0x01, 0x97, 0x4a, 0x22, 0x04, 0x35, 0xc5, 0x62, 0xea, 0xd9, 0x3d, 0x6b, 0xcb,
0xc1, 0x26, 0x46, 0x9b, 0x50, 0x8f, 0x69, 0xcc, 0xd3, 0xcc, 0x73, 0x8c, 0x3a, 0x1f, 0xa1, 0x1e,
0xb4, 0x04, 0x49, 0x49, 0x14, 0xd1, 0x88, 0xc9, 0xd8, 0xab, 0x99, 0xc9, 0xaa, 0xe4, 0x7f, 0x86,
0xee, 0xc7, 0x94, 0x08, 0x41, 0xc3, 0x63, 0x9a, 0x1d, 0x10, 0x45, 0x50, 0x17, 0xec, 0xa3, 0x33,
0xcf, 0xea, 0x59, 0x5b, 0x6d, 0x6c, 0x1f, 0x9d, 0xa1, 0x07, 0xd0, 0xa1, 0x49, 0x90, 0x66, 0x42,
0xd1, 0x70, 0x7c, 0x45, 0x33, 0x63, 0xdc, 0xc6, 0xed, 0x52, 0x3c, 0xa6, 0x99, 0x2e, 0x6a, 0x12,
0x93, 0xc0, 0xd8, 0xb7, 0xb1, 0x89, 0xfd, 0xef, 0x36, 0x74, 0x4e, 0x53, 0xae, 0x68, 0xa0, 0x78,
0x6a, 0x52, 0xef, 0xc2, 0x86, 0x28, 0x84, 0x71, 0x48, 0x65, 0x90, 0x32, 0xa1, 0x78, 0x6a, 0xcc,
0x9a, 0x78, 0xbd, 0x9c, 0x3b, 0x28, 0xa7, 0xd0, 0x36, 0xd4, 0x25, 0x9f, 0xa6, 0x41, 0x7e, 0xde,
0x6e, 0x7f, 0x63, 0xa7, 0x04, 0x35, 0x34, 0xfa, 0x28, 0x13, 0x14, 0xcf, 0xd7, 0xe8, 0x32, 0x12,
0x12, 0x53, 0x53, 0x46, 0x13, 0x9b, 0x18, 0x6d, 0xc3, 0x4a, 0xa0, 0xc1, 0x99, 0xd3, 0xb7, 0xfa,
0x9b, 0x8b, 0x04, 0x55, 0xac, 0x38, 0x5f, 0xa4, 0x33, 0x48, 0x12, 0x29, 0x6f, 0x25, 0x3f, 0x88,
0x8e, 0x91, 0x0b, 0xce, 0x94, 0x85, 0x5e, 0xdd, 0xd0, 0xd3, 0x21, 0x7a, 0x01, 0xad, 0x59, 0x4e,
0xcd, 0x10, 0x69, 0x98, 0xcc, 0xde, 0x22, 0xf3, 0x32, 0x52, 0x0c, 0xb3, 0x72, 0xec, 0xff, 0xb0,
0x61, 0xed, 0x30, 0x47, 0xc7, 0x78, 0xf2, 0xde, 0x7c, 0x25, 0xf2, 0xa0, 0x21, 0x48, 0x18, 0xb2,
0xe4, 0xd2, 0xc0, 0x70, 0x70, 0x31, 0x44, 0x2f, 0x61, 0x35, 0xe0, 0x89, 0xa2, 0x89, 0x92, 0x73,
0x04, 0xbd, 0x85, 0xcf, 0x8d, 0x44, 0x3b, 0x27, 0x3c, 0xa4, 0xb8, 0xdc, 0x81, 0x5e, 0x41, 0xf3,
0x82, 0x45, 0x54, 0x83, 0x90, 0x86, 0xca, 0x5d, 0xb6, 0x2f, 0xb6, 0xf8, 0xdf, 0x2c, 0xa8, 0x69,
0x0d, 0xb5, 0xa0, 0x11, 0xd2, 0x0b, 0x32, 0x8d, 0x94, 0xfb, 0x0f, 0xfa, 0x17, 0x5a, 0xfb, 0x87,
0xc3, 0x71, 0x7f, 0xef, 0xd9, 0xf8, 0xd3, 0x68, 0xe8, 0x5a, 0x55, 0xe1, 0xcd, 0xe0, 0xc4, 0xb5,
0xab, 0xc2, 0xe0, 0xf5, 0xc0, 0x75, 0x96, 0x84, 0xd1, 0xd0, 0xad, 0x15, 0xc2, 0x6e, 0xff, 0xb9,
0x59, 0xb1, 0xb2, 0x24, 0x8c, 0x86, 0x6e, 0x1d, 0xb5, 0x61, 0x75, 0x3f, 0x64, 0x24, 0x51, 0xd3,
0xd8, 0x6d, 0xfa, 0x5f, 0x2d, 0x70, 0xe7, 0x58, 0x4f, 0x79, 0xc4, 0x82, 0x4c, 0x3f, 0xbb, 0xbf,
0x78, 0x50, 0xd7, 0xae, 0xce, 0xfe, 0x83, 0xab, 0xfb, 0x69, 0x01, 0xe4, 0xde, 0xe6, 0x35, 0x3f,
0x82, 0xee, 0x15, 0xcd, 0x6e, 0xda, 0x76, 0xae, 0x68, 0x56, 0x31, 0xdc, 0x83, 0x06, 0xcf, 0xe9,
0xce, 0xcd, 0xee, 0xdf, 0x72, 0x01, 0xb8, 0x58, 0x8b, 0xde, 0xc1, 0x7a, 0x51, 0xa7, 0x30, 0x9e,
0xba, 0x5c, 0x7d, 0x87, 0xce, 0x56, 0xab, 0x7f, 0xef, 0x46, 0xbd, 0x25, 0x13, 0xbc, 0x36, 0xbb,
0xa6, 0x48, 0xff, 0x97, 0x05, 0xf5, 0x01, 0x4f, 0x2e, 0xd8, 0x65, 0xa5, 0x9f, 0xac, 0x3b, 0xf4,
0xd3, 0x1e, 0xc0, 0x84, 0xc8, 0xc9, 0x38, 0x6f, 0x20, 0xfb, 0xd6, 0x06, 0x6a, 0xea, 0x95, 0xf9,
0x2f, 0xea, 0x21, 0x74, 0x02, 0x1e, 0x0b, 0xa2, 0xd8, 0x39, 0x8b, 0x98, 0xca, 0xe6, 0xfd, 0xb8,
0x2c, 0x56, 0xc1, 0xd4, 0xee, 0x0e, 0xe6, 0xf1, 0x07, 0x80, 0x45, 0xa5, 0xcb, 0xef, 0x12, 0x41,
0x57, 0x90, 0x78, 0x2c, 0x88, 0x94, 0x62, 0x92, 0x12, 0x49, 0x5d, 0x0b, 0xfd, 0x07, 0x6b, 0xc1,
0x54, 0x2a, 0xbe, 0x24, 0xdb, 0x7a, 0x5f, 0x4a, 0x66, 0x9a, 0xa9, 0xeb, 0x9c, 0xd7, 0xcd, 0x3f,
0xf7, 0xe9, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4b, 0xbe, 0x84, 0xbc, 0x8e, 0x05, 0x00, 0x00,
}
fscrypt-0.2.5/metadata/metadata.proto 0000664 0000000 0000000 00000005100 13570323517 0017637 0 ustar 00root root 0000000 0000000 /*
* 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 you modify this file, be sure to run "go generate" on this package.
syntax = "proto3";
package metadata;
// Cost parameters to be used in our hashing functions.
message HashingCosts {
int64 time = 2;
int64 memory = 3;
int64 parallelism = 4;
}
// This structure is used for our authenticated wrapping/unwrapping of keys.
message WrappedKeyData {
bytes IV = 1;
bytes encrypted_key = 2;
bytes hmac = 3;
}
// Specifies the method in which an outside secret is obtained for a Protector
enum SourceType {
default = 0;
pam_passphrase = 1;
custom_passphrase = 2;
raw_key = 3;
}
// The associated data for each protector
message ProtectorData {
string protector_descriptor = 1;
SourceType source = 2;
// These are only used by some of the protector types
string name = 3;
HashingCosts costs = 4;
bytes salt = 5;
int64 uid = 6;
WrappedKeyData wrapped_key = 7;
}
// Encryption policy specifics, corresponds to the fscrypt_policy struct
message EncryptionOptions {
int64 padding = 1;
// Type of encryption; should match declarations of unix.FS_ENCRYPTION_MODE
enum Mode {
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;
}
Mode contents = 2;
Mode filenames = 3;
}
message WrappedPolicyKey {
string protector_descriptor = 1;
WrappedKeyData wrapped_key = 2;
}
// The associated data for each policy
message PolicyData {
string key_descriptor = 1;
EncryptionOptions options = 2;
repeated WrappedPolicyKey wrapped_policy_keys = 3;
}
// Data stored in the config file
message Config {
SourceType source = 1;
HashingCosts hash_costs = 2;
string compatibility = 3;
EncryptionOptions options = 4;
}
fscrypt-0.2.5/metadata/policy.go 0000664 0000000 0000000 00000016053 13570323517 0016631 0 ustar 00root root 0000000 0000000 /*
* 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"
"log"
"math"
"os"
"unsafe"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/google/fscrypt/util"
)
// Encryption specific errors
var (
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")
)
// 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:
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
}
}
// 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) {
file, err := os.Open(path)
if err != nil {
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)
}
// Convert the padding flag into an amount of padding
paddingFlag := int64(policy.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 &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
}
// 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
}
// Whitelist the modes that take a 24+ byte IV (enough room for the per-file nonce)
return options.Contents == EncryptionOptions_Adiantum
}
// 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 {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
if err = data.CheckValidity(); err != nil {
return errors.Wrap(err, "invalid policy")
}
// This lookup should always succeed (as policy is valid)
flags, 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)
}
if shouldUseDirectKeyFlag(data.Options) {
// TODO: use unix.FS_POLICY_FLAG_DIRECT_KEY here once available
flags |= 0x4
}
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(flags),
}
copy(policy.Master_key_descriptor[:], descriptorBytes)
if err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &policy); 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.
if info, statErr := os.Stat(path); statErr != nil || !info.IsDir() {
// Checking if the path is not a directory
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
}
}
return errors.Wrapf(err, "set encryption policy %s", path)
}
// CheckSupport returns an error if the filesystem containing path does not
// support filesystem encryption. This can be for many reasons including an
// incompatible kernel or filesystem or not enabling the right feature flags.
func CheckSupport(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// On supported directories, giving a bad policy will return EINVAL
badPolicy := unix.FscryptPolicy{
Version: math.MaxUint8,
Contents_encryption_mode: math.MaxUint8,
Filenames_encryption_mode: math.MaxUint8,
Flags: math.MaxUint8,
}
err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &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
}
return err
}
fscrypt-0.2.5/metadata/policy_test.go 0000664 0000000 0000000 00000010752 13570323517 0017670 0 ustar 00root root 0000000 0000000 /*
* policy_test.go - Tests the getting/setting of encryption 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 metadata
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/golang/protobuf/proto"
"github.com/google/fscrypt/util"
)
const goodDescriptor = "0123456789abcdef"
var goodPolicy = &PolicyData{
KeyDescriptor: goodDescriptor,
Options: DefaultOptions,
}
// Creates a temporary directory for testing.
func createTestDirectory(t *testing.T) (directory string, err error) {
baseDirectory, err := util.TestRoot()
if err != nil {
t.Skip(err)
}
if s, err := os.Stat(baseDirectory); err != nil || !s.IsDir() {
return "", fmt.Errorf("test directory %q is not valid", baseDirectory)
}
directoryPath := filepath.Join(baseDirectory, "test")
return directoryPath, os.MkdirAll(directoryPath, os.ModePerm)
}
// Makes a test directory, makes a file in the directory, and fills the file
// with data. Returns the directory name, file name, and error (if one).
func createTestFile(t *testing.T) (directory, file string, err error) {
if directory, err = createTestDirectory(t); err != nil {
return
}
// Cleanup if the file creation fails
defer func() {
if err != nil {
os.RemoveAll(directory)
}
}()
filePath := filepath.Join(directory, "test.txt")
fileHandle, err := os.Create(filePath)
if err != nil {
return directory, filePath, err
}
defer fileHandle.Close()
_, err = fileHandle.Write([]byte("Here is some test data!\n"))
return directory, filePath, err
}
// Tests that we can set a policy on an empty directory
func TestSetPolicyEmptyDirectory(t *testing.T) {
directory, err := createTestDirectory(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(directory)
if err = SetPolicy(directory, goodPolicy); err != nil {
t.Error(err)
}
}
// Tests that we cannot set a policy on a nonempty directory
func TestSetPolicyNonemptyDirectory(t *testing.T) {
directory, _, err := createTestFile(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(directory)
if err = SetPolicy(directory, goodPolicy); err == nil {
t.Error("should have failed to set policy on a nonempty directory")
}
}
// Tests that we cannot set a policy on a file
func TestSetPolicyFile(t *testing.T) {
directory, file, err := createTestFile(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(directory)
if err = SetPolicy(file, goodPolicy); err == nil {
t.Error("should have failed to set policy on a file")
}
}
// Tests that we fail when using bad policies
func TestSetPolicyBadDescriptors(t *testing.T) {
// Policies that are too short, have invalid chars, or are too long
badDescriptors := []string{"123456789abcde", "xxxxxxxxxxxxxxxx", "0123456789abcdef00"}
for _, badDescriptor := range badDescriptors {
badPolicy := &PolicyData{KeyDescriptor: badDescriptor, Options: DefaultOptions}
directory, err := createTestDirectory(t)
if err != nil {
t.Fatal(err)
}
if err = SetPolicy(directory, badPolicy); err == nil {
t.Errorf("descriptor %q should have made SetPolicy fail", badDescriptor)
}
os.RemoveAll(directory)
}
}
// Tests that we get back the same policy that we set on a directory
func TestGetPolicyEmptyDirectory(t *testing.T) {
directory, err := createTestDirectory(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(directory)
var actualPolicy *PolicyData
if err = SetPolicy(directory, goodPolicy); err != nil {
t.Fatal(err)
}
if actualPolicy, err = GetPolicy(directory); err != nil {
t.Fatal(err)
}
if !proto.Equal(actualPolicy, goodPolicy) {
t.Errorf("policy %+v does not equal expected policy %+v", actualPolicy, goodPolicy)
}
}
// Tests that we cannot get a policy on an unencrypted directory
func TestGetPolicyUnencrypted(t *testing.T) {
directory, err := createTestDirectory(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(directory)
if _, err = GetPolicy(directory); err == nil {
t.Error("should have failed to set policy on a file")
}
}
fscrypt-0.2.5/pam/ 0000775 0000000 0000000 00000000000 13570323517 0013773 5 ustar 00root root 0000000 0000000 fscrypt-0.2.5/pam/constants.go 0000664 0000000 0000000 00000010404 13570323517 0016335 0 ustar 00root root 0000000 0000000 /*
* constants.go - PAM flags and item types from github.com/msteinert/pam
*
* Modifications Copyright 2017 Google Inc.
* Modifications 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.
*/
/*
* Copyright 2011, krockot
* Copyright 2015, Michael Steinert
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package pam
/*
#cgo LDFLAGS: -lpam
#include
*/
import "C"
// Item is a PAM information type.
type Item int
// PAM Item types.
const (
// Service is the name which identifies the PAM stack.
Service Item = C.PAM_SERVICE
// User identifies the username identity used by a service.
User = C.PAM_USER
// Tty is the terminal name.
Tty = C.PAM_TTY
// Rhost is the requesting host name.
Rhost = C.PAM_RHOST
// Authtok is the currently active authentication token.
Authtok = C.PAM_AUTHTOK
// Oldauthtok is the old authentication token.
Oldauthtok = C.PAM_OLDAUTHTOK
// Ruser is the requesting user name.
Ruser = C.PAM_RUSER
// UserPrompt is the string use to prompt for a username.
UserPrompt = C.PAM_USER_PROMPT
)
// Flag is used as input to various PAM functions. Flags can be combined with a
// bitwise or. Refer to the official PAM documentation for which flags are
// accepted by which functions.
type Flag int
// PAM Flag types.
const (
// Silent indicates that no messages should be emitted.
Silent Flag = C.PAM_SILENT
// DisallowNullAuthtok indicates that authorization should fail
// if the user does not have a registered authentication token.
DisallowNullAuthtok = C.PAM_DISALLOW_NULL_AUTHTOK
// EstablishCred indicates that credentials should be established
// for the user.
EstablishCred = C.PAM_ESTABLISH_CRED
// DeleteCred inidicates that credentials should be deleted.
DeleteCred = C.PAM_DELETE_CRED
// ReinitializeCred indicates that credentials should be fully
// reinitialized.
ReinitializeCred = C.PAM_REINITIALIZE_CRED
// RefreshCred indicates that the lifetime of existing credentials
// should be extended.
RefreshCred = C.PAM_REFRESH_CRED
// ChangeExpiredAuthtok indicates that the authentication token
// should be changed if it has expired.
ChangeExpiredAuthtok = C.PAM_CHANGE_EXPIRED_AUTHTOK
// PrelimCheck indicates that the modules are being probed as to their
// ready status for altering the user's authentication token.
PrelimCheck = C.PAM_PRELIM_CHECK
// UpdateAuthtok informs the module that this is the call it should
// change the authorization tokens.
UpdateAuthtok = C.PAM_UPDATE_AUTHTOK
)
fscrypt-0.2.5/pam/login.go 0000664 0000000 0000000 00000006365 13570323517 0015444 0 ustar 00root root 0000000 0000000 /*
* login.go - Checks the validity of a login token key against PAM.
*
* 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 pam contains all the functionality for interfacing with Linux
// Pluggable Authentication Modules (PAM). Currently, all this package does is
// check the validity of a user's login passphrase.
// See http://www.linux-pam.org/Linux-PAM-html/ for more information.
package pam
import "C"
import (
"fmt"
"log"
"sync"
"github.com/pkg/errors"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/util"
)
// Pam error values
var (
ErrPassphrase = errors.New("incorrect login passphrase")
)
// Global state is needed for the PAM callback, so we guard this function with a
// lock. tokenToCheck is only ever non-nil when tokenLock is held.
var (
tokenLock sync.Mutex
tokenToCheck *crypto.Key
)
// userInput is run when the callback needs some input from the user. We prompt
// the user for information and return their answer. A return value of nil
// indicates an error occurred.
//export userInput
func userInput(prompt *C.char) *C.char {
fmt.Print(C.GoString(prompt))
input, err := util.ReadLine()
if err != nil {
log.Printf("getting input for PAM: %s", err)
return nil
}
return C.CString(input)
}
// passphraseInput is run when the callback needs a passphrase from the user. We
// pass along the tokenToCheck without prompting. A return value of nil
// indicates an error occurred.
//export passphraseInput
func passphraseInput(prompt *C.char) *C.char {
log.Printf("getting secret data for PAM: %q", C.GoString(prompt))
if tokenToCheck == nil {
log.Print("secret data requested multiple times")
return nil
}
// Subsequent calls to passphrase input should fail
input := (*C.char)(tokenToCheck.UnsafeToCString())
tokenToCheck = nil
return input
}
// IsUserLoginToken returns nil if the presented token is the user's login key,
// and returns an error otherwise. Note that unless we are currently running as
// root, this check will only work for the user running this process.
func IsUserLoginToken(username string, token *crypto.Key, quiet bool) error {
log.Printf("Checking login token for %s", username)
// We require global state for the function. This function never takes
// ownership of the token, so it is not responsible for wiping it.
tokenLock.Lock()
tokenToCheck = token
defer func() {
tokenToCheck = nil
tokenLock.Unlock()
}()
transaction, err := Start("fscrypt", username)
if err != nil {
return err
}
defer transaction.End()
// Ask PAM to authenticate the token.
authenticated, err := transaction.Authenticate(quiet)
if err != nil {
return err
}
if !authenticated {
return ErrPassphrase
}
return nil
}
fscrypt-0.2.5/pam/pam.c 0000664 0000000 0000000 00000006165 13570323517 0014724 0 ustar 00root root 0000000 0000000 /*
* pam.c - Functions to let us call into libpam from Go.
*
* 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.
*/
#include "pam.h"
#include
#include
#include
#include
#include // mlock/munlock
#include "_cgo_export.h" // for input callbacks
static int conversation(int num_msg, const struct pam_message** msg,
struct pam_response** resp, void* appdata_ptr) {
if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) {
return PAM_CONV_ERR;
}
// Allocate the response table with num_msg entries.
*resp = calloc(num_msg, sizeof **resp);
if (!*resp) {
return PAM_BUF_ERR;
}
// Check each message to see if we need to run a callback.
char* callback_msg = NULL;
char* callback_resp = NULL;
int i;
for (i = 0; i < num_msg; ++i) {
callback_msg = (char*)msg[i]->msg;
// We run our input callback if the style tells us we need data. Otherwise,
// we just print the error messages or text info to standard output.
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
callback_resp = passphraseInput(callback_msg);
break;
case PAM_PROMPT_ECHO_ON:
callback_resp = userInput(callback_msg);
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
fprintf(stderr, "%s\n", callback_msg);
continue;
}
if (!callback_resp) {
// If the callback failed, free each nonempty response in the response
// table and the response table itself.
while (--i >= 0) {
free((*resp)[i].resp);
}
free(*resp);
*resp = NULL;
return PAM_CONV_ERR;
}
(*resp)[i].resp = callback_resp;
}
return PAM_SUCCESS;
}
static const struct pam_conv conv = {conversation, NULL};
const struct pam_conv* goConv = &conv;
void freeData(pam_handle_t* pamh, void* data, int error_status) { free(data); }
void freeArray(pam_handle_t* pamh, void** array, int error_status) {
int i;
for (i = 0; array[i]; ++i) {
free(array[i]);
}
free(array);
}
void* copyIntoSecret(void* data) {
size_t size = strlen(data) + 1; // include null terminator
void* copy = malloc(size);
mlock(copy, size);
memcpy(copy, data, size);
return copy;
}
void freeSecret(pam_handle_t* pamh, char* data, int error_status) {
size_t size = strlen(data) + 1; // Include null terminator
// Use volatile function pointer to actually clear the memory.
static void* (*const volatile memset_sec)(void*, int, size_t) = &memset;
memset_sec(data, 0, size);
munlock(data, size);
free(data);
}
fscrypt-0.2.5/pam/pam.go 0000664 0000000 0000000 00000013424 13570323517 0015103 0 ustar 00root root 0000000 0000000 /*
* pam.go - Utility functions for interfacing with the PAM libraries.
*
* 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 pam
/*
#cgo LDFLAGS: -lpam
#include "pam.h"
#include
#include
#include
*/
import "C"
import (
"errors"
"log"
"os/user"
"unsafe"
"github.com/google/fscrypt/security"
)
// Handle wraps the C pam_handle_t type. This is used from within modules.
type Handle struct {
handle *C.pam_handle_t
status C.int
origPrivs *security.Privileges
// PamUser is the user for whom the PAM module is running.
PamUser *user.User
}
// NewHandle creates a Handle from a raw pointer.
func NewHandle(pamh unsafe.Pointer) (*Handle, error) {
var err error
h := &Handle{
handle: (*C.pam_handle_t)(pamh),
status: C.PAM_SUCCESS,
}
var pamUsername *C.char
h.status = C.pam_get_user(h.handle, &pamUsername, nil)
if err = h.err(); err != nil {
return nil, err
}
h.PamUser, err = user.Lookup(C.GoString(pamUsername))
return h, err
}
func (h *Handle) setData(name string, data unsafe.Pointer, cleanup C.CleanupFunc) error {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
h.status = C.pam_set_data(h.handle, cName, data, cleanup)
return h.err()
}
func (h *Handle) getData(name string) (unsafe.Pointer, error) {
var data unsafe.Pointer
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
h.status = C.pam_get_data(h.handle, cName, &data)
return data, h.err()
}
// ClearData remotes the PAM data with the specified name.
func (h *Handle) ClearData(name string) error {
return h.setData(name, unsafe.Pointer(C.CString("")), C.CleanupFunc(C.freeData))
}
// SetSecret sets a copy of the C string secret into the PAM data with the
// specified name. This copy will be held in locked memory until this PAM data
// is cleared.
func (h *Handle) SetSecret(name string, secret unsafe.Pointer) error {
return h.setData(name, C.copyIntoSecret(secret), C.CleanupFunc(C.freeSecret))
}
// GetSecret returns a pointer to the C string PAM data with the specified name.
// This a pointer directory to the data, so it shouldn't be modified. It should
// have been previously set with SetSecret().
func (h *Handle) GetSecret(name string) (unsafe.Pointer, error) {
return h.getData(name)
}
// SetString sets a string value for the PAM data with the specified name.
func (h *Handle) SetString(name string, s string) error {
return h.setData(name, unsafe.Pointer(C.CString(s)), C.CleanupFunc(C.freeData))
}
// GetString gets a string value for the PAM data with the specified name. It
// should have been previously set with SetString().
func (h *Handle) GetString(name string) (string, error) {
data, err := h.getData(name)
if err != nil {
return "", err
}
return C.GoString((*C.char)(data)), nil
}
// GetItem retrieves a PAM information item. This is a pointer directly to the
// data, so it shouldn't be modified.
func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) {
var data unsafe.Pointer
h.status = C.pam_get_item(h.handle, C.int(i), &data)
if err := h.err(); err != nil {
return nil, err
}
if data == nil {
return nil, errors.New("item not found")
}
return data, nil
}
// StartAsPamUser sets the effective privileges to that of the PAM user, and
// configures the PAM user's keyrings to be properly linked.
func (h *Handle) StartAsPamUser() error {
if _, err := security.UserKeyringID(h.PamUser, true); err != nil {
log.Printf("Setting up keyrings in PAM: %v", err)
}
userPrivs, err := security.UserPrivileges(h.PamUser)
if err != nil {
return err
}
if h.origPrivs, err = security.ProcessPrivileges(); err != nil {
return err
}
return security.SetProcessPrivileges(userPrivs)
}
// StopAsPamUser restores the original privileges that were running the
// PAM module (this is usually root).
func (h *Handle) StopAsPamUser() error {
err := security.SetProcessPrivileges(h.origPrivs)
if err != nil {
log.Print(err)
}
return err
}
func (h *Handle) err() error {
if h.status == C.PAM_SUCCESS {
return nil
}
s := C.GoString(C.pam_strerror(h.handle, C.int(h.status)))
return errors.New(s)
}
// Transaction represents a wrapped pam_handle_t type created with pam_start
// form an application.
type Transaction Handle
// Start initializes a pam Transaction. End() should be called after the
// Transaction is no longer needed.
func Start(service, username string) (*Transaction, error) {
cService := C.CString(service)
defer C.free(unsafe.Pointer(cService))
cUsername := C.CString(username)
defer C.free(unsafe.Pointer(cUsername))
t := &Transaction{
handle: nil,
status: C.PAM_SUCCESS,
}
t.status = C.pam_start(
cService,
cUsername,
C.goConv,
&t.handle)
return t, (*Handle)(t).err()
}
// End finalizes a pam Transaction with pam_end().
func (t *Transaction) End() {
C.pam_end(t.handle, t.status)
}
// Authenticate returns a boolean indicating if the user authenticated correctly
// or not. If the authentication check did not complete, an error is returned.
func (t *Transaction) Authenticate(quiet bool) (bool, error) {
var flags C.int = C.PAM_DISALLOW_NULL_AUTHTOK
if quiet {
flags |= C.PAM_SILENT
}
t.status = C.pam_authenticate(t.handle, flags)
if t.status == C.PAM_AUTH_ERR {
return false, nil
}
return true, (*Handle)(t).err()
}
fscrypt-0.2.5/pam/pam.h 0000664 0000000 0000000 00000003040 13570323517 0014716 0 ustar 00root root 0000000 0000000 /*
* pam.h - Functions to let us call into libpam from Go.
*
* 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.
*/
#ifndef FSCRYPT_PAM_H
#define FSCRYPT_PAM_H
#include
// Conversation that will call back into Go code when appropriate.
const struct pam_conv *goConv;
// CleaupFuncs are used to cleanup specific PAM data.
typedef void (*CleanupFunc)(pam_handle_t *pamh, void *data, int error_status);
// CleaupFunc that calls free() on data.
void freeData(pam_handle_t *pamh, void *data, int error_status);
// CleaupFunc that frees each item in a null terminated array of pointers and
// then frees the array itself.
void freeArray(pam_handle_t *pamh, void **array, int error_status);
// Creates a copy of a C string, which resides in a locked buffer.
void *copyIntoSecret(void *data);
// CleaupFunc that Zeros wipes a C string and unlocks and frees its memory.
void freeSecret(pam_handle_t *pamh, char *data, int error_status);
#endif // FSCRYPT_PAM_H
fscrypt-0.2.5/pam/pam_test.go 0000664 0000000 0000000 00000001413 13570323517 0016135 0 ustar 00root root 0000000 0000000 /*
* pam_test.go - Stub test file that has one test that always passes.
*
* Copyright 2018 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 pam
import "testing"
func TestTrivial(t *testing.T) {}
fscrypt-0.2.5/pam_fscrypt/ 0000775 0000000 0000000 00000000000 13570323517 0015545 5 ustar 00root root 0000000 0000000 fscrypt-0.2.5/pam_fscrypt/config 0000664 0000000 0000000 00000000473 13570323517 0016741 0 ustar 00root root 0000000 0000000 Name: fscrypt PAM passphrase support
Default: yes
Priority: 0
Auth-Type: Additional
Auth-Final:
optional PAM_INSTALL_PATH
Session-Type: Additional
Session-Interactive-Only: yes
Session-Final:
optional PAM_INSTALL_PATH drop_caches lock_policies
Password-Type: Additional
Password-Final:
optional PAM_INSTALL_PATH
fscrypt-0.2.5/pam_fscrypt/pam_fscrypt.go 0000664 0000000 0000000 00000021030 13570323517 0020417 0 ustar 00root root 0000000 0000000 /*
* pam_fscrypt.go - Checks the validity of a login token key against PAM.
*
* 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 main
/*
#cgo LDFLAGS: -lpam -fPIC
#include
#include
#include
*/
import "C"
import (
"log"
"unsafe"
"github.com/pkg/errors"
"github.com/google/fscrypt/actions"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/pam"
"github.com/google/fscrypt/security"
)
const (
moduleName = "pam_fscrypt"
// authtokLabel tags the AUTHTOK in the PAM data.
authtokLabel = "fscrypt_authtok"
// These flags are used to toggle behavior of the PAM module.
debugFlag = "debug"
lockFlag = "lock_policies"
cacheFlag = "drop_caches"
)
var (
// PamFuncs for our 4 provided methods
authenticateFunc = PamFunc{"Authenticate", Authenticate}
openSessionFunc = PamFunc{"OpenSession", OpenSession}
closeSessionFunc = PamFunc{"CloseSession", CloseSession}
chauthtokFunc = PamFunc{"Chauthtok", Chauthtok}
)
// Authenticate copies the AUTHTOK (if necessary) into the PAM data so it can be
// used in pam_sm_open_session.
func Authenticate(handle *pam.Handle, _ map[string]bool) error {
if err := handle.StartAsPamUser(); err != nil {
return err
}
defer handle.StopAsPamUser()
// If this user doesn't have a login protector, no unlocking is needed.
if _, err := loginProtector(handle); err != nil {
log.Printf("no protector, no need for AUTHTOK: %s", err)
return nil
}
log.Print("copying AUTHTOK for use in the session open")
authtok, err := handle.GetItem(pam.Authtok)
if err != nil {
return errors.Wrap(err, "could not get AUTHTOK")
}
err = handle.SetSecret(authtokLabel, authtok)
return errors.Wrap(err, "could not set AUTHTOK data")
}
// OpenSession provisions any policies protected with the login protector.
func OpenSession(handle *pam.Handle, _ map[string]bool) error {
// We will always clear the AUTHTOK data
defer handle.ClearData(authtokLabel)
// Increment the count as we add a session
if _, err := AdjustCount(handle, +1); err != nil {
return err
}
if err := handle.StartAsPamUser(); err != nil {
return err
}
defer handle.StopAsPamUser()
// If there are no polices for the login protector, no unlocking needed.
protector, err := loginProtector(handle)
if err != nil {
log.Printf("no protector to unlock: %s", err)
return nil
}
policies := policiesUsingProtector(protector)
if len(policies) == 0 {
log.Print("no policies to unlock")
return nil
}
log.Printf("unlocking %d policies protected with AUTHTOK", len(policies))
keyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) {
if retry {
// Login passphrase and login protector have diverged.
// We could prompt the user for the old passphrase and
// rewrap, but we currently don't.
return nil, pam.ErrPassphrase
}
authtok, err := handle.GetSecret(authtokLabel)
if err != nil {
// pam_sm_authenticate was not run before the session is
// opened. This can happen when a user does something
// like "sudo su ". We could prompt for the
// login passphrase here, but we currently don't.
return nil, errors.Wrap(err, "AUTHTOK data missing")
}
return crypto.NewKeyFromCString(authtok)
}
if err := protector.Unlock(keyFn); err != nil {
return errors.Wrapf(err, "unlocking protector %s", protector.Descriptor())
}
defer protector.Lock()
// We don't stop provisioning polices on error, we try all of them.
for _, policy := range policies {
if policy.IsProvisioned() {
log.Printf("policy %s already provisioned", policy.Descriptor())
continue
}
if err := policy.UnlockWithProtector(protector); err != nil {
log.Printf("unlocking policy %s: %s", policy.Descriptor(), err)
continue
}
defer policy.Lock()
if err := policy.Provision(); err != nil {
log.Printf("provisioning policy %s: %s", policy.Descriptor(), err)
continue
}
log.Printf("policy %s provisioned", policy.Descriptor())
}
return nil
}
// CloseSession can deprovision all keys provisioned at the start of the
// session. It can also clear the cache so these changes take effect.
func CloseSession(handle *pam.Handle, args map[string]bool) error {
// Only do stuff on session close when we are the last session
if count, err := AdjustCount(handle, -1); err != nil || count != 0 {
log.Printf("count is %d and we are not locking", count)
return err
}
var errLock, errCache error
// Don't automatically drop privileges, we may need them to drop caches.
if args[lockFlag] {
log.Print("locking polices protected with login protector")
errLock = lockLoginPolicies(handle)
}
if args[cacheFlag] {
log.Print("dropping appropriate filesystem caches at session close")
errCache = security.DropFilesystemCache()
}
if errLock != nil {
return errLock
}
return errCache
}
// lockLoginPolicies deprovisions all policy keys that are protected by
// the user's login protector.
func lockLoginPolicies(handle *pam.Handle) error {
if err := handle.StartAsPamUser(); err != nil {
return err
}
defer handle.StopAsPamUser()
// If there are no polices for the login protector, no locking needed.
protector, err := loginProtector(handle)
if err != nil {
log.Printf("nothing to lock: %s", err)
return nil
}
policies := policiesUsingProtector(protector)
if len(policies) == 0 {
log.Print("no policies to lock")
return nil
}
// We will try to deprovision all of the policies.
for _, policy := range policies {
if !policy.IsProvisioned() {
log.Printf("policy %s not provisioned", policy.Descriptor())
continue
}
if err := policy.Deprovision(); err != nil {
log.Printf("deprovisioning policy %s: %s", policy.Descriptor(), err)
continue
}
log.Printf("policy %s deprovisioned", policy.Descriptor())
}
return nil
}
// Chauthtok rewraps the login protector when the passphrase changes.
func Chauthtok(handle *pam.Handle, _ map[string]bool) error {
if err := handle.StartAsPamUser(); err != nil {
return err
}
defer handle.StopAsPamUser()
protector, err := loginProtector(handle)
if err != nil {
log.Printf("no login protector to rewrap: %s", err)
return nil
}
oldKeyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) {
if retry {
// If the OLDAUTHTOK disagrees with the login protector,
// we do nothing, as the protector will (probably) still
// disagree after the login passphrase changes.
return nil, pam.ErrPassphrase
}
authtok, err := handle.GetItem(pam.Oldauthtok)
if err != nil {
return nil, errors.Wrap(err, "could not get OLDAUTHTOK")
}
return crypto.NewKeyFromCString(authtok)
}
newKeyFn := func(_ actions.ProtectorInfo, _ bool) (*crypto.Key, error) {
authtok, err := handle.GetItem(pam.Authtok)
if err != nil {
return nil, errors.Wrap(err, "could not get AUTHTOK")
}
return crypto.NewKeyFromCString(authtok)
}
log.Print("rewrapping login protector")
if err = protector.Unlock(oldKeyFn); err != nil {
return err
}
defer protector.Lock()
return protector.Rewrap(newKeyFn)
}
//export pam_sm_authenticate
func pam_sm_authenticate(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int {
return authenticateFunc.Run(pamh, argc, argv)
}
// pam_sm_setcred needed because we use pam_sm_authenticate.
//export pam_sm_setcred
func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int {
return C.PAM_SUCCESS
}
//export pam_sm_open_session
func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int {
return openSessionFunc.Run(pamh, argc, argv)
}
//export pam_sm_close_session
func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int {
return closeSessionFunc.Run(pamh, argc, argv)
}
//export pam_sm_chauthtok
func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int {
// Only do rewrapping if we have both AUTHTOKs and a login protector.
if pam.Flag(flags)&pam.PrelimCheck != 0 {
return C.PAM_SUCCESS
}
return chauthtokFunc.Run(pamh, argc, argv)
}
// main() is needed to make a shared library compile
func main() {}
fscrypt-0.2.5/pam_fscrypt/run_fscrypt.go 0000664 0000000 0000000 00000015000 13570323517 0020446 0 ustar 00root root 0000000 0000000 /*
* run_fscrypt.go - Helpers for running functions in the PAM module.
*
* 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 main
/*
#cgo LDFLAGS: -lpam -fPIC
#include
#include
#include
*/
import "C"
import (
"fmt"
"io"
"io/ioutil"
"log"
"log/syslog"
"os"
"path/filepath"
"runtime/debug"
"unsafe"
"golang.org/x/sys/unix"
"github.com/pkg/errors"
"github.com/google/fscrypt/actions"
"github.com/google/fscrypt/filesystem"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/pam"
"github.com/google/fscrypt/util"
)
const (
// countDirectory is in a tmpfs filesystem so it will reset on reboot.
countDirectory = "/run/fscrypt"
// count files should only be readable and writable by root
countDirectoryPermissions = 0700
countFilePermissions = 0600
countFileFormat = "%d\n"
)
// PamFunc is used to define the various actions in the PAM module.
type PamFunc struct {
// Name of the function being executed
name string
// Go implementation of this function
impl func(handle *pam.Handle, args map[string]bool) error
}
// Run is used to convert between the Go functions and exported C funcs.
func (f *PamFunc) Run(pamh unsafe.Pointer, argc C.int, argv **C.char) (ret C.int) {
args := parseArgs(argc, argv)
errorWriter := setupLogging(args)
// Log any panics to the errorWriter
defer func() {
if r := recover(); r != nil {
ret = C.PAM_SERVICE_ERR
fmt.Fprintf(errorWriter,
"%s(%v) panicked: %s\nPlease open a bug.\n%s",
f.name, args, r, debug.Stack())
}
}()
log.Printf("%s(%v) starting", f.name, args)
handle, err := pam.NewHandle(pamh)
if err == nil {
err = f.impl(handle, args)
}
if err != nil {
fmt.Fprintf(errorWriter, "%s(%v) failed: %s", f.name, args, err)
return C.PAM_SERVICE_ERR
}
log.Printf("%s(%v) succeeded", f.name, args)
return C.PAM_SUCCESS
}
// parseArgs takes a list of C arguments into a PAM function and returns a map
// where a key has a value of true if it appears in the argument list.
func parseArgs(argc C.int, argv **C.char) map[string]bool {
args := make(map[string]bool)
if argc == 0 || argv == nil {
return args
}
for _, cString := range util.PointerSlice(unsafe.Pointer(argv))[:argc] {
args[C.GoString((*C.char)(cString))] = true
}
return args
}
// setupLogging directs turns off standard logging (or redirects it to debug
// syslog if the "debug" argument is passed) and returns a writer to the error
// syslog.
func setupLogging(args map[string]bool) io.Writer {
log.SetFlags(0) // Syslog already includes time data itself
log.SetOutput(ioutil.Discard)
if args[debugFlag] {
debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName)
if err == nil {
log.SetOutput(debugWriter)
}
}
errorWriter, err := syslog.New(syslog.LOG_ERR, moduleName)
if err != nil {
return ioutil.Discard
}
return errorWriter
}
// loginProtector returns the login protector corresponding to the PAM_USER if
// one exists. This protector descriptor (if found) will be cached in the pam
// data, under descriptorLabel.
func loginProtector(handle *pam.Handle) (*actions.Protector, error) {
ctx, err := actions.NewContextFromMountpoint("/", handle.PamUser)
if err != nil {
return nil, err
}
// Find the user's PAM protector.
options, err := ctx.ProtectorOptions()
if err != nil {
return nil, err
}
uid := int64(util.AtoiOrPanic(handle.PamUser.Uid))
for _, option := range options {
if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid {
return actions.GetProtectorFromOption(ctx, option)
}
}
return nil, errors.Errorf("no PAM protector for UID=%d on %q", uid, ctx.Mount.Path)
}
// policiesUsingProtector searches all the mountpoints for any policies
// protected with the specified protector.
func policiesUsingProtector(protector *actions.Protector) []*actions.Policy {
mounts, err := filesystem.AllFilesystems()
if err != nil {
log.Print(err)
return nil
}
var policies []*actions.Policy
for _, mount := range mounts {
// Skip mountpoints that do not use the protector.
if _, _, err := mount.GetProtector(protector.Descriptor()); err != nil {
continue
}
policyDescriptors, err := mount.ListPolicies()
if err != nil {
log.Printf("listing policies: %s", err)
continue
}
// Clone context but modify the mountpoint
ctx := *protector.Context
ctx.Mount = mount
for _, policyDescriptor := range policyDescriptors {
policy, err := actions.GetPolicy(&ctx, policyDescriptor)
if err != nil {
log.Printf("reading policy: %s", err)
continue
}
if policy.UsesProtector(protector) {
policies = append(policies, policy)
}
}
}
return policies
}
// AdjustCount changes the session count for the pam user by the specified
// amount. If the count file does not exist, create it as if it had a count of
// zero. If the adjustment would bring the count below zero, the count is set to
// zero. The value of the new count is returned. Requires root privileges.
func AdjustCount(handle *pam.Handle, delta int) (int, error) {
// Make sure the directory exists
if err := os.MkdirAll(countDirectory, countDirectoryPermissions); err != nil {
return 0, err
}
path := filepath.Join(countDirectory, handle.PamUser.Uid+".count")
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, countFilePermissions)
if err != nil {
return 0, err
}
if err = unix.Flock(int(file.Fd()), unix.LOCK_EX); err != nil {
return 0, err
}
defer file.Close()
newCount := util.MaxInt(getCount(file)+delta, 0)
if _, err = file.Seek(0, io.SeekStart); err != nil {
return 0, err
}
if _, err = fmt.Fprintf(file, countFileFormat, newCount); err != nil {
return 0, err
}
log.Printf("Session count for UID=%s updated to %d", handle.PamUser.Uid, newCount)
return newCount, nil
}
// Returns the count in the file (or zero if the count cannot be read).
func getCount(file *os.File) int {
var count int
if _, err := fmt.Fscanf(file, countFileFormat, &count); err != nil {
return 0
}
return count
}
fscrypt-0.2.5/pam_fscrypt/run_test.go 0000664 0000000 0000000 00000001755 13570323517 0017747 0 ustar 00root root 0000000 0000000 /*
* run_test.go - tests that the PAM helper functions work properly
*
* 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 main
import (
"testing"
)
func TestParseArgsEmpty(t *testing.T) {
// An empty argv should create a map with no entries
args := parseArgs(0, nil)
if args == nil {
t.Fatal("args map should not be nil")
}
if len(args) > 0 {
t.Fatal("args map should not have any entries")
}
}
fscrypt-0.2.5/security/ 0000775 0000000 0000000 00000000000 13570323517 0015065 5 ustar 00root root 0000000 0000000 fscrypt-0.2.5/security/cache.go 0000664 0000000 0000000 00000003166 13570323517 0016465 0 ustar 00root root 0000000 0000000 /*
* cache.go - Handles cache clearing and management.
*
* 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 security
import (
"log"
"os"
"golang.org/x/sys/unix"
)
// DropFilesystemCache instructs the kernel to free the reclaimable inodes and
// dentries. This has the effect of making encrypted directories whose keys are
// not present no longer accessible. Requires root privileges.
func DropFilesystemCache() error {
// Dirty reclaimable inodes must be synced so that they will be freed.
log.Print("syncing changes to filesystem")
unix.Sync()
// See: https://www.kernel.org/doc/Documentation/sysctl/vm.txt
log.Print("freeing reclaimable inodes and dentries")
file, err := os.OpenFile("/proc/sys/vm/drop_caches", os.O_WRONLY|os.O_SYNC, 0)
if err != nil {
return err
}
defer file.Close()
// "2" just frees the reclaimable inodes and dentries. The associated
// pages to these inodes will be freed. We do not need to free the
// entire pagecache (as this will severely impact performance).
_, err = file.WriteString("2")
return err
}
fscrypt-0.2.5/security/keyring.go 0000664 0000000 0000000 00000015503 13570323517 0017070 0 ustar 00root root 0000000 0000000 /*
* keyring.go - Handles inserting/removing into user keyrings.
*
* 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 security
import (
"fmt"
"log"
"os/user"
"sync"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/google/fscrypt/util"
)
// KeyType is always logon as required by filesystem encryption.
const KeyType = "logon"
// Keyring related error values
var (
ErrKeySearch = errors.New("could not find key with descriptor")
ErrKeyRemove = util.SystemError("could not remove key from the keyring")
ErrKeyInsert = util.SystemError("could not insert key into the keyring")
ErrSessionUserKeying = errors.New("user keyring not linked into session keyring")
ErrAccessUserKeyring = errors.New("could not access user keyring")
ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring")
)
// FindKey tries to locate a key in the kernel keyring with the provided
// description. The key ID is returned if we can find the key. An error is
// returned if the key does not exist.
func FindKey(description string, target *user.User) (int, error) {
keyringID, err := UserKeyringID(target, false)
if err != nil {
return 0, err
}
keyID, err := unix.KeyctlSearch(keyringID, KeyType, description, 0)
log.Printf("KeyctlSearch(%d, %s, %s) = %d, %v", keyringID, KeyType, description, keyID, err)
if err != nil {
return 0, errors.Wrap(ErrKeySearch, err.Error())
}
return keyID, err
}
// RemoveKey tries to remove a policy key from the kernel keyring with the
// provided description. An error is returned if the key does not exist.
func RemoveKey(description string, target *user.User) error {
keyID, err := FindKey(description, target)
if err != nil {
return err
}
// We use KEYCTL_INVALIDATE instead of KEYCTL_REVOKE because
// invalidating a key immediately removes it.
_, err = unix.KeyctlInt(unix.KEYCTL_INVALIDATE, keyID, 0, 0, 0)
log.Printf("KeyctlInvalidate(%d) = %v", keyID, err)
if err != nil {
return errors.Wrap(ErrKeyRemove, err.Error())
}
return nil
}
// InsertKey puts the provided data into the kernel keyring with the provided
// description.
func InsertKey(data []byte, description string, target *user.User) error {
keyringID, err := UserKeyringID(target, true)
if err != nil {
return err
}
keyID, err := unix.AddKey(KeyType, description, data, keyringID)
log.Printf("KeyctlAddKey(%s, %s, , %d) = %d, %v",
KeyType, description, keyringID, keyID, err)
if err != nil {
return errors.Wrap(ErrKeyInsert, err.Error())
}
return nil
}
var (
keyringIDCache = make(map[int]int)
cacheLock sync.Mutex
)
// UserKeyringID returns the key id of the target user's user keyring. We also
// ensure that the keyring will be accessible by linking it into the process
// keyring and linking it into the root user keyring (permissions allowing). If
// checkSession is true, an error is returned if a normal user requests their
// user keyring, but it is not in the current session keyring.
func UserKeyringID(target *user.User, checkSession bool) (int, error) {
uid := util.AtoiOrPanic(target.Uid)
targetKeyring, err := userKeyringIDLookup(uid)
if err != nil {
return 0, errors.Wrap(ErrAccessUserKeyring, err.Error())
}
if !util.IsUserRoot() {
// Make sure the returned keyring will be accessible by checking
// that it is in the session keyring.
if checkSession && !isUserKeyringInSession(uid) {
return 0, ErrSessionUserKeying
}
return targetKeyring, nil
}
// Make sure the returned keyring will be accessible by linking it into
// the root user's user keyring (which will not be garbage collected).
rootKeyring, err := userKeyringIDLookup(0)
if err != nil {
return 0, errors.Wrap(ErrLinkUserKeyring, err.Error())
}
if rootKeyring != targetKeyring {
if err = keyringLink(targetKeyring, rootKeyring); err != nil {
return 0, errors.Wrap(ErrLinkUserKeyring, err.Error())
}
}
return targetKeyring, nil
}
func userKeyringIDLookup(uid int) (keyringID int, err error) {
cacheLock.Lock()
defer cacheLock.Unlock()
var ok bool
if keyringID, ok = keyringIDCache[uid]; ok {
return
}
// Our goals here are to:
// - Find the user keyring (for the provided uid)
// - Link it into the current process keyring (so we can use it)
// - Make no permanent changes to the process privileges
// Complicating this are the facts that:
// - The value of KEY_SPEC_USER_KEYRING is determined by the ruid
// - Keyring linking permissions use the euid
// So we have to change both the ruid and euid to make this work,
// setting the suid to 0 so that we can later switch back.
ruid, euid, suid := getUids()
if ruid != uid || euid != uid {
if err = setUids(uid, uid, 0); err != nil {
return
}
defer func() {
resetErr := setUids(ruid, euid, suid)
if resetErr != nil {
err = resetErr
}
}()
}
// We get the value of KEY_SPEC_USER_KEYRING. Note that this will also
// trigger the creation of the uid keyring if it does not yet exist.
keyringID, err = unix.KeyctlGetKeyringID(unix.KEY_SPEC_USER_KEYRING, true)
log.Printf("keyringID(_uid.%d) = %d, %v", uid, keyringID, err)
if err != nil {
return 0, err
}
// We still want to use this keyring after our privileges are reset. So
// we link it into the process keyring, preventing a loss of access.
if err = keyringLink(keyringID, unix.KEY_SPEC_PROCESS_KEYRING); err != nil {
return 0, err
}
keyringIDCache[uid] = keyringID
return keyringID, nil
}
// isUserKeyringInSession tells us if the user's uid keyring is in the current
// session keyring.
func isUserKeyringInSession(uid int) bool {
// We cannot use unix.KEY_SPEC_SESSION_KEYRING directly as that might
// create a session keyring if one does not exist.
sessionKeyring, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, false)
log.Printf("keyringID(session) = %d, %v", sessionKeyring, err)
if err != nil {
return false
}
description := fmt.Sprintf("_uid.%d", uid)
id, err := unix.KeyctlSearch(sessionKeyring, "keyring", description, 0)
log.Printf("KeyctlSearch(%d, keyring, %s) = %d, %v", sessionKeyring, description, id, err)
return err == nil
}
func keyringLink(keyID int, keyringID int) error {
_, err := unix.KeyctlInt(unix.KEYCTL_LINK, keyID, keyringID, 0, 0)
log.Printf("KeyctlLink(%d, %d) = %v", keyID, keyringID, err)
return err
}
fscrypt-0.2.5/security/privileges.go 0000664 0000000 0000000 00000012716 13570323517 0017574 0 ustar 00root root 0000000 0000000 /*
* privileges.go - Functions for managing users and privileges.
*
* 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 security manages:
// - Cache clearing (cache.go)
// - Keyring Operations (keyring.go)
// - Privilege manipulation (privileges.go)
// - Maintaining the link between the root and user keyrings.
package security
// Use the libc versions of setreuid, setregid, and setgroups instead of the
// "sys/unix" versions. The "sys/unix" versions use the raw syscalls which
// operate on the calling thread only, whereas the libc versions operate on the
// whole process. And we need to operate on the whole process, firstly for
// pam_fscrypt to prevent the privileges of Go worker threads from diverging
// from the PAM stack's "main" thread, violating libc's assumption and causing
// an abort() later in the PAM stack; and secondly because Go code may migrate
// between OS-level threads while it's running.
//
// See also: https://github.com/golang/go/issues/1435
//
// Also we need to wrap the libc functions in our own C functions rather than
// calling them directly because in the glibc headers (but not necessarily in
// the headers for other C libraries that may be used on Linux) they are
// declared to take __uid_t and __gid_t arguments rather than uid_t and gid_t.
// And while these are typedef'ed to the same underlying type, before Go 1.10,
// cgo maps them to different Go types.
/*
#define _GNU_SOURCE // for getresuid and setresuid
#include
#include // getting and setting uids and gids
#include // setgroups
*/
import "C"
import (
"log"
"os/user"
"syscall"
"github.com/pkg/errors"
"github.com/google/fscrypt/util"
)
// Privileges encapsulate the effective uid/gid and groups of a process.
type Privileges struct {
euid C.uid_t
egid C.gid_t
groups []C.gid_t
}
// ProcessPrivileges returns the process's current effective privileges.
func ProcessPrivileges() (*Privileges, error) {
ruid := C.getuid()
euid := C.geteuid()
rgid := C.getgid()
egid := C.getegid()
var groups []C.gid_t
n, err := C.getgroups(0, nil)
if n < 0 {
return nil, err
}
// If n == 0, the user isn't in any groups, so groups == nil is fine.
if n > 0 {
groups = make([]C.gid_t, n)
n, err = C.getgroups(n, &groups[0])
if n < 0 {
return nil, err
}
groups = groups[:n]
}
log.Printf("Current privs (real, effective): uid=(%d,%d) gid=(%d,%d) groups=%v",
ruid, euid, rgid, egid, groups)
return &Privileges{euid, egid, groups}, nil
}
// UserPrivileges returns the default privileges for the specified user.
func UserPrivileges(user *user.User) (*Privileges, error) {
privs := &Privileges{
euid: C.uid_t(util.AtoiOrPanic(user.Uid)),
egid: C.gid_t(util.AtoiOrPanic(user.Gid)),
}
userGroups, err := user.GroupIds()
if err != nil {
return nil, util.SystemError(err.Error())
}
privs.groups = make([]C.gid_t, len(userGroups))
for i, group := range userGroups {
privs.groups[i] = C.gid_t(util.AtoiOrPanic(group))
}
return privs, nil
}
// SetProcessPrivileges sets the privileges of the current process to have those
// specified by privs. The original privileges can be obtained by first saving
// the output of ProcessPrivileges, calling SetProcessPrivileges with the
// desired privs, then calling SetProcessPrivileges with the saved privs.
func SetProcessPrivileges(privs *Privileges) error {
log.Printf("Setting euid=%d egid=%d groups=%v", privs.euid, privs.egid, privs.groups)
// If setting privs as root, we need to set the euid to 0 first, so that
// we will have the necessary permissions to make the other changes to
// the groups/egid/euid, regardless of our original euid.
C.seteuid(0)
// Separately handle the case where the user is in no groups.
numGroups := C.size_t(len(privs.groups))
groupsPtr := (*C.gid_t)(nil)
if numGroups > 0 {
groupsPtr = &privs.groups[0]
}
if res, err := C.setgroups(numGroups, groupsPtr); res < 0 {
return errors.Wrapf(err.(syscall.Errno), "setting groups")
}
if res, err := C.setegid(privs.egid); res < 0 {
return errors.Wrapf(err.(syscall.Errno), "setting egid")
}
if res, err := C.seteuid(privs.euid); res < 0 {
return errors.Wrapf(err.(syscall.Errno), "setting euid")
}
ProcessPrivileges()
return nil
}
func setUids(ruid, euid, suid int) error {
log.Printf("Setting ruid=%d euid=%d suid=%d", ruid, euid, suid)
// We elevate all the privs before setting them. This prevents issues
// with (ruid=1000,euid=1000,suid=0), where just a single call to
// setresuid might fail with permission denied.
if res, err := C.setresuid(0, 0, 0); res < 0 {
return errors.Wrapf(err.(syscall.Errno), "setting uids")
}
if res, err := C.setresuid(C.uid_t(ruid), C.uid_t(euid), C.uid_t(suid)); res < 0 {
return errors.Wrapf(err.(syscall.Errno), "setting uids")
}
return nil
}
func getUids() (int, int, int) {
var ruid, euid, suid C.uid_t
C.getresuid(&ruid, &euid, &suid)
return int(ruid), int(euid), int(suid)
}
fscrypt-0.2.5/security/security_test.go 0000664 0000000 0000000 00000001425 13570323517 0020324 0 ustar 00root root 0000000 0000000 /*
* security_test.go - Stub test file that has one test that always passes.
*
* Copyright 2018 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 security
import "testing"
func TestTrivial(t *testing.T) {}
fscrypt-0.2.5/util/ 0000775 0000000 0000000 00000000000 13570323517 0014173 5 ustar 00root root 0000000 0000000 fscrypt-0.2.5/util/errors.go 0000664 0000000 0000000 00000007535 13570323517 0016050 0 ustar 00root root 0000000 0000000 /*
* errors.go - Custom errors and error functions used by fscrypt
*
* 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 util
import (
"fmt"
"io"
"log"
"os"
"github.com/pkg/errors"
)
// ErrReader wraps an io.Reader, passing along calls to Read() until a read
// fails. Then, the error is stored, and all subsequent calls to Read() do
// nothing. This allows you to write code which has many subsequent reads and
// do all of the error checking at the end. For example:
//
// r := NewErrReader(reader)
// r.Read(foo)
// r.Read(bar)
// r.Read(baz)
// if r.Err() != nil {
// // Handle error
// }
//
// Taken from https://blog.golang.org/errors-are-values by Rob Pike.
type ErrReader struct {
r io.Reader
err error
}
// NewErrReader creates an ErrReader which wraps the provided reader.
func NewErrReader(reader io.Reader) *ErrReader {
return &ErrReader{r: reader, err: nil}
}
// Read runs ReadFull on the wrapped reader if no errors have occurred.
// Otherwise, the previous error is just returned and no reads are attempted.
func (e *ErrReader) Read(p []byte) (n int, err error) {
if e.err == nil {
n, e.err = io.ReadFull(e.r, p)
}
return n, e.err
}
// Err returns the first encountered err (or nil if no errors occurred).
func (e *ErrReader) Err() error {
return e.err
}
// ErrWriter works exactly like ErrReader, except with io.Writer.
type ErrWriter struct {
w io.Writer
err error
}
// NewErrWriter creates an ErrWriter which wraps the provided writer.
func NewErrWriter(writer io.Writer) *ErrWriter {
return &ErrWriter{w: writer, err: nil}
}
// Write runs the wrapped writer's Write if no errors have occurred. Otherwise,
// the previous error is just returned and no writes are attempted.
func (e *ErrWriter) Write(p []byte) (n int, err error) {
if e.err == nil {
n, e.err = e.w.Write(p)
}
return n, e.err
}
// Err returns the first encountered err (or nil if no errors occurred).
func (e *ErrWriter) Err() error {
return e.err
}
// CheckValidLength returns an invalid length error if expected != actual
func CheckValidLength(expected, actual int) error {
if expected == actual {
return nil
}
return fmt.Errorf("expected length of %d, got %d", expected, actual)
}
// SystemError is an error that should indicate something has gone wrong in the
// underlying system (syscall failure, bad ioctl, etc...).
type SystemError string
func (s SystemError) Error() string {
return "system error: " + string(s)
}
// NeverError panics if a non-nil error is passed in. It should be used to check
// for logic errors, not to handle recoverable errors.
func NeverError(err error) {
if err != nil {
log.Panicf("NeverError() check failed: %v", err)
}
}
var (
// testEnvVarName is the name of an environment variable that should be
// set to an empty mountpoint. This is only used for integration tests.
// If not set, integration tests are skipped.
testEnvVarName = "TEST_FILESYSTEM_ROOT"
// ErrSkipIntegration indicates integration tests shouldn't be run.
ErrSkipIntegration = errors.New("skipping integration test")
)
// TestRoot returns a the root of a filesystem specified by testEnvVarName. This
// function is only used for integration tests.
func TestRoot() (string, error) {
path := os.Getenv(testEnvVarName)
if path == "" {
return "", ErrSkipIntegration
}
return path, nil
}
fscrypt-0.2.5/util/util.go 0000664 0000000 0000000 00000006642 13570323517 0015507 0 ustar 00root root 0000000 0000000 /*
* util.go - Various helpers used throughout fscrypt
*
* 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 util contains useful components for simplifying Go code.
//
// The package contains common error types (errors.go) and functions for
// converting arrays to pointers.
package util
import (
"bufio"
"os"
"os/user"
"strconv"
"unsafe"
)
// Ptr converts a Go byte array to a pointer to the start of the array.
func Ptr(slice []byte) unsafe.Pointer {
if len(slice) == 0 {
return nil
}
return unsafe.Pointer(&slice[0])
}
// ByteSlice takes a pointer to some data and views it as a slice of bytes.
// Note, indexing into this slice is unsafe.
func ByteSlice(ptr unsafe.Pointer) []byte {
// Slice must fit in the smallest address space go supports.
return (*[1 << 30]byte)(ptr)[:]
}
// PointerSlice takes a pointer to an array of pointers and views it as a slice
// of pointers. Note, indexing into this slice is unsafe.
func PointerSlice(ptr unsafe.Pointer) []unsafe.Pointer {
// Slice must fit in the smallest address space go supports.
return (*[1 << 28]unsafe.Pointer)(ptr)[:]
}
// Index returns the first index i such that inVal == inArray[i].
// ok is true if we find a match, false otherwise.
func Index(inVal int64, inArray []int64) (index int, ok bool) {
for index, val := range inArray {
if val == inVal {
return index, true
}
}
return 0, false
}
// Lookup finds inVal in inArray and returns the corresponding element in
// outArray. Specifically, if inVal == inArray[i], outVal == outArray[i].
// ok is true if we find a match, false otherwise.
func Lookup(inVal int64, inArray, outArray []int64) (outVal int64, ok bool) {
index, ok := Index(inVal, inArray)
if !ok {
return 0, false
}
return outArray[index], true
}
// MinInt returns the lesser of a and b.
func MinInt(a, b int) int {
if a < b {
return a
}
return b
}
// MaxInt returns the greater of a and b.
func MaxInt(a, b int) int {
if a > b {
return a
}
return b
}
// MinInt64 returns the lesser of a and b.
func MinInt64(a, b int64) int64 {
if a < b {
return a
}
return b
}
// ReadLine returns a line of input from standard input. An empty string is
// returned if the user didn't insert anything or on error.
func ReadLine() (string, error) {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
return scanner.Text(), scanner.Err()
}
// AtoiOrPanic converts a string to an int or it panics. Should only be used in
// situations where the input MUST be a decimal number.
func AtoiOrPanic(input string) int {
i, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
return i
}
// EffectiveUser returns the user entry corresponding to the effective user.
func EffectiveUser() (*user.User, error) {
return user.LookupId(strconv.Itoa(os.Geteuid()))
}
// IsUserRoot checks if the effective user is root.
func IsUserRoot() bool {
return os.Geteuid() == 0
}
fscrypt-0.2.5/util/util_test.go 0000664 0000000 0000000 00000004337 13570323517 0016545 0 ustar 00root root 0000000 0000000 /*
* util_test.go - Tests the util 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 util
import (
"bytes"
"testing"
"unsafe"
)
const offset = 3
var (
byteArr = []byte{'a', 'b', 'c', 'd'}
ptrArr = []*int{&a, &b, &c, &d}
a = 1
b = 2
c = 3
d = 4
)
// Make sure the address behaves well under slicing
func TestPtrOffset(t *testing.T) {
i1 := uintptr(Ptr(byteArr[offset:]))
i2 := uintptr(Ptr(byteArr))
if i1 != i2+offset {
t.Errorf("pointers %v and %v do not have an offset of %v", i1, i2, offset)
}
}
// Tests that the ByteSlice method essentially reverses the Ptr method
func TestByteSlice(t *testing.T) {
ptr := Ptr(byteArr)
generatedArr := ByteSlice(ptr)[:len(byteArr)]
if !bytes.Equal(byteArr, generatedArr) {
t.Errorf("generated array (%v) and original array (%v) do not agree",
generatedArr, byteArr)
}
}
// Tests that the PointerSlice method correctly handles Go Pointers
func TestPointerSlice(t *testing.T) {
arrPtr := unsafe.Pointer(&ptrArr[0])
// Convert an array of unsafe pointers to int pointers.
for i, ptr := range PointerSlice(arrPtr)[:len(ptrArr)] {
if ptrArr[i] != (*int)(ptr) {
t.Errorf("generated array and original array disagree at %d", i)
}
}
}
// Make sure NeverError actually panics
func TestNeverErrorPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("NeverError did not panic")
}
}()
err := SystemError("Hello")
NeverError(err)
}
// Make sure NeverError doesn't panic on nil
func TestNeverErrorNoPanic(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("NeverError panicked")
}
}()
NeverError(nil)
}