/src/samba/third_party/heimdal/lib/krb5/kx509.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2019 Kungliga Tekniska Högskolan |
3 | | * (Royal Institute of Technology, Stockholm, Sweden). |
4 | | * All rights reserved. |
5 | | * |
6 | | * Redistribution and use in source and binary forms, with or without |
7 | | * modification, are permitted provided that the following conditions |
8 | | * are met: |
9 | | * |
10 | | * 1. Redistributions of source code must retain the above copyright |
11 | | * notice, this list of conditions and the following disclaimer. |
12 | | * |
13 | | * 2. Redistributions in binary form must reproduce the above copyright |
14 | | * notice, this list of conditions and the following disclaimer in the |
15 | | * documentation and/or other materials provided with the distribution. |
16 | | * |
17 | | * 3. Neither the name of the Institute nor the names of its contributors |
18 | | * may be used to endorse or promote products derived from this software |
19 | | * without specific prior written permission. |
20 | | * |
21 | | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND |
22 | | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
23 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
24 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE |
25 | | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
26 | | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
27 | | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
28 | | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
29 | | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
30 | | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
31 | | * SUCH DAMAGE. |
32 | | */ |
33 | | |
34 | | #include "krb5_locl.h" |
35 | | #include <kx509_asn1.h> |
36 | | #include <kx509_err.h> |
37 | | #include "../hx509/hx_locl.h" /* XXX find a better way */ |
38 | | #include "hx509-private.h" |
39 | | |
40 | | /* |
41 | | * This file implements a client for the kx509 protocol -- a Kerberized online |
42 | | * CA that can issue a Certificate to a client that authenticates using |
43 | | * Kerberos. |
44 | | * |
45 | | * The kx509 protocol is the inverse of PKINIT. Whereas PKINIT allows users |
46 | | * with PKIX credentials to acquire Kerberos credentials, the kx509 protocol |
47 | | * allows users with Kerberos credentials to acquire PKIX credentials. |
48 | | * |
49 | | * I.e., kx509 is a bridge, just like PKINIT. |
50 | | * |
51 | | * The kx509 protocol is very simple, and very limited. |
52 | | * |
53 | | * A request consists of a DER-encoded Kx509Request message prefixed with four |
54 | | * bytes identifying the protocol (see `version_2_0' below). |
55 | | * |
56 | | * A Kx509Request message contains an AP-REQ, a public key, and an HMAC of the |
57 | | * public key made with the session key of the AP-REQ's ticket. |
58 | | * |
59 | | * The service principal can be either kca_service/hostname.fqdn or |
60 | | * krbtgt/REALM (a Heimdal innovation). |
61 | | * |
62 | | * If a request is missing a public key, then the request is a probe intended |
63 | | * to discover whether the service is enabled, thus helping the client avoid |
64 | | * a possibly-slow private key generation operation. |
65 | | * |
66 | | * The response is a DER-encoded Kx509Response also prefixed with |
67 | | * `version_2_0', and contains: an optional error code and error text, an |
68 | | * optional certificate (for the success case), and an optional HMAC of those |
69 | | * fields that is present when the service was able to verify the AP-REQ. |
70 | | * |
71 | | * Limitations: |
72 | | * |
73 | | * - no proof of possession for the public key |
74 | | * - only RSA keys are supported |
75 | | * - no way to express options (e.g., what KUs, EKUs, or SANs are desired) |
76 | | * - no sub-session key usage |
77 | | * - no reflection protection other than the HMAC's forgery protection and the |
78 | | * fact that the client could tell that a reflected attack isn't success |
79 | | * |
80 | | * Future directions: |
81 | | * |
82 | | * - Since the public key field of the request is an OCTET STRING, we could |
83 | | * send a CSR, or even an expired certificate (possibly self-signed, |
84 | | * possibly one issued earlier) that can serve as a template. |
85 | | * |
86 | | * This solves the first three limitations, as it allows the client to |
87 | | * demonstrate proof of possession, allows arbitrary public key types, and |
88 | | * allows the client to express desires about the to-be-issued certificate. |
89 | | * |
90 | | * - Use the AP-REQ's Authenticator's sub-session key for the HMAC, and derive |
91 | | * per-direction sub-sub-keys. |
92 | | * |
93 | | * - We might design a new protocol that better fits the RFC4120 KDC message |
94 | | * framework. |
95 | | */ |
96 | | |
97 | | static const unsigned char version_2_0[4] = {0 , 0, 2, 0}; |
98 | | |
99 | | struct krb5_kx509_req_ctx_data { |
100 | | krb5_auth_context ac; |
101 | | krb5_data given_csr; |
102 | | hx509_request csr; |
103 | | Kx509CSRPlus csr_plus; |
104 | | char *realm; /* Realm to which to send request */ |
105 | | krb5_keyblock *hmac_key; /* For HMAC validation */ |
106 | | hx509_private_key *keys; |
107 | | hx509_private_key priv_key; |
108 | | unsigned int expect_chain; |
109 | | }; |
110 | | |
111 | | /** |
112 | | * Create a kx509 request context. |
113 | | * |
114 | | * @param context The Kerberos library context |
115 | | * @param out Where to place the kx509 request context |
116 | | * |
117 | | * @return A krb5 error code. |
118 | | */ |
119 | | krb5_error_code |
120 | | krb5_kx509_ctx_init(krb5_context context, krb5_kx509_req_ctx *out) |
121 | 0 | { |
122 | 0 | krb5_kx509_req_ctx ctx; |
123 | 0 | krb5_error_code ret; |
124 | 0 | hx509_name name = NULL; |
125 | |
|
126 | 0 | ALLOC(ctx, 1); |
127 | 0 | if (ctx == NULL) |
128 | 0 | return krb5_enomem(context); |
129 | 0 | ctx->given_csr.data = NULL; |
130 | 0 | ctx->priv_key = NULL; |
131 | 0 | ctx->hmac_key = NULL; |
132 | 0 | ctx->realm = NULL; |
133 | 0 | ctx->keys = NULL; |
134 | 0 | ctx->csr = NULL; |
135 | 0 | ret = hx509_request_init(context->hx509ctx, &ctx->csr); |
136 | 0 | if (ret == 0) |
137 | 0 | ret = hx509_parse_name(context->hx509ctx, "", &name); |
138 | 0 | if (ret == 0) |
139 | 0 | ret = hx509_request_set_name(context->hx509ctx, ctx->csr, name); |
140 | 0 | if (ret == 0) |
141 | 0 | ret = krb5_auth_con_init(context, &ctx->ac); |
142 | 0 | if (name) |
143 | 0 | hx509_name_free(&name); |
144 | 0 | if (ret == 0) |
145 | 0 | *out = ctx; |
146 | 0 | else |
147 | 0 | krb5_kx509_ctx_free(context, &ctx); |
148 | 0 | return ret; |
149 | 0 | } |
150 | | |
151 | | /** |
152 | | * Free a kx509 request context. |
153 | | * |
154 | | * @param context The Kerberos library context |
155 | | * @param ctxp Pointer to krb5 request context to free |
156 | | * |
157 | | * @return A krb5 error code. |
158 | | */ |
159 | | void |
160 | | krb5_kx509_ctx_free(krb5_context context, krb5_kx509_req_ctx *ctxp) |
161 | 0 | { |
162 | 0 | krb5_kx509_req_ctx ctx = *ctxp; |
163 | |
|
164 | 0 | *ctxp = NULL; |
165 | 0 | if (ctx == NULL) |
166 | 0 | return; |
167 | 0 | krb5_free_keyblock(context, ctx->hmac_key); |
168 | 0 | krb5_auth_con_free(context, ctx->ac); |
169 | 0 | free_Kx509CSRPlus(&ctx->csr_plus); |
170 | 0 | free(ctx->realm); |
171 | 0 | hx509_request_free(&ctx->csr); |
172 | 0 | krb5_data_free(&ctx->given_csr); |
173 | 0 | hx509_private_key_free(&ctx->priv_key); |
174 | 0 | _hx509_certs_keys_free(context->hx509ctx, ctx->keys); |
175 | 0 | free(ctx); |
176 | 0 | } |
177 | | |
178 | | /** |
179 | | * Set a realm to send kx509 request to, if different from the client's. |
180 | | * |
181 | | * @param context The Kerberos library context |
182 | | * @param ctx The kx509 request context |
183 | | * @param realm Realm name |
184 | | * |
185 | | * @return A krb5 error code. |
186 | | */ |
187 | | krb5_error_code |
188 | | krb5_kx509_ctx_set_realm(krb5_context context, |
189 | | krb5_kx509_req_ctx kx509_ctx, |
190 | | const char *realm) |
191 | 0 | { |
192 | 0 | return ((kx509_ctx->realm = strdup(realm)) == NULL) ? |
193 | 0 | krb5_enomem(context) : 0; |
194 | 0 | } |
195 | | |
196 | | /** |
197 | | * Sets a CSR for a kx509 request. |
198 | | * |
199 | | * Normally kx509 will generate a CSR (and even a private key for it) |
200 | | * automatically. If a CSR is given then kx509 will use it instead of |
201 | | * generating one. |
202 | | * |
203 | | * @param context The Kerberos library context |
204 | | * @param ctx The kx509 request context |
205 | | * @param csr_der A DER-encoded PKCS#10 CSR |
206 | | * |
207 | | * @return A krb5 error code. |
208 | | */ |
209 | | krb5_error_code |
210 | | krb5_kx509_ctx_set_csr_der(krb5_context context, |
211 | | krb5_kx509_req_ctx ctx, |
212 | | krb5_data *csr_der) |
213 | 0 | { |
214 | 0 | krb5_data_free(&ctx->given_csr); |
215 | 0 | return krb5_data_copy(&ctx->given_csr, csr_der->data, csr_der->length); |
216 | 0 | } |
217 | | |
218 | | /** |
219 | | * Adds an EKU as an additional desired Certificate Extension or in the CSR if |
220 | | * the caller does not set a CSR. |
221 | | * |
222 | | * @param context The Kerberos library context |
223 | | * @param ctx The kx509 request context |
224 | | * @param oids A string representation of an OID |
225 | | * |
226 | | * @return A krb5 error code. |
227 | | */ |
228 | | krb5_error_code |
229 | | krb5_kx509_ctx_add_eku(krb5_context context, |
230 | | krb5_kx509_req_ctx kx509_ctx, |
231 | | const char *oids) |
232 | 0 | { |
233 | 0 | krb5_error_code ret; |
234 | 0 | heim_oid oid; |
235 | |
|
236 | 0 | ret = der_parse_heim_oid(oids, NULL, &oid); |
237 | 0 | if (ret == 0) |
238 | 0 | hx509_request_add_eku(context->hx509ctx, kx509_ctx->csr, &oid); |
239 | 0 | der_free_oid(&oid); |
240 | 0 | return ret; |
241 | 0 | } |
242 | | |
243 | | /** |
244 | | * Adds a dNSName SAN (domainname, hostname) as an additional desired |
245 | | * Certificate Extension or in the CSR if the caller does not set a CSR. |
246 | | * |
247 | | * @param context The Kerberos library context |
248 | | * @param ctx The kx509 request context |
249 | | * @param dname A string containing a DNS domainname |
250 | | * |
251 | | * @return A krb5 error code. |
252 | | */ |
253 | | krb5_error_code |
254 | | krb5_kx509_ctx_add_san_dns_name(krb5_context context, |
255 | | krb5_kx509_req_ctx kx509_ctx, |
256 | | const char *dname) |
257 | 0 | { |
258 | 0 | return hx509_request_add_dns_name(context->hx509ctx, kx509_ctx->csr, |
259 | 0 | dname); |
260 | 0 | } |
261 | | |
262 | | /** |
263 | | * Adds an xmppAddr SAN (jabber address) as an additional desired Certificate |
264 | | * Extension or in the CSR if the caller does not set a CSR. |
265 | | * |
266 | | * @param context The Kerberos library context |
267 | | * @param ctx The kx509 request context |
268 | | * @param jid A string containing a Jabber address |
269 | | * |
270 | | * @return A krb5 error code. |
271 | | */ |
272 | | krb5_error_code |
273 | | krb5_kx509_ctx_add_san_xmpp(krb5_context context, |
274 | | krb5_kx509_req_ctx kx509_ctx, |
275 | | const char *jid) |
276 | 0 | { |
277 | 0 | return hx509_request_add_xmpp_name(context->hx509ctx, kx509_ctx->csr, jid); |
278 | 0 | } |
279 | | |
280 | | /** |
281 | | * Adds an rfc822Name SAN (e-mail address) as an additional desired Certificate |
282 | | * Extension or in the CSR if the caller does not set a CSR. |
283 | | * |
284 | | * @param context The Kerberos library context |
285 | | * @param ctx The kx509 request context |
286 | | * @param email A string containing an e-mail address |
287 | | * |
288 | | * @return A krb5 error code. |
289 | | */ |
290 | | krb5_error_code |
291 | | krb5_kx509_ctx_add_san_rfc822Name(krb5_context context, |
292 | | krb5_kx509_req_ctx kx509_ctx, |
293 | | const char *email) |
294 | 0 | { |
295 | 0 | return hx509_request_add_email(context->hx509ctx, kx509_ctx->csr, email); |
296 | 0 | } |
297 | | |
298 | | /** |
299 | | * Adds an pkinit SAN (Kerberos principal name) as an additional desired |
300 | | * Certificate Extension or in the CSR if the caller does not set a CSR. |
301 | | * |
302 | | * @param context The Kerberos library context |
303 | | * @param ctx The kx509 request context |
304 | | * @param pname A string containing a representation of a Kerberos principal |
305 | | * name |
306 | | * |
307 | | * @return A krb5 error code. |
308 | | */ |
309 | | krb5_error_code |
310 | | krb5_kx509_ctx_add_san_pkinit(krb5_context context, |
311 | | krb5_kx509_req_ctx kx509_ctx, |
312 | | const char *pname) |
313 | 0 | { |
314 | 0 | return hx509_request_add_pkinit(context->hx509ctx, kx509_ctx->csr, pname); |
315 | 0 | } |
316 | | |
317 | | /** |
318 | | * Adds a Microsoft-style UPN (user principal name) as an additional desired |
319 | | * Certificate Extension or in the CSR if the caller does not set a CSR. |
320 | | * |
321 | | * @param context The Kerberos library context |
322 | | * @param ctx The kx509 request context |
323 | | * @param upn A string containing a representation of a UPN |
324 | | * |
325 | | * @return A krb5 error code. |
326 | | */ |
327 | | krb5_error_code |
328 | | krb5_kx509_ctx_add_san_ms_upn(krb5_context context, |
329 | | krb5_kx509_req_ctx kx509_ctx, |
330 | | const char *upn) |
331 | 0 | { |
332 | 0 | return hx509_request_add_ms_upn_name(context->hx509ctx, kx509_ctx->csr, |
333 | 0 | upn); |
334 | 0 | } |
335 | | |
336 | | /** |
337 | | * Adds an registeredID SAN (OID) as an additional desired Certificate |
338 | | * Extension or in the CSR if the caller does not set a CSR. |
339 | | * |
340 | | * @param context The Kerberos library context |
341 | | * @param ctx The kx509 request context |
342 | | * @param oids A string representation of an OID |
343 | | * |
344 | | * @return A krb5 error code. |
345 | | */ |
346 | | krb5_error_code |
347 | | krb5_kx509_ctx_add_san_registeredID(krb5_context context, |
348 | | krb5_kx509_req_ctx kx509_ctx, |
349 | | const char *oids) |
350 | 0 | { |
351 | 0 | krb5_error_code ret; |
352 | 0 | heim_oid oid; |
353 | |
|
354 | 0 | ret = der_parse_heim_oid(oids, NULL, &oid); |
355 | 0 | if (ret == 0) |
356 | 0 | hx509_request_add_registered(context->hx509ctx, kx509_ctx->csr, &oid); |
357 | 0 | der_free_oid(&oid); |
358 | 0 | return ret; |
359 | 0 | } |
360 | | |
361 | | static krb5_error_code |
362 | | load_priv_key(krb5_context context, |
363 | | krb5_kx509_req_ctx kx509_ctx, |
364 | | const char *fn) |
365 | 0 | { |
366 | 0 | hx509_private_key *keys = NULL; |
367 | 0 | hx509_certs certs = NULL; |
368 | 0 | krb5_error_code ret; |
369 | |
|
370 | 0 | ret = hx509_certs_init(context->hx509ctx, fn, 0, NULL, &certs); |
371 | 0 | if (ret == ENOENT) |
372 | 0 | return 0; |
373 | 0 | if (ret == 0) |
374 | 0 | ret = _hx509_certs_keys_get(context->hx509ctx, certs, &keys); |
375 | 0 | if (ret == 0 && keys[0] == NULL) |
376 | 0 | ret = ENOENT; |
377 | 0 | if (ret == 0) |
378 | 0 | kx509_ctx->priv_key = _hx509_private_key_ref(keys[0]); |
379 | 0 | if (ret) { |
380 | 0 | char *emsg = hx509_get_error_string(context->hx509ctx, ret); |
381 | |
|
382 | 0 | krb5_set_error_message(context, ret, "Could not load private key " |
383 | 0 | "from %s for kx509: %s", fn, emsg); |
384 | 0 | hx509_free_error_string(emsg); |
385 | 0 | } |
386 | 0 | hx509_certs_free(&certs); |
387 | 0 | return ret; |
388 | 0 | } |
389 | | |
390 | | /** |
391 | | * Set a private key. |
392 | | * |
393 | | * @param context The Kerberos library context |
394 | | * @param ctx The kx509 request context |
395 | | * @param store The name of a PKIX credential store |
396 | | * |
397 | | * @return A krb5 error code. |
398 | | */ |
399 | | krb5_error_code |
400 | | krb5_kx509_ctx_set_key(krb5_context context, |
401 | | krb5_kx509_req_ctx kx509_ctx, |
402 | | const char *store) |
403 | 0 | { |
404 | 0 | SubjectPublicKeyInfo key; |
405 | 0 | krb5_error_code ret; |
406 | |
|
407 | 0 | memset(&key, 0, sizeof(key)); |
408 | 0 | hx509_private_key_free(&kx509_ctx->priv_key); |
409 | 0 | _hx509_certs_keys_free(context->hx509ctx, kx509_ctx->keys); |
410 | 0 | kx509_ctx->keys = NULL; |
411 | 0 | ret = load_priv_key(context, kx509_ctx, store); |
412 | 0 | if (ret == 0) |
413 | 0 | ret = hx509_private_key2SPKI(context->hx509ctx, kx509_ctx->priv_key, |
414 | 0 | &key); |
415 | 0 | if (ret == 0) |
416 | 0 | ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx, |
417 | 0 | kx509_ctx->csr, &key); |
418 | 0 | free_SubjectPublicKeyInfo(&key); |
419 | 0 | return ret; |
420 | 0 | } |
421 | | |
422 | | static krb5_error_code |
423 | | gen_priv_key(krb5_context context, |
424 | | const char *gen_type, |
425 | | unsigned long gen_bits, |
426 | | hx509_private_key *key) |
427 | 0 | { |
428 | 0 | struct hx509_generate_private_context *key_gen_ctx = NULL; |
429 | 0 | krb5_error_code ret; |
430 | |
|
431 | 0 | _krb5_debug(context, 1, "kx509: gen priv key"); |
432 | 0 | if (strcmp(gen_type, "rsa") != 0) { |
433 | 0 | krb5_set_error_message(context, ENOTSUP, "Key type %s is not " |
434 | 0 | "supported for kx509; only \"rsa\" is " |
435 | 0 | "supported for kx509 at this time", |
436 | 0 | gen_type); |
437 | 0 | return ENOTSUP; |
438 | 0 | } |
439 | | |
440 | 0 | ret = _hx509_generate_private_key_init(context->hx509ctx, |
441 | 0 | ASN1_OID_ID_PKCS1_RSAENCRYPTION, |
442 | 0 | &key_gen_ctx); |
443 | 0 | if (ret == 0) |
444 | 0 | ret = _hx509_generate_private_key_bits(context->hx509ctx, key_gen_ctx, gen_bits); |
445 | |
|
446 | 0 | if (ret == 0) |
447 | 0 | ret = _hx509_generate_private_key(context->hx509ctx, key_gen_ctx, key); |
448 | 0 | _hx509_generate_private_key_free(&key_gen_ctx); |
449 | 0 | if (ret) { |
450 | 0 | char *emsg = hx509_get_error_string(context->hx509ctx, ret); |
451 | |
|
452 | 0 | krb5_set_error_message(context, ret, |
453 | 0 | "Could not generate a private key: %s", emsg); |
454 | 0 | hx509_free_error_string(emsg); |
455 | 0 | } |
456 | 0 | return ret; |
457 | 0 | } |
458 | | |
459 | | /** |
460 | | * Generate a private key. |
461 | | * |
462 | | * @param context The Kerberos library context |
463 | | * @param ctx The kx509 request context |
464 | | * @param gen_type The type of key (default: rsa) |
465 | | * @param gen_bits The size of the key (for non-ECC, really, for RSA) |
466 | | * |
467 | | * @return A krb5 error code. |
468 | | */ |
469 | | krb5_error_code |
470 | | krb5_kx509_ctx_gen_key(krb5_context context, |
471 | | krb5_kx509_req_ctx kx509_ctx, |
472 | | const char *gen_type, |
473 | | int gen_bits) |
474 | 0 | { |
475 | 0 | SubjectPublicKeyInfo key; |
476 | 0 | krb5_error_code ret; |
477 | |
|
478 | 0 | memset(&key, 0, sizeof(key)); |
479 | |
|
480 | 0 | if (gen_type == NULL) { |
481 | 0 | gen_type = krb5_config_get_string_default(context, NULL, "rsa", |
482 | 0 | "libdefaults", |
483 | 0 | "kx509_gen_key_type", NULL); |
484 | 0 | } |
485 | 0 | if (gen_bits == 0) { |
486 | | /* |
487 | | * The key size is really only for non-ECC, of which we'll only support |
488 | | * RSA. For ECC key sizes will either be implied by the `key_type' or |
489 | | * will have to be a magic value that allows us to pick from some small |
490 | | * set of curves (e.g., 255 == Curve25519). |
491 | | */ |
492 | 0 | gen_bits = krb5_config_get_int_default(context, NULL, 2048, |
493 | 0 | "libdefaults", |
494 | 0 | "kx509_gen_rsa_key_size", NULL); |
495 | 0 | } |
496 | 0 | hx509_private_key_free(&kx509_ctx->priv_key); |
497 | 0 | _hx509_certs_keys_free(context->hx509ctx, kx509_ctx->keys); |
498 | 0 | kx509_ctx->keys = NULL; |
499 | |
|
500 | 0 | ret = gen_priv_key(context, gen_type, gen_bits, &kx509_ctx->priv_key); |
501 | 0 | if (ret == 0) |
502 | 0 | ret = hx509_private_key2SPKI(context->hx509ctx, kx509_ctx->priv_key, |
503 | 0 | &key); |
504 | 0 | if (ret == 0) |
505 | 0 | ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx, |
506 | 0 | kx509_ctx->csr, &key); |
507 | 0 | free_SubjectPublicKeyInfo(&key); |
508 | 0 | return ret; |
509 | 0 | } |
510 | | |
511 | | /* Set a cc config entry indicating that the kx509 service is not available */ |
512 | | static void |
513 | | store_kx509_disabled(krb5_context context, const char *realm, krb5_ccache cc) |
514 | 0 | { |
515 | 0 | krb5_data data; |
516 | |
|
517 | 0 | if (!cc) |
518 | 0 | return; |
519 | | |
520 | 0 | data.data = (void *)(uintptr_t)realm; |
521 | 0 | data.length = strlen(realm); |
522 | 0 | krb5_cc_set_config(context, cc, NULL, "kx509_service_realm", &data); |
523 | 0 | data.data = "disabled"; |
524 | 0 | data.length = strlen(data.data); |
525 | 0 | krb5_cc_set_config(context, cc, NULL, "kx509_service_status", &data); |
526 | 0 | } |
527 | | |
528 | | static int KRB5_CALLCONV |
529 | | certs_export_func(hx509_context context, void *d, hx509_cert c) |
530 | 0 | { |
531 | 0 | heim_octet_string os; |
532 | 0 | Certificates *cs = d; |
533 | 0 | Certificate c2; |
534 | 0 | int ret; |
535 | |
|
536 | 0 | ret = hx509_cert_binary(context, c, &os); |
537 | 0 | if (ret) |
538 | 0 | return ret; |
539 | 0 | ret = decode_Certificate(os.data, os.length, &c2, NULL); |
540 | 0 | der_free_octet_string(&os); |
541 | 0 | if (ret) |
542 | 0 | return ret; |
543 | 0 | ret = add_Certificates(cs, &c2); |
544 | 0 | free_Certificate(&c2); |
545 | 0 | return ret; |
546 | 0 | } |
547 | | |
548 | | static krb5_error_code |
549 | | certs_export(hx509_context context, hx509_certs certs, heim_octet_string *out) |
550 | 0 | { |
551 | 0 | Certificates cs; |
552 | 0 | size_t len; |
553 | 0 | int ret; |
554 | |
|
555 | 0 | cs.len = 0; |
556 | 0 | cs.val = 0; |
557 | 0 | ret = hx509_certs_iter_f(context, certs, certs_export_func, &cs); |
558 | 0 | if (ret == 0) |
559 | 0 | ASN1_MALLOC_ENCODE(Certificates, out->data, out->length, &cs, &len, ret); |
560 | 0 | free_Certificates(&cs); |
561 | 0 | return ret; |
562 | 0 | } |
563 | | |
564 | | /* Store the private key and certificate where requested */ |
565 | | static krb5_error_code |
566 | | store(krb5_context context, |
567 | | const char *hx509_store, |
568 | | const char *realm, |
569 | | krb5_ccache cc, |
570 | | hx509_private_key key, |
571 | | hx509_cert cert, |
572 | | hx509_certs chain) |
573 | 0 | { |
574 | 0 | heim_octet_string hdata; |
575 | 0 | krb5_error_code ret = 0; |
576 | 0 | krb5_data data; |
577 | |
|
578 | 0 | krb5_clear_error_message(context); |
579 | |
|
580 | 0 | if (cc) { |
581 | | /* Record the realm we used */ |
582 | 0 | data.data = (void *)(uintptr_t)realm; |
583 | 0 | data.length = strlen(realm); |
584 | 0 | krb5_cc_set_config(context, cc, NULL, "kx509_service_realm", &data); |
585 | | |
586 | | /* Serialize and store the certificate in the ccache */ |
587 | 0 | ret = hx509_cert_binary(context->hx509ctx, cert, &hdata); |
588 | 0 | if (ret == 0) |
589 | 0 | ret = krb5_cc_set_config(context, cc, NULL, "kx509cert", &hdata); |
590 | 0 | der_free_octet_string(&hdata); |
591 | |
|
592 | 0 | if (ret == 0 && key) { |
593 | | /* |
594 | | * Serialized and store the key in the ccache. Use PKCS#8 so that we |
595 | | * store the algorithm OID too, which is needed in order to be able to |
596 | | * read the private key back. |
597 | | */ |
598 | 0 | if (ret == 0) |
599 | 0 | ret = _hx509_private_key_export(context->hx509ctx, key, |
600 | 0 | HX509_KEY_FORMAT_PKCS8, &hdata); |
601 | 0 | if (ret == 0) |
602 | 0 | ret = krb5_cc_set_config(context, cc, NULL, "kx509key", &hdata); |
603 | 0 | der_free_octet_string(&hdata); |
604 | 0 | if (ret) |
605 | 0 | krb5_set_error_message(context, ret, "Could not store kx509 " |
606 | 0 | "private key and certificate in ccache %s", |
607 | 0 | krb5_cc_get_name(context, cc)); |
608 | 0 | } |
609 | |
|
610 | 0 | if (ret == 0 && chain) { |
611 | 0 | ret = certs_export(context->hx509ctx, chain, &hdata); |
612 | 0 | if (ret == 0) |
613 | 0 | ret = krb5_cc_set_config(context, cc, NULL, "kx509cert-chain", |
614 | 0 | &hdata); |
615 | 0 | der_free_octet_string(&hdata); |
616 | 0 | } |
617 | 0 | } |
618 | | |
619 | | /* Store the private key and cert in an hx509 store */ |
620 | 0 | if (hx509_store != NULL) { |
621 | 0 | hx509_certs certs; |
622 | |
|
623 | 0 | if (key) |
624 | 0 | _hx509_cert_assign_key(cert, key); /* store both in the same store */ |
625 | |
|
626 | 0 | ret = hx509_certs_init(context->hx509ctx, hx509_store, |
627 | 0 | HX509_CERTS_CREATE, NULL, &certs); |
628 | 0 | if (ret == 0) |
629 | 0 | ret = hx509_certs_add(context->hx509ctx, certs, cert); |
630 | 0 | if (ret == 0 && chain != NULL) |
631 | 0 | ret = hx509_certs_merge(context->hx509ctx, certs, chain); |
632 | 0 | if (ret == 0) |
633 | 0 | ret = hx509_certs_store(context->hx509ctx, certs, 0, NULL); |
634 | 0 | hx509_certs_free(&certs); |
635 | 0 | if (ret) |
636 | 0 | krb5_prepend_error_message(context, ret, "Could not store kx509 " |
637 | 0 | "private key and certificate in key " |
638 | 0 | "store %s", hx509_store); |
639 | 0 | } |
640 | | |
641 | | /* Store the name of the hx509 store in the ccache too */ |
642 | 0 | if (cc && hx509_store) { |
643 | 0 | data.data = (void *)(uintptr_t)hx509_store; |
644 | 0 | data.length = strlen(hx509_store); |
645 | 0 | (void) krb5_cc_set_config(context, cc, NULL, "kx509store", &data); |
646 | 0 | } |
647 | 0 | return ret; |
648 | 0 | } |
649 | | |
650 | | /* Make a Kx509CSRPlus or a raw SPKI */ |
651 | | static krb5_error_code |
652 | | mk_kx509_req_body(krb5_context context, |
653 | | krb5_kx509_req_ctx kx509_ctx, |
654 | | krb5_data *out) |
655 | 0 | { |
656 | 0 | krb5_error_code ret; |
657 | 0 | size_t len; |
658 | |
|
659 | 0 | if (krb5_config_get_bool_default(context, NULL, FALSE, |
660 | 0 | "realms", kx509_ctx->realm, |
661 | 0 | "kx509_req_use_raw_spki", NULL)) { |
662 | 0 | SubjectPublicKeyInfo spki; |
663 | | |
664 | | /* Interop with old kx509 servers, send a raw SPKI, not a CSR */ |
665 | 0 | out->data = NULL; |
666 | 0 | out->length = 0; |
667 | 0 | memset(&spki, 0, sizeof(spki)); |
668 | 0 | ret = hx509_private_key2SPKI(context->hx509ctx, |
669 | 0 | kx509_ctx->priv_key, &spki); |
670 | 0 | if (ret == 0) { |
671 | 0 | out->length = spki.subjectPublicKey.length >> 3; |
672 | 0 | out->data = spki.subjectPublicKey.data; |
673 | 0 | } |
674 | 0 | kx509_ctx->expect_chain = 0; |
675 | 0 | return ret; |
676 | 0 | } |
677 | | |
678 | | /* |
679 | | * New kx509 servers use a CSR for proof of possession, and send back a |
680 | | * chain of certificates, with the issued certificate first. |
681 | | */ |
682 | 0 | kx509_ctx->expect_chain = 1; |
683 | |
|
684 | 0 | if (kx509_ctx->given_csr.length) { |
685 | 0 | krb5_data exts_der; |
686 | |
|
687 | 0 | exts_der.data = NULL; |
688 | 0 | exts_der.length = 0; |
689 | | |
690 | | /* Use the given CSR */ |
691 | 0 | ret = der_copy_octet_string(&kx509_ctx->given_csr, |
692 | 0 | &kx509_ctx->csr_plus.csr); |
693 | | |
694 | | /* |
695 | | * Extract the desired Certificate Extensions from our internal |
696 | | * as-yet-unsigned CSR, then decode them into place in the |
697 | | * Kx509CSRPlus. |
698 | | */ |
699 | 0 | if (ret == 0) |
700 | 0 | ret = hx509_request_get_exts(context->hx509ctx, |
701 | 0 | kx509_ctx->csr, |
702 | 0 | &exts_der); |
703 | 0 | if (ret == 0 && exts_der.data && exts_der.length && |
704 | 0 | (kx509_ctx->csr_plus.exts = |
705 | 0 | calloc(1, sizeof (kx509_ctx->csr_plus.exts[0]))) == NULL) |
706 | 0 | ret = krb5_enomem(context); |
707 | 0 | if (ret == 0 && exts_der.data && exts_der.length) |
708 | 0 | ret = decode_Extensions(exts_der.data, exts_der.length, |
709 | 0 | kx509_ctx->csr_plus.exts, NULL); |
710 | 0 | krb5_data_free(&exts_der); |
711 | 0 | } else { |
712 | | /* |
713 | | * Sign and use our internal CSR, which will carry all our desired |
714 | | * Certificate Extensions as an extReq CSR Attribute. |
715 | | */ |
716 | 0 | ret = hx509_request_to_pkcs10(context->hx509ctx, |
717 | 0 | kx509_ctx->csr, |
718 | 0 | kx509_ctx->priv_key, |
719 | 0 | &kx509_ctx->csr_plus.csr); |
720 | 0 | } |
721 | 0 | if (ret == 0) |
722 | 0 | ASN1_MALLOC_ENCODE(Kx509CSRPlus, out->data, out->length, |
723 | 0 | &kx509_ctx->csr_plus, &len, ret); |
724 | 0 | return ret; |
725 | 0 | } |
726 | | |
727 | | static krb5_error_code |
728 | | get_start_realm(krb5_context context, |
729 | | krb5_ccache cc, |
730 | | krb5_const_principal princ, |
731 | | char **out) |
732 | 0 | { |
733 | 0 | krb5_error_code ret; |
734 | 0 | krb5_data d; |
735 | |
|
736 | 0 | ret = krb5_cc_get_config(context, cc, NULL, "start_realm", &d); |
737 | 0 | if (ret == 0) { |
738 | 0 | *out = strndup(d.data, d.length); |
739 | 0 | krb5_data_free(&d); |
740 | 0 | } else if (princ) { |
741 | 0 | *out = strdup(krb5_principal_get_realm(context, princ)); |
742 | 0 | } else { |
743 | 0 | krb5_principal ccprinc = NULL; |
744 | |
|
745 | 0 | ret = krb5_cc_get_principal(context, cc, &ccprinc); |
746 | 0 | if (ret) |
747 | 0 | return ret; |
748 | 0 | *out = strdup(krb5_principal_get_realm(context, ccprinc)); |
749 | 0 | krb5_free_principal(context, ccprinc); |
750 | 0 | } |
751 | 0 | return (*out) ? 0 : krb5_enomem(context); |
752 | 0 | } |
753 | | |
754 | | /* |
755 | | * Make a request, which is a DER-encoded Kx509Request with version_2_0 |
756 | | * prefixed to it. |
757 | | * |
758 | | * If no private key is given, then a probe request will be made. |
759 | | */ |
760 | | static krb5_error_code |
761 | | mk_kx509_req(krb5_context context, |
762 | | krb5_kx509_req_ctx kx509_ctx, |
763 | | krb5_ccache incc, |
764 | | hx509_private_key private_key, |
765 | | krb5_data *req) |
766 | 0 | { |
767 | 0 | unsigned char digest[SHA_DIGEST_LENGTH]; |
768 | 0 | SubjectPublicKeyInfo spki; |
769 | 0 | struct Kx509Request kx509_req; |
770 | 0 | krb5_data pre_req; |
771 | 0 | krb5_error_code ret = 0; |
772 | 0 | krb5_creds this_cred; |
773 | 0 | krb5_creds *cred = NULL; |
774 | 0 | HMAC_CTX ctx; |
775 | 0 | const char *hostname; |
776 | 0 | char *start_realm = NULL; |
777 | 0 | size_t len = 0; |
778 | |
|
779 | 0 | krb5_data_zero(&pre_req); |
780 | 0 | memset(&spki, 0, sizeof(spki)); |
781 | 0 | memset(&this_cred, 0, sizeof(this_cred)); |
782 | 0 | memset(&kx509_req, 0, sizeof(kx509_req)); |
783 | 0 | kx509_req.pk_hash.data = digest; |
784 | 0 | kx509_req.pk_hash.length = SHA_DIGEST_LENGTH; |
785 | |
|
786 | 0 | if (private_key || kx509_ctx->given_csr.data) { |
787 | | /* Encode the CSR or public key for use in the request */ |
788 | 0 | ret = mk_kx509_req_body(context, kx509_ctx, &kx509_req.pk_key); |
789 | 0 | } else { |
790 | | /* Probe */ |
791 | 0 | kx509_req.pk_key.data = NULL; |
792 | 0 | kx509_req.pk_key.length = 0; |
793 | 0 | } |
794 | |
|
795 | 0 | if (ret == 0) |
796 | 0 | ret = krb5_cc_get_principal(context, incc, &this_cred.client); |
797 | 0 | if (ret == 0) |
798 | 0 | ret = get_start_realm(context, incc, this_cred.client, &start_realm); |
799 | 0 | if (ret == 0 && kx509_ctx->realm == NULL) |
800 | 0 | ret = krb5_kx509_ctx_set_realm(context, kx509_ctx, start_realm); |
801 | 0 | if (ret == 0) { |
802 | | /* |
803 | | * The kx509 protocol as deployed uses kca_service/kdc_hostname, but |
804 | | * this is inconvenient in libkrb5: we want to be able to use the |
805 | | * send_to_kdc machinery, and since the Heimdal KDC is also the kx509 |
806 | | * service, we want not to have to specify kx509 hosts separately from |
807 | | * KDCs. |
808 | | * |
809 | | * We'd much rather use krbtgt/CLIENT_REALM@REQUESTED_REALM. What |
810 | | * we do is assume all KDCs for `realm' support the kx509 service and |
811 | | * then sendto the KDCs for that realm while using a hostbased service |
812 | | * if still desired. |
813 | | * |
814 | | * Note that upstairs we try to get the start_realm cc config, so if |
815 | | * realm wasn't given to krb5_kx509_ext(), then it should be set to |
816 | | * that already unless there's no start_realm cc config, in which case |
817 | | * we'll use the ccache's default client principal's realm. |
818 | | */ |
819 | 0 | hostname = krb5_config_get_string(context, NULL, "realms", |
820 | 0 | kx509_ctx->realm, "kx509_hostname", |
821 | 0 | NULL); |
822 | 0 | if (hostname == NULL) |
823 | 0 | hostname = krb5_config_get_string(context, NULL, "libdefaults", |
824 | 0 | "kx509_hostname", NULL); |
825 | 0 | if (hostname) { |
826 | 0 | ret = krb5_sname_to_principal(context, hostname, "kca_service", |
827 | 0 | KRB5_NT_SRV_HST, &this_cred.server); |
828 | 0 | if (ret == 0) |
829 | 0 | ret = krb5_principal_set_realm(context, this_cred.server, |
830 | 0 | kx509_ctx->realm); |
831 | 0 | } else { |
832 | 0 | ret = krb5_make_principal(context, &this_cred.server, |
833 | 0 | start_realm, |
834 | 0 | KRB5_TGS_NAME, |
835 | 0 | kx509_ctx->realm, |
836 | 0 | NULL); |
837 | 0 | } |
838 | 0 | } |
839 | | |
840 | | /* Make the AP-REQ and extract the HMAC key */ |
841 | 0 | if (ret == 0) |
842 | 0 | ret = krb5_get_credentials(context, 0, incc, &this_cred, &cred); |
843 | 0 | if (ret == 0) |
844 | 0 | ret = krb5_mk_req_extended(context, &kx509_ctx->ac, AP_OPTS_USE_SUBKEY, |
845 | 0 | NULL, cred, &kx509_req.authenticator); |
846 | 0 | krb5_free_keyblock(context, kx509_ctx->hmac_key); |
847 | 0 | kx509_ctx->hmac_key = NULL; |
848 | 0 | if (ret == 0) |
849 | 0 | ret = krb5_auth_con_getkey(context, kx509_ctx->ac, |
850 | 0 | &kx509_ctx->hmac_key); |
851 | |
|
852 | 0 | if (ret) |
853 | 0 | goto out; |
854 | | |
855 | | /* Add the the key and HMAC to the message */ |
856 | 0 | HMAC_CTX_init(&ctx); |
857 | 0 | if (HMAC_Init_ex(&ctx, kx509_ctx->hmac_key->keyvalue.data, |
858 | 0 | kx509_ctx->hmac_key->keyvalue.length, |
859 | 0 | EVP_sha1(), NULL) == 0) { |
860 | 0 | HMAC_CTX_cleanup(&ctx); |
861 | 0 | ret = krb5_enomem(context); |
862 | 0 | } else { |
863 | 0 | HMAC_Update(&ctx, version_2_0, sizeof(version_2_0)); |
864 | 0 | if (private_key || kx509_ctx->given_csr.data) { |
865 | 0 | HMAC_Update(&ctx, kx509_req.pk_key.data, kx509_req.pk_key.length); |
866 | 0 | } else { |
867 | | /* Probe */ |
868 | 0 | HMAC_Update(&ctx, kx509_req.authenticator.data, kx509_req.authenticator.length); |
869 | 0 | } |
870 | 0 | HMAC_Final(&ctx, kx509_req.pk_hash.data, 0); |
871 | 0 | HMAC_CTX_cleanup(&ctx); |
872 | 0 | } |
873 | | |
874 | | /* Encode the message, prefix `version_2_0', output the result */ |
875 | 0 | if (ret == 0) |
876 | 0 | ASN1_MALLOC_ENCODE(Kx509Request, pre_req.data, pre_req.length, &kx509_req, &len, ret); |
877 | 0 | if (ret == 0) |
878 | 0 | ret = krb5_data_alloc(req, pre_req.length + sizeof(version_2_0)); |
879 | 0 | if (ret == 0) { |
880 | 0 | memcpy(req->data, version_2_0, sizeof(version_2_0)); |
881 | 0 | memcpy(((unsigned char *)req->data) + sizeof(version_2_0), |
882 | 0 | pre_req.data, pre_req.length); |
883 | 0 | } |
884 | |
|
885 | 0 | out: |
886 | 0 | free(start_realm); |
887 | 0 | free(pre_req.data); |
888 | 0 | krb5_free_creds(context, cred); |
889 | 0 | kx509_req.pk_hash.data = NULL; |
890 | 0 | kx509_req.pk_hash.length = 0; |
891 | 0 | free_Kx509Request(&kx509_req); |
892 | 0 | free_SubjectPublicKeyInfo(&spki); |
893 | 0 | krb5_free_cred_contents(context, &this_cred); |
894 | 0 | if (ret == 0 && req->length != len + sizeof(version_2_0)) { |
895 | 0 | krb5_data_free(req); |
896 | 0 | krb5_set_error_message(context, ret = ERANGE, |
897 | 0 | "Could not make a kx509 request"); |
898 | 0 | } |
899 | 0 | return ret; |
900 | 0 | } |
901 | | |
902 | | static krb5_error_code |
903 | | rd_chain(krb5_context context, |
904 | | heim_octet_string *d, |
905 | | hx509_cert *cert, |
906 | | hx509_certs *chain, |
907 | | heim_error_t *herr) |
908 | 0 | { |
909 | 0 | krb5_error_code ret; |
910 | 0 | Certificates certs; |
911 | 0 | size_t i, len; |
912 | |
|
913 | 0 | *cert = NULL; |
914 | 0 | *chain = NULL; |
915 | |
|
916 | 0 | if ((ret = decode_Certificates(d->data, d->length, &certs, &len))) |
917 | 0 | return ret; |
918 | 0 | if (certs.len == 0) { |
919 | 0 | *herr = heim_error_create(EINVAL, "Server sent empty Certificate list"); |
920 | 0 | return EINVAL; |
921 | 0 | } |
922 | 0 | *cert = hx509_cert_init(context->hx509ctx, &certs.val[0], herr); |
923 | 0 | if (*cert == NULL) { |
924 | 0 | free_Certificates(&certs); |
925 | 0 | return errno; |
926 | 0 | } |
927 | 0 | if (certs.len == 1) |
928 | 0 | _krb5_debug(context, 1, "kx509 server sent certificate but no chain"); |
929 | 0 | else |
930 | 0 | _krb5_debug(context, 1, "kx509 server sent %llu certificates", |
931 | 0 | (unsigned long long)certs.len); |
932 | |
|
933 | 0 | ret = hx509_certs_init(context->hx509ctx, "MEMORY:anonymous", |
934 | 0 | HX509_CERTS_CREATE, NULL, chain); |
935 | 0 | if (ret) { |
936 | 0 | hx509_cert_free(*cert); |
937 | 0 | *cert = NULL; |
938 | 0 | free_Certificates(&certs); |
939 | 0 | return ret; |
940 | 0 | } |
941 | | |
942 | 0 | for (i = 1; ret == 0 && i < certs.len; i++) { |
943 | 0 | hx509_cert c = hx509_cert_init(context->hx509ctx, &certs.val[i], herr); |
944 | |
|
945 | 0 | if (c == NULL) |
946 | 0 | ret = errno; |
947 | 0 | else |
948 | 0 | ret = hx509_certs_add(context->hx509ctx, *chain, c); |
949 | 0 | hx509_cert_free(c); |
950 | 0 | } |
951 | 0 | free_Certificates(&certs); |
952 | 0 | if (ret) { |
953 | 0 | hx509_certs_free(chain); |
954 | 0 | hx509_cert_free(*cert); |
955 | 0 | *cert = NULL; |
956 | 0 | } |
957 | 0 | return ret; |
958 | 0 | } |
959 | | |
960 | | /* Parse and validate a kx509 reply */ |
961 | | static krb5_error_code |
962 | | rd_kx509_resp(krb5_context context, |
963 | | krb5_kx509_req_ctx kx509_ctx, |
964 | | krb5_data *rep, |
965 | | hx509_cert *cert, |
966 | | hx509_certs *chain) |
967 | 0 | { |
968 | 0 | unsigned char digest[SHA_DIGEST_LENGTH]; |
969 | 0 | Kx509Response r; |
970 | 0 | krb5_error_code code = 0; |
971 | 0 | krb5_error_code ret = 0; |
972 | 0 | heim_string_t hestr; |
973 | 0 | heim_error_t herr = NULL; |
974 | 0 | const char *estr; |
975 | 0 | HMAC_CTX ctx; |
976 | 0 | size_t hdr_len = sizeof(version_2_0); |
977 | 0 | size_t len; |
978 | |
|
979 | 0 | *cert = NULL; |
980 | 0 | *chain = NULL; |
981 | | |
982 | | /* Strip `version_2_0' prefix */ |
983 | 0 | if (rep->length < hdr_len || memcmp(rep->data, version_2_0, hdr_len) != 0) { |
984 | 0 | krb5_set_error_message(context, ENOTSUP, |
985 | 0 | "KDC does not support kx509 protocol"); |
986 | 0 | return ENOTSUP; /* XXX */ |
987 | 0 | } |
988 | | |
989 | | /* Decode */ |
990 | 0 | ret = decode_Kx509Response(((unsigned char *)rep->data) + 4, |
991 | 0 | rep->length - 4, &r, &len); |
992 | 0 | if (ret == 0 && len + hdr_len != rep->length) |
993 | 0 | ret = EINVAL; /* XXX */ |
994 | 0 | if (ret) { |
995 | 0 | krb5_set_error_message(context, ret, "kx509 response is not valid"); |
996 | 0 | return ret; |
997 | 0 | } |
998 | | |
999 | 0 | HMAC_CTX_init(&ctx); |
1000 | 0 | if (HMAC_Init_ex(&ctx, kx509_ctx->hmac_key->keyvalue.data, |
1001 | 0 | kx509_ctx->hmac_key->keyvalue.length, EVP_sha1(), NULL) == 0) { |
1002 | 0 | free_Kx509Response(&r); |
1003 | 0 | HMAC_CTX_cleanup(&ctx); |
1004 | 0 | return krb5_enomem(context); |
1005 | 0 | } |
1006 | | |
1007 | 0 | HMAC_Update(&ctx, version_2_0, sizeof(version_2_0)); |
1008 | |
|
1009 | 0 | { |
1010 | 0 | int32_t t = r.error_code; |
1011 | 0 | unsigned char encint[sizeof(t) + 1]; |
1012 | 0 | size_t k; |
1013 | | |
1014 | | /* |
1015 | | * RFC6717 says this about how the error-code is included in the HMAC: |
1016 | | * |
1017 | | * o DER representation of the error-code exclusive of the tag and |
1018 | | * length, if it is present. |
1019 | | * |
1020 | | * So we use der_put_integer(), which encodes from the right. |
1021 | | * |
1022 | | * RFC6717 does not constrain the error-code's range. We assume it to |
1023 | | * be a 32-bit, signed integer, for which we'll need no more than 5 |
1024 | | * bytes. |
1025 | | */ |
1026 | 0 | ret = der_put_integer(&encint[sizeof(encint) - 1], |
1027 | 0 | sizeof(encint), &t, &k); |
1028 | 0 | if (ret == 0) |
1029 | 0 | HMAC_Update(&ctx, &encint[sizeof(encint)] - k, k); |
1030 | | |
1031 | | /* Normalize error code */ |
1032 | 0 | if (r.error_code == 0) { |
1033 | 0 | code = 0; /* No error */ |
1034 | 0 | } else if (r.error_code < 0) { |
1035 | 0 | code = KRB5KRB_ERR_GENERIC; /* ??? */ |
1036 | 0 | } else if (r.error_code <= KX509_ERR_SRV_OVERLOADED - ERROR_TABLE_BASE_kx59) { |
1037 | | /* |
1038 | | * RFC6717 (kx509) error code. These are actually not used on the |
1039 | | * wire in any existing implementations that we are aware of. Just |
1040 | | * in case, however, we'll map these. |
1041 | | */ |
1042 | 0 | code = KX509_ERR_CLNT_FATAL + r.error_code; |
1043 | 0 | } else if (r.error_code < kx509_krb5_error_base) { |
1044 | | /* Unknown error codes */ |
1045 | 0 | code = KRB5KRB_ERR_GENERIC; |
1046 | 0 | } else { |
1047 | | /* |
1048 | | * Heimdal-specific enhancement to RFC6171: Kerberos wire protocol |
1049 | | * error codes. |
1050 | | */ |
1051 | 0 | code = KRB5KDC_ERR_NONE + r.error_code - kx509_krb5_error_base; |
1052 | 0 | if (code >= KRB5_ERR_RCSID) |
1053 | 0 | code = KRB5KRB_ERR_GENERIC; |
1054 | 0 | if (code == KRB5KDC_ERR_NONE) |
1055 | 0 | code = 0; |
1056 | 0 | } |
1057 | 0 | } |
1058 | 0 | if (r.certificate) |
1059 | 0 | HMAC_Update(&ctx, r.certificate->data, r.certificate->length); |
1060 | 0 | if (r.e_text) |
1061 | 0 | HMAC_Update(&ctx, *r.e_text, strlen(*r.e_text)); |
1062 | 0 | HMAC_Final(&ctx, &digest, 0); |
1063 | 0 | HMAC_CTX_cleanup(&ctx); |
1064 | |
|
1065 | 0 | if (r.hash == NULL) { |
1066 | | /* |
1067 | | * No HMAC -> unauthenticated [error] response. |
1068 | | * |
1069 | | * Do not output any certificate. |
1070 | | */ |
1071 | 0 | free_Kx509Response(&r); |
1072 | 0 | return code; |
1073 | 0 | } |
1074 | | |
1075 | | /* |
1076 | | * WARNING: We do not validate that `r.certificate' is a DER-encoded |
1077 | | * Certificate, not here, and we don't use a different HMAC key |
1078 | | * for the response than for the request. |
1079 | | * |
1080 | | * If ever we start sending a Certificate as the Kx509Request |
1081 | | * pk-key field, then we'll have a reflection attack. As the |
1082 | | * Certificate we'd send in that case will be expired, the |
1083 | | * reflection attack would be just a DoS. |
1084 | | */ |
1085 | 0 | if (r.hash->length != sizeof(digest) || |
1086 | 0 | ct_memcmp(r.hash->data, digest, sizeof(digest)) != 0) { |
1087 | 0 | krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED, |
1088 | 0 | "kx509 response MAC mismatch"); |
1089 | 0 | free_Kx509Response(&r); |
1090 | 0 | return KRB5KRB_AP_ERR_BAD_INTEGRITY; |
1091 | 0 | } |
1092 | | |
1093 | 0 | if (r.certificate == NULL) { |
1094 | | /* Authenticated response, either an error or probe success */ |
1095 | 0 | free_Kx509Response(&r); |
1096 | 0 | if (code != KRB5KDC_ERR_POLICY && kx509_ctx->priv_key == NULL) |
1097 | 0 | return 0; /* Probe success */ |
1098 | 0 | return code ? code : KRB5KDC_ERR_POLICY; /* Not a probe -> must fail */ |
1099 | 0 | } |
1100 | | |
1101 | | /* Import the certificate payload */ |
1102 | 0 | if (kx509_ctx->expect_chain) { |
1103 | 0 | ret = rd_chain(context, r.certificate, cert, chain, &herr); |
1104 | 0 | } else { |
1105 | 0 | *cert = hx509_cert_init_data(context->hx509ctx, r.certificate->data, |
1106 | 0 | r.certificate->length, &herr); |
1107 | 0 | if (!*cert) |
1108 | 0 | ret = errno; |
1109 | 0 | } |
1110 | 0 | free_Kx509Response(&r); |
1111 | 0 | if (*cert) { |
1112 | 0 | heim_release(herr); |
1113 | 0 | return 0; |
1114 | 0 | } |
1115 | | |
1116 | 0 | hestr = herr ? heim_error_copy_string(herr) : NULL; |
1117 | 0 | estr = hestr ? heim_string_get_utf8(hestr) : "(no error message)"; |
1118 | 0 | krb5_set_error_message(context, ret, "Could not parse certificate " |
1119 | 0 | "produced by kx509 KDC: %s (%ld)", |
1120 | 0 | estr, |
1121 | 0 | herr ? (long)heim_error_get_code(herr) : 0L); |
1122 | |
|
1123 | 0 | heim_release(hestr); |
1124 | 0 | heim_release(herr); |
1125 | 0 | return HEIM_PKINIT_CERTIFICATE_INVALID; /* XXX */ |
1126 | 0 | } |
1127 | | |
1128 | | /* |
1129 | | * Make a request, send it, get the response, parse it, and store the |
1130 | | * private key and certificate. |
1131 | | */ |
1132 | | static krb5_error_code |
1133 | | kx509_core(krb5_context context, |
1134 | | krb5_kx509_req_ctx kx509_ctx, |
1135 | | krb5_ccache incc, |
1136 | | const char *hx509_store, |
1137 | | krb5_ccache outcc) |
1138 | 0 | { |
1139 | 0 | krb5_error_code ret; |
1140 | 0 | hx509_certs chain = NULL; |
1141 | 0 | hx509_cert cert = NULL; |
1142 | 0 | krb5_data req, resp; |
1143 | |
|
1144 | 0 | krb5_data_zero(&req); |
1145 | 0 | krb5_data_zero(&resp); |
1146 | | |
1147 | | /* Make the kx509 request */ |
1148 | 0 | ret = mk_kx509_req(context, kx509_ctx, incc, kx509_ctx->priv_key, &req); |
1149 | | |
1150 | | /* Send the kx509 request and get the response */ |
1151 | 0 | if (ret == 0) |
1152 | 0 | ret = krb5_sendto_context(context, NULL, &req, |
1153 | 0 | kx509_ctx->realm, &resp); |
1154 | 0 | if (ret == 0) |
1155 | 0 | ret = rd_kx509_resp(context, kx509_ctx, &resp, &cert, &chain); |
1156 | | |
1157 | | /* Store the key and cert! */ |
1158 | 0 | if (ret == 0 && cert && (kx509_ctx->priv_key || kx509_ctx->given_csr.data)) |
1159 | 0 | ret = store(context, hx509_store, kx509_ctx->realm, outcc, |
1160 | 0 | kx509_ctx->priv_key, cert, chain); |
1161 | 0 | else if (ret == KRB5KDC_ERR_POLICY) |
1162 | | /* Probe failed -> record that the realm does not support kx509 */ |
1163 | 0 | store_kx509_disabled(context, kx509_ctx->realm, outcc); |
1164 | |
|
1165 | 0 | hx509_certs_free(&chain); |
1166 | 0 | hx509_cert_free(cert); |
1167 | 0 | krb5_data_free(&resp); |
1168 | 0 | krb5_data_free(&req); |
1169 | 0 | return ret; |
1170 | 0 | } |
1171 | | |
1172 | | /** |
1173 | | * Use the kx509 v2 protocol to get a certificate for the client principal. |
1174 | | * |
1175 | | * Given a private key this function will get a certificate. If no private key |
1176 | | * is given, one will be generated. |
1177 | | * |
1178 | | * The private key and certificate will be stored in the given PKIX credential |
1179 | | * store (e.g, "PEM-FILE:/path/to/file.pem") and/or given output ccache. When |
1180 | | * stored in a ccache, the DER-encoded Certificate will be stored as the data |
1181 | | * payload of a "cc config" named "kx509cert", while the key will be stored as |
1182 | | * a DER-encoded PKCS#8 PrivateKeyInfo in a cc config named "kx509key". |
1183 | | * |
1184 | | * @param context The Kerberos library context |
1185 | | * @param kx509_ctx A kx509 request context |
1186 | | * @param incc A credential cache (if NULL use default ccache) |
1187 | | * @param hx509_store An PKIX credential store into which to store the private |
1188 | | * key and certificate (e.g, "PEM-FILE:/path/to/file.pem") |
1189 | | * @param outcc A ccache into which to store the private key and certificate |
1190 | | * (mandatory) |
1191 | | * |
1192 | | * @return A krb5 error code. |
1193 | | */ |
1194 | | KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL |
1195 | | krb5_kx509_ext(krb5_context context, |
1196 | | krb5_kx509_req_ctx kx509_ctx, |
1197 | | krb5_ccache incc, |
1198 | | const char *hx509_store, |
1199 | | krb5_ccache outcc) |
1200 | 0 | { |
1201 | 0 | krb5_ccache def_cc = NULL; |
1202 | 0 | krb5_error_code ret; |
1203 | |
|
1204 | 0 | if (incc == NULL) { |
1205 | 0 | if ((ret = krb5_cc_default(context, &def_cc))) |
1206 | 0 | return ret; |
1207 | 0 | incc = def_cc; |
1208 | 0 | } |
1209 | | |
1210 | 0 | if (kx509_ctx->realm == NULL && |
1211 | 0 | (ret = get_start_realm(context, incc, NULL, &kx509_ctx->realm))) { |
1212 | 0 | if (def_cc) |
1213 | 0 | krb5_cc_close(context, def_cc); |
1214 | 0 | return ret; |
1215 | 0 | } |
1216 | | |
1217 | 0 | if (kx509_ctx->priv_key || kx509_ctx->given_csr.data) { |
1218 | | /* If given a private key, use it */ |
1219 | 0 | ret = kx509_core(context, kx509_ctx, incc, hx509_store, outcc); |
1220 | 0 | if (def_cc) |
1221 | 0 | krb5_cc_close(context, def_cc); |
1222 | 0 | return ret; |
1223 | 0 | } |
1224 | | |
1225 | | /* |
1226 | | * No private key given, so we generate one. |
1227 | | * |
1228 | | * However, before taking the hit for generating a keypair we probe to see |
1229 | | * if we're likely to succeeed. |
1230 | | */ |
1231 | | |
1232 | | /* Probe == call kx509_core() w/o a private key */ |
1233 | 0 | ret = kx509_core(context, kx509_ctx, incc, NULL, outcc); |
1234 | 0 | if (ret == 0 && kx509_ctx->given_csr.data == NULL) |
1235 | 0 | ret = krb5_kx509_ctx_gen_key(context, kx509_ctx, NULL, 0); |
1236 | 0 | if (ret == 0) |
1237 | 0 | ret = kx509_core(context, kx509_ctx, incc, hx509_store, outcc); |
1238 | |
|
1239 | 0 | if (def_cc) |
1240 | 0 | krb5_cc_close(context, def_cc); |
1241 | 0 | return ret; |
1242 | 0 | } |
1243 | | |
1244 | | /** |
1245 | | * Generates a public key and uses the kx509 v2 protocol to get a certificate |
1246 | | * for that key and the client principal's subject name. |
1247 | | * |
1248 | | * The private key and certificate will be stored in the given ccache, and also |
1249 | | * in a corresponding PKIX credential store if one is configured via |
1250 | | * [libdefaults] kx509_store. |
1251 | | * |
1252 | | * XXX NOTE: Dicey feature here... Review carefully! |
1253 | | * |
1254 | | * @param context The Kerberos library context |
1255 | | * @param cc A credential cache |
1256 | | * @param realm A realm from which to get the certificate (uses the client |
1257 | | * principal's realm if NULL) |
1258 | | * |
1259 | | * @return A krb5 error code. |
1260 | | */ |
1261 | | KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL |
1262 | | krb5_kx509(krb5_context context, krb5_ccache cc, const char *realm) |
1263 | 0 | { |
1264 | 0 | krb5_kx509_req_ctx kx509_ctx; |
1265 | 0 | krb5_error_code ret; |
1266 | 0 | const char *defcc; |
1267 | 0 | char *ccache_full_name = NULL; |
1268 | 0 | char *store_exp = NULL; |
1269 | |
|
1270 | 0 | ret = krb5_kx509_ctx_init(context, &kx509_ctx); |
1271 | 0 | if (ret) |
1272 | 0 | return ret; |
1273 | 0 | if (realm) |
1274 | 0 | ret = krb5_kx509_ctx_set_realm(context, kx509_ctx, realm); |
1275 | | |
1276 | | /* |
1277 | | * The idea is that IF we are asked to do kx509 w/ creds from a default |
1278 | | * ccache THEN we should store the kx509 certificate (if we get one) and |
1279 | | * private key in the default hx509 store for kx509. |
1280 | | * |
1281 | | * Ideally we could have HTTP user-agents and/or TLS libraries look for |
1282 | | * client certificates and private keys in that default hx509 store. |
1283 | | * |
1284 | | * Of course, those user-agents / libraries should be configured to use |
1285 | | * those credentials with specific hostnames/domainnames, not the entire |
1286 | | * Internet, as the latter leaks the user's identity to the world. |
1287 | | * |
1288 | | * So we check if the full name for `cc' is the same as that of the default |
1289 | | * ccache name, and if so we get the [libdefaults] kx509_store string and |
1290 | | * expand it, then use it. |
1291 | | */ |
1292 | 0 | if (ret == 0 && |
1293 | 0 | (defcc = krb5_cc_configured_default_name(context)) && |
1294 | 0 | krb5_cc_get_full_name(context, cc, &ccache_full_name) == 0 && |
1295 | 0 | strcmp(defcc, ccache_full_name) == 0) { |
1296 | | |
1297 | | /* Find an hx509 store */ |
1298 | 0 | const char *store = krb5_config_get_string(context, NULL, |
1299 | 0 | "libdefaults", |
1300 | 0 | "kx509_store", NULL); |
1301 | 0 | if (store) |
1302 | 0 | ret = _krb5_expand_path_tokens(context, store, 1, &store_exp); |
1303 | | |
1304 | | /* |
1305 | | * If there's a private key in the store already, we'll use it, else |
1306 | | * we'll let krb5_kx509_ext() generate one, so we ignore this return |
1307 | | * value: |
1308 | | */ |
1309 | 0 | (void) krb5_kx509_ctx_set_key(context, kx509_ctx, store); |
1310 | 0 | } |
1311 | | |
1312 | | /* |
1313 | | * If we did settle on a default hx509 store, we'll use it for reading the |
1314 | | * private key from (if it exists) as well as for storing the certificate |
1315 | | * (and private key) into, which may save us some key generation cycles. |
1316 | | */ |
1317 | 0 | if (ret == 0) |
1318 | 0 | ret = krb5_kx509_ext(context, kx509_ctx, cc, store_exp, cc); |
1319 | 0 | krb5_kx509_ctx_free(context, &kx509_ctx); |
1320 | 0 | free(ccache_full_name); |
1321 | 0 | free(store_exp); |
1322 | 0 | return ret; |
1323 | 0 | } |