Skip to content

Commit 2f7f3d9

Browse files
lhowardjaltman
authored andcommitted
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 a1ef548. 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 38c797e)
1 parent 9f29437 commit 2f7f3d9

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

Diff for: lib/krb5/init_creds_pw.c

+20
Original file line numberDiff line numberDiff line change
@@ -2267,6 +2267,26 @@ krb5_init_creds_step(krb5_context context,
22672267
&ctx->req_buffer,
22682268
NULL,
22692269
NULL);
2270+
if (ret == 0 && ctx->pk_init_ctx) {
2271+
PA_DATA *pa_pkinit_kx;
2272+
int idx = 0;
2273+
2274+
pa_pkinit_kx =
2275+
krb5_find_padata(rep.kdc_rep.padata->val,
2276+
rep.kdc_rep.padata->len,
2277+
KRB5_PADATA_PKINIT_KX,
2278+
&idx);
2279+
2280+
ret = _krb5_pk_kx_confirm(context, ctx->pk_init_ctx,
2281+
ctx->fast_state.reply_key,
2282+
&ctx->cred.session,
2283+
pa_pkinit_kx);
2284+
if (ret)
2285+
krb5_set_error_message(context, ret,
2286+
N_("Failed to confirm PA-PKINIT-KX", ""));
2287+
else if (pa_pkinit_kx != NULL)
2288+
ctx->ic_flags |= KRB5_INIT_CREDS_PKINIT_KX_VALID;
2289+
}
22702290
if (ret == 0)
22712291
ret = copy_EncKDCRepPart(&rep.enc_part, &ctx->enc_part);
22722292

Diff for: lib/krb5/krb5_locl.h

+1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ struct _krb5_get_init_creds_opt_private {
208208
#define KRB5_INIT_CREDS_CANONICALIZE 1
209209
#define KRB5_INIT_CREDS_NO_C_CANON_CHECK 2
210210
#define KRB5_INIT_CREDS_NO_C_NO_EKU_CHECK 4
211+
#define KRB5_INIT_CREDS_PKINIT_KX_VALID 32
211212
struct {
212213
krb5_gic_process_last_req func;
213214
void *ctx;

Diff for: lib/krb5/pkinit.c

+92
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,98 @@ pk_rd_pa_reply_enckey(krb5_context context,
12201220
return ret;
12211221
}
12221222

1223+
/*
1224+
* RFC 8062 section 7:
1225+
*
1226+
* The client then decrypts the KDC contribution key and verifies that
1227+
* the ticket session key in the returned ticket is the combined key of
1228+
* the KDC contribution key and the reply key.
1229+
*/
1230+
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1231+
_krb5_pk_kx_confirm(krb5_context context,
1232+
krb5_pk_init_ctx ctx,
1233+
krb5_keyblock *reply_key,
1234+
krb5_keyblock *session_key,
1235+
PA_DATA *pa_pkinit_kx)
1236+
{
1237+
krb5_error_code ret;
1238+
EncryptedData ed;
1239+
krb5_keyblock ck, sk_verify;
1240+
krb5_crypto ck_crypto = NULL;
1241+
krb5_crypto rk_crypto = NULL;
1242+
size_t len;
1243+
krb5_data data;
1244+
krb5_data p1 = { sizeof("PKINIT") - 1, "PKINIT" };
1245+
krb5_data p2 = { sizeof("KEYEXCHANGE") - 1, "KEYEXCHANGE" };
1246+
1247+
heim_assert(ctx != NULL, "PKINIT context is non-NULL");
1248+
heim_assert(reply_key != NULL, "reply key is non-NULL");
1249+
heim_assert(session_key != NULL, "session key is non-NULL");
1250+
1251+
/* PA-PKINIT-KX is optional unless anonymous */
1252+
if (pa_pkinit_kx == NULL)
1253+
return ctx->anonymous ? KRB5_KDCREP_MODIFIED : 0;
1254+
1255+
memset(&ed, 0, sizeof(ed));
1256+
krb5_keyblock_zero(&ck);
1257+
krb5_keyblock_zero(&sk_verify);
1258+
krb5_data_zero(&data);
1259+
1260+
ret = decode_EncryptedData(pa_pkinit_kx->padata_value.data,
1261+
pa_pkinit_kx->padata_value.length,
1262+
&ed, &len);
1263+
if (ret)
1264+
goto out;
1265+
1266+
if (len != pa_pkinit_kx->padata_value.length) {
1267+
ret = KRB5_KDCREP_MODIFIED;
1268+
goto out;
1269+
}
1270+
1271+
ret = krb5_crypto_init(context, reply_key, 0, &rk_crypto);
1272+
if (ret)
1273+
goto out;
1274+
1275+
ret = krb5_decrypt_EncryptedData(context, rk_crypto,
1276+
KRB5_KU_PA_PKINIT_KX,
1277+
&ed, &data);
1278+
if (ret)
1279+
goto out;
1280+
1281+
ret = decode_EncryptionKey(data.data, data.length,
1282+
&ck, &len);
1283+
if (ret)
1284+
goto out;
1285+
1286+
ret = krb5_crypto_init(context, &ck, 0, &ck_crypto);
1287+
if (ret)
1288+
goto out;
1289+
1290+
ret = krb5_crypto_fx_cf2(context, ck_crypto, rk_crypto,
1291+
&p1, &p2, session_key->keytype,
1292+
&sk_verify);
1293+
if (ret)
1294+
goto out;
1295+
1296+
if (sk_verify.keytype != session_key->keytype ||
1297+
krb5_data_ct_cmp(&sk_verify.keyvalue, &session_key->keyvalue) != 0) {
1298+
ret = KRB5_KDCREP_MODIFIED;
1299+
goto out;
1300+
}
1301+
1302+
out:
1303+
free_EncryptedData(&ed);
1304+
krb5_free_keyblock_contents(context, &ck);
1305+
krb5_free_keyblock_contents(context, &sk_verify);
1306+
if (ck_crypto)
1307+
krb5_crypto_destroy(context, ck_crypto);
1308+
if (rk_crypto)
1309+
krb5_crypto_destroy(context, rk_crypto);
1310+
krb5_data_free(&data);
1311+
1312+
return ret;
1313+
}
1314+
12231315
static krb5_error_code
12241316
pk_rd_pa_reply_dh(krb5_context context,
12251317
const heim_octet_string *indata,

0 commit comments

Comments
 (0)