heimdal: always confirm PA-PKINIT-KX for anon PKINIT

Import upstream 38c797e1a.

Upstream notes:

    RFC8062 Section 7 requires verification of the PA-PKINIT-KX key
    excahnge when anonymous PKINIT is used.  Failure to do so can
    permit an active attacker to become a man-in-the-middle.

Reported by:	emaste
Obtained from:	upstream 38c797e1a
Security:	CVE-2019-12098

(cherry picked from commit 60616b445e)
This commit is contained in:
Cy Schubert 2024-02-14 17:58:06 -08:00
parent 5482dc8b9e
commit b1b256dd0f
2 changed files with 93 additions and 0 deletions

View file

@ -240,6 +240,7 @@ struct _krb5_get_init_creds_opt_private {
#define KRB5_INIT_CREDS_CANONICALIZE 1
#define KRB5_INIT_CREDS_NO_C_CANON_CHECK 2
#define KRB5_INIT_CREDS_NO_C_NO_EKU_CHECK 4
#define KRB5_INIT_CREDS_PKINIT_KX_VALID 32
struct {
krb5_gic_process_last_req func;
void *ctx;

View file

@ -1306,6 +1306,98 @@ pk_rd_pa_reply_enckey(krb5_context context,
return ret;
}
/*
* RFC 8062 section 7:
*
* The client then decrypts the KDC contribution key and verifies that
* the ticket session key in the returned ticket is the combined key of
* the KDC contribution key and the reply key.
*/
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_pk_kx_confirm(krb5_context context,
krb5_pk_init_ctx ctx,
krb5_keyblock *reply_key,
krb5_keyblock *session_key,
PA_DATA *pa_pkinit_kx)
{
krb5_error_code ret;
EncryptedData ed;
krb5_keyblock ck, sk_verify;
krb5_crypto ck_crypto = NULL;
krb5_crypto rk_crypto = NULL;
size_t len;
krb5_data data;
krb5_data p1 = { sizeof("PKINIT") - 1, "PKINIT" };
krb5_data p2 = { sizeof("KEYEXCHANGE") - 1, "KEYEXCHANGE" };
heim_assert(ctx != NULL, "PKINIT context is non-NULL");
heim_assert(reply_key != NULL, "reply key is non-NULL");
heim_assert(session_key != NULL, "session key is non-NULL");
/* PA-PKINIT-KX is optional unless anonymous */
if (pa_pkinit_kx == NULL)
return ctx->anonymous ? KRB5_KDCREP_MODIFIED : 0;
memset(&ed, 0, sizeof(ed));
krb5_keyblock_zero(&ck);
krb5_keyblock_zero(&sk_verify);
krb5_data_zero(&data);
ret = decode_EncryptedData(pa_pkinit_kx->padata_value.data,
pa_pkinit_kx->padata_value.length,
&ed, &len);
if (ret)
goto out;
if (len != pa_pkinit_kx->padata_value.length) {
ret = KRB5_KDCREP_MODIFIED;
goto out;
}
ret = krb5_crypto_init(context, reply_key, 0, &rk_crypto);
if (ret)
goto out;
ret = krb5_decrypt_EncryptedData(context, rk_crypto,
KRB5_KU_PA_PKINIT_KX,
&ed, &data);
if (ret)
goto out;
ret = decode_EncryptionKey(data.data, data.length,
&ck, &len);
if (ret)
goto out;
ret = krb5_crypto_init(context, &ck, 0, &ck_crypto);
if (ret)
goto out;
ret = krb5_crypto_fx_cf2(context, ck_crypto, rk_crypto,
&p1, &p2, session_key->keytype,
&sk_verify);
if (ret)
goto out;
if (sk_verify.keytype != session_key->keytype ||
krb5_data_ct_cmp(&sk_verify.keyvalue, &session_key->keyvalue) != 0) {
ret = KRB5_KDCREP_MODIFIED;
goto out;
}
out:
free_EncryptedData(&ed);
krb5_free_keyblock_contents(context, &ck);
krb5_free_keyblock_contents(context, &sk_verify);
if (ck_crypto)
krb5_crypto_destroy(context, ck_crypto);
if (rk_crypto)
krb5_crypto_destroy(context, rk_crypto);
krb5_data_free(&data);
return ret;
}
static krb5_error_code
pk_rd_pa_reply_dh(krb5_context context,
const heim_octet_string *indata,