2 * Copyright (c) 2019 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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
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
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.
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
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
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
;
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.
120 krb5_kx509_ctx_init(krb5_context context
, krb5_kx509_req_ctx
*out
)
122 krb5_kx509_req_ctx ctx
;
124 hx509_name name
= NULL
;
128 return krb5_enomem(context
);
129 ctx
->given_csr
.data
= NULL
;
130 ctx
->priv_key
= NULL
;
131 ctx
->hmac_key
= NULL
;
135 ret
= hx509_request_init(context
->hx509ctx
, &ctx
->csr
);
137 ret
= hx509_parse_name(context
->hx509ctx
, "", &name
);
139 ret
= hx509_request_set_name(context
->hx509ctx
, ctx
->csr
, name
);
141 ret
= krb5_auth_con_init(context
, &ctx
->ac
);
143 hx509_name_free(&name
);
147 krb5_kx509_ctx_free(context
, &ctx
);
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.
160 krb5_kx509_ctx_free(krb5_context context
, krb5_kx509_req_ctx
*ctxp
)
162 krb5_kx509_req_ctx ctx
= *ctxp
;
167 krb5_free_keyblock(context
, ctx
->hmac_key
);
168 krb5_auth_con_free(context
, ctx
->ac
);
169 free_Kx509CSRPlus(&ctx
->csr_plus
);
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
);
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.
188 krb5_kx509_ctx_set_realm(krb5_context context
,
189 krb5_kx509_req_ctx kx509_ctx
,
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
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.
210 krb5_kx509_ctx_set_csr_der(krb5_context context
,
211 krb5_kx509_req_ctx ctx
,
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.
229 krb5_kx509_ctx_add_eku(krb5_context context
,
230 krb5_kx509_req_ctx kx509_ctx
,
236 ret
= der_parse_heim_oid(oids
, NULL
, &oid
);
238 hx509_request_add_eku(context
->hx509ctx
, kx509_ctx
->csr
, &oid
);
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.
254 krb5_kx509_ctx_add_san_dns_name(krb5_context context
,
255 krb5_kx509_req_ctx kx509_ctx
,
258 return hx509_request_add_dns_name(context
->hx509ctx
, kx509_ctx
->csr
,
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.
273 krb5_kx509_ctx_add_san_xmpp(krb5_context context
,
274 krb5_kx509_req_ctx kx509_ctx
,
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.
291 krb5_kx509_ctx_add_san_rfc822Name(krb5_context context
,
292 krb5_kx509_req_ctx kx509_ctx
,
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
307 * @return A krb5 error code.
310 krb5_kx509_ctx_add_san_pkinit(krb5_context context
,
311 krb5_kx509_req_ctx kx509_ctx
,
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.
328 krb5_kx509_ctx_add_san_ms_upn(krb5_context context
,
329 krb5_kx509_req_ctx kx509_ctx
,
332 return hx509_request_add_ms_upn_name(context
->hx509ctx
, kx509_ctx
->csr
,
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.
347 krb5_kx509_ctx_add_san_registeredID(krb5_context context
,
348 krb5_kx509_req_ctx kx509_ctx
,
354 ret
= der_parse_heim_oid(oids
, NULL
, &oid
);
356 hx509_request_add_registered(context
->hx509ctx
, kx509_ctx
->csr
, &oid
);
361 static krb5_error_code
362 load_priv_key(krb5_context context
,
363 krb5_kx509_req_ctx kx509_ctx
,
366 hx509_private_key
*keys
= NULL
;
367 hx509_certs certs
= NULL
;
370 ret
= hx509_certs_init(context
->hx509ctx
, fn
, 0, NULL
, &certs
);
374 ret
= _hx509_certs_keys_get(context
->hx509ctx
, certs
, &keys
);
375 if (ret
== 0 && keys
[0] == NULL
)
378 kx509_ctx
->priv_key
= _hx509_private_key_ref(keys
[0]);
380 krb5_set_error_message(context
, ret
, "Could not load private key "
381 "from %s for kx509: %s", fn
,
382 hx509_get_error_string(context
->hx509ctx
, ret
));
383 hx509_certs_free(&certs
);
390 * @param context The Kerberos library context
391 * @param ctx The kx509 request context
392 * @param store The name of a PKIX credential store
394 * @return A krb5 error code.
397 krb5_kx509_ctx_set_key(krb5_context context
,
398 krb5_kx509_req_ctx kx509_ctx
,
401 SubjectPublicKeyInfo key
;
404 memset(&key
, 0, sizeof(key
));
405 hx509_private_key_free(&kx509_ctx
->priv_key
);
406 _hx509_certs_keys_free(context
->hx509ctx
, kx509_ctx
->keys
);
407 kx509_ctx
->keys
= NULL
;
408 ret
= load_priv_key(context
, kx509_ctx
, store
);
410 ret
= hx509_private_key2SPKI(context
->hx509ctx
, kx509_ctx
->priv_key
,
413 ret
= hx509_request_set_SubjectPublicKeyInfo(context
->hx509ctx
,
414 kx509_ctx
->csr
, &key
);
415 free_SubjectPublicKeyInfo(&key
);
419 static krb5_error_code
420 gen_priv_key(krb5_context context
,
421 const char *gen_type
,
422 unsigned long gen_bits
,
423 hx509_private_key
*key
)
425 struct hx509_generate_private_context
*key_gen_ctx
= NULL
;
428 _krb5_debug(context
, 1, "kx509: gen priv key");
429 if (strcmp(gen_type
, "rsa")) {
430 krb5_set_error_message(context
, ENOTSUP
, "Key type %s is not "
431 "supported for kx509; only \"rsa\" is "
432 "supported for kx509 at this time",
437 ret
= _hx509_generate_private_key_init(context
->hx509ctx
,
438 ASN1_OID_ID_PKCS1_RSAENCRYPTION
,
441 ret
= _hx509_generate_private_key_bits(context
->hx509ctx
, key_gen_ctx
, gen_bits
);
444 ret
= _hx509_generate_private_key(context
->hx509ctx
, key_gen_ctx
, key
);
445 _hx509_generate_private_key_free(&key_gen_ctx
);
447 krb5_set_error_message(context
, ret
,
448 "Could not generate a private key: %s",
449 hx509_get_error_string(context
->hx509ctx
, ret
));
454 * Generate a private key.
456 * @param context The Kerberos library context
457 * @param ctx The kx509 request context
458 * @param gen_type The type of key (default: rsa)
459 * @param gen_bits The size of the key (for non-ECC, really, for RSA)
461 * @return A krb5 error code.
464 krb5_kx509_ctx_gen_key(krb5_context context
,
465 krb5_kx509_req_ctx kx509_ctx
,
466 const char *gen_type
,
469 SubjectPublicKeyInfo key
;
472 memset(&key
, 0, sizeof(key
));
474 if (gen_type
== NULL
) {
475 gen_type
= krb5_config_get_string_default(context
, NULL
, "rsa",
477 "kx509_gen_key_type", NULL
);
481 * The key size is really only for non-ECC, of which we'll only support
482 * RSA. For ECC key sizes will either be implied by the `key_type' or
483 * will have to be a magic value that allows us to pick from some small
484 * set of curves (e.g., 255 == Curve25519).
486 gen_bits
= krb5_config_get_int_default(context
, NULL
, 2048,
488 "kx509_gen_rsa_key_size", NULL
);
490 hx509_private_key_free(&kx509_ctx
->priv_key
);
491 _hx509_certs_keys_free(context
->hx509ctx
, kx509_ctx
->keys
);
492 kx509_ctx
->keys
= NULL
;
494 ret
= gen_priv_key(context
, gen_type
, gen_bits
, &kx509_ctx
->priv_key
);
496 ret
= hx509_private_key2SPKI(context
->hx509ctx
, kx509_ctx
->priv_key
,
499 ret
= hx509_request_set_SubjectPublicKeyInfo(context
->hx509ctx
,
500 kx509_ctx
->csr
, &key
);
501 free_SubjectPublicKeyInfo(&key
);
505 /* Set a cc config entry indicating that the kx509 service is not available */
507 store_kx509_disabled(krb5_context context
, const char *realm
, krb5_ccache cc
)
514 data
.data
= (void *)(uintptr_t)realm
;
515 data
.length
= strlen(realm
);
516 krb5_cc_set_config(context
, cc
, NULL
, "kx509_service_realm", &data
);
517 data
.data
= "disabled";
518 data
.length
= strlen(data
.data
);
519 krb5_cc_set_config(context
, cc
, NULL
, "kx509_service_status", &data
);
522 static int KRB5_CALLCONV
523 certs_export_func(hx509_context context
, void *d
, hx509_cert c
)
525 heim_octet_string os
;
526 Certificates
*cs
= d
;
530 ret
= hx509_cert_binary(context
, c
, &os
);
533 ret
= decode_Certificate(os
.data
, os
.length
, &c2
, NULL
);
534 der_free_octet_string(&os
);
537 ret
= add_Certificates(cs
, &c2
);
538 free_Certificate(&c2
);
542 static krb5_error_code
543 certs_export(hx509_context context
, hx509_certs certs
, heim_octet_string
*out
)
551 ret
= hx509_certs_iter_f(context
, certs
, certs_export_func
, &cs
);
553 ASN1_MALLOC_ENCODE(Certificates
, out
->data
, out
->length
, &cs
, &len
, ret
);
554 free_Certificates(&cs
);
558 /* Store the private key and certificate where requested */
559 static krb5_error_code
560 store(krb5_context context
,
561 const char *hx509_store
,
564 hx509_private_key key
,
568 heim_octet_string hdata
;
569 krb5_error_code ret
= 0;
572 krb5_clear_error_message(context
);
575 /* Record the realm we used */
576 data
.data
= (void *)(uintptr_t)realm
;
577 data
.length
= strlen(realm
);
578 krb5_cc_set_config(context
, cc
, NULL
, "kx509_service_realm", &data
);
580 /* Serialize and store the certificate in the ccache */
581 ret
= hx509_cert_binary(context
->hx509ctx
, cert
, &hdata
);
583 ret
= krb5_cc_set_config(context
, cc
, NULL
, "kx509cert", &hdata
);
584 der_free_octet_string(&hdata
);
586 if (ret
== 0 && key
) {
588 * Serialized and store the key in the ccache. Use PKCS#8 so that we
589 * store the algorithm OID too, which is needed in order to be able to
590 * read the private key back.
593 ret
= _hx509_private_key_export(context
->hx509ctx
, key
,
594 HX509_KEY_FORMAT_PKCS8
, &hdata
);
596 ret
= krb5_cc_set_config(context
, cc
, NULL
, "kx509key", &hdata
);
597 der_free_octet_string(&hdata
);
599 krb5_set_error_message(context
, ret
, "Could not store kx509 "
600 "private key and certificate in ccache %s",
601 krb5_cc_get_name(context
, cc
));
604 if (ret
== 0 && chain
) {
605 ret
= certs_export(context
->hx509ctx
, chain
, &hdata
);
607 ret
= krb5_cc_set_config(context
, cc
, NULL
, "kx509cert-chain",
609 der_free_octet_string(&hdata
);
613 /* Store the private key and cert in an hx509 store */
614 if (hx509_store
!= NULL
) {
618 _hx509_cert_assign_key(cert
, key
); /* store both in the same store */
620 ret
= hx509_certs_init(context
->hx509ctx
, hx509_store
,
621 HX509_CERTS_CREATE
, NULL
, &certs
);
623 ret
= hx509_certs_add(context
->hx509ctx
, certs
, cert
);
624 if (ret
== 0 && chain
!= NULL
)
625 ret
= hx509_certs_merge(context
->hx509ctx
, certs
, chain
);
627 ret
= hx509_certs_store(context
->hx509ctx
, certs
, 0, NULL
);
628 hx509_certs_free(&certs
);
630 krb5_prepend_error_message(context
, ret
, "Could not store kx509 "
631 "private key and certificate in key "
632 "store %s", hx509_store
);
635 /* Store the name of the hx509 store in the ccache too */
636 if (cc
&& hx509_store
) {
637 data
.data
= (void *)(uintptr_t)hx509_store
;
638 data
.length
= strlen(hx509_store
);
639 (void) krb5_cc_set_config(context
, cc
, NULL
, "kx509store", &data
);
644 /* Make a Kx509CSRPlus or a raw SPKI */
645 static krb5_error_code
646 mk_kx509_req_body(krb5_context context
,
647 krb5_kx509_req_ctx kx509_ctx
,
653 if (krb5_config_get_bool_default(context
, NULL
, FALSE
,
654 "realms", kx509_ctx
->realm
,
655 "kx509_req_use_raw_spki", NULL
)) {
656 SubjectPublicKeyInfo spki
;
658 /* Interop with old kx509 servers, send a raw SPKI, not a CSR */
661 memset(&spki
, 0, sizeof(spki
));
662 ret
= hx509_private_key2SPKI(context
->hx509ctx
,
663 kx509_ctx
->priv_key
, &spki
);
665 out
->length
= spki
.subjectPublicKey
.length
>> 3;
666 out
->data
= spki
.subjectPublicKey
.data
;
668 kx509_ctx
->expect_chain
= 0;
673 * New kx509 servers use a CSR for proof of possession, and send back a
674 * chain of certificates, with the issued certificate first.
676 kx509_ctx
->expect_chain
= 1;
678 if (kx509_ctx
->given_csr
.length
) {
681 exts_der
.data
= NULL
;
684 /* Use the given CSR */
685 ret
= der_copy_octet_string(&kx509_ctx
->given_csr
,
686 &kx509_ctx
->csr_plus
.csr
);
689 * Extract the desired Certificate Extensions from our internal
690 * as-yet-unsigned CSR, then decode them into place in the
694 ret
= hx509_request_get_exts(context
->hx509ctx
,
697 if (ret
== 0 && exts_der
.data
&& exts_der
.length
&&
698 (kx509_ctx
->csr_plus
.exts
=
699 calloc(1, sizeof (kx509_ctx
->csr_plus
.exts
[0]))) == NULL
)
700 ret
= krb5_enomem(context
);
701 if (ret
== 0 && exts_der
.data
&& exts_der
.length
)
702 ret
= decode_Extensions(exts_der
.data
, exts_der
.length
,
703 kx509_ctx
->csr_plus
.exts
, NULL
);
704 krb5_data_free(&exts_der
);
707 * Sign and use our internal CSR, which will carry all our desired
708 * Certificate Extensions as an extReq CSR Attribute.
710 ret
= hx509_request_to_pkcs10(context
->hx509ctx
,
713 &kx509_ctx
->csr_plus
.csr
);
716 ASN1_MALLOC_ENCODE(Kx509CSRPlus
, out
->data
, out
->length
,
717 &kx509_ctx
->csr_plus
, &len
, ret
);
721 static krb5_error_code
722 get_start_realm(krb5_context context
,
724 krb5_const_principal princ
,
730 ret
= krb5_cc_get_config(context
, cc
, NULL
, "start_realm", &d
);
732 *out
= strndup(d
.data
, d
.length
);
735 *out
= strdup(krb5_principal_get_realm(context
, princ
));
737 krb5_principal ccprinc
= NULL
;
739 ret
= krb5_cc_get_principal(context
, cc
, &ccprinc
);
742 *out
= strdup(krb5_principal_get_realm(context
, ccprinc
));
743 krb5_free_principal(context
, ccprinc
);
745 return (*out
) ? 0 : krb5_enomem(context
);
749 * Make a request, which is a DER-encoded Kx509Request with version_2_0
752 * If no private key is given, then a probe request will be made.
754 static krb5_error_code
755 mk_kx509_req(krb5_context context
,
756 krb5_kx509_req_ctx kx509_ctx
,
758 hx509_private_key private_key
,
761 unsigned char digest
[SHA_DIGEST_LENGTH
];
762 SubjectPublicKeyInfo spki
;
763 struct Kx509Request kx509_req
;
765 krb5_error_code ret
= 0;
766 krb5_creds this_cred
;
767 krb5_creds
*cred
= NULL
;
769 const char *hostname
;
770 char *start_realm
= NULL
;
773 krb5_data_zero(&pre_req
);
774 memset(&spki
, 0, sizeof(spki
));
775 memset(&this_cred
, 0, sizeof(this_cred
));
776 memset(&kx509_req
, 0, sizeof(kx509_req
));
777 kx509_req
.pk_hash
.data
= digest
;
778 kx509_req
.pk_hash
.length
= SHA_DIGEST_LENGTH
;
780 if (private_key
|| kx509_ctx
->given_csr
.data
) {
781 /* Encode the CSR or public key for use in the request */
782 ret
= mk_kx509_req_body(context
, kx509_ctx
, &kx509_req
.pk_key
);
785 kx509_req
.pk_key
.data
= NULL
;
786 kx509_req
.pk_key
.length
= 0;
790 ret
= krb5_cc_get_principal(context
, incc
, &this_cred
.client
);
792 ret
= get_start_realm(context
, incc
, this_cred
.client
, &start_realm
);
793 if (ret
== 0 && kx509_ctx
->realm
== NULL
)
794 ret
= krb5_kx509_ctx_set_realm(context
, kx509_ctx
, start_realm
);
797 * The kx509 protocol as deployed uses kca_service/kdc_hostname, but
798 * this is inconvenient in libkrb5: we want to be able to use the
799 * send_to_kdc machinery, and since the Heimdal KDC is also the kx509
800 * service, we want not to have to specify kx509 hosts separately from
803 * We'd much rather use krbtgt/CLIENT_REALM@REQUESTED_REALM. What
804 * we do is assume all KDCs for `realm' support the kx509 service and
805 * then sendto the KDCs for that realm while using a hostbased service
808 * Note that upstairs we try to get the start_realm cc config, so if
809 * realm wasn't given to krb5_kx509_ext(), then it should be set to
810 * that already unless there's no start_realm cc config, in which case
811 * we'll use the ccache's default client principal's realm.
813 hostname
= krb5_config_get_string(context
, NULL
, "realms",
814 kx509_ctx
->realm
, "kx509_hostname",
816 if (hostname
== NULL
)
817 hostname
= krb5_config_get_string(context
, NULL
, "libdefaults",
818 "kx509_hostname", NULL
);
820 ret
= krb5_sname_to_principal(context
, hostname
, "kca_service",
821 KRB5_NT_SRV_HST
, &this_cred
.server
);
823 ret
= krb5_principal_set_realm(context
, this_cred
.server
,
826 ret
= krb5_make_principal(context
, &this_cred
.server
,
834 /* Make the AP-REQ and extract the HMAC key */
836 ret
= krb5_get_credentials(context
, 0, incc
, &this_cred
, &cred
);
838 ret
= krb5_mk_req_extended(context
, &kx509_ctx
->ac
, AP_OPTS_USE_SUBKEY
,
839 NULL
, cred
, &kx509_req
.authenticator
);
840 krb5_free_keyblock(context
, kx509_ctx
->hmac_key
);
841 kx509_ctx
->hmac_key
= NULL
;
843 ret
= krb5_auth_con_getkey(context
, kx509_ctx
->ac
,
844 &kx509_ctx
->hmac_key
);
849 /* Add the the key and HMAC to the message */
851 HMAC_Init_ex(&ctx
, kx509_ctx
->hmac_key
->keyvalue
.data
,
852 kx509_ctx
->hmac_key
->keyvalue
.length
, EVP_sha1(), NULL
);
853 HMAC_Update(&ctx
, version_2_0
, sizeof(version_2_0
));
854 if (private_key
|| kx509_ctx
->given_csr
.data
) {
855 HMAC_Update(&ctx
, kx509_req
.pk_key
.data
, kx509_req
.pk_key
.length
);
858 HMAC_Update(&ctx
, kx509_req
.authenticator
.data
, kx509_req
.authenticator
.length
);
860 HMAC_Final(&ctx
, kx509_req
.pk_hash
.data
, 0);
861 HMAC_CTX_cleanup(&ctx
);
863 /* Encode the message, prefix `version_2_0', output the result */
864 ASN1_MALLOC_ENCODE(Kx509Request
, pre_req
.data
, pre_req
.length
, &kx509_req
, &len
, ret
);
865 ret
= krb5_data_alloc(req
, pre_req
.length
+ sizeof(version_2_0
));
867 memcpy(req
->data
, version_2_0
, sizeof(version_2_0
));
868 memcpy(((unsigned char *)req
->data
) + sizeof(version_2_0
),
869 pre_req
.data
, pre_req
.length
);
875 krb5_free_creds(context
, cred
);
876 kx509_req
.pk_hash
.data
= NULL
;
877 kx509_req
.pk_hash
.length
= 0;
878 free_Kx509Request(&kx509_req
);
879 free_SubjectPublicKeyInfo(&spki
);
880 krb5_free_cred_contents(context
, &this_cred
);
881 if (ret
== 0 && req
->length
!= len
+ sizeof(version_2_0
)) {
883 krb5_set_error_message(context
, ret
= ERANGE
,
884 "Could not make a kx509 request");
889 static krb5_error_code
890 rd_chain(krb5_context context
,
891 heim_octet_string
*d
,
903 if ((ret
= decode_Certificates(d
->data
, d
->length
, &certs
, &len
)))
905 if (certs
.len
== 0) {
906 *herr
= heim_error_create(EINVAL
, "Server sent empty Certificate list");
909 *cert
= hx509_cert_init(context
->hx509ctx
, &certs
.val
[0], herr
);
911 free_Certificates(&certs
);
915 _krb5_debug(context
, 1, "kx509 server sent certificate but no chain");
917 _krb5_debug(context
, 1, "kx509 server sent %llu certificates",
918 (unsigned long long)certs
.len
);
920 ret
= hx509_certs_init(context
->hx509ctx
, "MEMORY:anonymous",
921 HX509_CERTS_CREATE
, NULL
, chain
);
923 hx509_cert_free(*cert
);
925 free_Certificates(&certs
);
929 for (i
= 1; ret
== 0 && i
< certs
.len
; i
++) {
930 hx509_cert c
= hx509_cert_init(context
->hx509ctx
, &certs
.val
[i
], herr
);
935 ret
= hx509_certs_add(context
->hx509ctx
, *chain
, c
);
938 free_Certificates(&certs
);
940 hx509_certs_free(chain
);
941 hx509_cert_free(*cert
);
947 /* Parse and validate a kx509 reply */
948 static krb5_error_code
949 rd_kx509_resp(krb5_context context
,
950 krb5_kx509_req_ctx kx509_ctx
,
955 unsigned char digest
[SHA_DIGEST_LENGTH
];
957 krb5_error_code code
= 0;
958 krb5_error_code ret
= 0;
960 heim_error_t herr
= NULL
;
963 size_t hdr_len
= sizeof(version_2_0
);
969 /* Strip `version_2_0' prefix */
970 if (rep
->length
< hdr_len
|| memcmp(rep
->data
, version_2_0
, hdr_len
)) {
971 krb5_set_error_message(context
, ENOTSUP
,
972 "KDC does not support kx509 protocol");
973 return ENOTSUP
; /* XXX */
977 ret
= decode_Kx509Response(((unsigned char *)rep
->data
) + 4,
978 rep
->length
- 4, &r
, &len
);
979 if (ret
== 0 && len
+ hdr_len
!= rep
->length
)
980 ret
= EINVAL
; /* XXX */
982 krb5_set_error_message(context
, ret
, "kx509 response is not valid");
987 HMAC_Init_ex(&ctx
, kx509_ctx
->hmac_key
->keyvalue
.data
,
988 kx509_ctx
->hmac_key
->keyvalue
.length
, EVP_sha1(), NULL
);
989 HMAC_Update(&ctx
, version_2_0
, sizeof(version_2_0
));
992 int32_t t
= r
.error_code
;
993 unsigned char encint
[sizeof(t
) + 1];
997 * RFC6717 says this about how the error-code is included in the HMAC:
999 * o DER representation of the error-code exclusive of the tag and
1000 * length, if it is present.
1002 * So we use der_put_integer(), which encodes from the right.
1004 * RFC6717 does not constrain the error-code's range. We assume it to
1005 * be a 32-bit, signed integer, for which we'll need no more than 5
1008 ret
= der_put_integer(&encint
[sizeof(encint
) - 1],
1009 sizeof(encint
), &t
, &k
);
1011 HMAC_Update(&ctx
, &encint
[sizeof(encint
)] - k
, k
);
1013 /* Normalize error code */
1014 if (r
.error_code
== 0) {
1015 code
= 0; /* No error */
1016 } else if (r
.error_code
< 0) {
1017 code
= KRB5KRB_ERR_GENERIC
; /* ??? */
1018 } else if (r
.error_code
<= KX509_ERR_SRV_OVERLOADED
) {
1020 * RFC6717 (kx509) error code. These are actually not used on the
1021 * wire in any existing implementations that we are aware of. Just
1022 * in case, however, we'll map these.
1024 code
= KX509_ERR_CLNT_FATAL
+ r
.error_code
;
1025 } else if (r
.error_code
< kx509_krb5_error_base
) {
1026 /* Unknown error codes */
1027 code
= KRB5KRB_ERR_GENERIC
;
1030 * Heimdal-specific enhancement to RFC6171: Kerberos wire protocol
1033 code
= KRB5KDC_ERR_NONE
+ r
.error_code
- kx509_krb5_error_base
;
1034 if (code
>= KRB5_ERR_RCSID
)
1035 code
= KRB5KRB_ERR_GENERIC
;
1036 if (code
== KRB5KDC_ERR_NONE
)
1041 HMAC_Update(&ctx
, r
.certificate
->data
, r
.certificate
->length
);
1043 HMAC_Update(&ctx
, *r
.e_text
, strlen(*r
.e_text
));
1044 HMAC_Final(&ctx
, &digest
, 0);
1045 HMAC_CTX_cleanup(&ctx
);
1047 if (r
.hash
== NULL
) {
1049 * No HMAC -> unauthenticated [error] response.
1051 * Do not output any certificate.
1053 free_Kx509Response(&r
);
1058 * WARNING: We do not validate that `r.certificate' is a DER-encoded
1059 * Certificate, not here, and we don't use a different HMAC key
1060 * for the response than for the request.
1062 * If ever we start sending a Certificate as the Kx509Request
1063 * pk-key field, then we'll have a reflection attack. As the
1064 * Certificate we'd send in that case will be expired, the
1065 * reflection attack would be just a DoS.
1067 if (r
.hash
->length
!= sizeof(digest
) ||
1068 ct_memcmp(r
.hash
->data
, digest
, sizeof(digest
)) != 0) {
1069 krb5_set_error_message(context
, KRB5KDC_ERR_PREAUTH_FAILED
,
1070 "kx509 response MAC mismatch");
1071 free_Kx509Response(&r
);
1072 return KRB5KRB_AP_ERR_BAD_INTEGRITY
;
1075 if (r
.certificate
== NULL
) {
1076 /* Authenticated response, either an error or probe success */
1077 free_Kx509Response(&r
);
1078 if (code
!= KRB5KDC_ERR_POLICY
&& kx509_ctx
->priv_key
== NULL
)
1079 return 0; /* Probe success */
1080 return code
? code
: KRB5KDC_ERR_POLICY
; /* Not a probe -> must fail */
1083 /* Import the certificate payload */
1084 if (kx509_ctx
->expect_chain
) {
1085 ret
= rd_chain(context
, r
.certificate
, cert
, chain
, &herr
);
1087 *cert
= hx509_cert_init_data(context
->hx509ctx
, r
.certificate
->data
,
1088 r
.certificate
->length
, &herr
);
1092 free_Kx509Response(&r
);
1098 hestr
= herr
? heim_error_copy_string(herr
) : NULL
;
1099 estr
= hestr
? heim_string_get_utf8(hestr
) : "(no error message)";
1100 krb5_set_error_message(context
, ret
, "Could not parse certificate "
1101 "produced by kx509 KDC: %s (%ld)",
1103 herr
? (long)heim_error_get_code(herr
) : 0L);
1105 heim_release(hestr
);
1107 return HEIM_PKINIT_CERTIFICATE_INVALID
; /* XXX */
1111 * Make a request, send it, get the response, parse it, and store the
1112 * private key and certificate.
1114 static krb5_error_code
1115 kx509_core(krb5_context context
,
1116 krb5_kx509_req_ctx kx509_ctx
,
1118 const char *hx509_store
,
1121 krb5_error_code ret
;
1122 hx509_certs chain
= NULL
;
1123 hx509_cert cert
= NULL
;
1124 krb5_data req
, resp
;
1126 krb5_data_zero(&req
);
1127 krb5_data_zero(&resp
);
1129 /* Make the kx509 request */
1130 ret
= mk_kx509_req(context
, kx509_ctx
, incc
, kx509_ctx
->priv_key
, &req
);
1132 /* Send the kx509 request and get the response */
1134 ret
= krb5_sendto_context(context
, NULL
, &req
,
1135 kx509_ctx
->realm
, &resp
);
1137 ret
= rd_kx509_resp(context
, kx509_ctx
, &resp
, &cert
, &chain
);
1139 /* Store the key and cert! */
1140 if (ret
== 0 && cert
&& (kx509_ctx
->priv_key
|| kx509_ctx
->given_csr
.data
))
1141 ret
= store(context
, hx509_store
, kx509_ctx
->realm
, outcc
,
1142 kx509_ctx
->priv_key
, cert
, chain
);
1143 else if (ret
== KRB5KDC_ERR_POLICY
)
1144 /* Probe failed -> record that the realm does not support kx509 */
1145 store_kx509_disabled(context
, kx509_ctx
->realm
, outcc
);
1147 hx509_certs_free(&chain
);
1148 hx509_cert_free(cert
);
1149 krb5_data_free(&resp
);
1150 krb5_data_free(&req
);
1155 * Use the kx509 v2 protocol to get a certificate for the client principal.
1157 * Given a private key this function will get a certificate. If no private key
1158 * is given, one will be generated.
1160 * The private key and certificate will be stored in the given PKIX credential
1161 * store (e.g, "PEM-FILE:/path/to/file.pem") and/or given output ccache. When
1162 * stored in a ccache, the DER-encoded Certificate will be stored as the data
1163 * payload of a "cc config" named "kx509cert", while the key will be stored as
1164 * a DER-encoded PKCS#8 PrivateKeyInfo in a cc config named "kx509key".
1166 * @param context The Kerberos library context
1167 * @param kx509_ctx A kx509 request context
1168 * @param incc A credential cache (if NULL use default ccache)
1169 * @param hx509_store An PKIX credential store into which to store the private
1170 * key and certificate (e.g, "PEM-FILE:/path/to/file.pem")
1171 * @param outcc A ccache into which to store the private key and certificate
1174 * @return A krb5 error code.
1176 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1177 krb5_kx509_ext(krb5_context context
,
1178 krb5_kx509_req_ctx kx509_ctx
,
1180 const char *hx509_store
,
1183 krb5_ccache def_cc
= NULL
;
1184 krb5_error_code ret
;
1187 if ((ret
= krb5_cc_default(context
, &def_cc
)))
1192 if (kx509_ctx
->realm
== NULL
&&
1193 (ret
= get_start_realm(context
, incc
, NULL
, &kx509_ctx
->realm
))) {
1195 krb5_cc_close(context
, def_cc
);
1199 if (kx509_ctx
->priv_key
|| kx509_ctx
->given_csr
.data
) {
1200 /* If given a private key, use it */
1201 ret
= kx509_core(context
, kx509_ctx
, incc
, hx509_store
, outcc
);
1203 krb5_cc_close(context
, def_cc
);
1208 * No private key given, so we generate one.
1210 * However, before taking the hit for generating a keypair we probe to see
1211 * if we're likely to succeeed.
1214 /* Probe == call kx509_core() w/o a private key */
1215 ret
= kx509_core(context
, kx509_ctx
, incc
, NULL
, outcc
);
1216 if (ret
== 0 && kx509_ctx
->given_csr
.data
== NULL
)
1217 ret
= krb5_kx509_ctx_gen_key(context
, kx509_ctx
, NULL
, 0);
1219 ret
= kx509_core(context
, kx509_ctx
, incc
, hx509_store
, outcc
);
1222 krb5_cc_close(context
, def_cc
);
1227 * Generates a public key and uses the kx509 v2 protocol to get a certificate
1228 * for that key and the client principal's subject name.
1230 * The private key and certificate will be stored in the given ccache, and also
1231 * in a corresponding PKIX credential store if one is configured via
1232 * [libdefaults] kx509_store.
1234 * XXX NOTE: Dicey feature here... Review carefully!
1236 * @param context The Kerberos library context
1237 * @param cc A credential cache
1238 * @param realm A realm from which to get the certificate (uses the client
1239 * principal's realm if NULL)
1241 * @return A krb5 error code.
1243 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1244 krb5_kx509(krb5_context context
, krb5_ccache cc
, const char *realm
)
1246 krb5_kx509_req_ctx kx509_ctx
;
1247 krb5_error_code ret
;
1249 char *ccache_full_name
= NULL
;
1250 char *store_exp
= NULL
;
1252 ret
= krb5_kx509_ctx_init(context
, &kx509_ctx
);
1253 if (ret
== 0 && realm
)
1254 ret
= krb5_kx509_ctx_set_realm(context
, kx509_ctx
, realm
);
1257 * The idea is that IF we are asked to do kx509 w/ creds from a default
1258 * ccache THEN we should store the kx509 certificate (if we get one) and
1259 * private key in the default hx509 store for kx509.
1261 * Ideally we could have HTTP user-agents and/or TLS libraries look for
1262 * client certificates and private keys in that default hx509 store.
1264 * Of course, those user-agents / libraries should be configured to use
1265 * those credentials with specific hostnames/domainnames, not the entire
1266 * Internet, as the latter leaks the user's identity to the world.
1268 * So we check if the full name for `cc' is the same as that of the default
1269 * ccache name, and if so we get the [libdefaults] kx509_store string and
1270 * expand it, then use it.
1273 (defcc
= krb5_cc_configured_default_name(context
)) &&
1274 krb5_cc_get_full_name(context
, cc
, &ccache_full_name
) == 0 &&
1275 strcmp(defcc
, ccache_full_name
) == 0) {
1277 /* Find an hx509 store */
1278 const char *store
= krb5_config_get_string(context
, NULL
,
1280 "kx509_store", NULL
);
1282 ret
= _krb5_expand_path_tokens(context
, store
, 1, &store_exp
);
1285 * If there's a private key in the store already, we'll use it, else
1286 * we'll let krb5_kx509_ext() generate one, so we ignore this return
1289 (void) krb5_kx509_ctx_set_key(context
, kx509_ctx
, store
);
1293 * If we did settle on a default hx509 store, we'll use it for reading the
1294 * private key from (if it exists) as well as for storing the certificate
1295 * (and private key) into, which may save us some key generation cycles.
1298 ret
= krb5_kx509_ext(context
, kx509_ctx
, cc
, store_exp
, cc
);
1299 krb5_kx509_ctx_free(context
, &kx509_ctx
);
1300 free(ccache_full_name
);