mirror of
https://gitlab.com/cryptsetup/cryptsetup.git
synced 2025-12-05 16:00:05 +01:00
Add kernel keyring functions for volume key.
Code is written by Ondrej Kozina. This patch adds ability to store volume key in kernel keyring (feature available in recent kernels) and avoid setting key through dm-ioctl and avoiding key in table mapping. Will be used in LUKS2. Signed-off-by: Milan Broz <gmazyland@gmail.com>
This commit is contained in:
25
configure.ac
25
configure.ac
@@ -37,11 +37,34 @@ PKG_PROG_PKG_CONFIG
|
||||
AC_HEADER_DIRENT
|
||||
AC_HEADER_STDC
|
||||
AC_CHECK_HEADERS(fcntl.h malloc.h inttypes.h sys/ioctl.h sys/mman.h \
|
||||
sys/sysmacros.h sys/statvfs.h ctype.h unistd.h locale.h byteswap.h endian.h)
|
||||
sys/sysmacros.h sys/statvfs.h ctype.h unistd.h locale.h byteswap.h endian.h stdint.h)
|
||||
|
||||
AC_CHECK_HEADERS(uuid/uuid.h,,[AC_MSG_ERROR([You need the uuid library.])])
|
||||
AC_CHECK_HEADER(libdevmapper.h,,[AC_MSG_ERROR([You need the device-mapper library.])])
|
||||
|
||||
AC_ARG_ENABLE(keyring, AS_HELP_STRING([--disable-keyring],[disable kernel keyring support and builtin kernel keyring token]),[], [enable_keyring=yes])
|
||||
if test "x$enable_keyring" = "xyes"; then
|
||||
AC_CHECK_HEADERS(linux/keyctl.h,,[AC_MSG_ERROR([You need Linux kernel headers with kernel keyring service compiled.])])
|
||||
|
||||
dnl ==========================================================================
|
||||
dnl check whether kernel is compiled with kernel keyring service syscalls
|
||||
AC_CHECK_DECL(__NR_add_key,,[AC_MSG_ERROR([The kernel is missing add_key syscall.])], [#include <syscall.h>])
|
||||
AC_CHECK_DECL(__NR_keyctl,,[AC_MSG_ERROR([The kernel is missing keyctl syscall.])], [#include <syscall.h>])
|
||||
AC_CHECK_DECL(__NR_request_key,,[AC_MSG_ERROR([The kernel is missing request_key syscall.])], [#include <syscall.h>])
|
||||
|
||||
dnl ==========================================================================
|
||||
dnl check that key_serial_t hasn't been adopted yet in stdlib
|
||||
AC_CHECK_TYPES([key_serial_t], [], [], [
|
||||
AC_INCLUDES_DEFAULT
|
||||
#ifdef HAVE_LINUX_KEYCTL_H
|
||||
# include <linux/keyctl.h>
|
||||
#endif
|
||||
])
|
||||
|
||||
AC_DEFINE(KERNEL_KEYRING, 1, [Enable kernel keyring service support])
|
||||
fi
|
||||
AM_CONDITIONAL(KERNEL_KEYRING, test x$enable_keyring = xyes)
|
||||
|
||||
saved_LIBS=$LIBS
|
||||
AC_CHECK_LIB(uuid, uuid_clear, ,[AC_MSG_ERROR([You need the uuid library.])])
|
||||
AC_SUBST(UUID_LIBS, $LIBS)
|
||||
|
||||
@@ -61,6 +61,8 @@ libcryptsetup_la_SOURCES = \
|
||||
utils_fips.c \
|
||||
utils_fips.h \
|
||||
utils_device.c \
|
||||
utils_keyring.c \
|
||||
utils_keyring.h \
|
||||
libdevmapper.c \
|
||||
utils_dm.h \
|
||||
volumekey.c \
|
||||
|
||||
@@ -371,10 +371,21 @@ static void hex_key(char *hexkey, size_t key_size, const char *key)
|
||||
sprintf(&hexkey[i * 2], "%02x", (unsigned char)key[i]);
|
||||
}
|
||||
|
||||
/* get string length for key_size written in decimal system */
|
||||
static size_t get_key_size_strlen(size_t key_size)
|
||||
{
|
||||
size_t ret = 1;
|
||||
|
||||
while ((key_size /= 10))
|
||||
ret++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* https://gitlab.com/cryptsetup/cryptsetup/wikis/DMCrypt */
|
||||
static char *get_dm_crypt_params(struct crypt_dm_active_device *dmd, uint32_t flags)
|
||||
{
|
||||
int r, max_size, null_cipher = 0, num_options = 0;
|
||||
int r, max_size, null_cipher = 0, num_options = 0, keystr_len = 0;
|
||||
char *params, *hexkey;
|
||||
char features[256];
|
||||
|
||||
@@ -399,13 +410,24 @@ static char *get_dm_crypt_params(struct crypt_dm_active_device *dmd, uint32_t fl
|
||||
if (!strncmp(dmd->u.crypt.cipher, "cipher_null-", 12))
|
||||
null_cipher = 1;
|
||||
|
||||
hexkey = crypt_safe_alloc(null_cipher ? 2 : (dmd->u.crypt.vk->keylength * 2 + 1));
|
||||
if (dmd->u.crypt.key_in_keyring) {
|
||||
keystr_len = strlen(dmd->u.crypt.key_description) + get_key_size_strlen(dmd->u.crypt.vk->keylength) + 9;
|
||||
hexkey = crypt_safe_alloc(keystr_len);
|
||||
} else
|
||||
hexkey = crypt_safe_alloc(null_cipher ? 2 : (dmd->u.crypt.vk->keylength * 2 + 1));
|
||||
|
||||
if (!hexkey)
|
||||
return NULL;
|
||||
|
||||
if (null_cipher)
|
||||
strncpy(hexkey, "-", 2);
|
||||
else
|
||||
else if (dmd->u.crypt.key_in_keyring) {
|
||||
r = snprintf(hexkey, keystr_len, ":%zu:logon:%s", dmd->u.crypt.vk->keylength, dmd->u.crypt.key_description);
|
||||
if (r < 0 || r >= keystr_len) {
|
||||
params = NULL;
|
||||
goto out;
|
||||
}
|
||||
} else
|
||||
hex_key(hexkey, dmd->u.crypt.vk->keylength, dmd->u.crypt.vk->key);
|
||||
|
||||
max_size = strlen(hexkey) + strlen(dmd->u.crypt.cipher) +
|
||||
@@ -1149,6 +1171,7 @@ static int _dm_query_crypt(uint32_t get_flags,
|
||||
char *rcipher, *key_, *rdevice, *endp, buffer[3], *arg;
|
||||
unsigned int i;
|
||||
int r;
|
||||
size_t key_size;
|
||||
|
||||
memset(dmd, 0, sizeof(*dmd));
|
||||
dmd->target = DM_CRYPT;
|
||||
@@ -1222,20 +1245,38 @@ static int _dm_query_crypt(uint32_t get_flags,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (key_[0] == ':')
|
||||
dmd->u.crypt.key_in_keyring = 1;
|
||||
|
||||
if (get_flags & DM_ACTIVE_CRYPT_KEYSIZE) {
|
||||
dmd->u.crypt.vk = crypt_alloc_volume_key(strlen(key_) / 2, NULL);
|
||||
/* we will trust kernel the key_string is in expected format */
|
||||
if (key_[0] == ':') {
|
||||
if (sscanf(key_ + 1, "%zu", &key_size) != 1)
|
||||
return -EINVAL;
|
||||
} else
|
||||
key_size = strlen(key_) / 2;
|
||||
|
||||
dmd->u.crypt.vk = crypt_alloc_volume_key(key_size, NULL);
|
||||
if (!dmd->u.crypt.vk)
|
||||
return -ENOMEM;
|
||||
|
||||
if (get_flags & DM_ACTIVE_CRYPT_KEY) {
|
||||
buffer[2] = '\0';
|
||||
for(i = 0; i < dmd->u.crypt.vk->keylength; i++) {
|
||||
memcpy(buffer, &key_[i * 2], 2);
|
||||
dmd->u.crypt.vk->key[i] = strtoul(buffer, &endp, 16);
|
||||
if (endp != &buffer[2]) {
|
||||
if (key_[0] == ':') {
|
||||
dmd->u.crypt.key_description = strdup(strpbrk(key_ + 1, ":") + 1);
|
||||
if (!dmd->u.crypt.key_description) {
|
||||
crypt_free_volume_key(dmd->u.crypt.vk);
|
||||
dmd->u.crypt.vk = NULL;
|
||||
return -EINVAL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
} else {
|
||||
buffer[2] = '\0';
|
||||
for(i = 0; i < dmd->u.crypt.vk->keylength; i++) {
|
||||
memcpy(buffer, &key_[i * 2], 2);
|
||||
dmd->u.crypt.vk->key[i] = strtoul(buffer, &endp, 16);
|
||||
if (endp != &buffer[2]) {
|
||||
crypt_free_volume_key(dmd->u.crypt.vk);
|
||||
dmd->u.crypt.vk = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1685,10 +1726,10 @@ out:
|
||||
}
|
||||
|
||||
int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name,
|
||||
size_t key_size, const char *key)
|
||||
size_t key_size, const char *key, unsigned key_in_keyring)
|
||||
{
|
||||
uint32_t dmt_flags;
|
||||
int msg_size = key_size * 2 + 10; // key set <key>
|
||||
int msg_size;
|
||||
char *msg = NULL;
|
||||
int r = -ENOTSUP;
|
||||
|
||||
@@ -1698,6 +1739,11 @@ int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name,
|
||||
if (!(dmt_flags & DM_KEY_WIPE_SUPPORTED))
|
||||
goto out;
|
||||
|
||||
if (key_in_keyring)
|
||||
msg_size = strlen(key) + get_key_size_strlen(key_size) + 17;
|
||||
else
|
||||
msg_size = key_size * 2 + 10; // key set <key>
|
||||
|
||||
msg = crypt_safe_alloc(msg_size);
|
||||
if (!msg) {
|
||||
r = -ENOMEM;
|
||||
@@ -1705,7 +1751,10 @@ int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name,
|
||||
}
|
||||
|
||||
strcpy(msg, "key set ");
|
||||
hex_key(&msg[8], key_size, key);
|
||||
if (key_in_keyring)
|
||||
snprintf(msg + 8, msg_size - 8, ":%zu:logon:%s", key_size, key);
|
||||
else
|
||||
hex_key(&msg[8], key_size, key);
|
||||
|
||||
if (!_dm_message(name, msg, dmt_flags) ||
|
||||
!_dm_simple(DM_DEVICE_RESUME, name, 1)) {
|
||||
|
||||
@@ -1814,7 +1814,7 @@ int crypt_resume_by_passphrase(struct crypt_device *cd,
|
||||
&cd->u.luks1.hdr, &vk, cd);
|
||||
if (r >= 0) {
|
||||
keyslot = r;
|
||||
r = dm_resume_and_reinstate_key(cd, name, vk->keylength, vk->key);
|
||||
r = dm_resume_and_reinstate_key(cd, name, vk->keylength, vk->key, 0);
|
||||
if (r == -ENOTSUP)
|
||||
log_err(cd, _("Resume is not supported for device %s.\n"), name);
|
||||
else if (r)
|
||||
@@ -1871,7 +1871,7 @@ int crypt_resume_by_keyfile_offset(struct crypt_device *cd,
|
||||
goto out;
|
||||
|
||||
keyslot = r;
|
||||
r = dm_resume_and_reinstate_key(cd, name, vk->keylength, vk->key);
|
||||
r = dm_resume_and_reinstate_key(cd, name, vk->keylength, vk->key, 0);
|
||||
if (r)
|
||||
log_err(cd, _("Error during resuming device %s.\n"), name);
|
||||
out:
|
||||
|
||||
@@ -75,6 +75,7 @@ struct crypt_dm_active_device {
|
||||
struct {
|
||||
const char *cipher;
|
||||
const char *integrity;
|
||||
char *key_description;
|
||||
|
||||
/* Active key for device */
|
||||
struct volume_key *vk;
|
||||
@@ -82,6 +83,8 @@ struct crypt_dm_active_device {
|
||||
/* struct crypt_active_device */
|
||||
uint64_t offset; /* offset in sectors */
|
||||
uint64_t iv_offset; /* IV initilisation sector */
|
||||
|
||||
unsigned key_in_keyring:1; /* status detected key loaded via kernel keyring */
|
||||
} crypt;
|
||||
struct {
|
||||
struct device *hash_device;
|
||||
@@ -133,7 +136,7 @@ int dm_create_device(struct crypt_device *cd, const char *name,
|
||||
int reload);
|
||||
int dm_suspend_and_wipe_key(struct crypt_device *cd, const char *name);
|
||||
int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name,
|
||||
size_t key_size, const char *key);
|
||||
size_t key_size, const char *key, unsigned key_in_keyring);
|
||||
|
||||
const char *dm_get_dir(void);
|
||||
|
||||
|
||||
275
lib/utils_keyring.c
Normal file
275
lib/utils_keyring.c
Normal file
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* kernel keyring utilities
|
||||
*
|
||||
* Copyright (C) 2016-2017, Red Hat, Inc. All rights reserved.
|
||||
* Copyright (C) 2016, Ondrej Kozina. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#ifndef HAVE_KEY_SERIAL_T
|
||||
#define HAVE_KEY_SERIAL_T
|
||||
#include <stdint.h>
|
||||
typedef int32_t key_serial_t;
|
||||
#endif
|
||||
|
||||
#include "internal.h"
|
||||
#include "utils_keyring.h"
|
||||
|
||||
#ifdef KERNEL_KEYRING
|
||||
|
||||
#include <linux/keyctl.h>
|
||||
|
||||
/* request_key */
|
||||
static key_serial_t request_key(const char *type,
|
||||
const char *description,
|
||||
const char *callout_info,
|
||||
key_serial_t keyring)
|
||||
{
|
||||
return syscall(__NR_request_key, type, description, callout_info, keyring);
|
||||
}
|
||||
|
||||
/* add_key */
|
||||
static key_serial_t add_key(const char *type,
|
||||
const char *description,
|
||||
const void *payload,
|
||||
size_t plen,
|
||||
key_serial_t keyring)
|
||||
{
|
||||
return syscall(__NR_add_key, type, description, payload, plen, keyring);
|
||||
}
|
||||
|
||||
/* keyctl_read */
|
||||
static long keyctl_read(key_serial_t key, char *buffer, size_t buflen)
|
||||
{
|
||||
return syscall(__NR_keyctl, KEYCTL_READ, key, buffer, buflen);
|
||||
}
|
||||
|
||||
/* keyctl_revoke */
|
||||
static long keyctl_revoke(key_serial_t key)
|
||||
{
|
||||
return syscall(__NR_keyctl, KEYCTL_REVOKE, key);
|
||||
}
|
||||
|
||||
/* keyctl_unlink */
|
||||
static long keyctl_unlink(key_serial_t key, key_serial_t keyring)
|
||||
{
|
||||
return syscall(__NR_keyctl, KEYCTL_UNLINK, key, keyring);
|
||||
}
|
||||
#endif
|
||||
|
||||
int keyring_check(void)
|
||||
{
|
||||
#ifdef KERNEL_KEYRING
|
||||
/* logon type key descriptions must be in format "prefix:description" */
|
||||
return syscall(__NR_request_key, "logon", "dummy", NULL, 0) == -1l && errno != ENOSYS;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int keyring_add_key_in_thread_keyring(const char *key_desc, const void *key, size_t key_size)
|
||||
{
|
||||
#ifdef KERNEL_KEYRING
|
||||
key_serial_t kid;
|
||||
|
||||
log_dbg("Loading key %s (%zu bytes) in thread keyring", key_desc, key_size);
|
||||
|
||||
kid = add_key("logon", key_desc, key, key_size, KEY_SPEC_THREAD_KEYRING);
|
||||
if (kid < 0) {
|
||||
switch (errno) {
|
||||
case EINVAL:
|
||||
log_dbg("add_key: the payload data is invalid.");
|
||||
break;
|
||||
case ENOMEM:
|
||||
log_dbg("add_key: insufficient memory to create a key.");
|
||||
break;
|
||||
case EDQUOT:
|
||||
log_dbg("add_key: quota would exceed.");
|
||||
break;
|
||||
}
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EINVAL;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef KERNEL_KEYRING
|
||||
static key_serial_t request_key_verbose(const char *type,
|
||||
const char *key_desc,
|
||||
const char *callout_info,
|
||||
key_serial_t keyring)
|
||||
{
|
||||
key_serial_t kid;
|
||||
|
||||
/*
|
||||
* Search for a key in this particular order (first found, first served):
|
||||
*
|
||||
* 1) thread keyring
|
||||
* 2) process keyring
|
||||
* 3) user keyring (if exists) or user session keyring (if exists)
|
||||
*/
|
||||
kid = request_key(type, key_desc, callout_info, keyring);
|
||||
if (kid < 0) {
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
log_dbg("request_key: The keyring wasn't available for modification by the user.");
|
||||
break;
|
||||
case EINTR:
|
||||
log_dbg("request_key: The request was interrupted by a signal.");
|
||||
break;
|
||||
case EDQUOT:
|
||||
log_dbg("request_key: The key quota for this user would be exceeded by creating this key or linking it to the keyring.");
|
||||
break;
|
||||
case EKEYEXPIRED:
|
||||
log_dbg("request_key: An expired key was found, but no replacement could be obtained.");
|
||||
break;
|
||||
case EKEYREVOKED:
|
||||
log_dbg("request_key: A revoked key was found, but no replacement could be obtained.");
|
||||
break;
|
||||
case ENOKEY:
|
||||
log_dbg("request_key: No matching key was found.");
|
||||
break;
|
||||
/* NOTE following error codes are unreachable unless key generation is triggered */
|
||||
case EKEYREJECTED:
|
||||
log_dbg("request_key: The attempt to generate a new key was rejected.");
|
||||
break;
|
||||
case ENOMEM:
|
||||
log_dbg("request_key: Insufficient memory to create a key.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return kid;
|
||||
}
|
||||
#endif
|
||||
|
||||
int keyring_get_passphrase(const char *key_desc,
|
||||
char **passphrase,
|
||||
size_t *passphrase_len)
|
||||
{
|
||||
#ifdef KERNEL_KEYRING
|
||||
int err;
|
||||
key_serial_t kid;
|
||||
long ret;
|
||||
char *buf = NULL;
|
||||
size_t len = 0;
|
||||
|
||||
log_dbg("Looking for key described with '%s'.", key_desc);
|
||||
|
||||
do
|
||||
kid = request_key_verbose("user", key_desc, NULL, 0);
|
||||
while (kid < 0 && errno == EINTR);
|
||||
|
||||
if (kid < 0)
|
||||
return -errno;
|
||||
|
||||
/* just get payload size */
|
||||
ret = keyctl_read(kid, NULL, 0);
|
||||
if (ret > 0) {
|
||||
len = ret;
|
||||
buf = malloc(len);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* retrieve actual payload data */
|
||||
ret = keyctl_read(kid, buf, len);
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
err = errno;
|
||||
crypt_memzero(buf, len);
|
||||
free(buf);
|
||||
switch (err) {
|
||||
case ENOKEY:
|
||||
log_dbg("keyctl_read: The key specified is invalid.");
|
||||
break;
|
||||
case EKEYEXPIRED:
|
||||
log_dbg("keyctl_read: The key specified has expired.");
|
||||
break;
|
||||
case EKEYREVOKED:
|
||||
log_dbg("keyctl_read: The key specified had been revoked.");
|
||||
break;
|
||||
case EACCES:
|
||||
log_dbg("keyctl_read: The key exists, but is not readable by the calling process.");
|
||||
break;
|
||||
}
|
||||
return -err;
|
||||
}
|
||||
|
||||
*passphrase = buf;
|
||||
*passphrase_len = len;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EINVAL;
|
||||
#endif
|
||||
}
|
||||
|
||||
int keyring_revoke_and_unlink_key(const char *key_desc)
|
||||
{
|
||||
#ifdef KERNEL_KEYRING
|
||||
key_serial_t kid;
|
||||
|
||||
log_dbg("requesting keyring key %s for removal", key_desc);
|
||||
|
||||
do
|
||||
kid = request_key_verbose("logon", key_desc, NULL, 0);
|
||||
while (kid < 0 && errno == EINTR);
|
||||
|
||||
if (kid < 0)
|
||||
return 0;
|
||||
|
||||
log_dbg("Revoking key %s", key_desc);
|
||||
|
||||
if (keyctl_revoke(kid)) {
|
||||
switch (errno) {
|
||||
case ENOKEY:
|
||||
log_dbg ("keyctl_revoke: The specified key does not exist.");
|
||||
break;
|
||||
case EKEYREVOKED:
|
||||
log_dbg("keyctl_revoke: The key has already been revoked.");
|
||||
break;
|
||||
case EACCES:
|
||||
log_dbg("keyctl_revoke: The name key exists, but is not writable by the calling process.");
|
||||
return -errno;
|
||||
default:
|
||||
log_dbg("keyctl_revoke: Unexpected errno: %d", errno);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* best effort only. the key could have been linked
|
||||
* in some other keyring and its payload is now
|
||||
* revoked anyway.
|
||||
*/
|
||||
keyctl_unlink(kid, KEY_SPEC_THREAD_KEYRING);
|
||||
keyctl_unlink(kid, KEY_SPEC_PROCESS_KEYRING);
|
||||
keyctl_unlink(kid, KEY_SPEC_USER_KEYRING);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EINVAL;
|
||||
#endif
|
||||
}
|
||||
38
lib/utils_keyring.h
Normal file
38
lib/utils_keyring.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* kernel keyring syscall wrappers
|
||||
*
|
||||
* Copyright (C) 2016, Red Hat, Inc. All rights reserved.
|
||||
* Copyright (C) 2016, Ondrej Kozina. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _UTILS_KEYRING
|
||||
#define _UTILS_KEYRING
|
||||
|
||||
int keyring_check(void);
|
||||
|
||||
int keyring_get_passphrase(const char *key_desc,
|
||||
char **passphrase,
|
||||
size_t *passphrase_len);
|
||||
|
||||
int keyring_add_key_in_thread_keyring(
|
||||
const char *key_desc,
|
||||
const void *key,
|
||||
size_t key_size);
|
||||
|
||||
int keyring_revoke_and_unlink_key(const char *key_desc);
|
||||
|
||||
#endif
|
||||
@@ -7,7 +7,8 @@ TESTS = api-test \
|
||||
password-hash-test \
|
||||
tcrypt-compat-test \
|
||||
luks1-compat-test \
|
||||
device-test
|
||||
device-test \
|
||||
keyring-test
|
||||
|
||||
if VERITYSETUP
|
||||
TESTS += verity-compat-test
|
||||
@@ -37,6 +38,7 @@ EXTRA_DIST = compatimage.img.bz2 compatv10image.img.bz2 \
|
||||
tcrypt-compat-test \
|
||||
luks1-compat-test \
|
||||
device-test \
|
||||
keyring-test \
|
||||
integrity-compat-test \
|
||||
cryptsetup-valg-supps valg.sh valg-api.sh
|
||||
|
||||
|
||||
234
tests/keyring-test
Executable file
234
tests/keyring-test
Executable file
@@ -0,0 +1,234 @@
|
||||
#!/bin/bash
|
||||
|
||||
DEV_ZERO="dmtst-zero"
|
||||
DEV_CRYPT="dmtst-crypt"
|
||||
|
||||
CIPHER="aes-xts-plain64"
|
||||
|
||||
TEST_KEYRING_NAME="dmtst_keyring"
|
||||
|
||||
USER_KEY_32_OK="dmtst:ukey_32_ok"
|
||||
USER_KEY_32_WRONG="dmtst:ukey_32_wrong_size"
|
||||
|
||||
LOGON_KEY_32_OK="dmtst:lkey_32_ok"
|
||||
LOGON_KEY_32_WRONG="dmtst:lkey_32_wrong_size"
|
||||
|
||||
PAYLOAD_32="bb21158c733229347bd4e681891e213d"
|
||||
PAYLOAD_31="bb21158c733229347bd4e681891e213"
|
||||
|
||||
HEXKEY_32="bb21158c733229347bd4e681891e213d94c685be6a5b84818afe7a78a6de7a1a";
|
||||
HEXKEY_32_BAD="bb21158c733229347bd4e68189XXXX3d94c685be6a5b84818afe7a78a6de7a1a"
|
||||
HEXKEY_31="bb21158c733229347bd4e681891e213d94c685be6a5b84818afe7a78a6de7a"
|
||||
|
||||
function skip()
|
||||
{
|
||||
[ -n "$1" ] && echo "$1"
|
||||
exit 0
|
||||
}
|
||||
|
||||
function remove_mapping()
|
||||
{
|
||||
[ -b /dev/mapper/$DEV_CRYPT ] && dmsetup remove $DEV_CRYPT
|
||||
[ -b /dev/mapper/$DEV_ZERO ] && dmsetup remove $DEV_ZERO
|
||||
|
||||
# unlink whole test keyring
|
||||
[ -n "$TEST_KEYRING" ] && keyctl unlink $TEST_KEYRING "@s" >/dev/null
|
||||
}
|
||||
|
||||
function fail()
|
||||
{
|
||||
[ -n "$1" ] && echo "$1"
|
||||
echo "FAILED"
|
||||
remove_mapping
|
||||
exit 2
|
||||
}
|
||||
|
||||
# $1 type
|
||||
# $2 description
|
||||
# $3 payload
|
||||
# $4 keyring
|
||||
function load_key()
|
||||
{
|
||||
keyctl add $@ >/dev/null
|
||||
}
|
||||
|
||||
function dm_crypt_keyring_support()
|
||||
{
|
||||
local str
|
||||
local major=0
|
||||
local minor=0
|
||||
|
||||
str=$(dmsetup targets | grep crypt | sed -e 's/crypt[[:space:]]\+v\(.*\)/\1/g')
|
||||
[ -z "$str" ] && fail "failed to parse dm-crypt version"
|
||||
|
||||
major=${str%%.*}
|
||||
minor=${str#*.}
|
||||
minor=${minor%.*}
|
||||
|
||||
[ $major -gt 1 ] && return 0
|
||||
[ $major -lt 1 ] && return 1
|
||||
[ $minor -ge 15 ]
|
||||
}
|
||||
|
||||
which dmsetup >/dev/null || skip "Cannot find dmsetup, test skipped"
|
||||
which keyctl >/dev/null || skip "Cannot find keyctl, test skipped"
|
||||
modprobe dm-crypt || fail "dm-crypt failed to load"
|
||||
dm_crypt_keyring_support || skip "dm-crypt doesn't support kernel keyring, test skipped."
|
||||
|
||||
keyctl new_session >/dev/null || fail "Cannot create new session keyring"
|
||||
TEST_KEYRING=$(keyctl newring $TEST_KEYRING_NAME "@s" 2> /dev/null)
|
||||
test -n "$TEST_KEYRING" || fail "Cannot create test keyring"
|
||||
|
||||
load_key logon $LOGON_KEY_32_OK $PAYLOAD_32 "$TEST_KEYRING" || fail "Cannot load 32 byte logon key type"
|
||||
load_key user $USER_KEY_32_OK $PAYLOAD_32 "$TEST_KEYRING" || fail "Cannot load 32 byte user key type"
|
||||
load_key logon $LOGON_KEY_32_WRONG $PAYLOAD_31 "$TEST_KEYRING" || fail "Cannot load 31 byte logon key type"
|
||||
load_key user $USER_KEY_32_WRONG $PAYLOAD_31 "$TEST_KEYRING" || fail "Cannot load 31 byte user key type"
|
||||
|
||||
dmsetup create $DEV_ZERO --table "0 100 zero" || fail
|
||||
|
||||
echo "[1] Valid keyring keys"
|
||||
|
||||
# load logon type kernel key
|
||||
KEY=":32:logon:$LOGON_KEY_32_OK"
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $KEY 0 /dev/mapper/$DEV_ZERO 0" || fail
|
||||
dmsetup table --showkeys $DEV_CRYPT | grep -q "crypt $CIPHER $KEY 0" || fail
|
||||
dmsetup remove $DEV_CRYPT || fail
|
||||
|
||||
# load user type kernel key
|
||||
KEY=":32:user:$USER_KEY_32_OK"
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $KEY 0 /dev/mapper/$DEV_ZERO 0" || fail
|
||||
dmsetup table --showkeys $DEV_CRYPT | grep -q "crypt $CIPHER $KEY 0" || fail
|
||||
dmsetup remove $DEV_CRYPT || fail
|
||||
|
||||
# load logon type kernel key...
|
||||
KEY=":32:logon:$LOGON_KEY_32_OK"
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $KEY 0 /dev/mapper/$DEV_ZERO 0" || fail
|
||||
dmsetup suspend $DEV_CRYPT || fail
|
||||
dmsetup message $DEV_CRYPT 0 "key wipe" || fail
|
||||
# ...replace the key with hexkey...
|
||||
dmsetup message $DEV_CRYPT 0 "key set $HEXKEY_32" || fail
|
||||
dmsetup table --showkeys $DEV_CRYPT | grep -q "crypt $CIPHER $HEXKEY_32 0" || fail
|
||||
dmsetup resume $DEV_CRYPT || fail
|
||||
dmsetup suspend $DEV_CRYPT || fail
|
||||
# ...and replace it again with user type kernel key...
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:user:$USER_KEY_32_OK" || fail
|
||||
dmsetup table --showkeys $DEV_CRYPT | grep -q "crypt $CIPHER :32:user:$USER_KEY_32_OK 0" || fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set $HEXKEY_32" || fail
|
||||
dmsetup table --showkeys $DEV_CRYPT | grep -q "crypt $CIPHER $HEXKEY_32 0" || fail
|
||||
dmsetup resume $DEV_CRYPT || fail
|
||||
dmsetup remove $DEV_CRYPT || fail
|
||||
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $HEXKEY_32 0 /dev/mapper/$DEV_ZERO 0" || fail
|
||||
dmsetup suspend $DEV_CRYPT || fail
|
||||
dmsetup message $DEV_CRYPT 0 "key wipe" || fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:user:$USER_KEY_32_OK" || fail
|
||||
dmsetup resume $DEV_CRYPT || fail
|
||||
dmsetup suspend $DEV_CRYPT || fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:logon:$LOGON_KEY_32_OK" || fail
|
||||
dmsetup resume $DEV_CRYPT || fail
|
||||
dmsetup remove $DEV_CRYPT || fail
|
||||
|
||||
echo "[2] message ioctl"
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $HEXKEY_32 0 /dev/mapper/$DEV_ZERO 0" || fail
|
||||
dmsetup suspend $DEV_CRYPT || fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:logon:$LOGON_KEY_32_WRONG" 2> /dev/null && fail
|
||||
# old key should be intact and valid
|
||||
dmsetup table --showkeys $DEV_CRYPT | grep -q "crypt $CIPHER $HEXKEY_32 0" || fail
|
||||
dmsetup resume $DEV_CRYPT || fail
|
||||
dmsetup suspend $DEV_CRYPT || fail
|
||||
# now the key gets destroyed by invalid input
|
||||
dmsetup message $DEV_CRYPT 0 "key set $HEXKEY_32_BAD" 2> /dev/null && fail
|
||||
dmsetup resume $DEV_CRYPT 2> /dev/null && fail
|
||||
# hmm... see the output. don't like it
|
||||
# dmsetup table --showkeys $DEV_CRYPT
|
||||
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:user:$USER_KEY_32_OK" || fail
|
||||
dmsetup table --showkeys $DEV_CRYPT | grep -q "crypt $CIPHER :32:user:$USER_KEY_32_OK 0" || fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :31:logon:$LOGON_KEY_32_OK" 2> /dev/null && fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :" 2> /dev/null && fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set ::::" 2> /dev/null && fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :0:logon:$LOGON_KEY_32_OK" 2> /dev/null && fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32" 2> /dev/null && fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:" 2> /dev/null && fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:logon" 2> /dev/null && fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:logo" 2> /dev/null && fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:logon:" 2> /dev/null && fail
|
||||
dmsetup table --showkeys $DEV_CRYPT | grep -q "crypt $CIPHER :32:user:$USER_KEY_32_OK 0" || fail
|
||||
dmsetup message $DEV_CRYPT 0 "key set :32:user:$USER_KEY_32_OK" || fail
|
||||
dmsetup resume $DEV_CRYPT || fail
|
||||
dmsetup remove $DEV_CRYPT || fail
|
||||
|
||||
echo "[3] bOrked keys"
|
||||
# declare the key having 32 bytes but load key which has in fact 31 bytes only
|
||||
KEY=":32:logon:$LOGON_KEY_32_WRONG"
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $KEY 0 /dev/mapper/$DEV_ZERO 0" 2> /dev/null && fail "dm-crypt accepted wrong key size"
|
||||
|
||||
# declare the key having 31 bytes (incompatible with cipher) and load key with 32 bytes in real
|
||||
KEY=":31:logon:$LOGON_KEY_32_WRONG"
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $KEY 0 /dev/mapper/$DEV_ZERO 0" 2> /dev/null && fail "dm-crypt accepted wrong key size"
|
||||
|
||||
# declare the key being user type but try to load logon one
|
||||
KEY=":32:user:$LOGON_KEY_32"
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $KEY 0 /dev/mapper/$DEV_ZERO 0" 2> /dev/null && fail "dm-crypt accepted key description for invalid key type"
|
||||
|
||||
# now the other way
|
||||
KEY=":32:logon:$USER_KEY_32"
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $KEY 0 /dev/mapper/$DEV_ZERO 0" 2> /dev/null && fail "dm-crypt accepted key description for invalid key type"
|
||||
|
||||
BORKED_KEYS=":\ 32:logon:$LOGON_KEY_32_OK
|
||||
: 32:logon:$LOGON_KEY_32_OK
|
||||
:+32:logon:$LOGON_KEY_32_OK
|
||||
:-32:logon:$LOGON_KEY_32_OK
|
||||
:32 :logon:$LOGON_KEY_32_OK
|
||||
:32\ :logon:$LOGON_KEY_32_OK
|
||||
:32_:logon:$LOGON_KEY_32_OK
|
||||
:32+:logon:$LOGON_KEY_32_OK
|
||||
:30+2:logon:$LOGON_KEY_32_OK
|
||||
:32+0:logon:$LOGON_KEY_32_OK
|
||||
:32: logon:$LOGON_KEY_32_OK
|
||||
:32:\ logon:$LOGON_KEY_32_OK
|
||||
:32:logonA:$LOGON_KEY_32_OK
|
||||
:32:logo:$LOGON_KEY_32_OK
|
||||
:32:llogon:$LOGON_KEY_32_OK
|
||||
:32xlogon:$LOGON_KEY_32_OK
|
||||
:32logon:$LOGON_KEY_32_OK
|
||||
:32:logonx$LOGON_KEY_32_OK
|
||||
:32:logon$LOGON_KEY_32_OK
|
||||
: 32:user:$USER_KEY_32_OK
|
||||
:\ 32:user:$USER_KEY_32_OK
|
||||
:+32:user:$USER_KEY_32_OK
|
||||
:-32:user:$USER_KEY_32_OK
|
||||
:32 :user:$USER_KEY_32_OK
|
||||
:32\ :user:$USER_KEY_32_OK
|
||||
:32_:user:$USER_KEY_32_OK
|
||||
:32+:user:$USER_KEY_32_OK
|
||||
:30+2:user:$USER_KEY_32_OK
|
||||
:32+0:user:$USER_KEY_32_OK
|
||||
:32: user:$USER_KEY_32_OK
|
||||
:32:\ user:$USER_KEY_32_OK
|
||||
:32:userA:$USER_KEY_32_OK
|
||||
:32:use:$USER_KEY_32_OK
|
||||
:32:uuser:$USER_KEY_32_OK
|
||||
:32xuser:$USER_KEY_32_OK
|
||||
:32user:$USER_KEY_32_OK
|
||||
:32:userx$USER_KEY_32_OK
|
||||
:32:user$USER_KEY_32_OK
|
||||
:32:userlogon:$USER_KEY_32_OK
|
||||
:32:userlogon:$LOGON_KEY_32_OK
|
||||
:32:logonuser:$USER_KEY_32_OK
|
||||
:32:logonuser:$LOGON_KEY_32_OK
|
||||
:32:logon:user:$USER_KEY_32_OK
|
||||
:32:logon:user:$LOGON_KEY_32_OK
|
||||
:32:user:logon:$USER_KEY_32_OK
|
||||
:32:user:logon:$LOGON_KEY_32_OK"
|
||||
|
||||
# TODO: add tests with whitespace in key description (not possible with current libdevmapper)
|
||||
|
||||
IFS="
|
||||
"
|
||||
|
||||
for key in $BORKED_KEYS; do
|
||||
dmsetup create $DEV_CRYPT --table "0 100 crypt $CIPHER $key 0 /dev/mapper/$DEV_ZERO 0" 2> /dev/null && fail "dm-crypt accepted seriously borked key string"
|
||||
done
|
||||
|
||||
remove_mapping
|
||||
Reference in New Issue
Block a user