/*
* Copyright (C) 2015, Trustees of Indiana University
*
- * Copyright (c) 2016, Intel Corporation.
+ * Copyright (c) 2016, 2017, Intel Corporation.
*
* Author: Jeremy Filizetti <jfilizet@iu.edu>
*/
#include <openssl/hmac.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <lnet/nidstr.h>
+#include <libcfs/util/string.h>
+#include <sys/time.h>
#include "sk_utils.h"
#include "write_bytes.h"
#define SK_PBKDF2_ITERATIONS 10000
-static struct sk_crypt_type sk_crypt_types[] = {
- [SK_CRYPT_AES256_CTR] = {
- .cht_name = "ctr(aes)",
- .cht_bytes = 32,
- },
-};
-
-/*
-static struct sk_hmac_type sk_hmac_types[] = {
- [SK_HMAC_SHA256] = {
- .cht_name = "sha256",
- .cht_bytes = 32,
- },
- [SK_HMAC_SHA512] = {
- .cht_name = "sha512",
- .cht_bytes = 64,
- },
-};*/
-
#ifdef _NEW_BUILD_
# include "lgss_utils.h"
#else
key = add_key("user", description, &payload, sizeof(payload),
KEY_SPEC_USER_KEYRING);
- if (key != -1)
+ 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(2, "Failed to set perm 0x%x on key %d\n",
+ perm, key);
printerr(2, "Added key %d with description %s\n", key,
description);
- else
+ } else {
printerr(0, "Failed to add key with %s\n", description);
+ }
return key;
}
for (i = 0; i < MAX_MGSNIDS; i++)
config->skc_mgsnids[i] = htobe64(config->skc_mgsnids[i]);
-
- return;
}
/**
for (i = 0; i < MAX_MGSNIDS; i++)
config->skc_mgsnids[i] = be64toh(config->skc_mgsnids[i]);
-
- return;
}
/**
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 != CFS_HASH_ALG_SHA256) &&
- (config->skc_hmac_alg != CFS_HASH_ALG_SHA512)) {
+
+ if (config->skc_hmac_alg == SK_HMAC_INVALID) {
printerr(0, "Invalid HMAC algorithm\n");
return -1;
}
- if (config->skc_crypt_alg >= SK_CRYPT_MAX) {
+
+ 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
kctx = &skc->sc_kctx;
kctx->skc_version = config->skc_version;
- kctx->skc_hmac_alg = config->skc_hmac_alg;
- kctx->skc_crypt_alg = config->skc_crypt_alg;
+ 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 */
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.
+ */
+static bool sk_is_dh_valid(const DH *dh, int num_rounds)
+{
+ const BIGNUM *p, *g;
+ BN_ULONG word;
+ BN_CTX *ctx;
+ BIGNUM *r;
+ bool valid = false;
+ int rc;
+
+ if (num_rounds == 0) {
+ int codes = 0;
+
+ rc = DH_check(dh, &codes);
+ if (rc != 1 || codes) {
+ printerr(0, "DH_check(0) failed: codes=%#x: rc=%d\n",
+ codes, rc);
+ return false;
+ }
+ return true;
+ }
+
+ DH_get0_pqg(dh, &p, NULL, &g);
+
+ 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);
+ if (word != 11) {
+ printerr(0, "%s: Diffie-Hellman prime modulo=%lu unsuitable\n",
+ program_invocation_short_name, word);
+ return false;
+ }
+
+ 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;
+}
+
+#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)
+{
+ DH *dh;
+ BIGNUM *p, *g;
+ int num_rounds, prev_rounds = 0;
+
+ 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;
+}
+
/**
* Populates the DH parameters for the DHKE
*
* \retval GSS_S_COMPLETE success
* \retval GSS_S_FAILURE failure
*/
-uint32_t sk_gen_params(struct sk_cred *skc)
+uint32_t sk_gen_params(struct sk_cred *skc, int num_rounds)
{
uint32_t random;
- int rc;
+ BIGNUM *p, *g;
+ const BIGNUM *pub_key;
/* Random value used by both the request and response as part of the
* key binding material. This also should ensure we have unqiue
return GSS_S_FAILURE;
}
- skc->sc_params->p = BN_bin2bn(skc->sc_p.value, skc->sc_p.length, NULL);
- if (!skc->sc_params->p) {
+ 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 */
- skc->sc_params->g = BN_new();
- if (!skc->sc_params->g) {
+ g = BN_new();
+ if (!g) {
printerr(0, "Failed to allocate new BIGNUM\n");
return GSS_S_FAILURE;
}
- if (BN_set_word(skc->sc_params->g, SK_GENERATOR) != 1) {
+ if (BN_set_word(g, SK_GENERATOR) != 1) {
printerr(0, "Failed to set g value for DH params\n");
return GSS_S_FAILURE;
}
- /* Verify that we have a safe prime and valid generator */
- if (DH_check(skc->sc_params, &rc) != 1) {
- printerr(0, "DH_check() failed: %d\n", rc);
- return GSS_S_FAILURE;
- } else if (rc) {
- printerr(0, "DH_check() returned error codes: 0x%x\n", rc);
+ if (!DH_set0_pqg(skc->sc_params, p, NULL, g)) {
+ printerr(0, "Failed to set pqg\n");
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;
}
- skc->sc_pub_key.length = BN_num_bytes(skc->sc_params->pub_key);
+ 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(skc->sc_params->pub_key, skc->sc_pub_key.value);
+ BN_bn2bin(pub_key, skc->sc_pub_key.value);
return GSS_S_COMPLETE;
}
int sk_sign_bufs(gss_buffer_desc *key, gss_buffer_desc *bufs, const int numbufs,
const EVP_MD *hash_alg, gss_buffer_desc *hmac)
{
- HMAC_CTX hctx;
+ HMAC_CTX *hctx;
unsigned int hashlen = EVP_MD_size(hash_alg);
int i;
int rc = -1;
return -1;
}
- HMAC_CTX_init(&hctx);
+ hctx = HMAC_CTX_new();
hmac->length = hashlen;
hmac->value = malloc(hashlen);
goto out;
}
- if (HMAC_Init_ex(&hctx, key->value, key->length, hash_alg, NULL) != 1) {
+ if (HMAC_Init_ex(hctx, key->value, key->length, hash_alg, NULL) != 1) {
printerr(0, "Failed to init HMAC\n");
goto out;
}
for (i = 0; i < numbufs; i++) {
- if (HMAC_Update(&hctx, bufs[i].value, bufs[i].length) != 1) {
+ if (HMAC_Update(hctx, bufs[i].value, bufs[i].length) != 1) {
printerr(0, "Failed to update HMAC\n");
goto out;
}
}
/* The result gets populated in hmac */
- if (HMAC_Final(&hctx, hmac->value, &hashlen) != 1) {
+ if (HMAC_Final(hctx, hmac->value, &hashlen) != 1) {
printerr(0, "Failed to finalize HMAC\n");
goto out;
}
rc = 0;
out:
- HMAC_CTX_cleanup(&hctx);
+ HMAC_CTX_free(hctx);
return rc;
}
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;
- session_key->length = sk_crypt_types[kctx->skc_crypt_alg].cht_bytes;
+ 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");
bufs[4] = *server_token;
return sk_kdf(&kctx->skc_session_key, &kctx->skc_shared_key, bufs,
- 5, kctx->skc_hmac_alg);
+ 5, cfs_crypto_hash_alg(kctx->skc_hmac_alg));
}
/* Uses the session key to create an HMAC key and encryption key. In
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_key->length = cfs_crypto_hash_digestsize(kctx->skc_hmac_alg);
+ 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(kctx->skc_hmac_alg),
+ sk_hash_to_evp_md(hmac_alg),
hmac_key->length, hmac_key->value);
if (rc == 0)
return -EINVAL;
if ((skc->sc_flags & LGSS_SVC_PRIV) == 0)
return 0;
- encrypt_key->length = cfs_crypto_hash_digestsize(kctx->skc_hmac_alg);
+ 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(kctx->skc_hmac_alg),
+ sk_hash_to_evp_md(hmac_alg),
encrypt_key->length, encrypt_key->value);
if (rc == 0)
return -EINVAL;
ERR_error_string(ERR_get_error(), NULL));
goto out_err;
} else if (status < dh_shared->length) {
- printerr(0, "DH_compute_key() returned a short key of %d "
- "bytes, expected: %zu\n", status, dh_shared->length);
- rc = GSS_S_DEFECTIVE_TOKEN;
- goto out_err;
+ /* there is around 1 chance out of 256 that the returned
+ * shared key is shorter than expected
+ */
+ if (status >= dh_shared->length - 2) {
+ int shift = dh_shared->length - status;
+ /* if the key is short by only 1 or 2 bytes, just
+ * prepend it with 0s
+ */
+ memmove((void *)(dh_shared->value + shift),
+ dh_shared->value, status);
+ memset(dh_shared->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_compute_key() returned a short key of %d bytes, expected: %zu\n",
+ status, dh_shared->length);
+ rc = GSS_S_BAD_QOP;
+ goto out_err;
+ }
}
rc = GSS_S_COMPLETE;
ptr = ns->value;
for (i = 0; i < numbufs; i++) {
/* size */
- rc = snprintf((char *) ptr, size, "%zu:", bufs[i].length);
+ rc = scnprintf((char *) ptr, size, "%zu:", bufs[i].length);
ptr += rc;
/* contents */