Package: heimdal / 7.1.0+dfsg-13+deb9u3

0022-CVE-2019-12098-krb5-always-confirm-PA-PKINIT-KX-for-.patch Patch series | download
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 9653734..943e5ec 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,