Whamcloud - gitweb
misc: add e4crypt tool
authorIldar Muslukhov <muslukhovi@gmail.com>
Sat, 7 Feb 2015 00:30:11 +0000 (16:30 -0800)
committerTheodore Ts'o <tytso@mit.edu>
Thu, 26 Mar 2015 13:30:03 +0000 (09:30 -0400)
This patch adds new e4crypt tool for encryption management in the ext4
filesystem.

Signed-off-by: Ildar Muslukhov <muslukhovi@gmail.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
lib/ext2fs/ext2_fs.h
misc/Makefile.in
misc/e4crypt.8.in [new file with mode: 0644]
misc/e4crypt.c [new file with mode: 0644]

index 6715d4e..0109551 100644 (file)
@@ -565,12 +565,41 @@ struct ext2_inode_large {
 /* Metadata checksum algorithms */
 #define EXT2_CRC32C_CHKSUM             1
 
-/* Encryption algorithms */
+/* Encryption algorithms, key size and key reference len */
 #define EXT4_ENCRYPTION_MODE_INVALID           0
 #define EXT4_ENCRYPTION_MODE_AES_256_XTS       1
 #define EXT4_ENCRYPTION_MODE_AES_256_GCM       2
 #define EXT4_ENCRYPTION_MODE_AES_256_CBC       3
 
+#define EXT4_AES_256_XTS_KEY_SIZE              64
+#define EXT4_AES_256_GCM_KEY_SIZE              32
+#define EXT4_AES_256_CBC_KEY_SIZE              32
+#define EXT4_MAX_KEY_SIZE                      64
+
+#define EXT4_KEY_DESCRIPTOR_SIZE               8
+
+/* Password derivation constants */
+#define EXT4_MAX_PASSPHRASE_SIZE               1024
+#define EXT4_MAX_SALT_SIZE                     256
+#define EXT4_PBKDF2_ITERATIONS                 0xFFFF
+
+/*
+ * Policy provided via an ioctl on the topmost directory. This
+ * structure is also in the kernel.
+ */
+struct ext4_encryption_policy {
+  char version;
+  char contents_encryption_mode;
+  char filenames_encryption_mode;
+  char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];
+} __attribute__((__packed__));
+
+struct ext4_encryption_key {
+        __u32 mode;
+        char raw[EXT4_MAX_KEY_SIZE];
+        __u32 size;
+} __attribute__((__packed__));
+
 /*
  * Structure of the super block
  */
index dbe524a..5646685 100644 (file)
@@ -14,6 +14,9 @@ INSTALL = @INSTALL@
 @DEFRAG_CMT@@LINUX_CMT@E4DEFRAG_PROG= e4defrag
 @DEFRAG_CMT@@LINUX_CMT@E4DEFRAG_MAN= e4defrag.8
 
+@LINUX_CMT@E4CRYPT_PROG = e4crypt
+@LINUX_CMT@E4CRYPT_MAN= e4crypt.8
+
 @IMAGER_CMT@E2IMAGE_PROG= e2image
 @IMAGER_CMT@E2IMAGE_MAN= e2image.8
 
@@ -29,11 +32,12 @@ INSTALL = @INSTALL@
 
 SPROGS=                mke2fs badblocks tune2fs dumpe2fs $(BLKID_PROG) logsave \
                        $(E2IMAGE_PROG) @FSCK_PROG@ e2undo
-USPROGS=       mklost+found filefrag e2freefrag $(UUIDD_PROG) $(E4DEFRAG_PROG)
+USPROGS=       mklost+found filefrag e2freefrag $(UUIDD_PROG) \
+                       $(E4DEFRAG_PROG) $(E4CRYPT_PROG)
 SMANPAGES=     tune2fs.8 mklost+found.8 mke2fs.8 dumpe2fs.8 badblocks.8 \
                        e2label.8 $(FINDFS_MAN) $(BLKID_MAN) $(E2IMAGE_MAN) \
                        logsave.8 filefrag.8 e2freefrag.8 e2undo.8 \
-                       $(UUIDD_MAN) $(E4DEFRAG_MAN) @FSCK_MAN@
+                       $(UUIDD_MAN) $(E4DEFRAG_MAN) $(E4CRYPT_MAN) @FSCK_MAN@
 FMANPAGES=     mke2fs.conf.5 ext4.5
 
 UPROGS=                chattr lsattr @UUID_CMT@ uuidgen
@@ -57,6 +61,7 @@ BLKID_OBJS=   blkid.o
 FILEFRAG_OBJS= filefrag.o
 E2UNDO_OBJS=  e2undo.o
 E4DEFRAG_OBJS= e4defrag.o
+E4CRYPT_OBJS=   e4crypt.o
 E2FREEFRAG_OBJS= e2freefrag.o
 E2FUZZ_OBJS=   e2fuzz.o
 
@@ -81,6 +86,7 @@ PROFILED_FILEFRAG_OBJS=       profiled/filefrag.o
 PROFILED_E2FREEFRAG_OBJS= profiled/e2freefrag.o
 PROFILED_E2UNDO_OBJS=  profiled/e2undo.o
 PROFILED_E4DEFRAG_OBJS=        profiled/e4defrag.o
+PROFILED_E4CRYPT_OBJS= profiled/e4crypt.o
 
 SRCS=  $(srcdir)/tune2fs.c $(srcdir)/mklost+found.c $(srcdir)/mke2fs.c $(srcdir)/mk_hugefiles.c \
                $(srcdir)/chattr.c $(srcdir)/lsattr.c $(srcdir)/dumpe2fs.c \
@@ -112,12 +118,13 @@ COMPILE_ET=$(top_builddir)/lib/et/compile_et --build-tree
 @PROFILE_CMT@  $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $<
 
 all:: profiled $(SPROGS) $(UPROGS) $(USPROGS) $(SMANPAGES) $(UMANPAGES) \
-       $(FMANPAGES) $(LPROGS) $(E4DEFRAG_PROG) e2fuzz
+       $(FMANPAGES) $(LPROGS) $(E4DEFRAG_PROG) $(E4CRYPT_PROGS) e2fuzz
 
 @PROFILE_CMT@all:: tune2fs.profiled blkid.profiled e2image.profiled \
        e2undo.profiled mke2fs.profiled dumpe2fs.profiled fsck.profiled \
        logsave.profiled filefrag.profiled uuidgen.profiled $(UUIDD_PROFILED) \
-       e2image.profiled e4defrag.profiled e2freefrag.profiled
+       e2image.profiled e4defrag.profiled e4crypt.profiled \
+       e2freefrag.profiled
 
 profiled:
 @PROFILE_CMT@  $(E) "  MKDIR $@"
@@ -227,11 +234,20 @@ e4defrag: $(E4DEFRAG_OBJS) $(DEPLIBS)
        $(Q) $(CC) $(ALL_LDFLAGS) -o e4defrag $(E4DEFRAG_OBJS) $(LIBS) \
                $(SYSLIBS)
 
+e4crypt: $(E4CRYPT_OBJS) $(DEPLIBS)
+       $(E) "  LD $@"
+       $(Q) $(CC) $(ALL_LDFLAGS) -o e4crypt $(E4CRYPT_OBJS) $(STATIC_LIBS)
+
 e4defrag.profiled: $(E4DEFRAG_OBJS) $(PROFILED_DEPLIBS)
        $(E) "  LD $@"
        $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o e4defrag.profiled \
                $(PROFILED_E4DEFRAG_OBJS) $(PROFILED_LIBS) $(SYSLIBS)
 
+e4crypt.profiled: $(E4CRYPT_OBJS) $(PROFILED_DEPLIBS)
+       $(E) "  LD $@"
+       $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o e4crypt.profiled \
+               $(PROFILED_E4CRYPT_OBJS) $(PROFILED_LIBS) $(SYSLIBS)
+
 base_device: base_device.c
        $(E) "  LD $@"
        $(Q) $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) $(srcdir)/base_device.c \
@@ -415,6 +431,10 @@ e4defrag.8: $(DEP_SUBSTITUTE) $(srcdir)/e4defrag.8.in
        $(E) "  SUBST $@"
        $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e4defrag.8.in e4defrag.8
 
+e4crypt.8: $(DEP_SUBSTITUTE) $(srcdir)/e4crypt.8.in
+       $(E) "  SUBST $@"
+       $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e4crypt.8.in e4crypt.8
+
 dumpe2fs.8: $(DEP_SUBSTITUTE) $(srcdir)/dumpe2fs.8.in 
        $(E) "  SUBST $@"
        $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/dumpe2fs.8.in dumpe2fs.8
diff --git a/misc/e4crypt.8.in b/misc/e4crypt.8.in
new file mode 100644 (file)
index 0000000..c3246a2
--- /dev/null
@@ -0,0 +1,59 @@
+.TH E4CRYPT 8 "Mar 2015" "e4crypt version 1"
+.SH NAME
+e4crypt \- ext4 filesystem encryption utility
+.SH SYNOPSIS
+.B e4crypt \-a \-n
+.I salt
+[
+.B \-k
+.I keyring
+]
+[
+.I path\fR ...
+]
+.br
+.B e4crypt \-s
+.I policy
+.I path\fR ...
+.SH DESCRIPTION
+.B e4crypt
+performs encryption management for ext4 file systems.
+.SH OPTIONS
+.TP
+.B \-a
+Prompts the user for a passphrase and transforms it into an ecryption
+key for use by ext4.   The encryption key will be added to the specified
+keyring, with a type "logon" (which makes the key accessible to the
+user, but which does not allow the key to be returned to userspace), and
+with the descrpiptor of the key set to "ext4:" followed the encryption
+key identifer, which is composed of 16 hexadecimal characters calculated
+by taking a cryptographic hash of the key.
+.TP
+.B \-k
+.I keyring
+This option specifies the keyring to which the key will be added.  See
+the keyctl man page for more details, but the only keyring which only
+makes sence is @u, @s, and @us, which specifies the user, session, and
+user session keyrings, respectively.   By default,
+.B e4crypt
+will add the key to the session keyring if it has been establishd for
+the current process, or the user session keyring if it has not.
+.TP
+.B \-n
+.I salt
+The salt must be specified, and it should be unique for each
+passphrase.  The salt consists of up to 256 hexadecimal bytes.
+.TP
+.B \-s
+Sets a policy for the directories specified on the command line.
+All directories must be empty to set the policy; if the directory
+already has a policy established,
+.B e4crypt
+will validate that the policy is what was specified.  A policy is an
+encryption key identifier of length 16 hexadecimal characters.
+.SH AUTHOR
+Written by Michael Halcrow <mhalcrow@google.com> and Ildar Muslukhov
+<muslukhovi@gmail.com>.
+.SH SEE ALSO
+.BR mke2fs (8),
+.BR mount (8).
diff --git a/misc/e4crypt.c b/misc/e4crypt.c
new file mode 100644 (file)
index 0000000..7221180
--- /dev/null
@@ -0,0 +1,494 @@
+/*
+ * e4crypt.c - ext4 encryption management utility
+ *
+ * Copyright (c) 2014 Google, Inc.
+ *     SHA512 implementation from libtomcrypt.
+ *
+ * Authors: Michael Halcrow <mhalcrow@google.com>,
+ *     Ildar Muslukhov <ildarm@google.com>
+ */
+
+#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 <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <asm/unistd.h>
+
+#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, &current_settings);
+       if (rc)
+               return rc;
+       *saved_settings = current_settings;
+       current_settings.c_lflag &= ~ECHO;
+       rc = tcsetattr(0, TCSANOW, &current_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(&current_settings);
+       p = fgets(passphrase, sizeof(passphrase), stdin);
+       tcsetattr(0, TCSANOW, &current_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;
+}