Line data Source code
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 0 : krb5_kx509_ctx_init(krb5_context context, krb5_kx509_req_ctx *out)
121 : {
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 : else
147 0 : krb5_kx509_ctx_free(context, &ctx);
148 0 : return ret;
149 : }
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 0 : krb5_kx509_ctx_free(krb5_context context, krb5_kx509_req_ctx *ctxp)
161 : {
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 : }
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 0 : krb5_kx509_ctx_set_realm(krb5_context context,
189 : krb5_kx509_req_ctx kx509_ctx,
190 : const char *realm)
191 : {
192 0 : return ((kx509_ctx->realm = strdup(realm)) == NULL) ?
193 0 : krb5_enomem(context) : 0;
194 : }
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 0 : krb5_kx509_ctx_set_csr_der(krb5_context context,
211 : krb5_kx509_req_ctx ctx,
212 : krb5_data *csr_der)
213 : {
214 0 : krb5_data_free(&ctx->given_csr);
215 0 : return krb5_data_copy(&ctx->given_csr, csr_der->data, csr_der->length);
216 : }
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 0 : krb5_kx509_ctx_add_eku(krb5_context context,
230 : krb5_kx509_req_ctx kx509_ctx,
231 : const char *oids)
232 : {
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 : }
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 0 : krb5_kx509_ctx_add_san_dns_name(krb5_context context,
255 : krb5_kx509_req_ctx kx509_ctx,
256 : const char *dname)
257 : {
258 0 : return hx509_request_add_dns_name(context->hx509ctx, kx509_ctx->csr,
259 : dname);
260 : }
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 0 : krb5_kx509_ctx_add_san_xmpp(krb5_context context,
274 : krb5_kx509_req_ctx kx509_ctx,
275 : const char *jid)
276 : {
277 0 : return hx509_request_add_xmpp_name(context->hx509ctx, kx509_ctx->csr, jid);
278 : }
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 0 : krb5_kx509_ctx_add_san_rfc822Name(krb5_context context,
292 : krb5_kx509_req_ctx kx509_ctx,
293 : const char *email)
294 : {
295 0 : return hx509_request_add_email(context->hx509ctx, kx509_ctx->csr, email);
296 : }
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 0 : krb5_kx509_ctx_add_san_pkinit(krb5_context context,
311 : krb5_kx509_req_ctx kx509_ctx,
312 : const char *pname)
313 : {
314 0 : return hx509_request_add_pkinit(context->hx509ctx, kx509_ctx->csr, pname);
315 : }
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 0 : krb5_kx509_ctx_add_san_ms_upn(krb5_context context,
329 : krb5_kx509_req_ctx kx509_ctx,
330 : const char *upn)
331 : {
332 0 : return hx509_request_add_ms_upn_name(context->hx509ctx, kx509_ctx->csr,
333 : upn);
334 : }
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 0 : krb5_kx509_ctx_add_san_registeredID(krb5_context context,
348 : krb5_kx509_req_ctx kx509_ctx,
349 : const char *oids)
350 : {
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 : }
360 :
361 : static krb5_error_code
362 0 : load_priv_key(krb5_context context,
363 : krb5_kx509_req_ctx kx509_ctx,
364 : const char *fn)
365 : {
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 : "from %s for kx509: %s", fn, emsg);
384 0 : hx509_free_error_string(emsg);
385 : }
386 0 : hx509_certs_free(&certs);
387 0 : return ret;
388 : }
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 0 : krb5_kx509_ctx_set_key(krb5_context context,
401 : krb5_kx509_req_ctx kx509_ctx,
402 : const char *store)
403 : {
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 : &key);
415 0 : if (ret == 0)
416 0 : ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
417 : kx509_ctx->csr, &key);
418 0 : free_SubjectPublicKeyInfo(&key);
419 0 : return ret;
420 : }
421 :
422 : static krb5_error_code
423 0 : gen_priv_key(krb5_context context,
424 : const char *gen_type,
425 : unsigned long gen_bits,
426 : hx509_private_key *key)
427 : {
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 : "supported for kx509; only \"rsa\" is "
435 : "supported for kx509 at this time",
436 : gen_type);
437 0 : return ENOTSUP;
438 : }
439 :
440 0 : ret = _hx509_generate_private_key_init(context->hx509ctx,
441 : ASN1_OID_ID_PKCS1_RSAENCRYPTION,
442 : &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 : "Could not generate a private key: %s", emsg);
454 0 : hx509_free_error_string(emsg);
455 : }
456 0 : return ret;
457 : }
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 0 : 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 : {
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 : "libdefaults",
483 : "kx509_gen_key_type", NULL);
484 : }
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 : "libdefaults",
494 : "kx509_gen_rsa_key_size", NULL);
495 : }
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 : &key);
504 0 : if (ret == 0)
505 0 : ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
506 : kx509_ctx->csr, &key);
507 0 : free_SubjectPublicKeyInfo(&key);
508 0 : return ret;
509 : }
510 :
511 : /* Set a cc config entry indicating that the kx509 service is not available */
512 : static void
513 0 : store_kx509_disabled(krb5_context context, const char *realm, krb5_ccache cc)
514 : {
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 : }
527 :
528 : static int KRB5_CALLCONV
529 0 : certs_export_func(hx509_context context, void *d, hx509_cert c)
530 : {
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 : }
547 :
548 : static krb5_error_code
549 0 : certs_export(hx509_context context, hx509_certs certs, heim_octet_string *out)
550 : {
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 : }
563 :
564 : /* Store the private key and certificate where requested */
565 : static krb5_error_code
566 0 : 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 : {
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 : 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 : "private key and certificate in ccache %s",
607 : krb5_cc_get_name(context, cc));
608 : }
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 : &hdata);
615 0 : der_free_octet_string(&hdata);
616 : }
617 : }
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 : 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 : "private key and certificate in key "
638 : "store %s", hx509_store);
639 : }
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 : }
647 0 : return ret;
648 : }
649 :
650 : /* Make a Kx509CSRPlus or a raw SPKI */
651 : static krb5_error_code
652 0 : mk_kx509_req_body(krb5_context context,
653 : krb5_kx509_req_ctx kx509_ctx,
654 : krb5_data *out)
655 : {
656 0 : krb5_error_code ret;
657 0 : size_t len;
658 :
659 0 : if (krb5_config_get_bool_default(context, NULL, FALSE,
660 : "realms", kx509_ctx->realm,
661 : "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 : 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 : }
674 0 : kx509_ctx->expect_chain = 0;
675 0 : return ret;
676 : }
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 : &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 : kx509_ctx->csr,
702 : &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 : kx509_ctx->csr_plus.exts, NULL);
710 0 : krb5_data_free(&exts_der);
711 : } 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 : kx509_ctx->csr,
718 : kx509_ctx->priv_key,
719 : &kx509_ctx->csr_plus.csr);
720 : }
721 0 : if (ret == 0)
722 0 : ASN1_MALLOC_ENCODE(Kx509CSRPlus, out->data, out->length,
723 : &kx509_ctx->csr_plus, &len, ret);
724 0 : return ret;
725 : }
726 :
727 : static krb5_error_code
728 0 : get_start_realm(krb5_context context,
729 : krb5_ccache cc,
730 : krb5_const_principal princ,
731 : char **out)
732 : {
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 : } 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 : }
751 0 : return (*out) ? 0 : krb5_enomem(context);
752 : }
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 0 : 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 : {
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 : } else {
790 : /* Probe */
791 0 : kx509_req.pk_key.data = NULL;
792 0 : kx509_req.pk_key.length = 0;
793 : }
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 : kx509_ctx->realm, "kx509_hostname",
821 : NULL);
822 0 : if (hostname == NULL)
823 0 : hostname = krb5_config_get_string(context, NULL, "libdefaults",
824 : "kx509_hostname", NULL);
825 0 : if (hostname) {
826 0 : ret = krb5_sname_to_principal(context, hostname, "kca_service",
827 : 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 : } else {
832 0 : ret = krb5_make_principal(context, &this_cred.server,
833 : start_realm,
834 : KRB5_TGS_NAME,
835 : kx509_ctx->realm,
836 : NULL);
837 : }
838 : }
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 : 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 : &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 : EVP_sha1(), NULL) == 0) {
860 0 : HMAC_CTX_cleanup(&ctx);
861 0 : ret = krb5_enomem(context);
862 : } 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 : } else {
867 : /* Probe */
868 0 : HMAC_Update(&ctx, kx509_req.authenticator.data, kx509_req.authenticator.length);
869 : }
870 0 : HMAC_Final(&ctx, kx509_req.pk_hash.data, 0);
871 0 : HMAC_CTX_cleanup(&ctx);
872 : }
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 : }
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 : "Could not make a kx509 request");
898 : }
899 0 : return ret;
900 : }
901 :
902 : static krb5_error_code
903 0 : rd_chain(krb5_context context,
904 : heim_octet_string *d,
905 : hx509_cert *cert,
906 : hx509_certs *chain,
907 : heim_error_t *herr)
908 : {
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 : }
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 : }
927 0 : if (certs.len == 1)
928 0 : _krb5_debug(context, 1, "kx509 server sent certificate but no chain");
929 : 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 : 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 : }
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 : else
948 0 : ret = hx509_certs_add(context->hx509ctx, *chain, c);
949 0 : hx509_cert_free(c);
950 : }
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 : }
957 0 : return ret;
958 : }
959 :
960 : /* Parse and validate a kx509 reply */
961 : static krb5_error_code
962 0 : 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 : {
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 : "KDC does not support kx509 protocol");
986 0 : return ENOTSUP; /* XXX */
987 : }
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 : }
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 : }
1006 :
1007 0 : HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
1008 :
1009 : {
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 : 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 : } 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 : }
1057 : }
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 : }
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 : "kx509 response MAC mismatch");
1089 0 : free_Kx509Response(&r);
1090 0 : return KRB5KRB_AP_ERR_BAD_INTEGRITY;
1091 : }
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 : }
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 : } 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 : }
1110 0 : free_Kx509Response(&r);
1111 0 : if (*cert) {
1112 0 : heim_release(herr);
1113 0 : return 0;
1114 : }
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 : "produced by kx509 KDC: %s (%ld)",
1120 : 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 : }
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 0 : 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 : {
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 : 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 : }
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 0 : 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 : {
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 : }
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 : }
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 : }
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 : }
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 0 : krb5_kx509(krb5_context context, krb5_ccache cc, const char *realm)
1263 : {
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 : "libdefaults",
1300 : "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 : }
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 : }
|