1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
From: Luke Howard <lukeh@padl.com>
Date: Tue, 7 May 2019 13:15:15 +1000
Subject: CVE-2019-12098: krb5: always confirm PA-PKINIT-KX for anon PKINIT
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.
Introduced by a1ef548600c5bb51cf52a9a9ea12676506ede19f. First tagged
release Heimdal 1.4.0.
CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N (4.8)
Change-Id: I6cc1c0c24985936468af08693839ac6c3edda133
Signed-off-by: Jeffrey Altman <jaltman@auristor.com>
Approved-by: Jeffrey Altman <jaltman@auritor.com>
(cherry picked from commit 38c797e1ae9b9c8f99ae4aa2e73957679031fd2b)
---
lib/krb5/init_creds_pw.c | 20 +++++++++++
lib/krb5/krb5_locl.h | 1 +
lib/krb5/pkinit.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 113 insertions(+)
diff --git a/lib/krb5/init_creds_pw.c b/lib/krb5/init_creds_pw.c
index 1eece17..9ec07d0 100644
--- a/lib/krb5/init_creds_pw.c
+++ b/lib/krb5/init_creds_pw.c
@@ -2267,6 +2267,26 @@ krb5_init_creds_step(krb5_context context,
&ctx->req_buffer,
NULL,
NULL);
+ if (ret == 0 && ctx->pk_init_ctx) {
+ PA_DATA *pa_pkinit_kx;
+ int idx = 0;
+
+ pa_pkinit_kx =
+ krb5_find_padata(rep.kdc_rep.padata->val,
+ rep.kdc_rep.padata->len,
+ KRB5_PADATA_PKINIT_KX,
+ &idx);
+
+ ret = _krb5_pk_kx_confirm(context, ctx->pk_init_ctx,
+ ctx->fast_state.reply_key,
+ &ctx->cred.session,
+ pa_pkinit_kx);
+ if (ret)
+ krb5_set_error_message(context, ret,
+ N_("Failed to confirm PA-PKINIT-KX", ""));
+ else if (pa_pkinit_kx != NULL)
+ ctx->ic_flags |= KRB5_INIT_CREDS_PKINIT_KX_VALID;
+ }
if (ret == 0)
ret = copy_EncKDCRepPart(&rep.enc_part, &ctx->enc_part);
diff --git a/lib/krb5/krb5_locl.h b/lib/krb5/krb5_locl.h
index 4d524ce..f16fbc0 100644
--- a/lib/krb5/krb5_locl.h
+++ b/lib/krb5/krb5_locl.h
@@ -208,6 +208,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;
diff --git a/lib/krb5/pkinit.c b/lib/krb5/pkinit.c
index 0adb65e..69b11ec 100644
--- a/lib/krb5/pkinit.c
+++ b/lib/krb5/pkinit.c
@@ -1220,6 +1220,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,
|