Viewing: sk_utils.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2015, Trustees of Indiana University
*
* Copyright (c) 2016, 2017, Intel Corporation.
*
* Author: Jeremy Filizetti <jfilizet@iu.edu>
*/
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <openssl/dh.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#ifdef HAVE_OPENSSL_EVP_PKEY
#include <openssl/param_build.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <libcfs/util/string.h>
#include <sys/time.h>
#include <signal.h>
#include <linux/lustre/lgss.h>
#include <lustre/lustreapi.h>
#include "sk_utils.h"
#include "write_bytes.h"
#define SK_PBKDF2_ITERATIONS 10000
#ifdef _NEW_BUILD_
# include "lgss_utils.h"
#else
# include "gss_util.h"
# include "gss_oids.h"
# include "err_util.h"
#endif
int fips_mode = -1;
#ifdef _ERR_UTIL_H_
/**
* Initializes logging
* \param[in] program Program name to output
* \param[in] verbose Verbose flag
* \param[in] fg Whether or not to run in foreground
*
*/
void sk_init_logging(char *program, int verbose, int fg)
{
initerr(program, verbose, fg);
}
#endif
#if !defined(HAVE_OPENSSL_EVP_PKEY) && OPENSSL_VERSION_NUMBER >= 0x10100000L
static int __fetch_ssk_prime(struct sk_keyfile_config *config)
{
const BIGNUM *p;
DH *dh = NULL;
int primenid;
int rc = -1;
primenid = sk_primebits2primenid(config->skc_prime_bits);
dh = DH_new_by_nid(primenid);
if (!dh) {
fprintf(stderr, "error: dh cannot be init\n");
goto prime_end;
}
p = DH_get0_p(dh);
if (!p) {
fprintf(stderr, "error: cannot get p from dh\n");
goto prime_end;
}
if (BN_num_bytes(p) > SK_MAX_P_BYTES) {
fprintf(stderr,
"error: requested length %d exceeds maximum %d\n",
BN_num_bytes(p), SK_MAX_P_BYTES * 8);
goto prime_end;
}
if (BN_bn2bin(p, config->skc_p) != BN_num_bytes(p)) {
fprintf(stderr, "error: convert BIGNUM p to binary failed\n");
goto prime_end;
}
rc = 0;
prime_end:
if (rc)
fprintf(stderr,
"error: fetching SSK prime failed: %s\n",
ERR_error_string(ERR_get_error(), NULL));
DH_free(dh);
return rc;
}
#endif
/**
* Generates the prime required by the client key, and stores it in the
* struct sk_keyfile_config.
*
* \param[in] config config describing the key
*
* \return 0 success
* \return -1 failure
*/
int gen_ssk_prime(struct sk_keyfile_config *config)
{
int rc = -1;
const char *primename;
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *dh = NULL;
BIGNUM *p = NULL;
if (fips_mode < 0)
fips_mode = FIPS_mode();
if (fips_mode) {
primename = sk_primebits2name(config->skc_prime_bits);
if (!primename) {
fprintf(stderr,
"error: prime len %d not supported in FIPS mode\n",
config->skc_prime_bits);
return rc;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
fprintf(stdout,
"FIPS mode, using well-known prime %s\n", primename);
#ifndef HAVE_OPENSSL_EVP_PKEY
return __fetch_ssk_prime(config);
#endif
#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
}
ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
if (!ctx || EVP_PKEY_paramgen_init(ctx) != 1) {
fprintf(stderr, "error: ctx cannot be init\n");
goto prime_end;
}
if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx,
config->skc_prime_bits) <= 0 ||
EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, SK_GENERATOR) <= 0) {
fprintf(stderr, "error: cannot set prime or generator\n");
goto prime_end;
}
if (EVP_PKEY_paramgen(ctx, &dh) != 1) {
fprintf(stderr, "error: cannot generate DH parameters\n");
goto prime_end;
}
if (!EVP_PKEY_get_bn_param(dh, OSSL_PKEY_PARAM_FFC_P, &p)) {
fprintf(stderr, "error: cannot get p from dh\n");
goto prime_end;
}
if (BN_num_bytes(p) > SK_MAX_P_BYTES) {
fprintf(stderr,
"error: cannot generate DH parameters: requested length %d exceeds maximum %d\n",
config->skc_prime_bits, SK_MAX_P_BYTES * 8);
goto prime_end;
}
if (BN_bn2bin(p, config->skc_p) != BN_num_bytes(p)) {
fprintf(stderr,
"error: convert BIGNUM p to binary failed\n");
goto prime_end;
}
rc = 0;
prime_end:
if (rc)
fprintf(stderr,
"error: generating SSK prime failed: %s\n",
ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_free(dh);
EVP_PKEY_CTX_free(ctx);
return rc;
}
static int write_data_with_error_handling(int fd, const void *data,
size_t expected_size,
const char *output_file,
const char *data_type)
{
ssize_t rc;
int retry = 10;
retry_write:
rc = write(fd, data, expected_size);
if (rc < 0) {
fprintf(stderr, "error: writing %zu %s bytes to '%s': %s\n",
expected_size, data_type, output_file, strerror(errno));
return -errno;
} else if (rc != expected_size) {
/* short write: advance past the bytes written and retry */
if (--retry > 0) {
data += rc;
expected_size -= rc;
goto retry_write;
}
fprintf(stderr,
"error: short write of %s to '%s' (%zd/%zu bytes)\n",
data_type, output_file, rc, expected_size);
return -ENOSPC;
}
return 0;
}
/**
* Writes sk config to file.
*
* \param[in] output_file output file to write sk config to
* \param[in] config the sk config to write
* \param[in] overwrite true to overwrite existing output file
* \param[in] ascii_format true to write ASCII-encoded output
*
* \return 0 success
* \return -errno on failure
*/
int write_config_file(char *output_file, struct sk_keyfile_config *config,
bool overwrite, bool ascii_format)
{
int flags = O_WRONLY | O_CREAT;
char *ascii_data = NULL;
size_t ascii_len = 0;
int fd, rc;
if (!overwrite)
flags |= O_EXCL;
else
flags |= O_TRUNC | O_SYNC;
sk_config_cpu_to_disk(config);
fd = open(output_file, flags, 0400);
if (fd < 0) {
fprintf(stderr, "error: opening '%s': %s\n", output_file,
strerror(errno));
return -errno;
}
if (ascii_format) {
/* Generate ASCII-encoded output */
rc = sk_encode_ascii_key(config, &ascii_data, &ascii_len);
if (rc) {
fprintf(stderr,
"error: failed to encode key in ASCII format\n");
close(fd);
return -EINVAL;
}
rc = write_data_with_error_handling(fd, ascii_data, ascii_len,
output_file, "ASCII data");
free(ascii_data);
} else {
/* Binary format output */
rc = write_data_with_error_handling(fd, config, sizeof(*config),
output_file, "data");
}
close(fd);
return rc;
}
/**
* Loads the key from \a filename and returns the struct sk_keyfile_config.
* It should be freed by the caller.
*
* \param[in] filename Disk or key payload data
*
* \return sk_keyfile_config sucess
* \return NULL failure
*/
struct sk_keyfile_config *sk_read_file(char *filename)
{
struct sk_keyfile_config *config = NULL;
char *file_data = NULL;
char *ptr;
size_t bytes_read = 0;
size_t max_size;
struct stat st;
int fd;
ssize_t rc;
/* allow standard input override */
if (strcmp(filename, "-") == 0)
fd = STDIN_FILENO;
else
fd = open(filename, O_RDONLY);
if (fd == -1) {
printerr(0, "Error opening key file '%s': %s\n", filename,
strerror(errno));
return NULL;
}
/* Check file permissions for regular files */
if (fd != STDIN_FILENO) {
rc = fstat(fd, &st);
if (rc == 0 && (st.st_mode & ~(S_IFREG | 0600))) {
fprintf(stderr, "warning: "
"secret key '%s' has insecure file mode %#o\n",
filename, st.st_mode);
}
}
/* Allocate fixed buffer - twice config size should be enough */
max_size = 2 * sizeof(struct sk_keyfile_config);
file_data = malloc(max_size + 1);
if (!file_data) {
printerr(0, "Failed to allocate memory for file data\n");
goto out_close;
}
/* Read file with size limit */
ptr = file_data;
while (bytes_read < max_size) {
rc = read(fd, ptr, max_size - bytes_read);
if (rc == -1) {
if (errno == EINTR)
continue;
printerr(0, "read() failed on %s: %s\n", filename,
strerror(errno));
goto out_free;
} else if (rc == 0) {
break;
}
ptr += rc;
bytes_read += rc;
}
/* Check if file is too large */
if (bytes_read >= max_size) {
printerr(0,
"File %s too large, exceeds maximum expected size\n",
filename);
goto out_free;
}
if (fd != STDIN_FILENO)
close(fd);
fd = -1;
/* Null-terminate for ASCII processing */
file_data[bytes_read] = '\0';
if (sk_is_ascii_encoded(file_data, bytes_read)) {
config = sk_decode_ascii_key(file_data, bytes_read);
/* Free original buffer since decode allocates new one */
free(file_data);
} else {
/* Binary format - check size and process */
if (bytes_read != sizeof(struct sk_keyfile_config)) {
printerr(0,
"File %s does not have a complete key: got %zu bytes, expected %zu bytes\n",
filename, bytes_read,
sizeof(struct sk_keyfile_config));
goto out_free;
}
/* Use the existing buffer as config */
config = (struct sk_keyfile_config *)file_data;
}
if (config)
sk_config_disk_to_cpu(config);
return config;
out_free:
free(file_data);
out_close:
if (fd != -1)
close(fd);
return NULL;
}
/**
* Build an array of all keys in the user keyring that start with
* the given description.
*
* \param[in] description description to match keys against
* \param[out] keys_array found array of keys, last element is a zero key
*
* \retval 0 success
* \retval -errno on failure
*/
static int build_keys_array(char *description, key_serial_t **keys_array)
{
key_serial_t *tmp_keys, *curr_key;
key_serial_t *keys_array_;
ssize_t user_kr_len;
int i;
if (!keys_array)
return -EINVAL;
user_kr_len = keyctl_read_alloc(KEY_SPEC_USER_KEYRING,
(void **)&tmp_keys);
if (user_kr_len < 0) {
printerr(0, "Cannot find any key in user keyring\n");
*keys_array = NULL;
return -ENOENT;
}
keys_array_ = malloc((user_kr_len / sizeof(key_serial_t) + 1) *
sizeof(key_serial_t));
if (!keys_array_) {
free(tmp_keys);
*keys_array = NULL;
return -ENOMEM;
}
curr_key = keys_array_;
for (i = 0; i < user_kr_len / sizeof(key_serial_t); i++) {
key_serial_t k = tmp_keys[i];
char *desc = NULL, *sep;
ssize_t len;
len = keyctl_describe_alloc(k, &desc);
if (len < 0)
/* ignore keys that cannot be read */
continue;
/* The whole description string looks like:
* "type;uid;gid;perm;description"
* We are only interested in the final "description".
*/
sep = strstr(desc, description);
if (!sep || sep == desc || *(sep - 1) != ';')
goto next;
if (sep[strlen(description)] != '\0' &&
sep[strlen(description)] != ':')
goto next;
*curr_key = k;
curr_key++;
next:
free(desc);
}
/* Finish the array with a zero key */
*curr_key = 0;
free(tmp_keys);
*keys_array = keys_array_;
return 0;
}
/*
* Link @u into the caller's session keyring so we possess the keys we add to
* @u. keyctl_setperm() here (and keyctl_read()/keyctl_revoke() in
* sk_remove_key()) require a permission the kernel grants on a newly added key
* only via possession, not by UID match. @u is linked into @s by pam_keyinit
* at interactive login, but not under sudo, systemd, cron, or Pacemaker - so
* without this the setperm silently fails and the key keeps restrictive perms.
*/
static void sk_link_user_keyring(void)
{
/* EEXIST just means @u was already linked (e.g. interactive login). */
if (keyctl_link(KEY_SPEC_USER_KEYRING,
KEY_SPEC_SESSION_KEYRING) < 0 && errno != EEXIST)
printerr(0, "Failed to link @u to @s: %s\n", strerror(errno));
}
/**
* Checks if a key matching \a description is found in the keyring for
* logging purposes and then attempts to load the payload from \a skc keyfile
* config into a key with \a description.
*
* \param[in] skc keyfile config to load
* \param[in] description Description used for key in keyring
* \param[in] suffix true to avoid overwriting existing server key,
* by adding a suffix to the key desc
* \param[in] timeout timeout in seconds to apply on already existing
* keys with same desc
*
* \return >= 0 key serial of key successfully loaded
* \return -1 failure
*/
static key_serial_t sk_load_key(const struct sk_keyfile_config *skc,
char *description, bool suffix, int timeout)
{
key_serial_t *keys = NULL, *key_p;
struct sk_keyfile_config payload;
key_serial_t key;
memcpy(&payload, skc, sizeof(*skc));
/* In the keyring use the disk layout so keyctl pipe can be used */
sk_config_cpu_to_disk(&payload);
/* Possess keys in @u so the keyctl_setperm() below - and the
* keyctl_set_timeout() on former keys - succeed; see
* sk_link_user_keyring().
*/
sk_link_user_keyring();
/* Check to see if a key is already loaded matching description */
key = keyctl_search(KEY_SPEC_USER_KEYRING, "user", description, 0);
if (key != -1) {
if (suffix) {
/* append timestamp to key desc,
* SK_DESCRIPTION_SIZE has room for it.
*/
size_t desclen = strlen(description);
struct timeval tv;
struct tm *tm;
int rc;
/* get list of former keys */
rc = build_keys_array(description, &keys);
if (rc) {
printerr(0,
"Failed to build keys array: rc=%d\n",
rc);
free(keys);
return -1;
}
gettimeofday(&tv, NULL);
tm = localtime(&tv.tv_sec);
/* timestamp expressed as yyyymmdd_HHMMSS_USECS */
if (snprintf((char *)description + desclen,
SK_DESCRIPTION_SIZE - desclen,
":%04d%02d%02d_%02d%02d%02d_%06ld",
tm->tm_year + 1900, tm->tm_mon + 1,
tm->tm_mday, tm->tm_hour, tm->tm_min,
tm->tm_sec, tv.tv_usec) >=
SK_DESCRIPTION_SIZE - desclen) {
free(keys);
return -1;
}
printerr(2,
"Key %d found in user keyring, inserting new key with desc %s\n",
key, description);
} else {
printerr(2,
"Key %d found in user keyring, replacing\n",
key);
}
}
key = add_key("user", description, &payload, sizeof(payload),
KEY_SPEC_USER_KEYRING);
if (key != -1) {
key_perm_t perm = KEY_POS_ALL | KEY_USR_ALL |
KEY_GRP_ALL | KEY_OTH_ALL;
if (keyctl_setperm(key, perm) < 0)
printerr(0,
"Failed to set perm 0x%x on key %d: %s\n",
perm, key, strerror(errno));
printerr(2, "Added key %d with description %s\n", key,
description);
/* expire former keys to some time in the future */
key_p = keys;
while (key_p && *key_p) {
if (timeout == -1)
timeout = 2 * 24 * 60 * 60; /* 2 days */
if (keyctl_set_timeout(*key_p, timeout) < 0)
printerr(2,
"Failed to set timeout %ds on key %d\n",
timeout, *key_p);
else
printerr(2,
"Setting timeout %ds on former key %d\n",
timeout, *key_p);
key_p++;
}
} else {
printerr(0, "Failed to add key with %s\n", description);
}
free(keys);
return key;
}
/**
* Reads the key from \a path, verifies it and loads into the session keyring
* using a description determined by the the \a type. Existing keys with the
* same description are replaced.
*
* \param[in] path Path to key file
* \param[in] client Client is mounting with a server key
* \param[in] randomize true to randomize key desc, only apply to client
* \param[in] mntdir Client mount dir
* \param[in] suffix true to avoid overwriting existing server key,
* by adding a suffix to the key desc
* \param[in] timeout timeout in seconds to apply on already existing
* keys with same desc
*
* \return > 0 client file system key id if successfully loaded
* \return 0 other key type successfully loaded
* \return < 0 -errno on failure
*/
int sk_load_keyfile(char *path, bool client, bool randomize, char *mntdir,
bool suffix, int timeout)
{
struct sk_keyfile_config *config;
char description[SK_DESCRIPTION_SIZE + 1];
struct stat buf;
int keyid = 0;
int i, rc;
rc = stat(path, &buf);
if (rc == -1) {
printerr(0, "stat() failed for file %s: %s\n", path,
strerror(errno));
return -errno;
}
read_sk:
config = sk_read_file(path);
if (!config)
return -ENOKEY;
/* Similar to ssh, require adequate care of key files */
if (buf.st_mode & (S_IRGRP | S_IWGRP | S_IWOTH | S_IXOTH)) {
printerr(0,
"Shared key files must be readable/writeable only by owner\n");
return -EACCES;
}
if (sk_validate_config(config)) {
rc = -ENOKEY;
goto out;
}
/* The server side can have multiple key files per file system so
* the nodemap name is appended to the key description to uniquely
* identify it */
if (config->skc_type & SK_TYPE_MGS) {
/* Any key can be an MGS key as long as we are told to use it */
rc = snprintf(description, sizeof(description), "lustre:MGS:%s",
config->skc_nodemap);
if (rc >= sizeof(description)) {
rc = -ENAMETOOLONG;
goto out;
}
if (sk_load_key(config, description, suffix, timeout) == -1) {
rc = -ENOKEY;
goto out;
}
}
if (config->skc_type & SK_TYPE_SERVER) {
if (client) {
/* Client is mounting with a server key:
* generate prime on the fly and reload.
*/
config->skc_type = SK_TYPE_CLIENT;
/* This message is verified by sanity-sec.sh test_100 */
printf("Generating DH parameters to turn %s into a client key, this can take a while...\n",
path);
if (gen_ssk_prime(config))
goto out;
if (write_config_file(path, config, true, false))
goto out;
free(config);
goto read_sk;
}
/* Server keys need to have the file system name in the key */
if (config->skc_fsname[0] == '\0') {
printerr(0,
"Key configuration has no file system attribute. Can't load as server type\n");
rc = -ENOKEY;
goto out;
}
rc = snprintf(description, sizeof(description), "lustre:%s:%s",
config->skc_fsname, config->skc_nodemap);
if (rc >= sizeof(description)) {
rc = -ENAMETOOLONG;
goto out;
}
if (sk_load_key(config, description, suffix, timeout) == -1) {
rc = -ENOKEY;
goto out;
}
}
if (config->skc_type & SK_TYPE_CLIENT) {
/* Load client file system key */
if (config->skc_fsname[0] != '\0') {
char uuid[UUID_MAX + 2] = { 0 }; /* additional '\n' */
char rand[5];
if (mntdir) {
char fsname[LUSTRE_MAXFSNAME + 1];
/* If mount path is provided, check it refers to
* the same fs as the key.
*/
rc = llapi_get_fsname(mntdir, fsname,
sizeof(fsname));
if (rc < 0) {
printerr(0,
"Cannot get Lustre fsname for %s: rc=%d\n",
mntdir, rc);
goto out;
}
if (strcmp(fsname, config->skc_fsname) != 0) {
rc = -EINVAL;
printerr(0,
"Key to load is for file system %s, but %s is mounted at %s: rc=%d\n",
config->skc_fsname, fsname,
mntdir, rc);
goto out;
}
/* Fetch uuid from mount point */
rc = llapi_get_agent_uuid(mntdir, uuid,
sizeof(uuid));
if (rc < 0) {
printerr(0,
"Cannot get client uuid for %s: rc=%d\n",
mntdir, rc);
goto out;
}
if (uuid[strlen(uuid) - 1] == '\n')
uuid[strlen(uuid) - 1] = '\0';
}
/* add random chars at end of key if requested... */
if (randomize)
snprintf(rand, sizeof(rand), "%.4lX", random());
rc = snprintf(description, sizeof(description),
"lustre:%s%s%s", config->skc_fsname,
randomize || mntdir ? ":" : "",
randomize ? rand : (mntdir ? uuid : ""));
if (rc >= sizeof(description)) {
rc = -ENAMETOOLONG;
goto out;
}
keyid = sk_load_key(config, description, false, -1);
if (keyid == -1) {
rc = -ENOKEY;
goto out;
}
/* ... and also load legacy key desc for interop */
if (randomize) {
rc = snprintf(description, sizeof(description),
"lustre:%s", config->skc_fsname);
if (rc < sizeof(description))
(void)sk_load_key(config, description,
false, -1);
}
}
/* Load client MGC keys */
for (i = 0; i < MAX_MGSNIDS; i++) {
if (config->skc_mgsnids[i] == LNET_NID_ANY)
continue;
rc = snprintf(description, sizeof(description),
"lustre:MGC%s",
libcfs_nid2str(config->skc_mgsnids[i]));
if (rc >= sizeof(description)) {
rc = -ENAMETOOLONG;
goto out;
}
if (sk_load_key(config, description, false, -1) == -1) {
rc = -ENOKEY;
goto out;
}
}
}
rc = 0;
out:
free(config);
if (keyid > 0)
return keyid;
return rc;
}
/**
* Checks if keys matching \a description are found in the keyring. Each key is
* verified against the key from \a skc, and if it matches, the key is revoked
* and removed from the keyring.
*
* \param[in] skc keyfile config to load
* \param[in] description Description used for key in keyring
*
* \retval 0 success (at least one key removed)
* \retval < 0 failure (no key removed)
*/
static int sk_remove_key(const struct sk_keyfile_config *skc, char *description)
{
key_serial_t *keys = NULL;
struct sk_keyfile_config file_payload;
struct sk_keyfile_config *keyring_payload;
size_t key_bytes;
int removed_cnt = 0;
int i, payload_len, rc;
memcpy(&file_payload, skc, sizeof(*skc));
/* store key length in bytes for later key comparison before swab */
key_bytes = file_payload.skc_shared_keylen / 8;
/* In the keyring use the disk layout so keyctl pipe can be used */
sk_config_cpu_to_disk(&file_payload);
/* Possess keys in @u so the keyctl_read()/keyctl_revoke() below can act
* on keys that were loaded with the restrictive default perms (e.g. by
* an older mount.lustre, or before this fix); see
* sk_link_user_keyring().
*/
sk_link_user_keyring();
rc = build_keys_array(description, &keys);
if (rc)
return rc;
/* for each key on the keyring, sanity check first before unlink:
* 1. skc_shared_keylen matches for the file key and keyring key
* 2. skc_shared_key matches for the file key and keyring key
*/
for (i = 0; keys[i] != 0; i++) {
payload_len = keyctl_read_alloc(keys[i], (void **)&keyring_payload);
if (payload_len < 0) {
printerr(0, "failed to read key %d - skipping\n", keys[i]);
continue;
}
if (payload_len != sizeof(struct sk_keyfile_config)) {
printerr(0, "key %d has unexpected size %d - skipping\n",
keys[i], payload_len);
goto next;
}
if (keyring_payload->skc_shared_keylen !=
file_payload.skc_shared_keylen) {
printerr(1,
"key %d has different shared key length - skipping\n",
keys[i]);
goto next;
}
if (memcmp(keyring_payload->skc_shared_key,
file_payload.skc_shared_key, key_bytes) != 0) {
printerr(1,
"key %d has different shared key - skipping\n",
keys[i]);
goto next;
}
rc = keyctl_revoke(keys[i]);
if (rc == -1) {
printerr(0, "failed to revoke key %d: %s\n", keys[i],
strerror(errno));
goto next;
}
rc = keyctl_unlink(keys[i], KEY_SPEC_USER_KEYRING);
if (rc == -1) {
printerr(0, "failed to unlink key %d: %s\n", keys[i],
strerror(errno));
goto next;
}
printerr(2, "Key %d revoked and removed from keyring\n",
keys[i]);
removed_cnt++;
next:
free(keyring_payload);
}
free(keys);
if (removed_cnt > 0)
printerr(1, "Removed %d key(s) from keyring\n", removed_cnt);
else
printerr(0, "No matching key found to remove\n");
return removed_cnt > 0 ? 0 : -ENOKEY;
}
/**
* Removes the key from \a path, verifies it and removes matching keys from the
* keyring.
*
* \param[in] path Path to key file
*
* \retval 0 success (at least one key removed)
* \retval < 0 failure (no key removed)
*/
int sk_remove_keyfile(char *path)
{
struct sk_keyfile_config *config;
char description[SK_DESCRIPTION_SIZE + 1] = { 0 };
struct stat buf;
int rc;
rc = stat(path, &buf);
if (rc == -1) {
printerr(0, "stat() failed for file %s: %s\n", path,
strerror(errno));
return -errno;
}
config = sk_read_file(path);
if (!config)
return -ENOKEY;
rc = sk_validate_config(config);
if (rc != 0) {
rc = -EINVAL;
goto out;
}
if (config->skc_type & SK_TYPE_MGS) {
rc = snprintf(description, sizeof(description), "lustre:MGS:%s",
config->skc_nodemap);
} else if (config->skc_type & SK_TYPE_SERVER) {
rc = snprintf(description, sizeof(description), "lustre:%s:%s",
config->skc_fsname, config->skc_nodemap);
} else if (config->skc_type & SK_TYPE_CLIENT) {
rc = snprintf(description, sizeof(description), "lustre:%s",
config->skc_fsname);
} else {
printerr(0, "Invalid key type\n");
rc = -EINVAL;
goto out;
}
if (rc >= sizeof(description)) {
rc = -ENAMETOOLONG;
goto out;
}
rc = sk_remove_key(config, description);
out:
free(config);
return rc;
}
/**
* Byte swaps config from cpu format to disk
*
* \param[in,out] config sk_keyfile_config to swap
*/
void sk_config_cpu_to_disk(struct sk_keyfile_config *config)
{
int i;
if (!config)
return;
config->skc_version = htobe32(config->skc_version);
config->skc_hmac_alg = htobe16(config->skc_hmac_alg);
config->skc_crypt_alg = htobe16(config->skc_crypt_alg);
config->skc_expire = htobe32(config->skc_expire);
config->skc_shared_keylen = htobe32(config->skc_shared_keylen);
config->skc_prime_bits = htobe32(config->skc_prime_bits);
for (i = 0; i < MAX_MGSNIDS; i++)
config->skc_mgsnids[i] = htobe64(config->skc_mgsnids[i]);
}
/**
* Byte swaps config from disk format to cpu
*
* \param[in,out] config sk_keyfile_config to swap
*/
void sk_config_disk_to_cpu(struct sk_keyfile_config *config)
{
int i;
if (!config)
return;
config->skc_version = be32toh(config->skc_version);
config->skc_hmac_alg = be16toh(config->skc_hmac_alg);
config->skc_crypt_alg = be16toh(config->skc_crypt_alg);
config->skc_expire = be32toh(config->skc_expire);
config->skc_shared_keylen = be32toh(config->skc_shared_keylen);
config->skc_prime_bits = be32toh(config->skc_prime_bits);
for (i = 0; i < MAX_MGSNIDS; i++)
config->skc_mgsnids[i] = be64toh(config->skc_mgsnids[i]);
}
/**
* Verifies the on key payload format is valid
*
* \param[in] config sk_keyfile_config
*
* \return -1 failure
* \return 0 success
*/
int sk_validate_config(const struct sk_keyfile_config *config)
{
int i;
if (!config) {
printerr(0, "Null configuration passed\n");
return -1;
}
if (config->skc_version != SK_CONF_VERSION) {
printerr(0, "Invalid version\n");
return -1;
}
if (config->skc_hmac_alg == SK_HMAC_INVALID) {
printerr(0, "Invalid HMAC algorithm\n");
return -1;
}
if (config->skc_crypt_alg == SK_CRYPT_INVALID) {
printerr(0, "Invalid crypt algorithm\n");
return -1;
}
if (config->skc_expire < 60 || config->skc_expire > INT_MAX) {
/* Try to limit key expiration to some reasonable minimum and
* also prevent values over INT_MAX because there appears
* to be a type conversion issue */
printerr(0, "Invalid expiration time should be between %d "
"and %d\n", 60, INT_MAX);
return -1;
}
if (config->skc_prime_bits % 8 != 0 ||
config->skc_prime_bits > SK_MAX_P_BYTES * 8) {
printerr(0, "Invalid session key length must be a multiple of 8"
" and less then %d bits\n",
SK_MAX_P_BYTES * 8);
return -1;
}
if (config->skc_shared_keylen % 8 != 0 ||
config->skc_shared_keylen > SK_MAX_KEYLEN_BYTES * 8){
printerr(0, "Invalid shared key max length must be a multiple "
"of 8 and less then %d bits\n",
SK_MAX_KEYLEN_BYTES * 8);
return -1;
}
/* Check for terminating nulls on strings */
for (i = 0; i < sizeof(config->skc_fsname) &&
config->skc_fsname[i] != '\0'; i++)
; /* empty loop */
if (i == sizeof(config->skc_fsname)) {
printerr(0, "File system name not null terminated\n");
return -1;
}
for (i = 0; i < sizeof(config->skc_nodemap) &&
config->skc_nodemap[i] != '\0'; i++)
; /* empty loop */
if (i == sizeof(config->skc_nodemap)) {
printerr(0, "Nodemap name not null terminated\n");
return -1;
}
if (config->skc_type == SK_TYPE_INVALID) {
printerr(0, "Invalid key type\n");
return -1;
}
return 0;
}
/**
* Checks if the given data is ASCII-encoded SSK key format
*
* \param[in] data Data to check
* \param[in] len Length of data
*
* \return 1 ASCII-encoded format
* \return 0 binary format
*/
int sk_is_ascii_encoded(const char *data, size_t len)
{
if (len < SK_ASCII_HEADER_LEN)
return 0;
return memcmp(data, SK_ASCII_HEADER, SK_ASCII_HEADER_LEN) == 0;
}
/**
* Decodes ASCII-encoded SSK key data into sk_keyfile_config structure
*
* \param[in,out] ascii_data ASCII-encoded key data (may be modified)
* \param[in] len Length of ASCII data
*
* \return sk_keyfile_config success
* \return NULL failure
*/
struct sk_keyfile_config *sk_decode_ascii_key(char *ascii_data, size_t len)
{
struct sk_keyfile_config *config;
const char *encoded_start;
size_t encoded_len;
int decoded_len;
if (!sk_is_ascii_encoded(ascii_data, len)) {
printerr(0, "Data is not ASCII-encoded SSK key format\n");
return NULL;
}
/* Skip the header string and any whitespace */
encoded_start = ascii_data + SK_ASCII_HEADER_LEN;
while (encoded_start < ascii_data + len &&
(*encoded_start == ' ' || *encoded_start == '\t' ||
*encoded_start == '\n' || *encoded_start == '\r'))
encoded_start++;
encoded_len = len - (encoded_start - ascii_data);
if (encoded_len <= 0) {
printerr(0, "No encoded data found after header string\n");
return NULL;
}
/* Remove trailing whitespace */
while (encoded_len > 0 &&
(encoded_start[encoded_len - 1] == ' ' ||
encoded_start[encoded_len - 1] == '\t' ||
encoded_start[encoded_len - 1] == '\n' ||
encoded_start[encoded_len - 1] == '\r'))
encoded_len--;
decoded_len = gss_base64url_decode((char **)&encoded_start,
ascii_data,
sizeof(struct sk_keyfile_config));
if (decoded_len != sizeof(struct sk_keyfile_config)) {
printerr(0,
"Failed to decode base64url data or incorrect size: got %d bytes, expected %zu bytes\n",
decoded_len, sizeof(struct sk_keyfile_config));
return NULL;
}
/* Allocate new buffer for the result */
config = malloc(sizeof(struct sk_keyfile_config));
if (!config) {
printerr(0, "Failed to allocate memory for config\n");
return NULL;
}
/* Copy the decoded data to the result buffer */
memcpy(config, ascii_data, sizeof(struct sk_keyfile_config));
return config;
}
/**
* Encodes sk_keyfile_config structure into ASCII format
*
* \param[in] config Key configuration to encode
* \param[out] ascii_data Allocated ASCII-encoded data (caller must free)
* \param[out] ascii_len Length of ASCII-encoded data
*
* \return 0 success
* \return -1 failure
*/
int sk_encode_ascii_key(const struct sk_keyfile_config *config,
char **ascii_data, size_t *ascii_len)
{
char *output = NULL;
size_t encoded_len;
size_t total_len;
char *ptr;
int len;
int rc;
if (!config || !ascii_data || !ascii_len) {
printerr(0, "Invalid parameters for ASCII encoding\n");
return -1;
}
/* Calculate total length: header + encoded data + newline + null
* terminator
*/
encoded_len = BASE64URL_CHARS(sizeof(struct sk_keyfile_config));
total_len = SK_ASCII_HEADER_LEN + encoded_len + 2;
output = malloc(total_len);
if (!output) {
printerr(0, "Failed to allocate memory for ASCII output\n");
return -1;
}
/* header */
memcpy(output, SK_ASCII_HEADER, SK_ASCII_HEADER_LEN);
ptr = output + SK_ASCII_HEADER_LEN;
len = encoded_len + 1; /* +1 for trailing space padding */
rc = gss_base64url_encode(&ptr, &len, (const __u8 *)config,
sizeof(*config));
if (rc < 0) {
printerr(0, "Failed to base64url encode key data\n");
free(output);
return -1;
}
/* back up pointer to trailing space and bound check for new line */
ptr--;
if (ptr < output || ptr > output + total_len - 2) {
printerr(0,
"Invalid pointer position after base64url encoding\n");
free(output);
return -1;
}
/* add newline, overwrite trailing space */
*ptr++ = '\n';
*ptr = '\0';
*ascii_data = output;
*ascii_len = strlen(output);
return 0;
}
/**
* Hashes \a string and places the hash in \a hash
* at \a hash
*
* \param[in] string Null terminated string to hash
* \param[in] hash_alg OpenSSL EVP_MD to use for hash
* \param[in,out] hash gss_buffer_desc to hold the result
*
* \return -1 failure
* \return 0 success
*/
static int sk_hash_string(const char *string, const EVP_MD *hash_alg,
gss_buffer_desc *hash)
{
EVP_MD_CTX *ctx = EVP_MD_CTX_create();
size_t len = strlen(string);
unsigned int hashlen;
if (!hash->value || hash->length < EVP_MD_size(hash_alg))
goto out_err;
if (!EVP_DigestInit_ex(ctx, hash_alg, NULL))
goto out_err;
if (!EVP_DigestUpdate(ctx, string, len))
goto out_err;
if (!EVP_DigestFinal_ex(ctx, hash->value, &hashlen))
goto out_err;
EVP_MD_CTX_destroy(ctx);
hash->length = hashlen;
return 0;
out_err:
EVP_MD_CTX_destroy(ctx);
return -1;
}
/**
* Hashes \a string and verifies the resulting hash matches the value
* in \a current_hash
*
* \param[in] string Null terminated string to hash
* \param[in] hash_alg OpenSSL EVP_MD to use for hash
* \param[in,out] current_hash gss_buffer_desc to compare to
*
* \return gss error failure
* \return GSS_S_COMPLETE success
*/
uint32_t sk_verify_hash(const char *string, const EVP_MD *hash_alg,
const gss_buffer_desc *current_hash)
{
gss_buffer_desc hash;
unsigned char hashbuf[EVP_MAX_MD_SIZE];
hash.value = hashbuf;
hash.length = sizeof(hashbuf);
if (sk_hash_string(string, hash_alg, &hash))
return GSS_S_FAILURE;
if (current_hash->length != hash.length)
return GSS_S_DEFECTIVE_TOKEN;
if (memcmp(current_hash->value, hash.value, hash.length))
return GSS_S_BAD_SIG;
return GSS_S_COMPLETE;
}
static inline int sk_config_has_mgsnid(struct sk_keyfile_config *config,
const char *mgsnid)
{
lnet_nid_t nid;
int i;
nid = libcfs_str2nid(mgsnid);
if (nid == LNET_NID_ANY)
return 0;
for (i = 0; i < MAX_MGSNIDS; i++)
if (config->skc_mgsnids[i] == nid)
return 1;
return 0;
}
/**
* Create an sk_cred structure populated with initial configuration info and the
* key. \a tgt \a uuid and \a nodemap are used in determining the expected key
* description so the key can be found by searching the keyring.
* This is done because there is no easy way to pass keys from the mount command
* all the way to the request_key call. In addition any keys can be dynamically
* added to the keyrings and still found. The keyring that needs to be used
* must be the session keyring.
* This function can be called repeatedly to cycle through multiple matching
* keys in the kernel keyring, if non-NULL pointers are provided for
* \a user_keys_p and \a key_p. The \a key_p pointer is internally updated to
* point to the key to use. When \a user_keys_p is no longer needed, it must be
* freed by the caller.
*
* \param[in] tgt Target file system
* \param[in] nodemap Cluster name for the key. This correlates to
* the nodemap name and is used by the server side.
* For the client this will be NULL.
* \param[in] uuid Client uuid
* \param[in] flags Flags for the credentials
* \param[out] user_keys_p Pointer to keys array, must be freed by caller
* \param[in,out] key_p Pointer to current key in array, internal use
*
* \return sk_cred Allocated struct sk_cred on success
* \return NULL failure
*/
struct sk_cred *sk_create_cred(const char *tgt, const char *nodemap,
const char *uuid, const uint32_t flags,
void **user_keys_p, void **key_p)
{
char description[SK_DESCRIPTION_SIZE + 1] = { 0 };
char fsname[MTI_NAME_MAXLEN + 1] = { 0 };
struct sk_keyfile_config *config;
struct sk_kernel_ctx *kctx;
struct sk_cred *skc = NULL;
const char *mgsnid = NULL;
long sk_key;
char *ptr;
int len, keylen, rc;
/* extract the file system name from target */
ptr = index(tgt, '-');
if (!ptr) {
len = strlen(tgt);
/* This must be an MGC target */
if (strncmp(tgt, "MGC", 3) || len <= 3) {
printerr(0, "Invalid target name\n");
return NULL;
}
mgsnid = tgt + 3;
} else {
len = ptr - tgt;
}
if (len > MTI_NAME_MAXLEN) {
printerr(0, "Invalid target name\n");
return NULL;
}
memcpy(fsname, tgt, len);
if (key_p && *key_p)
goto use_key;
printerr(2, "Creating credentials for target: %s with nodemap: %s\n",
tgt, nodemap);
build_desc:
if (nodemap) {
if (mgsnid)
rc = snprintf(description, sizeof(description),
"lustre:MGS:%s", nodemap);
else
rc = snprintf(description, sizeof(description),
"lustre:%s:%s", fsname, nodemap);
} else {
rc = snprintf(description, sizeof(description), "lustre:%s%s%s",
fsname, uuid ? ":" : "", uuid ?: "");
}
if (rc >= sizeof(description)) {
printerr(0, "Invalid key description\n");
return NULL;
}
if (key_p && user_keys_p && *user_keys_p == NULL) {
if (*key_p) {
printerr(0, "Invalid key index\n");
return NULL;
}
rc = build_keys_array(description,
(key_serial_t **)user_keys_p);
if (rc) {
printerr(0, "Failed to build keys array: rc=%d\n", rc);
return NULL;
}
*key_p = *user_keys_p;
}
if (key_p && *key_p) {
use_key:
sk_key = *((key_serial_t *)*key_p);
if (!sk_key) {
printerr(0, "No more valid keys in user keyring\n");
return NULL;
}
printerr(2, "Using key with serial 0x%lx\n", sk_key);
} else {
printerr(2, "Searching for key with description: %s\n",
description);
sk_key = keyctl_search(KEY_SPEC_USER_KEYRING, "user",
description, 0);
if (sk_key == -1) {
if (!nodemap && uuid) {
uuid = NULL;
goto build_desc;
}
printerr(1, "No key found for %s\n", description);
return NULL;
}
}
keylen = keyctl_read_alloc(sk_key, (void **)&config);
if (keylen == -1) {
printerr(0, "keyctl_read() failed for key %ld: %s\n", sk_key,
strerror(errno));
return NULL;
} else if (keylen != sizeof(*config)) {
printerr(0, "Unexpected key size: %d returned for key %ld, "
"expected %zu bytes\n",
keylen, sk_key, sizeof(*config));
goto out_err;
}
sk_config_disk_to_cpu(config);
if (sk_validate_config(config)) {
printerr(0, "Invalid key configuration for key: %ld\n", sk_key);
goto out_err;
}
if (mgsnid && !sk_config_has_mgsnid(config, mgsnid)) {
printerr(0, "Target name does not match key's MGS NIDs\n");
goto out_err;
}
if (!mgsnid && strcmp(fsname, config->skc_fsname)) {
printerr(0, "Target name does not match key's file system\n");
goto out_err;
}
skc = malloc(sizeof(*skc));
if (!skc) {
printerr(0, "Failed to allocate memory for sk_cred\n");
goto out_err;
}
/* this initializes all gss_buffer_desc to empty as well */
memset(skc, 0, sizeof(*skc));
skc->sc_flags = flags;
skc->sc_tgt.length = strlen(tgt) + 1;
skc->sc_tgt.value = malloc(skc->sc_tgt.length);
if (!skc->sc_tgt.value) {
printerr(0, "Failed to allocate memory for target\n");
goto out_err;
}
memcpy(skc->sc_tgt.value, tgt, skc->sc_tgt.length);
skc->sc_nodemap_hash.length = EVP_MD_size(EVP_sha256());
skc->sc_nodemap_hash.value = malloc(skc->sc_nodemap_hash.length);
if (!skc->sc_nodemap_hash.value) {
printerr(0, "Failed to allocate memory for nodemap hash\n");
goto out_err;
}
if (sk_hash_string(config->skc_nodemap, EVP_sha256(),
&skc->sc_nodemap_hash)) {
printerr(0, "Failed to generate hash for nodemap name\n");
goto out_err;
}
kctx = &skc->sc_kctx;
kctx->skc_version = config->skc_version;
strcpy(kctx->skc_hmac_alg, sk_hmac2name(config->skc_hmac_alg));
strcpy(kctx->skc_crypt_alg, sk_crypt2name(config->skc_crypt_alg));
kctx->skc_expire = config->skc_expire;
/* key payload format is in bits, convert to bytes */
kctx->skc_shared_key.length = config->skc_shared_keylen / 8;
kctx->skc_shared_key.value = malloc(kctx->skc_shared_key.length);
if (!kctx->skc_shared_key.value) {
printerr(0, "Failed to allocate memory for shared key\n");
goto out_err;
}
memcpy(kctx->skc_shared_key.value, config->skc_shared_key,
kctx->skc_shared_key.length);
skc->sc_p.length = config->skc_prime_bits / 8;
skc->sc_p.value = malloc(skc->sc_p.length);
if (!skc->sc_p.value) {
printerr(0, "Failed to allocate p\n");
goto out_err;
}
memcpy(skc->sc_p.value, config->skc_p, skc->sc_p.length);
free(config);
if (key_p && *key_p)
/* update key pointer to next in array */
*key_p = ((key_serial_t *)*key_p) + 1;
return skc;
out_err:
sk_free_cred(skc);
free(config);
return NULL;
}
#define SK_GENERATOR 2
#define DH_NUMBER_ITERATIONS_FOR_PRIME 64
/* OpenSSL 1.1.1c increased the number of rounds used for Miller-Rabin testing
* of the prime provided as input parameter to DH_check(). This makes the check
* roughly x10 longer, and causes request timeouts when an SSK flavor is being
* used.
* Instead, use a dynamic number Miller-Rabin rounds based on the speed of the
* check on the current system, evaluated when the lsvcgssd daemon starts, but
* at least as many as OpenSSL 1.1.1b used for the same key size. If default
* DH_check() duration is OK, use it directly instead of limiting the rounds.
* If \a num_rounds == 0, we just call original DH_check() directly.
*
* OpenSSL v3 internally forces a minimum of 64 rounds when checking prime, so
* it is no longer possible to test prime check speed with fewer rounds. In this
* case, do not bother and directly call EVP_PKEY_param_check.
*/
#ifdef HAVE_OPENSSL_EVP_PKEY
static bool sk_is_dh_valid(EVP_PKEY_CTX *ctx)
{
if (EVP_PKEY_param_check(ctx) != 1) {
printerr(0, "EVP_PKEY_param_check failed\n");
ERR_print_errors_fp(stderr);
return false;
}
return true;
}
#else
static inline bool sk_check_dh(const DH *dh, int num_rounds, bool fullcheck)
{
const BIGNUM *p = NULL, *g = NULL;
BN_ULONG word;
BN_CTX *ctx;
BIGNUM *r;
bool valid = false;
int rc;
DH_get0_pqg(dh, &p, NULL, &g);
if (!p || !g)
return false;
if (!BN_is_word(g, SK_GENERATOR)) {
printerr(0, "%s: Diffie-Hellman generator is not %u\n",
program_invocation_short_name, SK_GENERATOR);
return false;
}
word = BN_mod_word(p, 24);
/* OpenSSL v3 changed the way the prime is generated,
* using p mod 24 == 23.
* So we must accept word == 23 if the prime was generated
* by a client with OpenSSL v3.
*/
if ((word != 11) && (word != 23)) {
printerr(0, "%s: Diffie-Hellman prime modulo=%lu unsuitable\n",
program_invocation_short_name, word);
return false;
}
if (!fullcheck)
return true;
ctx = BN_CTX_new();
if (ctx == NULL) {
printerr(0, "%s: Diffie-Hellman error allocating context\n",
program_invocation_short_name);
return false;
}
BN_CTX_start(ctx);
r = BN_CTX_get(ctx); /* must be called before "ctx" used elsewhere */
rc = BN_is_prime_ex(p, num_rounds, ctx, NULL);
if (rc == 0)
printerr(0, "%s: Diffie-Hellman 'p' not prime in %u rounds\n",
program_invocation_short_name, num_rounds);
if (rc <= 0)
goto out_free;
if (!BN_rshift1(r, p)) {
printerr(0, "%s: error shifting BigNum 'r' by 'p'\n",
program_invocation_short_name);
goto out_free;
}
rc = BN_is_prime_ex(r, num_rounds, ctx, NULL);
if (rc == 0)
printerr(0, "%s: Diffie-Hellman 'r' not prime in %u rounds\n",
program_invocation_short_name, num_rounds);
if (rc <= 0)
goto out_free;
valid = true;
out_free:
BN_CTX_end(ctx);
BN_CTX_free(ctx);
return valid;
}
static bool sk_is_dh_valid(const DH *dh, int num_rounds)
{
int rc;
if (num_rounds == 0) {
int codes = 0;
rc = DH_check(dh, &codes);
if (codes == DH_NOT_SUITABLE_GENERATOR &&
sk_check_dh(dh, num_rounds, false))
return true;
if (rc != 1 || codes) {
printerr(0, "DH_check(0) failed: codes=%#x: rc=%d\n",
codes, rc);
return false;
}
return true;
}
return sk_check_dh(dh, num_rounds, true);
}
#endif
#ifndef HAVE_OPENSSL_EVP_PKEY
#define VALUE_LENGTH 256
static unsigned char test_prime[VALUE_LENGTH] =
"\xf7\xfa\x49\xd8\xec\xb1\x3b\xff\x26\x10\x3f\xc5\x3a\xc5\xcc\x40"
"\x4f\xbf\x92\xe1\x8b\x83\xe7\xa2\xba\x0f\x51\x5a\x91\x48\xe0\xa3"
"\xf1\x4d\xbc\xbb\x8a\x28\x14\xac\x02\x23\x76\x42\x17\x4d\x3c\xdc"
"\x5e\x4f\x80\x1f\xd7\x54\x1c\x50\xac\x3b\x28\x68\x8d\x71\x41\x7f"
"\xa7\x1c\x2f\x22\xd3\xa8\x91\xb2\x64\xb6\x84\xa6\xcf\x06\x16\x91"
"\x2f\xb8\xb4\x42\x1d\x3a\x4e\x3a\x0c\x7f\x04\x69\x78\xb5\x8f\x92"
"\x07\x89\xac\x24\x06\x53\x2c\x23\xec\xaa\x5c\xb4\x7b\x49\xbc\xf4"
"\x90\x67\x71\x9c\x24\x2c\x1d\x8d\x76\xc8\x85\x4e\x19\xf1\xf9\x33"
"\x45\xbd\x9f\x7d\x0a\x08\x8c\x22\xcc\x35\xf3\x5b\xab\x3f\x24\x9d"
"\x61\x70\x86\xbb\xbe\xd8\xb0\xf8\x34\xfa\xeb\x5b\x8e\xf2\x62\x23"
"\xd1\xfb\xbb\xb8\x21\x71\x1e\x39\x39\x59\xe0\x82\x98\x41\x84\x40"
"\x1f\xd3\x9b\xa3\x73\xdb\xec\xe0\xc0\xde\x2d\x1c\xea\x43\x40\x93"
"\x98\x38\x03\x36\x1e\xe1\xe7\x39\x7b\x35\x92\x4a\x51\xa5\x91\x63"
"\xd5\x31\x98\x3d\x89\x27\x6f\xcc\x69\xff\xbe\x31\x13\xdc\x2f\x72"
"\x2d\xab\x6a\xb7\x13\xd3\x47\xda\xaa\xf3\x3c\xa0\xfd\xaa\x0f\x02"
"\x96\x81\x1a\x26\xe8\xf7\x25\x65\x33\x78\xd9\x6b\x6d\xb0\xd9\xfb";
/**
* Measure time taken by prime testing routine for a 2048 bit long prime,
* depending on the number of check rounds.
*
* \param[in] usec_check_max max time allowed for DH_check completion
*
* \retval max number of rounds to keep prime testing under usec_check_max
* return 0 if we should use the default DH_check rounds
*/
int sk_speedtest_dh_valid(unsigned int usec_check_max, pid_t *child)
{
DH *dh;
BIGNUM *p, *g;
struct sigaction sa;
int num_rounds, prev_rounds = 0;
/* Set SIGCHLD disposition so that child that terminates
* does not become a zombie.
*/
sa.sa_handler = NULL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_NOCLDWAIT;
if (sigaction(SIGCHLD, &sa, NULL) == -1)
printerr(2, "SIGCHLD sigaction failed\n");
*child = fork();
if (*child == -1) {
printerr(0, "cannot fork child for speedtest: %s\n",
strerror(errno));
/* in this case the speedtest cannot be run, so return 0
* to use default num rounds for prime testing
*/
return 0;
} else if (*child != 0) {
/* parent returns immediately,
* 0 means num rounds is not determined yet
*/
return 0;
}
/* now in forked child process, start speed test */
dh = DH_new();
if (!dh)
return 0;
p = BN_bin2bn(test_prime, VALUE_LENGTH, NULL);
if (!p)
goto free_dh;
g = BN_new();
if (!g)
goto free_p;
if (!BN_set_word(g, SK_GENERATOR))
goto free_g;
/* "dh" takes over freeing of 'p' and 'g' if this succeeds */
if (!DH_set0_pqg(dh, p, NULL, g)) {
free_g:
BN_free(g);
free_p:
BN_free(p);
goto free_dh;
}
for (num_rounds = 0;
num_rounds <= DH_NUMBER_ITERATIONS_FOR_PRIME;
num_rounds += (num_rounds <= 4 ? 4 : 8)) {
unsigned int usec_this;
int j;
/* get max duration of 4 runs at current number of rounds */
usec_this = 0;
for (j = 0; j < 4; j++) {
struct timeval now, prev;
unsigned int usec_curr;
gettimeofday(&prev, NULL);
if (!sk_is_dh_valid(dh, num_rounds)) {
/* if test_prime is found bad, use default */
prev_rounds = 0;
goto free_dh;
}
gettimeofday(&now, NULL);
usec_curr = (now.tv_sec - prev.tv_sec) * 1000000 +
now.tv_usec - prev.tv_usec;
if (usec_curr > usec_this)
usec_this = usec_curr;
}
printerr(2, "%s: %d rounds: %d usec\n",
program_invocation_short_name, num_rounds, usec_this);
if (num_rounds == 0) {
if (usec_this <= usec_check_max)
/* using original check rounds as implemented in
* DH_check() took less time than the max allowed,
* so just use original DH_check()
*/
break;
} else if (usec_this >= usec_check_max) {
break;
}
prev_rounds = num_rounds;
}
free_dh:
DH_free(dh);
return prev_rounds;
}
#endif /* !HAVE_OPENSSL_EVP_PKEY */
#ifdef HAVE_OPENSSL_EVP_PKEY
static uint32_t __sk_gen_params(struct sk_cred *skc, BIGNUM *p, BIGNUM *g,
int num_rounds)
{
EVP_PKEY_CTX *ctx = NULL, *ctx_from_key = NULL;
OSSL_PARAM_BLD *tmpl = NULL;
OSSL_PARAM *params = NULL;
EVP_PKEY *key = NULL;
uint32_t rc = GSS_S_FAILURE;
tmpl = OSSL_PARAM_BLD_new();
if (!tmpl ||
!OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_FFC_P, p) ||
!OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_FFC_G, g)) {
printerr(0, "error: params cannot be pushed\n");
goto err;
}
params = OSSL_PARAM_BLD_to_param(tmpl);
if (!params) {
printerr(0, "error: params cannot be allocated\n");
goto err;
}
ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
if (!ctx ||
EVP_PKEY_fromdata_init(ctx) != 1 ||
EVP_PKEY_fromdata(ctx, &key,
EVP_PKEY_KEY_PARAMETERS, params) != 1) {
printerr(0, "error: params cannot be set\n");
goto err;
}
ctx_from_key = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL);
if (!ctx_from_key) {
printerr(0, "error: ctx_from_key cannot be allocated\n");
goto err;
}
/* Verify that we have a safe prime and valid generator */
if (!sk_is_dh_valid(ctx_from_key))
goto err;
skc->sc_params = NULL;
if (EVP_PKEY_keygen_init(ctx_from_key) != 1 ||
EVP_PKEY_keygen(ctx_from_key, &skc->sc_params) != 1) {
printerr(0, "Failed to generate public DH key: %s\n",
ERR_error_string(ERR_get_error(), NULL));
goto err;
}
/* skc->sc_pub_key.value is allocated by
* EVP_PKEY_get1_encoded_public_key
*/
skc->sc_pub_key.length =
EVP_PKEY_get1_encoded_public_key(skc->sc_params,
(unsigned char **)&skc->sc_pub_key.value);
if (skc->sc_pub_key.length == 0) {
printerr(0, "error: cannot get pub key\n");
skc->sc_pub_key.value = NULL;
goto err;
}
rc = GSS_S_COMPLETE;
err:
EVP_PKEY_CTX_free(ctx_from_key);
EVP_PKEY_free(key);
EVP_PKEY_CTX_free(ctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(tmpl);
BN_free(g);
BN_free(p);
return rc;
}
#else /* !HAVE_OPENSSL_EVP_PKEY */
static uint32_t __sk_gen_params(struct sk_cred *skc, BIGNUM *p, BIGNUM *g,
int num_rounds)
{
const BIGNUM *pub_key;
/* Populate DH parameters */
/* "dh" takes over freeing of 'p' and 'g' if this succeeds */
skc->sc_params = DH_new();
if (!skc->sc_params || !DH_set0_pqg(skc->sc_params, p, NULL, g)) {
printerr(0, "Failed to set pqg\n");
BN_free(g);
BN_free(p);
return GSS_S_FAILURE;
}
/* Verify that we have a safe prime and valid generator */
if (!sk_is_dh_valid(skc->sc_params, num_rounds))
return GSS_S_FAILURE;
if (DH_generate_key(skc->sc_params) != 1) {
printerr(0, "Failed to generate public DH key: %s\n",
ERR_error_string(ERR_get_error(), NULL));
return GSS_S_FAILURE;
}
DH_get0_key(skc->sc_params, &pub_key, NULL);
skc->sc_pub_key.length = BN_num_bytes(pub_key);
skc->sc_pub_key.value = malloc(skc->sc_pub_key.length);
if (!skc->sc_pub_key.value) {
printerr(0, "Failed to allocate memory for public key\n");
return GSS_S_FAILURE;
}
BN_bn2bin(pub_key, skc->sc_pub_key.value);
return GSS_S_COMPLETE;
}
#endif /* HAVE_OPENSSL_EVP_PKEY */
/**
* Populates the DH parameters for the DHKE
*
* \param[in,out] skc Shared key credentials structure to
* populate with DH parameters
*
* \retval GSS_S_COMPLETE success
* \retval GSS_S_FAILURE failure
*/
uint32_t sk_gen_params(struct sk_cred *skc, int num_rounds)
{
uint32_t random;
BIGNUM *p, *g;
/* Random value used by both the request and response as part of the
* key binding material. This also should ensure we have unqiue
* tokens that are sent to the remote server which is important because
* the token is hashed for the sunrpc cache lookups and a failure there
* would cause connection attempts to fail indefinitely due to the large
* timeout value on the server side.
*/
if (RAND_bytes((unsigned char *)&random, sizeof(random)) != 1) {
printerr(0, "Failed to get data for random parameter: %s\n",
ERR_error_string(ERR_get_error(), NULL));
return GSS_S_FAILURE;
}
/* The random value will always be used in byte range operations
* so we keep it as big endian from this point on.
*/
skc->sc_kctx.skc_host_random = random;
p = BN_bin2bn(skc->sc_p.value, skc->sc_p.length, NULL);
if (!p) {
printerr(0, "Failed to convert binary to BIGNUM\n");
return GSS_S_FAILURE;
}
/* We use a static generator for shared key */
g = BN_new();
if (!g) {
printerr(0, "Failed to allocate new BIGNUM\n");
goto free_p;
}
if (BN_set_word(g, SK_GENERATOR) != 1) {
printerr(0, "Failed to set g value for DH params\n");
goto free_g;
}
return __sk_gen_params(skc, p, g, num_rounds);
free_g:
BN_free(g);
free_p:
BN_free(p);
return GSS_S_FAILURE;
}
/**
* Convert SK hash algorithm into openssl message digest
*
* \param[in,out] alg SK hash algorithm
*
* \retval EVP_MD
*/
static inline const EVP_MD *sk_hash_to_evp_md(enum cfs_crypto_hash_alg alg)
{
switch (alg) {
case CFS_HASH_ALG_SHA256:
return EVP_sha256();
case CFS_HASH_ALG_SHA512:
return EVP_sha512();
default:
return EVP_md_null();
}
}
/**
* Signs (via HMAC) the parameters used only in the key initialization protocol.
*
* \param[in] key Key to use for HMAC
* \param[in] bufs Array of gss_buffer_desc to generate
* HMAC for
* \param[in] numbufs Number of buffers in array
* \param[in] hash_alg OpenSSL EVP_MD to use for hash
* \param[in,out] hmac HMAC of buffers is allocated and placed
* in this gss_buffer_desc. Caller must
* free this.
*
* \retval 0 success
* \retval -1 failure
*/
int sk_sign_bufs(gss_buffer_desc *key, gss_buffer_desc *bufs, const int numbufs,
const EVP_MD *hash_alg, gss_buffer_desc *hmac)
{
unsigned int hashlen = EVP_MD_size(hash_alg);
EVP_MAC_CTX *ctx = NULL;
EVP_MAC *mac = NULL;
size_t len = 0;
int i, rc = -1;
DECLARE_EVP_MD(subalg, hash_alg);
if (hash_alg == EVP_md_null()) {
printerr(0, "Invalid hash algorithm\n");
return -1;
}
hmac->length = hashlen;
hmac->value = malloc(hashlen);
if (!hmac->value) {
printerr(0, "Failed to allocate memory for HMAC\n");
goto out;
}
mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
if (!mac) {
printerr(0, "Failed to fetch HMAC\n");
goto out;
}
ctx = EVP_MAC_CTX_new(mac);
if (!ctx) {
printerr(0, "Failed to init HMAC ctx\n");
goto out;
}
if (EVP_MAC_init(ctx, key->value, key->length, subalg) != 1) {
printerr(0, "Failed to init HMAC\n");
goto out;
}
for (i = 0; i < numbufs; i++) {
if (EVP_MAC_update(ctx, bufs[i].value, bufs[i].length) != 1) {
printerr(0, "Failed to update HMAC\n");
goto out;
}
}
/* The result gets populated in hmac */
if (EVP_MAC_final(ctx, hmac->value, &len, hashlen) != 1) {
printerr(0, "Failed to finalize HMAC\n");
goto out;
}
if (hmac->length != len) {
printerr(0, "HMAC size %zu does not match expected %zu\n",
len, hmac->length);
goto out;
}
rc = 0;
out:
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
return rc;
}
/**
* Generates an HMAC for gss_buffer_desc array in \a bufs of \a numbufs
* and verifies against \a hmac.
*
* \param[in] skc Shared key credentials
* \param[in] bufs Array of gss_buffer_desc to generate HMAC for
* \param[in] numbufs Number of buffers in array
* \param[in] hash_alg OpenSSL EVP_MD to use for hash
* \param[in] hmac HMAC to verify against
*
* \retval GSS_S_COMPLETE success (match)
* \retval gss error failure
*/
uint32_t sk_verify_hmac(struct sk_cred *skc, gss_buffer_desc *bufs,
const int numbufs, const EVP_MD *hash_alg,
gss_buffer_desc *hmac)
{
gss_buffer_desc bufs_hmac;
int rc;
if (sk_sign_bufs(&skc->sc_kctx.skc_shared_key, bufs, numbufs, hash_alg,
&bufs_hmac)) {
printerr(0, "Failed to sign buffers to verify HMAC\n");
if (bufs_hmac.value)
free(bufs_hmac.value);
return GSS_S_FAILURE;
}
if (hmac->length != bufs_hmac.length) {
printerr(0, "Invalid HMAC size\n");
free(bufs_hmac.value);
return GSS_S_BAD_SIG;
}
rc = memcmp(hmac->value, bufs_hmac.value, bufs_hmac.length);
free(bufs_hmac.value);
if (rc)
return GSS_S_BAD_SIG;
return GSS_S_COMPLETE;
}
/**
* Cleanup an sk_cred freeing any resources
*
* \param[in,out] skc Shared key credentials to free
*/
void sk_free_cred(struct sk_cred *skc)
{
if (!skc)
return;
if (skc->sc_p.value)
free(skc->sc_p.value);
if (skc->sc_pub_key.value)
free(skc->sc_pub_key.value);
if (skc->sc_tgt.value)
free(skc->sc_tgt.value);
if (skc->sc_nodemap_hash.value)
free(skc->sc_nodemap_hash.value);
if (skc->sc_hmac.value)
free(skc->sc_hmac.value);
/* Overwrite keys and IV before freeing */
if (skc->sc_dh_shared_key.value) {
memset(skc->sc_dh_shared_key.value, 0,
skc->sc_dh_shared_key.length);
free(skc->sc_dh_shared_key.value);
}
if (skc->sc_kctx.skc_hmac_key.value) {
memset(skc->sc_kctx.skc_hmac_key.value, 0,
skc->sc_kctx.skc_hmac_key.length);
free(skc->sc_kctx.skc_hmac_key.value);
}
if (skc->sc_kctx.skc_encrypt_key.value) {
memset(skc->sc_kctx.skc_encrypt_key.value, 0,
skc->sc_kctx.skc_encrypt_key.length);
free(skc->sc_kctx.skc_encrypt_key.value);
}
if (skc->sc_kctx.skc_shared_key.value) {
memset(skc->sc_kctx.skc_shared_key.value, 0,
skc->sc_kctx.skc_shared_key.length);
free(skc->sc_kctx.skc_shared_key.value);
}
if (skc->sc_kctx.skc_session_key.value) {
memset(skc->sc_kctx.skc_session_key.value, 0,
skc->sc_kctx.skc_session_key.length);
free(skc->sc_kctx.skc_session_key.value);
}
if (skc->sc_params) {
EVP_PKEY_free(skc->sc_params);
skc->sc_params = NULL;
}
free(skc);
skc = NULL;
}
/* This function handles key derivation using the hash algorithm specified in
* \a hash_alg, buffers in \a key_binding_bufs, and original key in
* \a origin_key to produce a \a derived_key. The first element of the
* key_binding_bufs array is reserved for the counter used in the KDF. The
* derived key in \a derived_key could differ in size from \a origin_key and
* must be populated with the expected size and a valid buffer to hold the
* contents.
*
* If the derived key size is greater than the HMAC algorithm size it will be
* a done using several iterations of a counter and the key binding bufs.
*
* If the size is smaller it will take copy the first N bytes necessary to
* fill the derived key. */
static int sk_kdf(gss_buffer_desc *derived_key, gss_buffer_desc *origin_key,
gss_buffer_desc *key_binding_bufs, int numbufs,
enum cfs_crypto_hash_alg hmac_alg)
{
size_t remain;
size_t bytes;
uint32_t counter;
char *keydata;
gss_buffer_desc tmp_hash;
int i;
int rc;
if (numbufs < 1)
return -EINVAL;
/* Use a counter as the first buffer followed by the key binding
* buffers in the event we need more than one a single cycle to
* produced a symmetric key large enough in size */
key_binding_bufs[0].value = &counter;
key_binding_bufs[0].length = sizeof(counter);
remain = derived_key->length;
keydata = derived_key->value;
i = 0;
while (remain > 0) {
counter = htobe32(i++);
rc = sk_sign_bufs(origin_key, key_binding_bufs, numbufs,
sk_hash_to_evp_md(hmac_alg), &tmp_hash);
if (rc) {
if (tmp_hash.value)
free(tmp_hash.value);
return rc;
}
if (cfs_crypto_hash_digestsize(hmac_alg) != tmp_hash.length) {
free(tmp_hash.value);
return -EINVAL;
}
bytes = (remain < tmp_hash.length) ? remain : tmp_hash.length;
memcpy(keydata, tmp_hash.value, bytes);
free(tmp_hash.value);
remain -= bytes;
keydata += bytes;
}
return 0;
}
/* Populates the sk_cred's session_key using the a Key Derviation Function (KDF)
* based on the recommendations in NIST Special Publication SP 800-56B Rev 1
* (Sep 2014) Section 5.5.1
*
* \param[in,out] skc Shared key credentials structure with
*
* \return -1 failure
* \return 0 success
*/
int sk_session_kdf(struct sk_cred *skc, lnet_nid_t client_nid,
gss_buffer_desc *client_token, gss_buffer_desc *server_token)
{
struct sk_kernel_ctx *kctx = &skc->sc_kctx;
gss_buffer_desc *session_key = &kctx->skc_session_key;
gss_buffer_desc bufs[5];
enum cfs_crypto_crypt_alg crypt_alg;
int rc = -1;
crypt_alg = cfs_crypto_crypt_alg(kctx->skc_crypt_alg);
session_key->length = cfs_crypto_crypt_keysize(crypt_alg);
session_key->value = malloc(session_key->length);
if (!session_key->value) {
printerr(0, "Failed to allocate memory for session key\n");
return rc;
}
/* Key binding info ordering
* 1. Reserved for counter
* 1. DH shared key
* 2. Client's NIDs
* 3. Client's token
* 4. Server's token */
bufs[0].value = NULL;
bufs[0].length = 0;
bufs[1] = skc->sc_dh_shared_key;
bufs[2].value = &client_nid;
bufs[2].length = sizeof(client_nid);
bufs[3] = *client_token;
bufs[4] = *server_token;
return sk_kdf(&kctx->skc_session_key, &kctx->skc_shared_key, bufs,
5, cfs_crypto_hash_alg(kctx->skc_hmac_alg));
}
/* Uses the session key to create an HMAC key and encryption key. In
* integrity mode the session key used to generate the HMAC key uses
* session information which is available on the wire but by creating
* a session based HMAC key we can prevent potential replay as both the
* client and server have random numbers used as part of the key creation.
*
* The keys used for integrity and privacy are formulated as below using
* the session key that is the output of the key derivation function. The
* HMAC algorithm is determined by the shared key algorithm selected in the
* key file.
*
* For ski mode:
* Session HMAC Key = PBKDF2("Integrity", KDF derived Session Key)
*
* For skpi mode:
* Session HMAC Key = PBKDF2("Integrity", KDF derived Session Key)
* Session Encryption Key = PBKDF2("Encrypt", KDF derived Session Key)
*
* \param[in,out] skc Shared key credentials structure with
*
* \return -1 failure
* \return 0 success
*/
int sk_compute_keys(struct sk_cred *skc)
{
struct sk_kernel_ctx *kctx = &skc->sc_kctx;
gss_buffer_desc *session_key = &kctx->skc_session_key;
gss_buffer_desc *hmac_key = &kctx->skc_hmac_key;
gss_buffer_desc *encrypt_key = &kctx->skc_encrypt_key;
enum cfs_crypto_hash_alg hmac_alg;
enum cfs_crypto_crypt_alg crypt_alg;
char *encrypt = "Encrypt";
char *integrity = "Integrity";
int rc;
hmac_alg = cfs_crypto_hash_alg(kctx->skc_hmac_alg);
hmac_key->length = cfs_crypto_hash_digestsize(hmac_alg);
hmac_key->value = malloc(hmac_key->length);
if (!hmac_key->value)
return -ENOMEM;
rc = PKCS5_PBKDF2_HMAC(integrity, -1, session_key->value,
session_key->length, SK_PBKDF2_ITERATIONS,
sk_hash_to_evp_md(hmac_alg),
hmac_key->length, hmac_key->value);
if (rc == 0)
return -EINVAL;
/* Encryption key is only populated in privacy mode */
if ((skc->sc_flags & LGSS_SVC_PRIV) == 0)
return 0;
crypt_alg = cfs_crypto_crypt_alg(kctx->skc_crypt_alg);
encrypt_key->length = cfs_crypto_crypt_keysize(crypt_alg);
encrypt_key->value = malloc(encrypt_key->length);
if (!encrypt_key->value)
return -ENOMEM;
rc = PKCS5_PBKDF2_HMAC(encrypt, -1, session_key->value,
session_key->length, SK_PBKDF2_ITERATIONS,
sk_hash_to_evp_md(hmac_alg),
encrypt_key->length, encrypt_key->value);
if (rc == 0)
return -EINVAL;
return 0;
}
static uint32_t __sk_compute_dh_key(struct sk_cred *skc,
const gss_buffer_desc *pub_key,
size_t *expected_len)
{
gss_buffer_desc *dh_shared = &skc->sc_dh_shared_key;
uint32_t rc = GSS_S_FAILURE;
#ifdef HAVE_OPENSSL_EVP_PKEY
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *peerkey = NULL;
peerkey = EVP_PKEY_new();
if (!peerkey ||
EVP_PKEY_copy_parameters(peerkey, skc->sc_params) != 1) {
printerr(0, "error: peerkey cannot be init\n");
goto out_err;
}
if (EVP_PKEY_set1_encoded_public_key(peerkey,
pub_key->value,
pub_key->length) != 1) {
printerr(0, "error: peerkey cannot be set\n");
goto out_err;
}
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, skc->sc_params, NULL);
if (!ctx) {
printerr(0, "error: ctx cannot be allocated\n");
goto out_err;
}
if (EVP_PKEY_derive_init(ctx) != 1 ||
EVP_PKEY_derive_set_peer(ctx, peerkey) != 1) {
printerr(0, "error: ctx cannot be init\n");
goto out_err;
}
if (EVP_PKEY_derive(ctx, NULL, expected_len) != 1) {
printerr(0, "error: cannot get dh length\n");
goto out_err;
}
dh_shared->length = *expected_len;
dh_shared->value = malloc(*expected_len);
if (!dh_shared->value) {
printerr(0, "error: cannot allocate memory for shared key\n");
goto out_err;
}
if (EVP_PKEY_derive(ctx, dh_shared->value, &dh_shared->length) != 1) {
printerr(0, "error: cannot derive dh key\n");
ERR_print_errors_fp(stderr);
goto out_err;
}
rc = GSS_S_COMPLETE;
out_err:
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(peerkey);
#else /* !HAVE_OPENSSL_EVP_PKEY */
BIGNUM *remote_pub_key;
remote_pub_key = BN_bin2bn(pub_key->value, pub_key->length, NULL);
if (!remote_pub_key) {
printerr(0, "Failed to convert binary to BIGNUM\n");
return rc;
}
*expected_len = DH_size(skc->sc_params);
dh_shared->length = *expected_len;
dh_shared->value = malloc(*expected_len);
if (!dh_shared->value) {
printerr(0,
"Failed to allocate memory for computed shared secret key\n");
goto out_err;
}
/* This computes the shared key from the DHKE */
dh_shared->length = DH_compute_key(dh_shared->value, remote_pub_key,
skc->sc_params);
if (dh_shared->length == -1) {
printerr(0, "DH key derivation failed: %s\n",
ERR_error_string(ERR_get_error(), NULL));
goto out_err;
}
rc = GSS_S_COMPLETE;
out_err:
BN_free(remote_pub_key);
#endif /* HAVE_OPENSSL_EVP_PKEY */
return rc;
}
/**
* Computes a session key based on the DH parameters from the host and its peer
*
* \param[in,out] skc Shared key credentials structure with
* the session key populated with the
* compute key
* \param[in] pub_key Public key returned from peer in
* gss_buffer_desc
* \return gss error failure
* \return GSS_S_COMPLETE success
*/
uint32_t sk_compute_dh_key(struct sk_cred *skc, const gss_buffer_desc *pub_key)
{
size_t expected_len;
uint32_t rc;
rc = __sk_compute_dh_key(skc, pub_key, &expected_len);
if (rc != GSS_S_COMPLETE)
return rc;
if (skc->sc_dh_shared_key.length < expected_len) {
/* there is around 1 chance out of 256 that the returned
* shared key is shorter than expected
*/
if (skc->sc_dh_shared_key.length >= expected_len - 2) {
int shift = expected_len - skc->sc_dh_shared_key.length;
/* if the key is short by only 1 or 2 bytes, just
* prepend it with 0s
*/
memmove((void *)(skc->sc_dh_shared_key.value + shift),
skc->sc_dh_shared_key.value,
skc->sc_dh_shared_key.length);
memset(skc->sc_dh_shared_key.value, 0, shift);
} else {
/* if the key is really too short, return GSS_S_BAD_QOP
* so that the caller can retry to generate
*/
printerr(0,
"DH derivation returned a short key of %zu bytes, expected: %zu\n",
skc->sc_dh_shared_key.length, expected_len);
rc = GSS_S_BAD_QOP;
}
}
return rc;
}
/**
* Creates a serialized buffer for the kernel in the order of struct
* sk_kernel_ctx.
*
* \param[in,out] skc Shared key credentials structure
* \param[in,out] ctx_token Serialized buffer for kernel.
* Caller must free this buffer.
*
* \return 0 success
* \return -1 failure
*/
int sk_serialize_kctx(struct sk_cred *skc, gss_buffer_desc *ctx_token)
{
struct sk_kernel_ctx *kctx = &skc->sc_kctx;
char *p, *end;
size_t bufsize;
bufsize = sizeof(*kctx) + kctx->skc_hmac_key.length +
kctx->skc_encrypt_key.length;
ctx_token->value = malloc(bufsize);
if (!ctx_token->value)
return -1;
ctx_token->length = bufsize;
p = ctx_token->value;
end = p + ctx_token->length;
if (WRITE_BYTES(&p, end, kctx->skc_version))
return -1;
if (WRITE_BYTES(&p, end, kctx->skc_hmac_alg))
return -1;
if (WRITE_BYTES(&p, end, kctx->skc_crypt_alg))
return -1;
if (WRITE_BYTES(&p, end, kctx->skc_expire))
return -1;
if (WRITE_BYTES(&p, end, kctx->skc_host_random))
return -1;
if (WRITE_BYTES(&p, end, kctx->skc_peer_random))
return -1;
if (write_buffer(&p, end, &kctx->skc_hmac_key))
return -1;
if (write_buffer(&p, end, &kctx->skc_encrypt_key))
return -1;
printerr(2, "Serialized buffer of %zu bytes for kernel\n", bufsize);
return 0;
}
/**
* Decodes a netstring \a ns into array of gss_buffer_descs at \a bufs
* up to \a numbufs. Memory is allocated for each value and length
* will be populated with the length
*
* \param[in,out] bufs Array of gss_buffer_descs
* \param[in,out] numbufs number of gss_buffer_desc in array
* \param[in] ns netstring to decode
*
* \return buffers populated success
* \return -1 failure
*/
int sk_decode_netstring(gss_buffer_desc *bufs, int numbufs, gss_buffer_desc *ns)
{
char *ptr = ns->value;
size_t remain = ns->length;
unsigned int size;
int digits;
int sep;
int rc;
int i;
for (i = 0; i < numbufs; i++) {
/* read the size of first buffer */
rc = sscanf(ptr, "%9u", &size);
if (rc < 1)
goto out_err;
digits = (size) ? ceil(log10(size + 1)) : 1;
/* sep of current string */
sep = size + digits + 2;
/* check to make sure it's valid */
if (remain < sep || ptr[digits] != ':' ||
ptr[sep - 1] != ',')
goto out_err;
bufs[i].length = size;
if (size == 0) {
bufs[i].value = NULL;
} else {
bufs[i].value = malloc(size);
if (!bufs[i].value)
goto out_err;
memcpy(bufs[i].value, &ptr[digits + 1], size);
}
remain -= sep;
ptr += sep;
}
printerr(2, "Decoded netstring of %zu bytes\n", ns->length);
return i;
out_err:
while (i-- > 0) {
if (bufs[i].value)
free(bufs[i].value);
bufs[i].length = 0;
}
return -1;
}
/**
* Creates a netstring in a gss_buffer_desc that consists of all
* the gss_buffer_desc found in \a bufs. The netstring should be treated
* as binary as it can contain null characters.
*
* \param[in] bufs Array of gss_buffer_desc to use as input
* \param[in] numbufs Number of buffers in array
* \param[in,out] ns Destination gss_buffer_desc to hold
* netstring
*
* \return -1 failure
* \return 0 success
*/
int sk_encode_netstring(gss_buffer_desc *bufs, int numbufs,
gss_buffer_desc *ns)
{
unsigned char *ptr;
int size = 0;
int rc;
int i;
/* size of string in decimal, string size, colon, and comma */
for (i = 0; i < numbufs; i++) {
if (bufs[i].length == 0)
size += 3;
else
size += ceil(log10(bufs[i].length + 1)) +
bufs[i].length + 2;
}
ns->length = size;
ns->value = malloc(ns->length);
if (!ns->value) {
ns->length = 0;
return -1;
}
ptr = ns->value;
for (i = 0; i < numbufs; i++) {
/* size */
rc = scnprintf((char *) ptr, size, "%zu:", bufs[i].length);
ptr += rc;
/* contents */
memcpy(ptr, bufs[i].value, bufs[i].length);
ptr += bufs[i].length;
/* delimeter */
*ptr++ = ',';
size -= bufs[i].length + rc + 1;
/* should not happen */
if (size < 0)
abort();
}
printerr(2, "Encoded netstring of %zu bytes\n", ns->length);
return 0;
}