Skip to content

Commit

Permalink
Remove support for v1 encryption policies
Browse files Browse the repository at this point in the history
Since not many people are using fscryptctl, and v1 encryption policies
have some annoying limitations, it's better to just drop the fscryptctl
support for v1 policies and only support v2.  This keeps things simpler
going forward.  People who want v1 support can continue to use an older
version of fscryptctl.
  • Loading branch information
ebiggers committed Feb 2, 2021
1 parent 46a531e commit 2abb15b
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 860 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ name: CI
on: [pull_request, push]
env:
CFLAGS: -O2 -Wall -Werror
TEST_DEPENDENCIES: e2fsprogs python3 python3-pytest python3-keyutils
TEST_DEPENDENCIES: e2fsprogs python3 python3-pytest

jobs:
build-and-test:
Expand Down
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ information on using pull requests.
When making any changes to `fscryptctl`, run the following commands:
* `make format`, which formats the source code (requires `clang-format`)
* `make test-all`, which builds `fscryptctl` and runs the tests. The tests
require the `e2fsprogs` and `python3` packages, the `pytest` and `keyutils`
Python packages, and kernel support for ext4 encryption.
require the `e2fsprogs` and `python3` packages, the `pytest` Python package,
and kernel support for ext4 encryption.

The userspace dependencies can be installed with:
``` bash
> sudo apt-get install e2fsprogs python3-pip libkeyutils-dev clang-format
> sudo -H pip3 install -U pip pytest keyutils
> sudo apt-get install e2fsprogs python3-pip clang-format
> sudo -H pip3 install -U pip pytest
```

Your Linux kernel must be version 5.4 or later and have the following
Expand Down
45 changes: 16 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ See the `Makefile` for compilation and installation options.
runtime dependencies are the kernel and filesystem support for encryption. In
most cases that means the kernel must have been built `CONFIG_FS_ENCRYPTION=y`,
and a command like `tune2fs -O encrypt` must have been run on the filesystem.

Since v1.0, `fscryptctl` only supports v2 filesystem encryption policies. This
means that it must be used with Linux kernel 5.4 or later. If you need support
for v1 encryption policies, use an earlier version of `fscryptctl`. However, be
aware that v1 had some significant usability and security limitations.

For more information about the kernel and filesystem prerequisites, see the
[`fscrypt`
documentation](https://github.com/google/fscrypt#runtime-dependencies),
Expand All @@ -58,37 +64,18 @@ tips](https://github.com/google/fscrypt#getting-encryption-not-enabled-on-an-ext
* `fscryptctl get_policy` - get the encryption policy of a file or directory
* `fscryptctl set_policy` - set the encryption policy of an empty directory

There are also two deprecated commands:

* `fscryptctl insert_key` - add a v1 policy key to the session keyring
* `fscryptctl get_descriptor` - compute key descriptor for a v1 policy key

Run `fscryptctl --help` for full usage details.

The `add_key` and `insert_key` commands accept the encryption key in binary on
standard input. It is critical that this be a real cryptographic key (and not a
passphrase, for example), since `fscryptctl` doesn't do key stretching itself.
Obviously, don't store the raw encryption key alongside the encrypted files.
(If you need support for passphrases, use `fscrypt` instead of `fscryptctl`.)

`fscryptctl` supports both v1 and v2 encryption policies. (An "encryption
policy" refers to the way in which a directory is encrypted: a reference to a
key, plus the encryption options.) v2 encryption policies are supported by
kernel 5.4 and later, and they should be used whenever possible, since they have
various security and usability improvements over v1. See the [kernel
documentation](https://www.kernel.org/doc/html/latest/filesystems/fscrypt.html#limitations-of-v1-policies)
for more details.

From the `fscryptctl` user's perspective, v1 and v2 policies differ primarily in
how encryption keys are managed. Keys for v1 policies are placed into the Linux
session keyring by `fscryptctl insert_key` and are identified by 16-character
"key descriptors". Keys for v2 policies are placed in a filesystem keyring
using `fscryptctl add_key` and are identified by 32-character "key identifiers".

`fscryptctl set_policy` accepts either a key descriptor, in which case it sets a
v1 policy, or a key identifier, in which case it sets a v2 policy. So
effectively, `fscryptctl set_policy` will set a v1 policy if `fscryptctl
insert_key` was used, or a v2 policy if `fscryptctl add_key` was used.
The `add_key` command accepts the encryption key in binary on standard input.
It is critical that this be a real cryptographic key (and not a passphrase, for
example), since `fscryptctl` doesn't do key stretching itself. Obviously, don't
store the raw encryption key alongside the encrypted files. (If you need
support for passphrases, use `fscrypt` instead of `fscryptctl`.)

After running the `add_key` command to add an encryption key to a filesystem,
you can use the `set_policy` command to create an encrypted directory on that
filesystem. The encryption key is specified by the 32-character hex "key
identifier" that was printed by `add_key`. The directory must be empty.

## Example Usage

Expand Down
232 changes: 31 additions & 201 deletions fscryptctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,22 @@
#include <unistd.h>

#include "fscrypt_uapi.h"
#include "keyutils.h"
#include "sha512.h"

#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))

static void secure_wipe(void *v, size_t n) {
#ifdef memset_s
memset_s(v, n, 0, n);
#elif defined(explicit_bzero)
explicit_bzero(v, n);
#else
volatile uint8_t *p = v;
while (n--) {
*p++ = 0;
}
#endif
}

// Although the kernel always allows 64-byte keys, it may allow shorter keys
// too, depending on the encryption mode(s) used. The shortest key the kernel
// can ever accept is 16 bytes, which occurs when AES-128-CBC contents
Expand Down Expand Up @@ -89,16 +100,9 @@ static void __attribute__((__noreturn__)) usage(FILE *out) {
" specified mounted filesystem.\n"
" fscryptctl get_policy <file or directory>\n"
" Print out the encryption policy for the specified path.\n"
" fscryptctl set_policy <key identifier or descriptor> <directory>\n"
" fscryptctl set_policy <key identifier> <directory>\n"
" Set up an encryption policy on the specified directory with the\n"
" specified key identifier or descriptor.\n"
"\nDeprecated commands (for v1 encryption policies):\n"
" fscryptctl get_descriptor\n"
" Read a key from stdin, and print the hex descriptor of the key.\n"
" fscryptctl insert_key\n"
" Read a key from stdin, insert the key into the current session\n"
" keyring (or the user session keyring if a session keyring does not\n"
" exist), and print the descriptor of the key.\n"
" specified key identifier.\n"
"\nOptions:\n"
" -h, --help\n"
" print this help screen\n"
Expand All @@ -120,19 +124,9 @@ static void __attribute__((__noreturn__)) usage(FILE *out) {
" optimize for UFS inline crypto hardware\n"
" --iv-ino-lblk-32\n"
" optimize for eMMC inline crypto hardware (not recommended)\n"
" insert_key (deprecated)\n"
" --ext4\n"
" for use with an ext4 filesystem before kernel v4.8\n"
" --f2fs\n"
" for use with an F2FS filesystem before kernel v4.6\n"
"\nNotes:\n"
" On recent kernels (5.4 and later), use add_key and set_policy.\n"
" In this case, keys are identified by 32-character hex strings\n"
" (\"key identifiers\").\n"
"\n"
" On older kernels, use insert_key and set_policy to set a v1\n"
" encryption policy. In this case, keys are described by 16-character\n"
" hex strings (\"key descriptors\").\n"
" Keys are identified by 32-character hex strings (\"key "
"identifiers\").\n"
"\n"
" Raw keys are given on stdin in binary and usually must be 64 bytes.\n",
out);
Expand Down Expand Up @@ -339,11 +333,6 @@ static bool get_policy(const char *path,

arg->policy_size = sizeof(arg->policy);
int ret = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY_EX, arg);
if (ret != 0 && errno == ENOTTY) {
// The kernel may be too old to support FS_IOC_GET_ENCRYPTION_POLICY_EX.
// Try FS_IOC_GET_ENCRYPTION_POLICY instead.
ret = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, arg->policy.v1);
}
close(fd);

if (ret != 0) {
Expand All @@ -354,14 +343,8 @@ static bool get_policy(const char *path,
return true;
}

#undef fscrypt_policy
union fscrypt_policy {
uint8_t version;
struct fscrypt_policy_v1 v1;
struct fscrypt_policy_v2 v2;
};

static bool set_policy(const char *path, const union fscrypt_policy *policy) {
static bool set_policy(const char *path,
const struct fscrypt_policy_v2 *policy) {
int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
fprintf(stderr, "error: opening %s: %s\n", path, strerror(errno));
Expand Down Expand Up @@ -685,40 +668,19 @@ static int cmd_set_policy(int argc, char *const argv[]) {
fputs("error: must specify a key and a directory\n", stderr);
return EXIT_FAILURE;
}
const char *key_specifier = argv[0];
const char *key_identifier = argv[0];
const char *path = argv[1];

// Initialize the encryption policy struct. Determine the policy version by
// the length of the key specifier. v1 uses a key descriptor of 8 bytes (16
// hex chars). v2 uses a key identifier of 16 bytes (32 hex chars).
union fscrypt_policy policy = {};
switch (strlen(key_specifier) + 1 /* count the null terminator */) {
case FSCRYPT_KEY_DESCRIPTOR_HEX_SIZE:
policy.version = FSCRYPT_POLICY_V1;
if (!hex_to_bytes(key_specifier, policy.v1.master_key_descriptor,
FSCRYPT_KEY_DESCRIPTOR_SIZE)) {
fprintf(stderr, "error: invalid key descriptor: %s\n", key_specifier);
return EXIT_FAILURE;
}
policy.v1.contents_encryption_mode = contents_encryption_mode;
policy.v1.filenames_encryption_mode = filenames_encryption_mode;
policy.v1.flags = flags;
break;
case FSCRYPT_KEY_IDENTIFIER_HEX_SIZE:
policy.version = FSCRYPT_POLICY_V2;
if (!hex_to_bytes(key_specifier, policy.v2.master_key_identifier,
FSCRYPT_KEY_IDENTIFIER_SIZE)) {
fprintf(stderr, "error: invalid key identifier: %s\n", key_specifier);
return EXIT_FAILURE;
}
policy.v2.contents_encryption_mode = contents_encryption_mode;
policy.v2.filenames_encryption_mode = filenames_encryption_mode;
policy.v2.flags = flags;
break;
default:
fprintf(stderr, "error: invalid key specifier: %s\n", key_specifier);
return EXIT_FAILURE;
// Initialize the encryption policy struct.
struct fscrypt_policy_v2 policy = {.version = FSCRYPT_POLICY_V2};
if (!hex_to_bytes(key_identifier, policy.master_key_identifier,
FSCRYPT_KEY_IDENTIFIER_SIZE)) {
fprintf(stderr, "error: invalid key identifier: %s\n", key_identifier);
return EXIT_FAILURE;
}
policy.contents_encryption_mode = contents_encryption_mode;
policy.filenames_encryption_mode = filenames_encryption_mode;
policy.flags = flags;

// Set the encryption policy on the directory.
if (!set_policy(path, &policy)) {
Expand All @@ -728,133 +690,6 @@ static int cmd_set_policy(int argc, char *const argv[]) {
return EXIT_SUCCESS;
}

// -----------------------------------------------------------------------------
// Deprecated commands
// -----------------------------------------------------------------------------

// Service prefixes for encryption keys
#define EXT4_KEY_DESC_PREFIX "ext4:" // For ext4 before 4.8 kernel
#define F2FS_KEY_DESC_PREFIX "f2fs:" // For f2fs before 4.6 kernel
#define MAX_KEY_DESC_PREFIX_SIZE 8

// The descriptor is just the first 8 bytes of a double application of SHA512
// formatted as hex (so 16 characters).
static void compute_descriptor(
const uint8_t *raw_key, size_t keysize,
char descriptor_hex[FSCRYPT_KEY_DESCRIPTOR_HEX_SIZE]) {
uint8_t digest1[SHA512_DIGEST_LENGTH];
SHA512(raw_key, keysize, digest1);

uint8_t digest2[SHA512_DIGEST_LENGTH];
SHA512(digest1, SHA512_DIGEST_LENGTH, digest2);

bytes_to_hex(digest2, FSCRYPT_KEY_DESCRIPTOR_SIZE, descriptor_hex);
secure_wipe(digest1, SHA512_DIGEST_LENGTH);
secure_wipe(digest2, SHA512_DIGEST_LENGTH);
}

// Inserts the key into the current session keyring with type logon and the
// service specified by service_prefix.
static bool insert_logon_key(
const uint8_t *raw_key, size_t keysize,
const char descriptor_hex[FSCRYPT_KEY_DESCRIPTOR_HEX_SIZE],
const char *service_prefix) {
// We cannot add directly to KEY_SPEC_SESSION_KEYRING, as that will make a new
// session keyring if one does not exist, rather than adding it to the user
// session keyring.
int keyring_id = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
if (keyring_id < 0) {
return false;
}

char description[MAX_KEY_DESC_PREFIX_SIZE + FSCRYPT_KEY_DESCRIPTOR_HEX_SIZE];
sprintf(description, "%s%s", service_prefix, descriptor_hex);

struct fscrypt_key payload = {.size = keysize};
memcpy(payload.raw, raw_key, keysize);

int key_id =
add_key("logon", description, &payload, sizeof(payload), keyring_id);

secure_wipe(payload.raw, keysize);
return key_id >= 0;
}

// (Deprecated) Computes the descriptor for a key passed via stdin. This only
// supports v1 encryption policies, not v2.
static int cmd_get_descriptor(int argc, char *const argv[]) {
handle_no_options(&argc, &argv);
if (argc != 0) {
fputs("error: unexpected arguments\n", stderr);
return EXIT_FAILURE;
}

int status = EXIT_FAILURE;
uint8_t raw_key[FSCRYPT_MAX_KEY_SIZE];
size_t keysize = read_key(raw_key);
if (keysize == 0) {
goto cleanup;
}

char descriptor_hex[FSCRYPT_KEY_DESCRIPTOR_HEX_SIZE];
compute_descriptor(raw_key, keysize, descriptor_hex);
puts(descriptor_hex);
status = EXIT_SUCCESS;
cleanup:
secure_wipe(raw_key, keysize);
return status;
}

// (Deprecated) Inserts a key read from stdin into the current session keyring.
// This has the effect of unlocking files encrypted with that key. This only
// works for v1 encryption policies, not v2. Use add_key for v2.
static int cmd_insert_key(int argc, char *const argv[]) {
// Which prefix will be used in this program, changed via command line flag.
const char *service_prefix = FSCRYPT_KEY_DESC_PREFIX;

static const struct option insert_key_options[] = {
{"ext4", no_argument, NULL, 'e'},
{"f2fs", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}};

int ch;
while ((ch = getopt_long(argc, argv, "", insert_key_options, NULL)) != -1) {
switch (ch) {
case 'e':
service_prefix = EXT4_KEY_DESC_PREFIX;
break;
case 'f':
service_prefix = F2FS_KEY_DESC_PREFIX;
break;
default:
usage(stderr);
}
}
if (argc != optind) {
fputs("error: unexpected arguments\n", stderr);
return EXIT_FAILURE;
}

int status = EXIT_FAILURE;
uint8_t raw_key[FSCRYPT_MAX_KEY_SIZE];
size_t keysize = read_key(raw_key);
if (keysize == 0) {
goto cleanup;
}

char descriptor_hex[FSCRYPT_KEY_DESCRIPTOR_HEX_SIZE];
compute_descriptor(raw_key, keysize, descriptor_hex);
if (!insert_logon_key(raw_key, keysize, descriptor_hex, service_prefix)) {
fprintf(stderr, "error: inserting key: %s\n", strerror(errno));
goto cleanup;
}
puts(descriptor_hex);
status = EXIT_SUCCESS;
cleanup:
secure_wipe(raw_key, keysize);
return status;
}

// -----------------------------------------------------------------------------
// The main() function
// -----------------------------------------------------------------------------
Expand All @@ -863,14 +698,9 @@ static const struct {
const char *name;
int (*func)(int argc, char *const argv[]);
} commands[] = {
{"add_key", cmd_add_key},
{"remove_key", cmd_remove_key},
{"key_status", cmd_key_status},
{"get_policy", cmd_get_policy},
{"add_key", cmd_add_key}, {"remove_key", cmd_remove_key},
{"key_status", cmd_key_status}, {"get_policy", cmd_get_policy},
{"set_policy", cmd_set_policy},
// deprecated commands
{"get_descriptor", cmd_get_descriptor},
{"insert_key", cmd_insert_key},
};

int main(int argc, char *const argv[]) {
Expand Down

0 comments on commit 2abb15b

Please sign in to comment.