Blame SOURCES/Use-OpenSSL-s-SSKDF-in-PKINIT-when-available.patch

cb4cef
From 8bbb492f2be1418e1e4bb2cf197414810dac9589 Mon Sep 17 00:00:00 2001
cb4cef
From: Robbie Harwood <rharwood@redhat.com>
cb4cef
Date: Fri, 20 Sep 2019 17:20:59 -0400
cb4cef
Subject: [PATCH] Use OpenSSL's SSKDF in PKINIT when available
cb4cef
cb4cef
Starting in 3.0, OpenSSL implements SSKDF, which is the basis of our
cb4cef
id-pkinit-kdf (RFC 8636).  Factor out common setup code around
cb4cef
other_info.  Adjust code to comply to existing style.
cb4cef
cb4cef
(cherry picked from commit 4376a22e41fb639be31daf81275a332d3f930996)
cb4cef
---
cb4cef
 .../preauth/pkinit/pkinit_crypto_openssl.c    | 294 +++++++++++-------
cb4cef
 1 file changed, 181 insertions(+), 113 deletions(-)
cb4cef
cb4cef
diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
cb4cef
index e1153344e..350c2118a 100644
cb4cef
--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
cb4cef
+++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
cb4cef
@@ -38,6 +38,12 @@
cb4cef
 #include <dirent.h>
cb4cef
 #include <arpa/inet.h>
cb4cef
 
cb4cef
+#ifdef HAVE_EVP_KDF_FETCH
cb4cef
+#include <openssl/core_names.h>
cb4cef
+#include <openssl/kdf.h>
cb4cef
+#include <openssl/params.h>
cb4cef
+#endif
cb4cef
+
cb4cef
 static krb5_error_code pkinit_init_pkinit_oids(pkinit_plg_crypto_context );
cb4cef
 static void pkinit_fini_pkinit_oids(pkinit_plg_crypto_context );
cb4cef
 
cb4cef
@@ -2294,15 +2300,16 @@ cleanup:
cb4cef
 }
cb4cef
 
cb4cef
 
cb4cef
-/**
cb4cef
+/*
cb4cef
  * Given an algorithm_identifier, this function returns the hash length
cb4cef
  * and EVP function associated with that algorithm.
cb4cef
+ *
cb4cef
+ * RFC 8636 defines a SHA384 variant, but we don't use it.
cb4cef
  */
cb4cef
 static krb5_error_code
cb4cef
-pkinit_alg_values(krb5_context context,
cb4cef
-                  const krb5_data *alg_id,
cb4cef
-                  size_t *hash_bytes,
cb4cef
-                  const EVP_MD *(**func)(void))
cb4cef
+pkinit_alg_values(krb5_context context, const krb5_data *alg_id,
cb4cef
+                  size_t *hash_bytes, const EVP_MD *(**func)(void),
cb4cef
+                  char **hash_name)
cb4cef
 {
cb4cef
     *hash_bytes = 0;
cb4cef
     *func = NULL;
cb4cef
@@ -2311,18 +2318,21 @@ pkinit_alg_values(krb5_context context,
cb4cef
                      krb5_pkinit_sha1_oid_len))) {
cb4cef
         *hash_bytes = 20;
cb4cef
         *func = &EVP_sha1;
cb4cef
+        *hash_name = strdup("SHA1");
cb4cef
         return 0;
cb4cef
     } else if ((alg_id->length == krb5_pkinit_sha256_oid_len) &&
cb4cef
                (0 == memcmp(alg_id->data, krb5_pkinit_sha256_oid,
cb4cef
                             krb5_pkinit_sha256_oid_len))) {
cb4cef
         *hash_bytes = 32;
cb4cef
         *func = &EVP_sha256;
cb4cef
+        *hash_name = strdup("SHA256");
cb4cef
         return 0;
cb4cef
     } else if ((alg_id->length == krb5_pkinit_sha512_oid_len) &&
cb4cef
                (0 == memcmp(alg_id->data, krb5_pkinit_sha512_oid,
cb4cef
                             krb5_pkinit_sha512_oid_len))) {
cb4cef
         *hash_bytes = 64;
cb4cef
         *func = &EVP_sha512;
cb4cef
+        *hash_name = strdup("SHA512");
cb4cef
         return 0;
cb4cef
     } else {
cb4cef
         krb5_set_error_message(context, KRB5_ERR_BAD_S2K_PARAMS,
cb4cef
@@ -2331,11 +2341,60 @@ pkinit_alg_values(krb5_context context,
cb4cef
     }
cb4cef
 } /* pkinit_alg_values() */
cb4cef
 
cb4cef
+#ifdef HAVE_EVP_KDF_FETCH
cb4cef
+static krb5_error_code
cb4cef
+openssl_sskdf(krb5_context context, size_t hash_bytes, krb5_data *key,
cb4cef
+              krb5_data *info, char *out, size_t out_len, char *digest)
cb4cef
+{
cb4cef
+    krb5_error_code ret;
cb4cef
+    EVP_KDF *kdf = NULL;
cb4cef
+    EVP_KDF_CTX *kctx = NULL;
cb4cef
+    OSSL_PARAM params[4];
cb4cef
+    size_t i = 0;
cb4cef
 
cb4cef
-/* pkinit_alg_agility_kdf() --
cb4cef
- * This function generates a key using the KDF described in
cb4cef
- * draft_ietf_krb_wg_pkinit_alg_agility-04.txt.  The algorithm is
cb4cef
- * described as follows:
cb4cef
+    if (digest == NULL) {
cb4cef
+        ret = oerr(context, ENOMEM,
cb4cef
+                   _("Failed to allocate space for digest algorithm name"));
cb4cef
+        goto done;
cb4cef
+    }
cb4cef
+
cb4cef
+    kdf = EVP_KDF_fetch(NULL, "SSKDF", NULL);
cb4cef
+    if (kdf == NULL) {
cb4cef
+        ret = oerr(context, KRB5_CRYPTO_INTERNAL, _("Failed to fetch SSKDF"));
cb4cef
+        goto done;
cb4cef
+    }
cb4cef
+
cb4cef
+    kctx = EVP_KDF_CTX_new(kdf);
cb4cef
+    if (!kctx) {
cb4cef
+        ret = oerr(context, KRB5_CRYPTO_INTERNAL,
cb4cef
+                   _("Failed to instantiate SSKDF"));
cb4cef
+        goto done;
cb4cef
+    }
cb4cef
+
cb4cef
+    params[i++] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
cb4cef
+                                                   digest, 0);
cb4cef
+    params[i++] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
cb4cef
+                                                    key->data, key->length);
cb4cef
+    params[i++] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO,
cb4cef
+                                                    info->data, info->length);
cb4cef
+    params[i] = OSSL_PARAM_construct_end();
cb4cef
+    if (EVP_KDF_derive(kctx, (unsigned char *)out, out_len, params) <= 0) {
cb4cef
+        ret = oerr(context, KRB5_CRYPTO_INTERNAL,
cb4cef
+                   _("Failed to derive key using SSKDF"));
cb4cef
+        goto done;
cb4cef
+    }
cb4cef
+
cb4cef
+    ret = 0;
cb4cef
+done:
cb4cef
+    EVP_KDF_free(kdf);
cb4cef
+    EVP_KDF_CTX_free(kctx);
cb4cef
+    return ret;
cb4cef
+}
cb4cef
+#else
cb4cef
+/*
cb4cef
+ * Generate a key using the KDF described in RFC 8636, also known as SSKDF
cb4cef
+ * (single-step kdf).  Our caller precomputes `reps`, but otherwise the
cb4cef
+ * algorithm is as follows:
cb4cef
  *
cb4cef
  *     1.  reps = keydatalen (K) / hash length (H)
cb4cef
  *
cb4cef
@@ -2349,95 +2408,16 @@ pkinit_alg_values(krb5_context context,
cb4cef
  *
cb4cef
  *     4.  Set key = Hash1 || Hash2 || ... so that length of key is K bytes.
cb4cef
  */
cb4cef
-krb5_error_code
cb4cef
-pkinit_alg_agility_kdf(krb5_context context,
cb4cef
-                       krb5_data *secret,
cb4cef
-                       krb5_data *alg_oid,
cb4cef
-                       krb5_const_principal party_u_info,
cb4cef
-                       krb5_const_principal party_v_info,
cb4cef
-                       krb5_enctype enctype,
cb4cef
-                       krb5_data *as_req,
cb4cef
-                       krb5_data *pk_as_rep,
cb4cef
-                       krb5_keyblock *key_block)
cb4cef
+static krb5_error_code
cb4cef
+builtin_sskdf(krb5_context context, unsigned int reps, size_t hash_len,
cb4cef
+              const EVP_MD *(*EVP_func)(void), krb5_data *secret,
cb4cef
+              krb5_data *other_info, char *out, size_t out_len)
cb4cef
 {
cb4cef
-    krb5_error_code retval = 0;
cb4cef
+    krb5_error_code ret = 0;
cb4cef
 
cb4cef
-    unsigned int reps = 0;
cb4cef
-    uint32_t counter = 1;       /* Does this type work on Windows? */
cb4cef
+    uint32_t counter = 1;
cb4cef
     size_t offset = 0;
cb4cef
-    size_t hash_len = 0;
cb4cef
-    size_t rand_len = 0;
cb4cef
-    size_t key_len = 0;
cb4cef
-    krb5_data random_data;
cb4cef
-    krb5_sp80056a_other_info other_info_fields;
cb4cef
-    krb5_pkinit_supp_pub_info supp_pub_info_fields;
cb4cef
-    krb5_data *other_info = NULL;
cb4cef
-    krb5_data *supp_pub_info = NULL;
cb4cef
-    krb5_algorithm_identifier alg_id;
cb4cef
     EVP_MD_CTX *ctx = NULL;
cb4cef
-    const EVP_MD *(*EVP_func)(void);
cb4cef
-
cb4cef
-    /* initialize random_data here to make clean-up safe */
cb4cef
-    random_data.length = 0;
cb4cef
-    random_data.data = NULL;
cb4cef
-
cb4cef
-    /* allocate and initialize the key block */
cb4cef
-    key_block->magic = 0;
cb4cef
-    key_block->enctype = enctype;
cb4cef
-    if (0 != (retval = krb5_c_keylengths(context, enctype, &rand_len,
cb4cef
-                                         &key_len)))
cb4cef
-        goto cleanup;
cb4cef
-
cb4cef
-    random_data.length = rand_len;
cb4cef
-    key_block->length = key_len;
cb4cef
-
cb4cef
-    if (NULL == (key_block->contents = malloc(key_block->length))) {
cb4cef
-        retval = ENOMEM;
cb4cef
-        goto cleanup;
cb4cef
-    }
cb4cef
-
cb4cef
-    memset (key_block->contents, 0, key_block->length);
cb4cef
-
cb4cef
-    /* If this is anonymous pkinit, use the anonymous principle for party_u_info */
cb4cef
-    if (party_u_info && krb5_principal_compare_any_realm(context, party_u_info,
cb4cef
-                                                         krb5_anonymous_principal()))
cb4cef
-        party_u_info = (krb5_principal)krb5_anonymous_principal();
cb4cef
-
cb4cef
-    if (0 != (retval = pkinit_alg_values(context, alg_oid, &hash_len, &EVP_func)))
cb4cef
-        goto cleanup;
cb4cef
-
cb4cef
-    /* 1.  reps = keydatalen (K) / hash length (H) */
cb4cef
-    reps = key_block->length/hash_len;
cb4cef
-
cb4cef
-    /* ... and round up, if necessary */
cb4cef
-    if (key_block->length > (reps * hash_len))
cb4cef
-        reps++;
cb4cef
-
cb4cef
-    /* Allocate enough space in the random data buffer to hash directly into
cb4cef
-     * it, even if the last hash will make it bigger than the key length. */
cb4cef
-    if (NULL == (random_data.data = malloc(reps * hash_len))) {
cb4cef
-        retval = ENOMEM;
cb4cef
-        goto cleanup;
cb4cef
-    }
cb4cef
-
cb4cef
-    /* Encode the ASN.1 octet string for "SuppPubInfo" */
cb4cef
-    supp_pub_info_fields.enctype = enctype;
cb4cef
-    supp_pub_info_fields.as_req = *as_req;
cb4cef
-    supp_pub_info_fields.pk_as_rep = *pk_as_rep;
cb4cef
-    if (0 != ((retval = encode_krb5_pkinit_supp_pub_info(&supp_pub_info_fields,
cb4cef
-                                                         &supp_pub_info))))
cb4cef
-        goto cleanup;
cb4cef
-
cb4cef
-    /* Now encode the ASN.1 octet string for "OtherInfo" */
cb4cef
-    memset(&alg_id, 0, sizeof alg_id);
cb4cef
-    alg_id.algorithm = *alg_oid; /*alias*/
cb4cef
-
cb4cef
-    other_info_fields.algorithm_identifier = alg_id;
cb4cef
-    other_info_fields.party_u_info = (krb5_principal) party_u_info;
cb4cef
-    other_info_fields.party_v_info = (krb5_principal) party_v_info;
cb4cef
-    other_info_fields.supp_pub_info = *supp_pub_info;
cb4cef
-    if (0 != (retval = encode_krb5_sp80056a_other_info(&other_info_fields, &other_info)))
cb4cef
-        goto cleanup;
cb4cef
 
cb4cef
     /* 2.  Initialize a 32-bit, big-endian bit string counter as 1.
cb4cef
      * 3.  For i = 1 to reps by 1, do the following:
cb4cef
@@ -2450,7 +2430,7 @@ pkinit_alg_agility_kdf(krb5_context context,
cb4cef
 
cb4cef
         ctx = EVP_MD_CTX_new();
cb4cef
         if (ctx == NULL) {
cb4cef
-            retval = KRB5_CRYPTO_INTERNAL;
cb4cef
+            ret = KRB5_CRYPTO_INTERNAL;
cb4cef
             goto cleanup;
cb4cef
         }
cb4cef
 
cb4cef
@@ -2458,7 +2438,7 @@ pkinit_alg_agility_kdf(krb5_context context,
cb4cef
         if (!EVP_DigestInit(ctx, EVP_func())) {
cb4cef
             krb5_set_error_message(context, KRB5_CRYPTO_INTERNAL,
cb4cef
                                    "Call to OpenSSL EVP_DigestInit() returned an error.");
cb4cef
-            retval = KRB5_CRYPTO_INTERNAL;
cb4cef
+            ret = KRB5_CRYPTO_INTERNAL;
cb4cef
             goto cleanup;
cb4cef
         }
cb4cef
 
cb4cef
@@ -2467,15 +2447,16 @@ pkinit_alg_agility_kdf(krb5_context context,
cb4cef
             !EVP_DigestUpdate(ctx, other_info->data, other_info->length)) {
cb4cef
             krb5_set_error_message(context, KRB5_CRYPTO_INTERNAL,
cb4cef
                                    "Call to OpenSSL EVP_DigestUpdate() returned an error.");
cb4cef
-            retval = KRB5_CRYPTO_INTERNAL;
cb4cef
+            ret = KRB5_CRYPTO_INTERNAL;
cb4cef
             goto cleanup;
cb4cef
         }
cb4cef
 
cb4cef
-        /* 4.  Set key = Hash1 || Hash2 || ... so that length of key is K bytes. */
cb4cef
-        if (!EVP_DigestFinal(ctx, (uint8_t *)random_data.data + offset, &s)) {
cb4cef
+        /* 4.  Set key = Hash1 || Hash2 || ... so that length of key is K
cb4cef
+         * bytes. */
cb4cef
+        if (!EVP_DigestFinal(ctx, (unsigned char *)out + offset, &s)) {
cb4cef
             krb5_set_error_message(context, KRB5_CRYPTO_INTERNAL,
cb4cef
                                    "Call to OpenSSL EVP_DigestUpdate() returned an error.");
cb4cef
-            retval = KRB5_CRYPTO_INTERNAL;
cb4cef
+            ret = KRB5_CRYPTO_INTERNAL;
cb4cef
             goto cleanup;
cb4cef
         }
cb4cef
         offset += s;
cb4cef
@@ -2484,26 +2465,113 @@ pkinit_alg_agility_kdf(krb5_context context,
cb4cef
         EVP_MD_CTX_free(ctx);
cb4cef
         ctx = NULL;
cb4cef
     }
cb4cef
-
cb4cef
-    retval = krb5_c_random_to_key(context, enctype, &random_data,
cb4cef
-                                  key_block);
cb4cef
-
cb4cef
 cleanup:
cb4cef
     EVP_MD_CTX_free(ctx);
cb4cef
+    return ret;
cb4cef
+} /* builtin_sskdf() */
cb4cef
+#endif /* HAVE_EVP_KDF_FETCH */
cb4cef
 
cb4cef
-    /* If this has been an error, free the allocated key_block, if any */
cb4cef
-    if (retval) {
cb4cef
-        krb5_free_keyblock_contents(context, key_block);
cb4cef
+/* id-pkinit-kdf family, as specified by RFC 8636. */
cb4cef
+krb5_error_code
cb4cef
+pkinit_alg_agility_kdf(krb5_context context, krb5_data *secret,
cb4cef
+                       krb5_data *alg_oid, krb5_const_principal party_u_info,
cb4cef
+                       krb5_const_principal party_v_info,
cb4cef
+                       krb5_enctype enctype, krb5_data *as_req,
cb4cef
+                       krb5_data *pk_as_rep, krb5_keyblock *key_block)
cb4cef
+{
cb4cef
+    krb5_error_code ret;
cb4cef
+    size_t hash_len = 0, rand_len = 0, key_len = 0;
cb4cef
+    const EVP_MD *(*EVP_func)(void);
cb4cef
+    krb5_sp80056a_other_info other_info_fields;
cb4cef
+    krb5_pkinit_supp_pub_info supp_pub_info_fields;
cb4cef
+    krb5_data *other_info = NULL, *supp_pub_info = NULL;
cb4cef
+    krb5_data random_data = empty_data();
cb4cef
+    krb5_algorithm_identifier alg_id;
cb4cef
+    unsigned int reps;
cb4cef
+    char *hash_name = NULL;
cb4cef
+
cb4cef
+    /* Allocate and initialize the key block. */
cb4cef
+    key_block->magic = 0;
cb4cef
+    key_block->enctype = enctype;
cb4cef
+
cb4cef
+    /* Use separate variables to avoid alignment restriction problems. */
cb4cef
+    ret = krb5_c_keylengths(context, enctype, &rand_len, &key_len);
cb4cef
+    if (ret)
cb4cef
+        goto cleanup;
cb4cef
+    random_data.length = rand_len;
cb4cef
+    key_block->length = key_len;
cb4cef
+
cb4cef
+    key_block->contents = k5calloc(key_block->length, 1, &ret;;
cb4cef
+    if (key_block->contents == NULL)
cb4cef
+        goto cleanup;
cb4cef
+
cb4cef
+    /* If this is anonymous pkinit, use the anonymous principle for
cb4cef
+     * party_u_info. */
cb4cef
+    if (party_u_info &&
cb4cef
+        krb5_principal_compare_any_realm(context, party_u_info,
cb4cef
+                                         krb5_anonymous_principal())) {
cb4cef
+        party_u_info = (krb5_principal)krb5_anonymous_principal();
cb4cef
     }
cb4cef
 
cb4cef
-    /* free other allocated resources, either way */
cb4cef
-    if (random_data.data)
cb4cef
-        free(random_data.data);
cb4cef
+    ret = pkinit_alg_values(context, alg_oid, &hash_len, &EVP_func,
cb4cef
+                            &hash_name);
cb4cef
+    if (ret)
cb4cef
+        goto cleanup;
cb4cef
+
cb4cef
+    /* 1.  reps = keydatalen (K) / hash length (H) */
cb4cef
+    reps = key_block->length / hash_len;
cb4cef
+
cb4cef
+    /* ... and round up, if necessary. */
cb4cef
+    if (key_block->length > (reps * hash_len))
cb4cef
+        reps++;
cb4cef
+
cb4cef
+    /* Allocate enough space in the random data buffer to hash directly into
cb4cef
+     * it, even if the last hash will make it bigger than the key length. */
cb4cef
+    random_data.data = k5alloc(reps * hash_len, &ret;;
cb4cef
+    if (random_data.data == NULL)
cb4cef
+        goto cleanup;
cb4cef
+
cb4cef
+    /* Encode the ASN.1 octet string for "SuppPubInfo". */
cb4cef
+    supp_pub_info_fields.enctype = enctype;
cb4cef
+    supp_pub_info_fields.as_req = *as_req;
cb4cef
+    supp_pub_info_fields.pk_as_rep = *pk_as_rep;
cb4cef
+    ret = encode_krb5_pkinit_supp_pub_info(&supp_pub_info_fields,
cb4cef
+                                           &supp_pub_info);
cb4cef
+    if (ret)
cb4cef
+        goto cleanup;
cb4cef
+
cb4cef
+    /* Now encode the ASN.1 octet string for "OtherInfo". */
cb4cef
+    memset(&alg_id, 0, sizeof(alg_id));
cb4cef
+    alg_id.algorithm = *alg_oid;
cb4cef
+    other_info_fields.algorithm_identifier = alg_id;
cb4cef
+    other_info_fields.party_u_info = (krb5_principal)party_u_info;
cb4cef
+    other_info_fields.party_v_info = (krb5_principal)party_v_info;
cb4cef
+    other_info_fields.supp_pub_info = *supp_pub_info;
cb4cef
+    ret = encode_krb5_sp80056a_other_info(&other_info_fields, &other_info);
cb4cef
+    if (ret)
cb4cef
+        goto cleanup;
cb4cef
+
cb4cef
+#ifdef HAVE_EVP_KDF_FETCH
cb4cef
+    ret = openssl_sskdf(context, hash_len, secret, other_info,
cb4cef
+                        random_data.data, key_block->length, hash_name);
cb4cef
+#else
cb4cef
+    ret = builtin_sskdf(context, reps, hash_len, EVP_func, secret,
cb4cef
+                        other_info, random_data.data, key_block->length);
cb4cef
+#endif
cb4cef
+    if (ret)
cb4cef
+        goto cleanup;
cb4cef
+
cb4cef
+    ret = krb5_c_random_to_key(context, enctype, &random_data, key_block);
cb4cef
+cleanup:
cb4cef
+    if (ret)
cb4cef
+        krb5_free_keyblock_contents(context, key_block);
cb4cef
+
cb4cef
+    free(hash_name);
cb4cef
+    zapfree(random_data.data, random_data.length);
cb4cef
     krb5_free_data(context, other_info);
cb4cef
     krb5_free_data(context, supp_pub_info);
cb4cef
-
cb4cef
-    return retval;
cb4cef
-} /*pkinit_alg_agility_kdf() */
cb4cef
+    return ret;
cb4cef
+}
cb4cef
 
cb4cef
 /* Call DH_compute_key() and ensure that we left-pad short results instead of
cb4cef
  * leaving junk bytes at the end of the buffer. */