krb5: Red Hat gssproxy FILE ccache remove cred compatibility
[heimdal.git] / lib / krb5 / kx509.c
blob3bacdf10db07d44d8b9fc00c5fbda22978ea7a37
1 /*
2 * Copyright (c) 2019 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
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"
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.
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.
49 * I.e., kx509 is a bridge, just like PKINIT.
51 * The kx509 protocol is very simple, and very limited.
53 * A request consists of a DER-encoded Kx509Request message prefixed with four
54 * bytes identifying the protocol (see `version_2_0' below).
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.
59 * The service principal can be either kca_service/hostname.fqdn or
60 * krbtgt/REALM (a Heimdal innovation).
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.
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.
71 * Limitations:
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
80 * Future directions:
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.
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.
90 * - Use the AP-REQ's Authenticator's sub-session key for the HMAC, and derive
91 * per-direction sub-sub-keys.
93 * - We might design a new protocol that better fits the RFC4120 KDC message
94 * framework.
97 static const unsigned char version_2_0[4] = {0 , 0, 2, 0};
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;
112 * Create a kx509 request context.
114 * @param context The Kerberos library context
115 * @param out Where to place the kx509 request context
117 * @return A krb5 error code.
119 krb5_error_code
120 krb5_kx509_ctx_init(krb5_context context, krb5_kx509_req_ctx *out)
122 krb5_kx509_req_ctx ctx;
123 krb5_error_code ret;
124 hx509_name name = NULL;
126 ALLOC(ctx, 1);
127 if (ctx == NULL)
128 return krb5_enomem(context);
129 ctx->given_csr.data = NULL;
130 ctx->priv_key = NULL;
131 ctx->hmac_key = NULL;
132 ctx->realm = NULL;
133 ctx->keys = NULL;
134 ctx->csr = NULL;
135 ret = hx509_request_init(context->hx509ctx, &ctx->csr);
136 if (ret == 0)
137 ret = hx509_parse_name(context->hx509ctx, "", &name);
138 if (ret == 0)
139 ret = hx509_request_set_name(context->hx509ctx, ctx->csr, name);
140 if (ret == 0)
141 ret = krb5_auth_con_init(context, &ctx->ac);
142 if (name)
143 hx509_name_free(&name);
144 if (ret == 0)
145 *out = ctx;
146 else
147 krb5_kx509_ctx_free(context, &ctx);
148 return ret;
152 * Free a kx509 request context.
154 * @param context The Kerberos library context
155 * @param ctxp Pointer to krb5 request context to free
157 * @return A krb5 error code.
159 void
160 krb5_kx509_ctx_free(krb5_context context, krb5_kx509_req_ctx *ctxp)
162 krb5_kx509_req_ctx ctx = *ctxp;
164 *ctxp = NULL;
165 if (ctx == NULL)
166 return;
167 krb5_free_keyblock(context, ctx->hmac_key);
168 krb5_auth_con_free(context, ctx->ac);
169 free_Kx509CSRPlus(&ctx->csr_plus);
170 free(ctx->realm);
171 hx509_request_free(&ctx->csr);
172 krb5_data_free(&ctx->given_csr);
173 hx509_private_key_free(&ctx->priv_key);
174 _hx509_certs_keys_free(context->hx509ctx, ctx->keys);
175 free(ctx);
179 * Set a realm to send kx509 request to, if different from the client's.
181 * @param context The Kerberos library context
182 * @param ctx The kx509 request context
183 * @param realm Realm name
185 * @return A krb5 error code.
187 krb5_error_code
188 krb5_kx509_ctx_set_realm(krb5_context context,
189 krb5_kx509_req_ctx kx509_ctx,
190 const char *realm)
192 return ((kx509_ctx->realm = strdup(realm)) == NULL) ?
193 krb5_enomem(context) : 0;
197 * Sets a CSR for a kx509 request.
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.
203 * @param context The Kerberos library context
204 * @param ctx The kx509 request context
205 * @param csr_der A DER-encoded PKCS#10 CSR
207 * @return A krb5 error code.
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)
214 krb5_data_free(&ctx->given_csr);
215 return krb5_data_copy(&ctx->given_csr, csr_der->data, csr_der->length);
219 * Adds an EKU as an additional desired Certificate Extension or in the CSR if
220 * the caller does not set a CSR.
222 * @param context The Kerberos library context
223 * @param ctx The kx509 request context
224 * @param oids A string representation of an OID
226 * @return A krb5 error code.
228 krb5_error_code
229 krb5_kx509_ctx_add_eku(krb5_context context,
230 krb5_kx509_req_ctx kx509_ctx,
231 const char *oids)
233 krb5_error_code ret;
234 heim_oid oid;
236 ret = der_parse_heim_oid(oids, NULL, &oid);
237 if (ret == 0)
238 hx509_request_add_eku(context->hx509ctx, kx509_ctx->csr, &oid);
239 der_free_oid(&oid);
240 return ret;
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.
247 * @param context The Kerberos library context
248 * @param ctx The kx509 request context
249 * @param dname A string containing a DNS domainname
251 * @return A krb5 error code.
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)
258 return hx509_request_add_dns_name(context->hx509ctx, kx509_ctx->csr,
259 dname);
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.
266 * @param context The Kerberos library context
267 * @param ctx The kx509 request context
268 * @param jid A string containing a Jabber address
270 * @return A krb5 error code.
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)
277 return hx509_request_add_xmpp_name(context->hx509ctx, kx509_ctx->csr, jid);
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.
284 * @param context The Kerberos library context
285 * @param ctx The kx509 request context
286 * @param email A string containing an e-mail address
288 * @return A krb5 error code.
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)
295 return hx509_request_add_email(context->hx509ctx, kx509_ctx->csr, email);
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.
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
307 * @return A krb5 error code.
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)
314 return hx509_request_add_pkinit(context->hx509ctx, kx509_ctx->csr, pname);
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.
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
325 * @return A krb5 error code.
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)
332 return hx509_request_add_ms_upn_name(context->hx509ctx, kx509_ctx->csr,
333 upn);
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.
340 * @param context The Kerberos library context
341 * @param ctx The kx509 request context
342 * @param oids A string representation of an OID
344 * @return A krb5 error code.
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)
351 krb5_error_code ret;
352 heim_oid oid;
354 ret = der_parse_heim_oid(oids, NULL, &oid);
355 if (ret == 0)
356 hx509_request_add_registered(context->hx509ctx, kx509_ctx->csr, &oid);
357 der_free_oid(&oid);
358 return ret;
361 static krb5_error_code
362 load_priv_key(krb5_context context,
363 krb5_kx509_req_ctx kx509_ctx,
364 const char *fn)
366 hx509_private_key *keys = NULL;
367 hx509_certs certs = NULL;
368 krb5_error_code ret;
370 ret = hx509_certs_init(context->hx509ctx, fn, 0, NULL, &certs);
371 if (ret == ENOENT)
372 return 0;
373 if (ret == 0)
374 ret = _hx509_certs_keys_get(context->hx509ctx, certs, &keys);
375 if (ret == 0 && keys[0] == NULL)
376 ret = ENOENT;
377 if (ret == 0)
378 kx509_ctx->priv_key = _hx509_private_key_ref(keys[0]);
379 if (ret) {
380 char *emsg = hx509_get_error_string(context->hx509ctx, ret);
382 krb5_set_error_message(context, ret, "Could not load private key "
383 "from %s for kx509: %s", fn, emsg);
384 hx509_free_error_string(emsg);
386 hx509_certs_free(&certs);
387 return ret;
391 * Set a private key.
393 * @param context The Kerberos library context
394 * @param ctx The kx509 request context
395 * @param store The name of a PKIX credential store
397 * @return A krb5 error code.
399 krb5_error_code
400 krb5_kx509_ctx_set_key(krb5_context context,
401 krb5_kx509_req_ctx kx509_ctx,
402 const char *store)
404 SubjectPublicKeyInfo key;
405 krb5_error_code ret;
407 memset(&key, 0, sizeof(key));
408 hx509_private_key_free(&kx509_ctx->priv_key);
409 _hx509_certs_keys_free(context->hx509ctx, kx509_ctx->keys);
410 kx509_ctx->keys = NULL;
411 ret = load_priv_key(context, kx509_ctx, store);
412 if (ret == 0)
413 ret = hx509_private_key2SPKI(context->hx509ctx, kx509_ctx->priv_key,
414 &key);
415 if (ret == 0)
416 ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
417 kx509_ctx->csr, &key);
418 free_SubjectPublicKeyInfo(&key);
419 return ret;
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)
428 struct hx509_generate_private_context *key_gen_ctx = NULL;
429 krb5_error_code ret;
431 _krb5_debug(context, 1, "kx509: gen priv key");
432 if (strcmp(gen_type, "rsa") != 0) {
433 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 return ENOTSUP;
440 ret = _hx509_generate_private_key_init(context->hx509ctx,
441 ASN1_OID_ID_PKCS1_RSAENCRYPTION,
442 &key_gen_ctx);
443 if (ret == 0)
444 ret = _hx509_generate_private_key_bits(context->hx509ctx, key_gen_ctx, gen_bits);
446 if (ret == 0)
447 ret = _hx509_generate_private_key(context->hx509ctx, key_gen_ctx, key);
448 _hx509_generate_private_key_free(&key_gen_ctx);
449 if (ret) {
450 char *emsg = hx509_get_error_string(context->hx509ctx, ret);
452 krb5_set_error_message(context, ret,
453 "Could not generate a private key: %s", emsg);
454 hx509_free_error_string(emsg);
456 return ret;
460 * Generate a private key.
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)
467 * @return A krb5 error code.
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)
475 SubjectPublicKeyInfo key;
476 krb5_error_code ret;
478 memset(&key, 0, sizeof(key));
480 if (gen_type == NULL) {
481 gen_type = krb5_config_get_string_default(context, NULL, "rsa",
482 "libdefaults",
483 "kx509_gen_key_type", NULL);
485 if (gen_bits == 0) {
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).
492 gen_bits = krb5_config_get_int_default(context, NULL, 2048,
493 "libdefaults",
494 "kx509_gen_rsa_key_size", NULL);
496 hx509_private_key_free(&kx509_ctx->priv_key);
497 _hx509_certs_keys_free(context->hx509ctx, kx509_ctx->keys);
498 kx509_ctx->keys = NULL;
500 ret = gen_priv_key(context, gen_type, gen_bits, &kx509_ctx->priv_key);
501 if (ret == 0)
502 ret = hx509_private_key2SPKI(context->hx509ctx, kx509_ctx->priv_key,
503 &key);
504 if (ret == 0)
505 ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
506 kx509_ctx->csr, &key);
507 free_SubjectPublicKeyInfo(&key);
508 return ret;
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)
515 krb5_data data;
517 if (!cc)
518 return;
520 data.data = (void *)(uintptr_t)realm;
521 data.length = strlen(realm);
522 krb5_cc_set_config(context, cc, NULL, "kx509_service_realm", &data);
523 data.data = "disabled";
524 data.length = strlen(data.data);
525 krb5_cc_set_config(context, cc, NULL, "kx509_service_status", &data);
528 static int KRB5_CALLCONV
529 certs_export_func(hx509_context context, void *d, hx509_cert c)
531 heim_octet_string os;
532 Certificates *cs = d;
533 Certificate c2;
534 int ret;
536 ret = hx509_cert_binary(context, c, &os);
537 if (ret)
538 return ret;
539 ret = decode_Certificate(os.data, os.length, &c2, NULL);
540 der_free_octet_string(&os);
541 if (ret)
542 return ret;
543 ret = add_Certificates(cs, &c2);
544 free_Certificate(&c2);
545 return ret;
548 static krb5_error_code
549 certs_export(hx509_context context, hx509_certs certs, heim_octet_string *out)
551 Certificates cs;
552 size_t len;
553 int ret;
555 cs.len = 0;
556 cs.val = 0;
557 ret = hx509_certs_iter_f(context, certs, certs_export_func, &cs);
558 if (ret == 0)
559 ASN1_MALLOC_ENCODE(Certificates, out->data, out->length, &cs, &len, ret);
560 free_Certificates(&cs);
561 return ret;
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)
574 heim_octet_string hdata;
575 krb5_error_code ret = 0;
576 krb5_data data;
578 krb5_clear_error_message(context);
580 if (cc) {
581 /* Record the realm we used */
582 data.data = (void *)(uintptr_t)realm;
583 data.length = strlen(realm);
584 krb5_cc_set_config(context, cc, NULL, "kx509_service_realm", &data);
586 /* Serialize and store the certificate in the ccache */
587 ret = hx509_cert_binary(context->hx509ctx, cert, &hdata);
588 if (ret == 0)
589 ret = krb5_cc_set_config(context, cc, NULL, "kx509cert", &hdata);
590 der_free_octet_string(&hdata);
592 if (ret == 0 && key) {
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.
598 if (ret == 0)
599 ret = _hx509_private_key_export(context->hx509ctx, key,
600 HX509_KEY_FORMAT_PKCS8, &hdata);
601 if (ret == 0)
602 ret = krb5_cc_set_config(context, cc, NULL, "kx509key", &hdata);
603 der_free_octet_string(&hdata);
604 if (ret)
605 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));
610 if (ret == 0 && chain) {
611 ret = certs_export(context->hx509ctx, chain, &hdata);
612 if (ret == 0)
613 ret = krb5_cc_set_config(context, cc, NULL, "kx509cert-chain",
614 &hdata);
615 der_free_octet_string(&hdata);
619 /* Store the private key and cert in an hx509 store */
620 if (hx509_store != NULL) {
621 hx509_certs certs;
623 if (key)
624 _hx509_cert_assign_key(cert, key); /* store both in the same store */
626 ret = hx509_certs_init(context->hx509ctx, hx509_store,
627 HX509_CERTS_CREATE, NULL, &certs);
628 if (ret == 0)
629 ret = hx509_certs_add(context->hx509ctx, certs, cert);
630 if (ret == 0 && chain != NULL)
631 ret = hx509_certs_merge(context->hx509ctx, certs, chain);
632 if (ret == 0)
633 ret = hx509_certs_store(context->hx509ctx, certs, 0, NULL);
634 hx509_certs_free(&certs);
635 if (ret)
636 krb5_prepend_error_message(context, ret, "Could not store kx509 "
637 "private key and certificate in key "
638 "store %s", hx509_store);
641 /* Store the name of the hx509 store in the ccache too */
642 if (cc && hx509_store) {
643 data.data = (void *)(uintptr_t)hx509_store;
644 data.length = strlen(hx509_store);
645 (void) krb5_cc_set_config(context, cc, NULL, "kx509store", &data);
647 return ret;
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)
656 krb5_error_code ret;
657 size_t len;
659 if (krb5_config_get_bool_default(context, NULL, FALSE,
660 "realms", kx509_ctx->realm,
661 "kx509_req_use_raw_spki", NULL)) {
662 SubjectPublicKeyInfo spki;
664 /* Interop with old kx509 servers, send a raw SPKI, not a CSR */
665 out->data = NULL;
666 out->length = 0;
667 memset(&spki, 0, sizeof(spki));
668 ret = hx509_private_key2SPKI(context->hx509ctx,
669 kx509_ctx->priv_key, &spki);
670 if (ret == 0) {
671 out->length = spki.subjectPublicKey.length >> 3;
672 out->data = spki.subjectPublicKey.data;
674 kx509_ctx->expect_chain = 0;
675 return ret;
679 * New kx509 servers use a CSR for proof of possession, and send back a
680 * chain of certificates, with the issued certificate first.
682 kx509_ctx->expect_chain = 1;
684 if (kx509_ctx->given_csr.length) {
685 krb5_data exts_der;
687 exts_der.data = NULL;
688 exts_der.length = 0;
690 /* Use the given CSR */
691 ret = der_copy_octet_string(&kx509_ctx->given_csr,
692 &kx509_ctx->csr_plus.csr);
695 * Extract the desired Certificate Extensions from our internal
696 * as-yet-unsigned CSR, then decode them into place in the
697 * Kx509CSRPlus.
699 if (ret == 0)
700 ret = hx509_request_get_exts(context->hx509ctx,
701 kx509_ctx->csr,
702 &exts_der);
703 if (ret == 0 && exts_der.data && exts_der.length &&
704 (kx509_ctx->csr_plus.exts =
705 calloc(1, sizeof (kx509_ctx->csr_plus.exts[0]))) == NULL)
706 ret = krb5_enomem(context);
707 if (ret == 0 && exts_der.data && exts_der.length)
708 ret = decode_Extensions(exts_der.data, exts_der.length,
709 kx509_ctx->csr_plus.exts, NULL);
710 krb5_data_free(&exts_der);
711 } else {
713 * Sign and use our internal CSR, which will carry all our desired
714 * Certificate Extensions as an extReq CSR Attribute.
716 ret = hx509_request_to_pkcs10(context->hx509ctx,
717 kx509_ctx->csr,
718 kx509_ctx->priv_key,
719 &kx509_ctx->csr_plus.csr);
721 if (ret == 0)
722 ASN1_MALLOC_ENCODE(Kx509CSRPlus, out->data, out->length,
723 &kx509_ctx->csr_plus, &len, ret);
724 return ret;
727 static krb5_error_code
728 get_start_realm(krb5_context context,
729 krb5_ccache cc,
730 krb5_const_principal princ,
731 char **out)
733 krb5_error_code ret;
734 krb5_data d;
736 ret = krb5_cc_get_config(context, cc, NULL, "start_realm", &d);
737 if (ret == 0) {
738 *out = strndup(d.data, d.length);
739 krb5_data_free(&d);
740 } else if (princ) {
741 *out = strdup(krb5_principal_get_realm(context, princ));
742 } else {
743 krb5_principal ccprinc = NULL;
745 ret = krb5_cc_get_principal(context, cc, &ccprinc);
746 if (ret)
747 return ret;
748 *out = strdup(krb5_principal_get_realm(context, ccprinc));
749 krb5_free_principal(context, ccprinc);
751 return (*out) ? 0 : krb5_enomem(context);
755 * Make a request, which is a DER-encoded Kx509Request with version_2_0
756 * prefixed to it.
758 * If no private key is given, then a probe request will be made.
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)
767 unsigned char digest[SHA_DIGEST_LENGTH];
768 SubjectPublicKeyInfo spki;
769 struct Kx509Request kx509_req;
770 krb5_data pre_req;
771 krb5_error_code ret = 0;
772 krb5_creds this_cred;
773 krb5_creds *cred = NULL;
774 HMAC_CTX ctx;
775 const char *hostname;
776 char *start_realm = NULL;
777 size_t len = 0;
779 krb5_data_zero(&pre_req);
780 memset(&spki, 0, sizeof(spki));
781 memset(&this_cred, 0, sizeof(this_cred));
782 memset(&kx509_req, 0, sizeof(kx509_req));
783 kx509_req.pk_hash.data = digest;
784 kx509_req.pk_hash.length = SHA_DIGEST_LENGTH;
786 if (private_key || kx509_ctx->given_csr.data) {
787 /* Encode the CSR or public key for use in the request */
788 ret = mk_kx509_req_body(context, kx509_ctx, &kx509_req.pk_key);
789 } else {
790 /* Probe */
791 kx509_req.pk_key.data = NULL;
792 kx509_req.pk_key.length = 0;
795 if (ret == 0)
796 ret = krb5_cc_get_principal(context, incc, &this_cred.client);
797 if (ret == 0)
798 ret = get_start_realm(context, incc, this_cred.client, &start_realm);
799 if (ret == 0 && kx509_ctx->realm == NULL)
800 ret = krb5_kx509_ctx_set_realm(context, kx509_ctx, start_realm);
801 if (ret == 0) {
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.
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.
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.
819 hostname = krb5_config_get_string(context, NULL, "realms",
820 kx509_ctx->realm, "kx509_hostname",
821 NULL);
822 if (hostname == NULL)
823 hostname = krb5_config_get_string(context, NULL, "libdefaults",
824 "kx509_hostname", NULL);
825 if (hostname) {
826 ret = krb5_sname_to_principal(context, hostname, "kca_service",
827 KRB5_NT_SRV_HST, &this_cred.server);
828 if (ret == 0)
829 ret = krb5_principal_set_realm(context, this_cred.server,
830 kx509_ctx->realm);
831 } else {
832 ret = krb5_make_principal(context, &this_cred.server,
833 start_realm,
834 KRB5_TGS_NAME,
835 kx509_ctx->realm,
836 NULL);
840 /* Make the AP-REQ and extract the HMAC key */
841 if (ret == 0)
842 ret = krb5_get_credentials(context, 0, incc, &this_cred, &cred);
843 if (ret == 0)
844 ret = krb5_mk_req_extended(context, &kx509_ctx->ac, AP_OPTS_USE_SUBKEY,
845 NULL, cred, &kx509_req.authenticator);
846 krb5_free_keyblock(context, kx509_ctx->hmac_key);
847 kx509_ctx->hmac_key = NULL;
848 if (ret == 0)
849 ret = krb5_auth_con_getkey(context, kx509_ctx->ac,
850 &kx509_ctx->hmac_key);
852 if (ret)
853 goto out;
855 /* Add the the key and HMAC to the message */
856 HMAC_CTX_init(&ctx);
857 if (HMAC_Init_ex(&ctx, kx509_ctx->hmac_key->keyvalue.data,
858 kx509_ctx->hmac_key->keyvalue.length,
859 EVP_sha1(), NULL) == 0) {
860 HMAC_CTX_cleanup(&ctx);
861 ret = krb5_enomem(context);
862 } else {
863 HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
864 if (private_key || kx509_ctx->given_csr.data) {
865 HMAC_Update(&ctx, kx509_req.pk_key.data, kx509_req.pk_key.length);
866 } else {
867 /* Probe */
868 HMAC_Update(&ctx, kx509_req.authenticator.data, kx509_req.authenticator.length);
870 HMAC_Final(&ctx, kx509_req.pk_hash.data, 0);
871 HMAC_CTX_cleanup(&ctx);
874 /* Encode the message, prefix `version_2_0', output the result */
875 if (ret == 0)
876 ASN1_MALLOC_ENCODE(Kx509Request, pre_req.data, pre_req.length, &kx509_req, &len, ret);
877 if (ret == 0)
878 ret = krb5_data_alloc(req, pre_req.length + sizeof(version_2_0));
879 if (ret == 0) {
880 memcpy(req->data, version_2_0, sizeof(version_2_0));
881 memcpy(((unsigned char *)req->data) + sizeof(version_2_0),
882 pre_req.data, pre_req.length);
885 out:
886 free(start_realm);
887 free(pre_req.data);
888 krb5_free_creds(context, cred);
889 kx509_req.pk_hash.data = NULL;
890 kx509_req.pk_hash.length = 0;
891 free_Kx509Request(&kx509_req);
892 free_SubjectPublicKeyInfo(&spki);
893 krb5_free_cred_contents(context, &this_cred);
894 if (ret == 0 && req->length != len + sizeof(version_2_0)) {
895 krb5_data_free(req);
896 krb5_set_error_message(context, ret = ERANGE,
897 "Could not make a kx509 request");
899 return ret;
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)
909 krb5_error_code ret;
910 Certificates certs;
911 size_t i, len;
913 *cert = NULL;
914 *chain = NULL;
916 if ((ret = decode_Certificates(d->data, d->length, &certs, &len)))
917 return ret;
918 if (certs.len == 0) {
919 *herr = heim_error_create(EINVAL, "Server sent empty Certificate list");
920 return EINVAL;
922 *cert = hx509_cert_init(context->hx509ctx, &certs.val[0], herr);
923 if (*cert == NULL) {
924 free_Certificates(&certs);
925 return errno;
927 if (certs.len == 1)
928 _krb5_debug(context, 1, "kx509 server sent certificate but no chain");
929 else
930 _krb5_debug(context, 1, "kx509 server sent %llu certificates",
931 (unsigned long long)certs.len);
933 ret = hx509_certs_init(context->hx509ctx, "MEMORY:anonymous",
934 HX509_CERTS_CREATE, NULL, chain);
935 if (ret) {
936 hx509_cert_free(*cert);
937 *cert = NULL;
938 free_Certificates(&certs);
939 return ret;
942 for (i = 1; ret == 0 && i < certs.len; i++) {
943 hx509_cert c = hx509_cert_init(context->hx509ctx, &certs.val[i], herr);
945 if (c == NULL)
946 ret = errno;
947 else
948 ret = hx509_certs_add(context->hx509ctx, *chain, c);
949 hx509_cert_free(c);
951 free_Certificates(&certs);
952 if (ret) {
953 hx509_certs_free(chain);
954 hx509_cert_free(*cert);
955 *cert = NULL;
957 return ret;
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)
968 unsigned char digest[SHA_DIGEST_LENGTH];
969 Kx509Response r;
970 krb5_error_code code = 0;
971 krb5_error_code ret = 0;
972 heim_string_t hestr;
973 heim_error_t herr = NULL;
974 const char *estr;
975 HMAC_CTX ctx;
976 size_t hdr_len = sizeof(version_2_0);
977 size_t len;
979 *cert = NULL;
980 *chain = NULL;
982 /* Strip `version_2_0' prefix */
983 if (rep->length < hdr_len || memcmp(rep->data, version_2_0, hdr_len) != 0) {
984 krb5_set_error_message(context, ENOTSUP,
985 "KDC does not support kx509 protocol");
986 return ENOTSUP; /* XXX */
989 /* Decode */
990 ret = decode_Kx509Response(((unsigned char *)rep->data) + 4,
991 rep->length - 4, &r, &len);
992 if (ret == 0 && len + hdr_len != rep->length)
993 ret = EINVAL; /* XXX */
994 if (ret) {
995 krb5_set_error_message(context, ret, "kx509 response is not valid");
996 return ret;
999 HMAC_CTX_init(&ctx);
1000 if (HMAC_Init_ex(&ctx, kx509_ctx->hmac_key->keyvalue.data,
1001 kx509_ctx->hmac_key->keyvalue.length, EVP_sha1(), NULL) == 0) {
1002 free_Kx509Response(&r);
1003 HMAC_CTX_cleanup(&ctx);
1004 return krb5_enomem(context);
1007 HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
1010 int32_t t = r.error_code;
1011 unsigned char encint[sizeof(t) + 1];
1012 size_t k;
1015 * RFC6717 says this about how the error-code is included in the HMAC:
1017 * o DER representation of the error-code exclusive of the tag and
1018 * length, if it is present.
1020 * So we use der_put_integer(), which encodes from the right.
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.
1026 ret = der_put_integer(&encint[sizeof(encint) - 1],
1027 sizeof(encint), &t, &k);
1028 if (ret == 0)
1029 HMAC_Update(&ctx, &encint[sizeof(encint)] - k, k);
1031 /* Normalize error code */
1032 if (r.error_code == 0) {
1033 code = 0; /* No error */
1034 } else if (r.error_code < 0) {
1035 code = KRB5KRB_ERR_GENERIC; /* ??? */
1036 } else if (r.error_code <= KX509_ERR_SRV_OVERLOADED - ERROR_TABLE_BASE_kx59) {
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.
1042 code = KX509_ERR_CLNT_FATAL + r.error_code;
1043 } else if (r.error_code < kx509_krb5_error_base) {
1044 /* Unknown error codes */
1045 code = KRB5KRB_ERR_GENERIC;
1046 } else {
1048 * Heimdal-specific enhancement to RFC6171: Kerberos wire protocol
1049 * error codes.
1051 code = KRB5KDC_ERR_NONE + r.error_code - kx509_krb5_error_base;
1052 if (code >= KRB5_ERR_RCSID)
1053 code = KRB5KRB_ERR_GENERIC;
1054 if (code == KRB5KDC_ERR_NONE)
1055 code = 0;
1058 if (r.certificate)
1059 HMAC_Update(&ctx, r.certificate->data, r.certificate->length);
1060 if (r.e_text)
1061 HMAC_Update(&ctx, *r.e_text, strlen(*r.e_text));
1062 HMAC_Final(&ctx, &digest, 0);
1063 HMAC_CTX_cleanup(&ctx);
1065 if (r.hash == NULL) {
1067 * No HMAC -> unauthenticated [error] response.
1069 * Do not output any certificate.
1071 free_Kx509Response(&r);
1072 return code;
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.
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.
1085 if (r.hash->length != sizeof(digest) ||
1086 ct_memcmp(r.hash->data, digest, sizeof(digest)) != 0) {
1087 krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
1088 "kx509 response MAC mismatch");
1089 free_Kx509Response(&r);
1090 return KRB5KRB_AP_ERR_BAD_INTEGRITY;
1093 if (r.certificate == NULL) {
1094 /* Authenticated response, either an error or probe success */
1095 free_Kx509Response(&r);
1096 if (code != KRB5KDC_ERR_POLICY && kx509_ctx->priv_key == NULL)
1097 return 0; /* Probe success */
1098 return code ? code : KRB5KDC_ERR_POLICY; /* Not a probe -> must fail */
1101 /* Import the certificate payload */
1102 if (kx509_ctx->expect_chain) {
1103 ret = rd_chain(context, r.certificate, cert, chain, &herr);
1104 } else {
1105 *cert = hx509_cert_init_data(context->hx509ctx, r.certificate->data,
1106 r.certificate->length, &herr);
1107 if (!*cert)
1108 ret = errno;
1110 free_Kx509Response(&r);
1111 if (*cert) {
1112 heim_release(herr);
1113 return 0;
1116 hestr = herr ? heim_error_copy_string(herr) : NULL;
1117 estr = hestr ? heim_string_get_utf8(hestr) : "(no error message)";
1118 krb5_set_error_message(context, ret, "Could not parse certificate "
1119 "produced by kx509 KDC: %s (%ld)",
1120 estr,
1121 herr ? (long)heim_error_get_code(herr) : 0L);
1123 heim_release(hestr);
1124 heim_release(herr);
1125 return HEIM_PKINIT_CERTIFICATE_INVALID; /* XXX */
1129 * Make a request, send it, get the response, parse it, and store the
1130 * private key and certificate.
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)
1139 krb5_error_code ret;
1140 hx509_certs chain = NULL;
1141 hx509_cert cert = NULL;
1142 krb5_data req, resp;
1144 krb5_data_zero(&req);
1145 krb5_data_zero(&resp);
1147 /* Make the kx509 request */
1148 ret = mk_kx509_req(context, kx509_ctx, incc, kx509_ctx->priv_key, &req);
1150 /* Send the kx509 request and get the response */
1151 if (ret == 0)
1152 ret = krb5_sendto_context(context, NULL, &req,
1153 kx509_ctx->realm, &resp);
1154 if (ret == 0)
1155 ret = rd_kx509_resp(context, kx509_ctx, &resp, &cert, &chain);
1157 /* Store the key and cert! */
1158 if (ret == 0 && cert && (kx509_ctx->priv_key || kx509_ctx->given_csr.data))
1159 ret = store(context, hx509_store, kx509_ctx->realm, outcc,
1160 kx509_ctx->priv_key, cert, chain);
1161 else if (ret == KRB5KDC_ERR_POLICY)
1162 /* Probe failed -> record that the realm does not support kx509 */
1163 store_kx509_disabled(context, kx509_ctx->realm, outcc);
1165 hx509_certs_free(&chain);
1166 hx509_cert_free(cert);
1167 krb5_data_free(&resp);
1168 krb5_data_free(&req);
1169 return ret;
1173 * Use the kx509 v2 protocol to get a certificate for the client principal.
1175 * Given a private key this function will get a certificate. If no private key
1176 * is given, one will be generated.
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".
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)
1192 * @return A krb5 error code.
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)
1201 krb5_ccache def_cc = NULL;
1202 krb5_error_code ret;
1204 if (incc == NULL) {
1205 if ((ret = krb5_cc_default(context, &def_cc)))
1206 return ret;
1207 incc = def_cc;
1210 if (kx509_ctx->realm == NULL &&
1211 (ret = get_start_realm(context, incc, NULL, &kx509_ctx->realm))) {
1212 if (def_cc)
1213 krb5_cc_close(context, def_cc);
1214 return ret;
1217 if (kx509_ctx->priv_key || kx509_ctx->given_csr.data) {
1218 /* If given a private key, use it */
1219 ret = kx509_core(context, kx509_ctx, incc, hx509_store, outcc);
1220 if (def_cc)
1221 krb5_cc_close(context, def_cc);
1222 return ret;
1226 * No private key given, so we generate one.
1228 * However, before taking the hit for generating a keypair we probe to see
1229 * if we're likely to succeeed.
1232 /* Probe == call kx509_core() w/o a private key */
1233 ret = kx509_core(context, kx509_ctx, incc, NULL, outcc);
1234 if (ret == 0 && kx509_ctx->given_csr.data == NULL)
1235 ret = krb5_kx509_ctx_gen_key(context, kx509_ctx, NULL, 0);
1236 if (ret == 0)
1237 ret = kx509_core(context, kx509_ctx, incc, hx509_store, outcc);
1239 if (def_cc)
1240 krb5_cc_close(context, def_cc);
1241 return ret;
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.
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.
1252 * XXX NOTE: Dicey feature here... Review carefully!
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)
1259 * @return A krb5 error code.
1261 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1262 krb5_kx509(krb5_context context, krb5_ccache cc, const char *realm)
1264 krb5_kx509_req_ctx kx509_ctx;
1265 krb5_error_code ret;
1266 const char *defcc;
1267 char *ccache_full_name = NULL;
1268 char *store_exp = NULL;
1270 ret = krb5_kx509_ctx_init(context, &kx509_ctx);
1271 if (ret)
1272 return ret;
1273 if (realm)
1274 ret = krb5_kx509_ctx_set_realm(context, kx509_ctx, realm);
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.
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.
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.
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.
1292 if (ret == 0 &&
1293 (defcc = krb5_cc_configured_default_name(context)) &&
1294 krb5_cc_get_full_name(context, cc, &ccache_full_name) == 0 &&
1295 strcmp(defcc, ccache_full_name) == 0) {
1297 /* Find an hx509 store */
1298 const char *store = krb5_config_get_string(context, NULL,
1299 "libdefaults",
1300 "kx509_store", NULL);
1301 if (store)
1302 ret = _krb5_expand_path_tokens(context, store, 1, &store_exp);
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:
1309 (void) krb5_kx509_ctx_set_key(context, kx509_ctx, store);
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.
1317 if (ret == 0)
1318 ret = krb5_kx509_ext(context, kx509_ctx, cc, store_exp, cc);
1319 krb5_kx509_ctx_free(context, &kx509_ctx);
1320 free(ccache_full_name);
1321 free(store_exp);
1322 return ret;