/* * e4crypt.c - ext4 encryption management utility * * Copyright (c) 2014 Google, Inc. * SHA512 implementation from libtomcrypt. * * Authors: Michael Halcrow , * Ildar Muslukhov */ #ifndef _LARGEFILE_SOURCE #define _LARGEFILE_SOURCE #endif #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ext2fs/ext2_fs.h" /* special process keyring shortcut IDs */ #define KEY_SPEC_THREAD_KEYRING -1 #define KEY_SPEC_PROCESS_KEYRING -2 #define KEY_SPEC_SESSION_KEYRING -3 #define KEY_SPEC_USER_KEYRING -4 #define KEY_SPEC_USER_SESSION_KEYRING -5 #define KEY_SPEC_GROUP_KEYRING -6 #define KEYCTL_GET_KEYRING_ID 0 #define KEYCTL_DESCRIBE 6 #define KEYCTL_SEARCH 10 typedef __s32 key_serial_t; #define EXT4_KEY_REF_STR_BUF_SIZE ((EXT4_KEY_DESCRIPTOR_SIZE * 2) + 1) static long keyctl(int cmd, ...) { va_list va; unsigned long arg2, arg3, arg4, arg5; va_start(va, cmd); arg2 = va_arg(va, unsigned long); arg3 = va_arg(va, unsigned long); arg4 = va_arg(va, unsigned long); arg5 = va_arg(va, unsigned long); va_end(va); return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); } static const char *hexchars = "0123456789abcdef"; static const size_t hexchars_size = 16; #define SHA512_LENGTH 64 #define EXT2FS_KEY_TYPE_LOGON "logon" #define EXT2FS_KEY_DESC_PREFIX "ext4:" #define EXT2FS_KEY_DESC_PREFIX_SIZE 5 #define MSG_USAGE \ "Usage:\te4crypt -a -n salt [ -k keyring ] [ path ... ]\n" \ "\te4crypt -s policy path ...\n" #define EXT4_IOC_ENCRYPTION_POLICY _IOW('f', 19, struct ext4_encryption_policy) static int is_path_valid(int argc, char *argv[], int path_start_index) { int x; int valid = 1; if (path_start_index == argc) { printf("At least one path option must be provided.\n"); return 0; } for (x = path_start_index; x < argc; x++) { int ret = access(argv[x], W_OK); if (ret) { printf("%s: %s\n", strerror(errno), argv[x]); valid = 0; } } return valid; } static int hex2byte(const char *hex, size_t hex_size, char *bytes, size_t bytes_size) { int x; char *h, *l; if (hex_size % 2) return -EINVAL; for (x = 0; x < hex_size; x += 2) { h = memchr(hexchars, hex[x], hexchars_size); if (!h) return -EINVAL; l = memchr(hexchars, hex[x + 1], hexchars_size); if (!l) return -EINVAL; if ((x >> 1) >= bytes_size) return -EINVAL; bytes[x >> 1] = (((unsigned char)(h - hexchars) << 4) + (unsigned char)(l - hexchars)); } return 0; } static void set_policy(const char key_descriptor[EXT4_KEY_REF_STR_BUF_SIZE], int argc, char *argv[], int path_start_index) { struct stat st; struct ext4_encryption_policy policy; int fd; int x; int rc; if (!is_path_valid(argc, argv, path_start_index)) { printf("Invalid path.\n"); exit(1); } if (!key_descriptor || (strlen(key_descriptor) != (EXT4_KEY_DESCRIPTOR_SIZE * 2))) { printf("Invalid key descriptor [%s]. Valid characters are " "0-9 and a-f, lower case. Length must be %d.\n", key_descriptor, (EXT4_KEY_DESCRIPTOR_SIZE * 2)); exit(1); } for (x = path_start_index; x < argc; x++) { stat(argv[x], &st); if (!S_ISDIR(st.st_mode)) { printf("You may only set policy on directories.\n"); exit(1); } policy.version = 0; policy.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS; policy.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CBC; if (hex2byte(key_descriptor, (EXT4_KEY_DESCRIPTOR_SIZE * 2), policy.master_key_descriptor, EXT4_KEY_DESCRIPTOR_SIZE)) { printf("Invalid key descriptor [%s]. Valid characters " "are 0-9 and a-f, lower case.\n", key_descriptor); exit(1); } fd = open(argv[x], O_DIRECTORY); if (fd == -1) { printf("Cannot open directory [%s]: [%s].\n", argv[x], strerror(errno)); exit(1); } rc = ioctl(fd, EXT4_IOC_ENCRYPTION_POLICY, &policy); close(fd); if (rc) { printf("Error [%s] setting policy.\nThe key descriptor " "[%s] may not match the existing encryption " "context for directory [%s].\n", strerror(errno), key_descriptor, argv[x]); exit(1); } printf("Key with descriptor [%s%s] successfully applied " "to directory [%s].\n", EXT2FS_KEY_DESC_PREFIX, key_descriptor, argv[x]); } } static void pbkdf2_sha512(const char *passphrase, const char *salt, int count, char derived_key[EXT4_MAX_KEY_SIZE]) { size_t salt_size = strlen(salt); size_t passphrase_size = strlen(passphrase); char buf[SHA512_LENGTH + EXT4_MAX_PASSPHRASE_SIZE] = {0}; char tempbuf[SHA512_LENGTH] = {0}; char final[SHA512_LENGTH] = {0}; char saltbuf[EXT4_MAX_SALT_SIZE + EXT4_MAX_PASSPHRASE_SIZE] = {0}; int actual_buf_len = SHA512_LENGTH + passphrase_size; int actual_saltbuf_len = EXT4_MAX_SALT_SIZE + passphrase_size; int x, y; __u32 *final_u32 = (__u32 *)final; __u32 *temp_u32 = (__u32 *)tempbuf; if (passphrase_size > EXT4_MAX_PASSPHRASE_SIZE) { printf("Salt size is %d; max is %d.\n", passphrase_size, EXT4_MAX_PASSPHRASE_SIZE); exit(1); } if (salt_size > EXT4_MAX_SALT_SIZE) { printf("Salt size is %d; max is %d.\n", salt_size, EXT4_MAX_SALT_SIZE); exit(1); } assert(EXT4_MAX_KEY_SIZE <= SHA512_LENGTH); if (hex2byte(salt, strlen(salt), saltbuf, sizeof(saltbuf))) { printf("Invalid salt hex value: [%s]. Valid characters are " "0-9 and a-f, lower case.\n", salt); exit(1); } memcpy(&saltbuf[EXT4_MAX_SALT_SIZE], passphrase, passphrase_size); memcpy(&buf[SHA512_LENGTH], passphrase, passphrase_size); for (x = 0; x < count; ++x) { if (x == 0) { ext2fs_sha512(saltbuf, actual_saltbuf_len, tempbuf); } else { /* * buf: [previous hash || passphrase] */ memcpy(buf, tempbuf, SHA512_LENGTH); ext2fs_sha512(buf, actual_buf_len, tempbuf); } for (y = 0; y < (sizeof(final) / sizeof(*final_u32)); ++y) final_u32[y] = final_u32[y] ^ temp_u32[y]; } memcpy(derived_key, final, EXT4_MAX_KEY_SIZE); } static int disable_echo(struct termios *saved_settings) { struct termios current_settings; int rc = 0; rc = tcgetattr(0, ¤t_settings); if (rc) return rc; *saved_settings = current_settings; current_settings.c_lflag &= ~ECHO; rc = tcsetattr(0, TCSANOW, ¤t_settings); return rc; } struct keyring_map { char name[4]; size_t name_len; int code; }; static const struct keyring_map keyrings[] = { {"@us", 3, KEY_SPEC_USER_SESSION_KEYRING}, {"@u", 2, KEY_SPEC_USER_KEYRING}, {"@s", 2, KEY_SPEC_SESSION_KEYRING}, {"@g", 2, KEY_SPEC_GROUP_KEYRING}, {"@p", 2, KEY_SPEC_PROCESS_KEYRING}, {"@t", 2, KEY_SPEC_THREAD_KEYRING}, }; static int get_keyring_id(const char *keyring) { int x; char *end; /* * If no keyring is specified, by default use either the user * session key ring or the session keyring. Fetching the * session keyring will return the user session keyring if no * session keyring has been set. * * We need to do this instead of simply adding the key to * KEY_SPEC_SESSION_KEYRING since trying to add a key to a * session keyring that does not yet exist will cause the * kernel to create a session keyring --- which wil then get * garbage collected as soon as e4crypt exits. * * The fact that the keyctl system call and the add_key system * call treats KEY_SPEC_SESSION_KEYRING differently when a * session keyring does not exist is very unfortunate and * confusing, but so it goes... */ if (keyring == NULL) return keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 0); for (x = 0; x < (sizeof(keyrings) / sizeof(keyrings[0])); ++x) { if (strcmp(keyring, keyrings[x].name) == 0) { return keyrings[x].code; } } x = strtol(keyring, &end, 10); if (*end == '\0') { if (keyctl(KEYCTL_DESCRIBE, x, NULL, 0) < 0) return 0; return x; } return 0; } static void insert_key_into_keyring( const char *keyring, const char raw_key[EXT4_MAX_KEY_SIZE], const char key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE]) { int keyring_id = get_keyring_id(keyring); struct ext4_encryption_key key; char key_ref_full[EXT2FS_KEY_DESC_PREFIX_SIZE + EXT4_KEY_REF_STR_BUF_SIZE]; int rc; if (keyring_id == 0) { printf("Invalid keyring [%s].\n", keyring); exit(1); } strcpy(key_ref_full, EXT2FS_KEY_DESC_PREFIX); strcpy(&key_ref_full[EXT2FS_KEY_DESC_PREFIX_SIZE], key_ref_str); rc = keyctl(KEYCTL_SEARCH, keyring_id, EXT2FS_KEY_TYPE_LOGON, key_ref_full, 0); if (rc != -1) { printf("Key with descriptor [%s] already exists\n", key_ref_str); exit(1); } else if ((rc == -1) && (errno != ENOKEY)) { printf("keyctl_search failed: %s\n", strerror(errno)); if (errno == -EINVAL) printf("Keyring [%s] is not available.\n", keyring); exit(1); } key.mode = EXT4_ENCRYPTION_MODE_AES_256_XTS; memcpy(key.raw, raw_key, EXT4_MAX_KEY_SIZE); key.size = EXT4_MAX_KEY_SIZE; rc = syscall(__NR_add_key, EXT2FS_KEY_TYPE_LOGON, key_ref_full, (void *)&key, sizeof(key), keyring_id); if (rc == -1) { if (errno == EDQUOT) { printf("Error adding key to keyring; quota exceeded\n"); } else { printf("Error adding key with key descriptor [%s]: " "%s\n", key_ref_str, strerror(errno)); } exit(1); } else { printf("Key with descriptor [%s] successfully inserted into " "keyring\n", key_ref_str); } } static void generate_key_ref_str_from_raw_key( const char raw_key[EXT4_MAX_KEY_SIZE], char key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE]) { char key_ref1[SHA512_LENGTH]; char key_ref2[SHA512_LENGTH]; int x; ext2fs_sha512(raw_key, EXT4_MAX_KEY_SIZE, key_ref1); ext2fs_sha512(key_ref1, SHA512_LENGTH, key_ref2); for (x = 0; x < EXT4_KEY_DESCRIPTOR_SIZE; ++x) { sprintf(&key_ref_str[x * 2], "%.2x", (unsigned char)key_ref2[x]); } key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE - 1] = '\0'; } static void insert_passphrase_into_keyring( const char *keyring, const char *salt, char key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE]) { char *p; char raw_key[EXT4_MAX_KEY_SIZE]; char passphrase[EXT4_MAX_PASSPHRASE_SIZE]; struct termios current_settings; if (!salt) { printf("Please provide a salt.\n"); exit(1); } printf("Enter passphrase (echo disabled): "); disable_echo(¤t_settings); p = fgets(passphrase, sizeof(passphrase), stdin); tcsetattr(0, TCSANOW, ¤t_settings); printf("\n"); if (!p) { printf("Aborting.\n"); exit(1); } p = strrchr(passphrase, '\n'); if (p) *p = '\0'; pbkdf2_sha512(passphrase, salt, EXT4_PBKDF2_ITERATIONS, raw_key); generate_key_ref_str_from_raw_key(raw_key, key_ref_str); insert_key_into_keyring(keyring, raw_key, key_ref_str); memset(passphrase, 0, sizeof(passphrase)); memset(raw_key, 0, sizeof(raw_key)); } static int is_keyring_valid(const char *keyring) { return (get_keyring_id(keyring) != 0); } static void process_passphrase(const char *keyring, const char *salt, int argc, char *argv[], int path_start_index) { char key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE]; if (!is_keyring_valid(keyring)) { printf("Invalid keyring name [%s]. Consult keyctl " "documentation for valid names.\n", keyring); exit(1); } insert_passphrase_into_keyring(keyring, salt, key_ref_str); if (path_start_index != argc) set_policy(key_ref_str, argc, argv, path_start_index); } int main(int argc, char *argv[]) { char *key_ref_str = NULL; char *keyring = NULL; char *salt = NULL; int add_passphrase = 0; int opt; if (argc == 1) goto fail; while ((opt = getopt(argc, argv, "ak:s:n:")) != -1) { switch (opt) { case 'k': /* Specify a keyring. */ keyring = optarg; break; case 'a': /* Add passphrase-based key to keyring. */ add_passphrase = 1; break; case 's': /* Set policy on a directory. */ key_ref_str = optarg; break; case 'n': /* Salt value for passphrase. */ salt = optarg; break; default: printf("Unrecognized option: %c\n", opt); goto fail; } } if (key_ref_str) { if (add_passphrase) { printf("-s option invalid with -a\n"); goto fail; } if (keyring) { printf("-s option invalid with -k\n"); goto fail; } if (salt) { printf("-s option invalid with -n\n"); goto fail; } set_policy(key_ref_str, argc, argv, optind); exit(0); } if (add_passphrase) { if (!salt) { printf("-a option requires -n\n"); goto fail; } if (key_ref_str) { printf("-a option invalid with -s\n"); goto fail; } process_passphrase(keyring, salt, argc, argv, optind); exit(0); } fail: printf(MSG_USAGE); return 1; }