cf/largefile.m4: Fix build with autoconf-2.72
[heimdal.git] / kdc / kx509.c
blob6efd94e3a12dddefd1f0fba1b27831e872a90311
1 /*
2 * Copyright (c) 2006 - 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 "kdc_locl.h"
35 #include <hex.h>
36 #include <rfc2459_asn1.h>
37 #include <hx509.h>
38 #include <hx509_err.h>
39 #include <kx509_err.h>
41 #include <stdarg.h>
44 * This file implements the kx509 service.
46 * The protocol, its shortcomings, and its future are described in
47 * lib/krb5/hx509.c. See also lib/asn1/kx509.asn1.
49 * The service handles requests, decides whether to issue a certificate, and
50 * does so by populating a "template" to generate a TBSCertificate and signing
51 * it with a configured CA issuer certificate and private key. See ca.c for
52 * details.
54 * A "template" is a Certificate that has ${variable} references in its
55 * subjectName, and may have EKUs.
57 * Some SANs may be included in issued certificates. See below.
59 * Besides future protocol improvements described in lib/krb5/hx509.c, here is
60 * a list of KDC functionality we'd like to add:
62 * - support templates as strings (rather than filenames) in configuration?
63 * - lookup an hx509 template for the client principal in its HDB entry?
64 * - lookup subjectName, SANs for a principal in its HDB entry
65 * - lookup a host-based client principal's HDB entry and add its canonical
66 * name / aliases as dNSName SANs
67 * (this would have to be if requested by the client, perhaps; see
68 * commentary about the protocol in lib/krb5/kx509.c)
69 * - add code to build a template on the fly
71 * (just SANs, with empty subjectName?
72 * or
73 * CN=component0,CN=component1,..,CN=componentN,DC=<from-REALM>
74 * and set KU and EKUs)
76 * Processing begins in _kdc_do_kx509().
78 * The sequence of events in _kdc_do_kx509() is:
80 * - parse outer request
81 * - authenticate request
82 * - extract CSR and AP-REQ Authenticator authz-data elements
83 * - characterize request as one of
84 * - default client cert req (no cert exts requested, client user princ)
85 * - default server cert req (no cert exts requested, client service princ)
86 * - client cert req (cert exts requested denoting client use)
87 * - server cert req (cert exts requested denoting server use)
88 * - mixed cert req (cert exts requested denoting client and server use)
89 * - authorize request based only on the request's details
90 * - there is a default authorizer, and a plugin authorizer
91 * - get configuration sub-tree corresponding to the request as characterized
92 * - missing configuration sub-tree -> reject (we have multiple ways to
93 * express "no")
94 * - get common config params from that sub-tree
95 * - set TBS template and details from CSR and such
96 * - issue certificate by signing TBS
99 #ifdef KX509
101 static const unsigned char version_2_0[4] = {0 , 0, 2, 0};
104 * Taste the request to see if it's a kx509 request.
106 krb5_error_code
107 _kdc_try_kx509_request(kx509_req_context r)
109 const unsigned char *p = (const void *)(uintptr_t)r->request.data;
110 size_t len = r->request.length;
111 size_t sz;
113 if (len < sizeof(version_2_0))
114 return -1;
115 if (memcmp(version_2_0, p, sizeof(version_2_0)) != 0)
116 return -1;
117 p += sizeof(version_2_0);
118 len -= sizeof(version_2_0);
119 if (len == 0)
120 return -1;
121 memset(&r->req, 0, sizeof(r->req));
122 return decode_Kx509Request(p, len, &r->req, &sz);
125 static krb5_boolean
126 get_bool_param(krb5_context context,
127 krb5_boolean def,
128 const char *crealm,
129 const char *name)
131 krb5_boolean global_default;
133 global_default = krb5_config_get_bool_default(context, NULL, def, "kdc",
134 name, NULL);
135 if (!crealm)
136 return global_default;
137 return krb5_config_get_bool_default(context, NULL, global_default,
138 "kdc", "realms", crealm, name, NULL);
142 * Verify the HMAC in the request.
144 static krb5_error_code
145 verify_req_hash(krb5_context context,
146 const Kx509Request *req,
147 krb5_keyblock *key)
149 unsigned char digest[SHA_DIGEST_LENGTH];
150 HMAC_CTX ctx;
152 if (req->pk_hash.length != sizeof(digest)) {
153 krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
154 "pk-hash has wrong length: %lu",
155 (unsigned long)req->pk_hash.length);
156 return KRB5KDC_ERR_PREAUTH_FAILED;
159 HMAC_CTX_init(&ctx);
160 if (HMAC_Init_ex(&ctx, key->keyvalue.data, key->keyvalue.length,
161 EVP_sha1(), NULL) == 0) {
162 HMAC_CTX_cleanup(&ctx);
163 return krb5_enomem(context);
165 if (sizeof(digest) != HMAC_size(&ctx))
166 krb5_abortx(context, "runtime error, hmac buffer wrong size in kx509");
167 HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
168 if (req->pk_key.length)
169 HMAC_Update(&ctx, req->pk_key.data, req->pk_key.length);
170 else
171 HMAC_Update(&ctx, req->authenticator.data, req->authenticator.length);
172 HMAC_Final(&ctx, digest, 0);
173 HMAC_CTX_cleanup(&ctx);
175 if (ct_memcmp(req->pk_hash.data, digest, sizeof(digest)) != 0) {
176 krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
177 "kx509 request MAC mismatch");
178 return KRB5KDC_ERR_PREAUTH_FAILED;
180 return 0;
184 * Set the HMAC in the response.
186 static krb5_error_code
187 calculate_reply_hash(krb5_context context,
188 krb5_keyblock *key,
189 Kx509Response *rep)
191 krb5_error_code ret = 0;
192 HMAC_CTX ctx;
194 HMAC_CTX_init(&ctx);
196 if (HMAC_Init_ex(&ctx, key->keyvalue.data, key->keyvalue.length,
197 EVP_sha1(), NULL) == 0)
198 ret = krb5_enomem(context);
200 if (ret == 0)
201 ret = krb5_data_alloc(rep->hash, HMAC_size(&ctx));
202 if (ret) {
203 HMAC_CTX_cleanup(&ctx);
204 return krb5_enomem(context);
207 HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
209 int32_t t = rep->error_code;
210 unsigned char encint[sizeof(t) + 1];
211 size_t k;
214 * RFC6717 says this about how the error-code is included in the HMAC:
216 * o DER representation of the error-code exclusive of the tag and
217 * length, if it is present.
219 * So we use der_put_integer(), which encodes from the right.
221 * RFC6717 does not constrain the error-code's range. We assume it to
222 * be a 32-bit, signed integer, for which we'll need no more than 5
223 * bytes.
225 ret = der_put_integer(&encint[sizeof(encint) - 1],
226 sizeof(encint), &t, &k);
227 if (ret == 0)
228 HMAC_Update(&ctx, &encint[sizeof(encint)] - k, k);
230 if (rep->certificate)
231 HMAC_Update(&ctx, rep->certificate->data, rep->certificate->length);
232 if (rep->e_text)
233 HMAC_Update(&ctx, (unsigned char *)*rep->e_text, strlen(*rep->e_text));
235 HMAC_Final(&ctx, rep->hash->data, 0);
236 HMAC_CTX_cleanup(&ctx);
238 return 0;
241 static void
242 frees(char **s)
244 free(*s);
245 *s = NULL;
248 /* Check that a krbtgt's second component is a local realm */
249 static krb5_error_code
250 is_local_realm(krb5_context context,
251 kx509_req_context reqctx,
252 const char *realm)
254 krb5_error_code ret;
255 krb5_principal tgs;
256 HDB *db;
257 hdb_entry *ent = NULL;
259 ret = krb5_make_principal(context, &tgs, realm, KRB5_TGS_NAME, realm,
260 NULL);
261 if (ret)
262 return ret;
263 if (ret == 0)
264 ret = _kdc_db_fetch(context, reqctx->config, tgs, HDB_F_GET_KRBTGT,
265 NULL, &db, &ent);
266 if (ent)
267 _kdc_free_ent(context, db, ent);
268 krb5_free_principal(context, tgs);
269 if (ret == HDB_ERR_NOENTRY || ret == HDB_ERR_NOT_FOUND_HERE)
270 return KRB5KRB_AP_ERR_NOT_US;
271 return ret;
275 * Since we're using the HDB as a keytab we have to check that the client used
276 * an acceptable name for the kx509 service.
278 * We accept two names: kca_service/hostname and krbtgt/REALM.
280 * We allow cross-realm requests.
282 * Maybe x-realm support should be configurable. Requiring INITIAL tickets
283 * does NOT preclude x-realm support! (Cross-realm TGTs can be INITIAL.)
285 * Support for specific client realms is configurable by configuring issuer
286 * credentials and TBS templates on a per-realm basis and configuring no
287 * default. But maybe we should have an explicit configuration parameter
288 * to enable support for clients from different realms than the service.
290 static krb5_error_code
291 kdc_kx509_verify_service_principal(krb5_context context,
292 kx509_req_context reqctx,
293 krb5_principal sprincipal)
295 krb5_error_code ret = 0;
296 krb5_principal principal = NULL;
297 char *expected = NULL;
298 char localhost[MAXHOSTNAMELEN];
300 if (krb5_principal_get_num_comp(context, sprincipal) != 2)
301 goto err;
303 /* Check if sprincipal is a krbtgt/REALM name */
304 if (strcmp(krb5_principal_get_comp_string(context, sprincipal, 0),
305 KRB5_TGS_NAME) == 0) {
306 const char *r = krb5_principal_get_comp_string(context, sprincipal, 1);
307 if ((ret = is_local_realm(context, reqctx, r)))
308 kdc_audit_addreason((kdc_request_t)reqctx,
309 "Client used wrong krbtgt for kx509");
310 goto out;
313 /* Must be hostbased kca_service name then */
314 ret = gethostname(localhost, sizeof(localhost) - 1);
315 if (ret != 0) {
316 ret = errno;
317 kdc_log(context, reqctx->config, 0, "Failed to get local hostname");
318 kdc_audit_addreason((kdc_request_t)reqctx,
319 "Failed to get local hostname");
320 return ret;
322 localhost[sizeof(localhost) - 1] = '\0';
324 ret = krb5_make_principal(context, &principal, "", "kca_service",
325 localhost, NULL);
326 if (ret)
327 goto out;
329 if (krb5_principal_compare_any_realm(context, sprincipal, principal))
330 goto out; /* found a match */
332 err:
333 ret = krb5_unparse_name(context, sprincipal, &expected);
334 if (ret)
335 goto out;
337 ret = KRB5KDC_ERR_SERVER_NOMATCH;
338 kdc_audit_addreason((kdc_request_t)reqctx, "Client used wrong kx509 "
339 "service principal (expected %s)", expected);
341 out:
342 krb5_xfree(expected);
343 krb5_free_principal(context, principal);
345 return ret;
348 static krb5_error_code
349 encode_reply(krb5_context context,
350 kx509_req_context reqctx,
351 Kx509Response *r)
353 krb5_error_code ret;
354 krb5_data data;
355 size_t size;
357 reqctx->reply->data = NULL;
358 reqctx->reply->length = 0;
359 ASN1_MALLOC_ENCODE(Kx509Response, data.data, data.length, r, &size, ret);
360 if (ret)
361 return ret;
362 if (size != data.length)
363 krb5_abortx(context, "ASN1 internal error");
365 ret = krb5_data_alloc(reqctx->reply, data.length + sizeof(version_2_0));
366 if (ret == 0) {
367 memcpy(reqctx->reply->data, version_2_0, sizeof(version_2_0));
368 memcpy(((unsigned char *)reqctx->reply->data) + sizeof(version_2_0),
369 data.data, data.length);
371 free(data.data);
372 return ret;
375 /* Make an error response, and log the error message as well */
376 static krb5_error_code
377 mk_error_response(krb5_context context,
378 kx509_req_context reqctx,
379 int level,
380 int32_t code,
381 const char *fmt,
382 ...)
384 krb5_error_code ret = code;
385 krb5_error_code ret2;
386 Kx509Response rep;
387 const char *msg;
388 char *freeme0 = NULL;
389 char *freeme1 = NULL;
390 va_list ap;
392 if (code != 0) {
393 /* Log errors where _kdc_audit_trail() is not enough */
394 if (code == ENOMEM)
395 level = 0;
396 if (level < 3) {
397 va_start(ap, fmt);
398 kdc_vlog(context, reqctx->config, level, fmt, ap);
399 va_end(ap);
402 va_start(ap, fmt);
403 kdc_audit_vaddreason((kdc_request_t)reqctx, fmt, ap);
404 va_end(ap);
407 if (!reqctx->config->enable_kx509)
408 code = KRB5KDC_ERR_POLICY;
410 /* Make sure we only send RFC4120 and friends wire protocol error codes */
411 if (code) {
412 if (code == KX509_ERR_NONE) {
413 code = 0;
414 } else if (code > KX509_ERR_NONE && code <= KX509_ERR_SRV_OVERLOADED) {
415 code -= KX509_ERR_NONE;
416 } else {
417 if (code < KRB5KDC_ERR_NONE || code >= KRB5_ERR_RCSID)
418 code = KRB5KRB_ERR_GENERIC;
419 code -= KRB5KDC_ERR_NONE;
420 code += kx509_krb5_error_base;
424 va_start(ap, fmt);
425 if (vasprintf(&freeme0, fmt, ap) == -1 || freeme0 == NULL)
426 msg = "Could not format error message (out of memory)";
427 else
428 msg = freeme0;
429 va_end(ap);
431 if (!reqctx->config->enable_kx509 &&
432 asprintf(&freeme1, "kx509 service is disabled (%s)", msg) > -1 &&
433 freeme1 != NULL) {
434 msg = freeme1;
437 rep.hash = NULL;
438 rep.certificate = NULL;
439 rep.error_code = code;
440 if (ALLOC(rep.e_text))
441 *rep.e_text = (void *)(uintptr_t)msg;
443 if (reqctx->key) {
444 if (ALLOC(rep.hash) != NULL &&
445 calculate_reply_hash(context, reqctx->key, &rep)) {
446 free(rep.hash);
447 rep.hash = NULL;
451 if ((ret2 = encode_reply(context, reqctx, &rep)))
452 ret = ret2;
453 if (rep.hash)
454 krb5_data_free(rep.hash);
455 free(rep.e_text);
456 free(rep.hash);
457 free(freeme0);
458 free(freeme1);
459 return ret;
462 /* Wrap a bare public (RSA) key with a CSR (not signed it, since we can't) */
463 static krb5_error_code
464 make_csr(krb5_context context, kx509_req_context reqctx, krb5_data *key)
466 krb5_error_code ret;
467 SubjectPublicKeyInfo spki;
468 heim_any any;
470 ret = hx509_request_init(context->hx509ctx, &reqctx->csr);
471 if (ret)
472 return ret;
474 memset(&spki, 0, sizeof(spki));
475 spki.subjectPublicKey.data = key->data;
476 spki.subjectPublicKey.length = key->length * 8;
478 ret = der_copy_oid(&asn1_oid_id_pkcs1_rsaEncryption,
479 &spki.algorithm.algorithm);
481 any.data = "\x05\x00";
482 any.length = 2;
483 spki.algorithm.parameters = &any;
485 if (ret == 0)
486 ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
487 reqctx->csr, &spki);
488 der_free_oid(&spki.algorithm.algorithm);
489 if (ret)
490 hx509_request_free(&reqctx->csr);
493 * TODO: Move a lot of the templating stuff here so we can let clients
494 * leave out extensions they don't want.
496 return ret;
499 /* Update a CSR with desired Certificate Extensions */
500 static krb5_error_code
501 update_csr(krb5_context context, kx509_req_context reqctx, Extensions *exts)
503 krb5_error_code ret = 0;
504 size_t i, k;
506 if (exts == NULL)
507 return 0;
509 for (i = 0; ret == 0 && i < exts->len; i++) {
510 Extension *e = &exts->val[i];
512 if (der_heim_oid_cmp(&e->extnID, &asn1_oid_id_x509_ce_keyUsage) == 0) {
513 KeyUsage ku;
515 ret = decode_KeyUsage(e->extnValue.data, e->extnValue.length, &ku,
516 NULL);
517 if (ret)
518 return ret;
519 ret = hx509_request_set_ku(context->hx509ctx, reqctx->csr, ku);
520 } else if (der_heim_oid_cmp(&e->extnID,
521 &asn1_oid_id_x509_ce_extKeyUsage) == 0) {
522 ExtKeyUsage eku;
524 ret = decode_ExtKeyUsage(e->extnValue.data, e->extnValue.length,
525 &eku, NULL);
526 for (k = 0; ret == 0 && k < eku.len; k++) {
527 ret = hx509_request_add_eku(context->hx509ctx, reqctx->csr,
528 &eku.val[k]);
530 free_ExtKeyUsage(&eku);
531 } else if (der_heim_oid_cmp(&e->extnID,
532 &asn1_oid_id_x509_ce_subjectAltName) == 0) {
533 GeneralNames san;
535 ret = decode_GeneralNames(e->extnValue.data, e->extnValue.length,
536 &san, NULL);
537 for (k = 0; ret == 0 && k < san.len; k++) {
538 ret = hx509_request_add_GeneralName(context->hx509ctx,
539 reqctx->csr, &san.val[k]);
541 free_GeneralNames(&san);
544 if (ret) {
545 const char *emsg = krb5_get_error_message(context, ret);
546 kdc_log(context, reqctx->config, 1,
547 "Error handling requested extensions: %s", emsg);
548 kdc_audit_addreason((kdc_request_t)reqctx,
549 "Error handling requested extensions: %s",
550 emsg);
551 krb5_free_error_message(context, emsg);
553 return ret;
558 * Parse the `pk_key' from the request as a CSR or raw public key, and if the
559 * latter, wrap it in a non-signed CSR.
561 static krb5_error_code
562 get_csr(krb5_context context, kx509_req_context reqctx)
564 krb5_error_code ret;
565 RSAPublicKey rsapkey;
566 heim_octet_string pk_key = reqctx->req.pk_key;
567 size_t size;
569 ret = decode_Kx509CSRPlus(pk_key.data, pk_key.length, &reqctx->csr_plus,
570 &size);
571 if (ret == 0) {
572 reqctx->have_csr = 1;
573 reqctx->send_chain = 1;
575 /* Parse CSR */
576 ret = hx509_request_parse_der(context->hx509ctx, &reqctx->csr_plus.csr,
577 &reqctx->csr);
579 * Handle any additional Certificate Extensions requested out of band
580 * of the CSR.
582 if (ret == 0)
583 return update_csr(context, reqctx, reqctx->csr_plus.exts);
584 kdc_audit_addreason((kdc_request_t)reqctx, "Invalid CSR");
585 return ret;
587 reqctx->send_chain = 0;
588 reqctx->have_csr = 0;
590 /* Check if proof of possession is required by configuration */
591 if (!get_bool_param(context, FALSE, reqctx->realm, "require_csr")) {
592 kdc_audit_addreason((kdc_request_t)reqctx,
593 "CSRs required but client did not send one");
594 krb5_set_error_message(context, KX509_STATUS_CLIENT_USE_CSR,
595 "CSRs required but kx509 client did not send "
596 "one");
597 return KX509_STATUS_CLIENT_USE_CSR;
600 /* Attempt to decode pk_key as RSAPublicKey */
601 ret = decode_RSAPublicKey(reqctx->req.pk_key.data,
602 reqctx->req.pk_key.length,
603 &rsapkey, &size);
604 free_RSAPublicKey(&rsapkey);
605 if (ret == 0 && size == reqctx->req.pk_key.length)
606 return make_csr(context, reqctx, &pk_key); /* Make pretend CSR */
608 /* Not an RSAPublicKey or garbage follows it */
609 if (ret == 0) {
610 ret = KRB5KDC_ERR_NULL_KEY;
611 kdc_audit_addreason((kdc_request_t)reqctx,
612 "Request has garbage after key");
613 krb5_set_error_message(context, ret, "Request has garbage after key");
614 return ret;
617 kdc_audit_addreason((kdc_request_t)reqctx,
618 "Could not decode CSR or RSA subject public key");
619 krb5_set_error_message(context, ret,
620 "Could not decode CSR or RSA subject public key");
621 return ret;
625 * Host-based principal _clients_ might ask for a cert for their host -- but
626 * which services are permitted to do that? This function answers that
627 * question.
629 static int
630 check_authz_svc_ok(krb5_context context, const char *svc)
632 const char *def[] = { "host", "HTTP", 0 };
633 const char * const *svcs;
634 char **strs;
636 strs = krb5_config_get_strings(context, NULL, "kdc",
637 "kx509_permitted_hostbased_services", NULL);
638 for (svcs = strs ? (const char * const *)strs : def; svcs[0]; svcs++) {
639 if (strcmp(svcs[0], svc) == 0) {
640 krb5_config_free_strings(strs);
641 return 1;
644 krb5_config_free_strings(strs);
645 return 0;
648 static krb5_error_code
649 check_authz(krb5_context context,
650 kx509_req_context reqctx,
651 krb5_principal cprincipal)
653 krb5_error_code ret;
654 const char *comp0 = krb5_principal_get_comp_string(context, cprincipal, 0);
655 const char *comp1 = krb5_principal_get_comp_string(context, cprincipal, 1);
656 unsigned int ncomp = krb5_principal_get_num_comp(context, cprincipal);
657 hx509_san_type san_type;
658 KeyUsage ku, ku_allowed;
659 size_t i;
660 const heim_oid *eku_whitelist[] = {
661 &asn1_oid_id_pkix_kp_serverAuth,
662 &asn1_oid_id_pkix_kp_clientAuth,
663 &asn1_oid_id_pkekuoid,
664 &asn1_oid_id_pkinit_ms_eku
666 char *cprinc = NULL;
667 char *s = NULL;
670 * In the no-CSR case we'll derive cert contents from client name and its
671 * HDB entry -- authorization is implied.
673 if (!reqctx->have_csr)
674 return 0;
675 ret = kdc_authorize_csr(context, reqctx->config->app, reqctx->csr,
676 cprincipal);
677 if (ret == 0) {
678 kdc_audit_setkv_bool((kdc_request_t)reqctx, "authorized", TRUE);
680 ret = hx509_request_get_san(reqctx->csr, 0, &san_type, &s);
681 if (ret == 0) {
682 const char *san_type_s;
684 /* This should be an hx509 function... */
685 switch (san_type) {
686 case HX509_SAN_TYPE_EMAIL: san_type_s = "rfc822Name"; break;
687 case HX509_SAN_TYPE_DNSNAME: san_type_s = "dNSName"; break;
688 case HX509_SAN_TYPE_DN: san_type_s = "DN"; break;
689 case HX509_SAN_TYPE_REGISTERED_ID: san_type_s = "registeredID"; break;
690 case HX509_SAN_TYPE_XMPP: san_type_s = "xMPPName"; break;
691 case HX509_SAN_TYPE_PKINIT: san_type_s = "krb5PrincipalName"; break;
692 case HX509_SAN_TYPE_MS_UPN: san_type_s = "ms-UPN"; break;
693 default: san_type_s = "unknown"; break;
695 kdc_audit_addkv((kdc_request_t)reqctx, 0, "san0_type", "%s",
696 san_type_s);
697 kdc_audit_addkv((kdc_request_t)reqctx, 0, "san0", "%s", s);
699 frees(&s);
700 ret = hx509_request_get_eku(reqctx->csr, 0, &s);
701 if (ret == 0)
702 kdc_audit_addkv((kdc_request_t)reqctx, 0, "eku0", "%s", s);
703 free(s);
704 return 0;
706 if (ret != KRB5_PLUGIN_NO_HANDLE) {
707 kdc_audit_addreason((kdc_request_t)reqctx,
708 "Requested extensions rejected by plugin");
709 return ret;
712 /* Default authz */
713 if ((ret = krb5_unparse_name(context, cprincipal, &cprinc)))
714 return ret;
716 for (i = 0; ret == 0; i++) {
718 frees(&s);
719 ret = hx509_request_get_san(reqctx->csr, i, &san_type, &s);
720 if (ret)
721 break;
722 switch (san_type) {
723 case HX509_SAN_TYPE_DNSNAME:
724 if (ncomp != 2 || strcasecmp(comp1, s) != 0 ||
725 strchr(s, '.') == NULL ||
726 !check_authz_svc_ok(context, comp0)) {
727 kdc_audit_addreason((kdc_request_t)reqctx,
728 "Requested extensions rejected by "
729 "default policy (dNSName SAN "
730 "does not match client)");
731 goto eacces;
733 break;
734 case HX509_SAN_TYPE_PKINIT:
735 if (strcmp(cprinc, s) != 0) {
736 kdc_audit_addreason((kdc_request_t)reqctx,
737 "Requested extensions rejected by "
738 "default policy (PKINIT SAN "
739 "does not match client)");
740 goto eacces;
742 break;
743 default:
744 kdc_audit_addreason((kdc_request_t)reqctx,
745 "Requested extensions rejected by "
746 "default policy (non-default SAN "
747 "requested)");
748 goto eacces;
751 frees(&s);
752 if (ret == HX509_NO_ITEM)
753 ret = 0;
754 if (ret)
755 goto out;
757 for (i = 0; ret == 0; i++) {
758 heim_oid oid;
759 size_t k;
761 frees(&s);
762 ret = hx509_request_get_eku(reqctx->csr, i, &s);
763 if (ret)
764 break;
766 if ((ret = der_parse_heim_oid(s, ".", &oid))) {
767 goto out;
769 for (k = 0; k < sizeof(eku_whitelist)/sizeof(eku_whitelist[0]); k++) {
770 if (der_heim_oid_cmp(eku_whitelist[k], &oid) == 0)
771 break;
773 der_free_oid(&oid);
774 if (k == sizeof(eku_whitelist)/sizeof(eku_whitelist[0])) {
775 kdc_audit_addreason((kdc_request_t)reqctx,
776 "Requested EKU rejected by default policy");
777 goto eacces;
780 if (ret == HX509_NO_ITEM)
781 ret = 0;
782 if (ret)
783 goto out;
785 memset(&ku_allowed, 0, sizeof(ku_allowed));
786 ku_allowed.digitalSignature = 1;
787 ku_allowed.nonRepudiation = 1;
788 ret = hx509_request_get_ku(context->hx509ctx, reqctx->csr, &ku);
789 if (ret)
790 goto out;
791 if (KeyUsage2int(ku) != (KeyUsage2int(ku) & KeyUsage2int(ku_allowed)))
792 goto eacces;
794 kdc_audit_setkv_bool((kdc_request_t)reqctx, "authorized", TRUE);
795 free(cprinc);
796 return 0;
798 eacces:
799 ret = EACCES;
800 goto out2;
802 out:
803 /* XXX Display error code */
804 kdc_audit_addreason((kdc_request_t)reqctx,
805 "Error handling requested extensions");
806 out2:
807 free(cprinc);
808 free(s);
809 return ret;
812 static int
813 chain_add1_func(hx509_context context, void *d, hx509_cert c)
815 heim_octet_string os;
816 Certificates *cs = d;
817 Certificate c2;
818 int ret;
820 ret = hx509_cert_binary(context, c, &os);
821 if (ret)
822 return ret;
823 ret = decode_Certificate(os.data, os.length, &c2, NULL);
824 der_free_octet_string(&os);
825 if (ret)
826 return ret;
827 ret = add_Certificates(cs, &c2);
828 free_Certificate(&c2);
829 return ret;
832 static krb5_error_code
833 encode_cert_and_chain(hx509_context hx509ctx,
834 hx509_certs certs,
835 krb5_data *out)
837 krb5_error_code ret;
838 Certificates cs;
839 size_t len;
841 cs.len = 0;
842 cs.val = 0;
844 ret = hx509_certs_iter_f(hx509ctx, certs, chain_add1_func, &cs);
845 if (ret == 0)
846 ASN1_MALLOC_ENCODE(Certificates, out->data, out->length,
847 &cs, &len, ret);
848 free_Certificates(&cs);
849 return ret;
853 * Process a request, produce a reply.
856 krb5_error_code
857 _kdc_do_kx509(kx509_req_context r)
859 krb5_error_code ret = 0;
860 krb5_ticket *ticket = NULL;
861 krb5_flags ap_req_options;
862 krb5_principal cprincipal = NULL;
863 krb5_principal sprincipal = NULL;
864 krb5_keytab id = NULL;
865 Kx509Response rep;
866 hx509_certs certs = NULL;
867 int is_probe = 0;
869 r->csr_plus.csr.data = NULL;
870 r->csr_plus.exts = NULL;
871 r->sname = NULL;
872 r->cname = NULL;
873 r->realm = NULL;
874 r->key = NULL;
875 r->csr = NULL;
876 r->ac = NULL;
879 * In order to support authenticated error messages we defer checking
880 * whether the kx509 service is enabled until after accepting the AP-REQ.
883 krb5_data_zero(r->reply);
884 memset(&rep, 0, sizeof(rep));
886 if (r->req.authenticator.length == 0) {
888 * Unauthenticated kx509 service availability probe.
890 * mk_error_response() will check whether the service is enabled and
891 * possibly change the error code and message.
893 is_probe = 1;
894 kdc_audit_addkv((kdc_request_t)r, 0, "probe", "unauthenticated");
895 ret = mk_error_response(r->context, r, 4, 0,
896 "kx509 service is available");
897 goto out;
900 /* Authenticate the request (consume the AP-REQ) */
901 ret = krb5_kt_resolve(r->context, "HDBGET:", &id);
902 if (ret) {
903 const char *msg = krb5_get_error_message(r->context, ret);
904 ret = mk_error_response(r->context, r, 1,
905 KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN,
906 "Can't open HDB/keytab for kx509: %s",
907 msg);
908 krb5_free_error_message(r->context, msg);
909 goto out;
912 ret = krb5_rd_req(r->context,
913 &r->ac,
914 &r->req.authenticator,
915 NULL,
917 &ap_req_options,
918 &ticket);
919 if (ret == 0)
920 ret = krb5_auth_con_getkey(r->context, r->ac, &r->key);
921 if (ret == 0 && r->key == NULL)
922 ret = KRB5KDC_ERR_NULL_KEY;
924 * Provided we got the session key, errors past this point will be
925 * authenticated.
927 if (ret == 0)
928 ret = krb5_ticket_get_client(r->context, ticket, &cprincipal);
930 /* Optional: check if Ticket is INITIAL */
931 if (ret == 0 &&
932 !ticket->ticket.flags.initial &&
933 !get_bool_param(r->context, TRUE,
934 krb5_principal_get_realm(r->context, cprincipal),
935 "require_initial_kca_tickets")) {
936 ret = mk_error_response(r->context, r, 4, KRB5KDC_ERR_POLICY,
937 "Client used non-INITIAL tickets, but kx509 "
938 "service is configured to require INITIAL "
939 "tickets");
940 goto out;
943 if (ret == 0)
944 ret = krb5_unparse_name(r->context, cprincipal, &r->cname);
946 /* Check that the service name is a valid kx509 service name */
947 if (ret == 0)
948 ret = krb5_ticket_get_server(r->context, ticket, &sprincipal);
949 if (ret == 0)
950 r->realm = krb5_principal_get_realm(r->context, sprincipal);
951 if (ret == 0)
952 ret = krb5_unparse_name(r->context, sprincipal, &r->sname);
953 if (ret == 0)
954 ret = kdc_kx509_verify_service_principal(r->context, r, sprincipal);
955 if (ret) {
956 ret = mk_error_response(r->context, r, 4, ret,
957 "kx509 client used incorrect service name");
958 goto out;
961 /* Authenticate the rest of the request */
962 ret = verify_req_hash(r->context, &r->req, r->key);
963 if (ret) {
964 ret = mk_error_response(r->context, r, 4, ret,
965 "Incorrect request HMAC on kx509 request");
966 goto out;
969 if (r->req.pk_key.length == 0) {
971 * The request is an authenticated kx509 service availability probe.
973 * mk_error_response() will check whether the service is enabled and
974 * possibly change the error code and message.
976 is_probe = 1;
977 kdc_audit_addkv((kdc_request_t)r, 0, "probe", "authenticated");
978 ret = mk_error_response(r->context, r, 4, 0,
979 "kx509 authenticated probe request");
980 goto out;
983 /* Extract and parse CSR or a DER-encoded RSA public key */
984 ret = get_csr(r->context, r);
985 if (ret) {
986 const char *msg = krb5_get_error_message(r->context, ret);
987 ret = mk_error_response(r->context, r, 3, ret,
988 "Failed to parse CSR: %s", msg);
989 krb5_free_error_message(r->context, msg);
990 goto out;
993 /* Authorize the request */
994 ret = check_authz(r->context, r, cprincipal);
995 if (ret) {
996 const char *msg = krb5_get_error_message(r->context, ret);
997 ret = mk_error_response(r->context, r, 3, ret,
998 "Rejected by policy: %s", msg);
999 krb5_free_error_message(r->context, msg);
1000 goto out;
1003 /* Issue the certificate */
1004 ALLOC(rep.hash);
1005 ALLOC(rep.certificate);
1006 if (rep.certificate == NULL || rep.hash == NULL) {
1007 ret = mk_error_response(r->context, r, 0, ENOMEM,
1008 "Could allocate memory for response");
1009 goto out;
1012 krb5_data_zero(rep.hash);
1013 krb5_data_zero(rep.certificate);
1014 krb5_ticket_get_times(r->context, ticket, &r->ticket_times);
1015 ret = kdc_issue_certificate(r->context, r->config->app, r->logf, r->csr,
1016 cprincipal, &r->ticket_times, 0 /*req_life*/,
1017 r->send_chain, &certs);
1018 if (ret) {
1019 int level = 1;
1020 const char *msg = krb5_get_error_message(r->context, ret);
1022 if (ret == KRB5KDC_ERR_POLICY)
1023 level = 4; /* _kdc_audit_trail() logs at level 3 */
1024 ret = mk_error_response(r->context, r, level, ret,
1025 "Certificate isuance failed: %s", msg);
1026 krb5_free_error_message(r->context, msg);
1027 goto out;
1030 ret = encode_cert_and_chain(r->context->hx509ctx, certs, rep.certificate);
1031 if (ret) {
1032 const char *msg = krb5_get_error_message(r->context, ret);
1033 ret = mk_error_response(r->context, r, 1, ret,
1034 "Could not encode certificate and chain: %s",
1035 msg);
1036 krb5_free_error_message(r->context, msg);
1037 goto out;
1040 /* Authenticate the response */
1041 ret = calculate_reply_hash(r->context, r->key, &rep);
1042 if (ret) {
1043 ret = mk_error_response(r->context, r, 1, ret,
1044 "Failed to compute response HMAC");
1045 goto out;
1048 /* Encode and output reply */
1049 ret = encode_reply(r->context, r, &rep);
1050 if (ret)
1051 /* Can't send an error message either in this case, surely */
1052 kdc_audit_addreason((kdc_request_t)r, "Could not encode response");
1054 out:
1055 hx509_certs_free(&certs);
1056 if (ret == 0 && !is_probe)
1057 kdc_audit_setkv_bool((kdc_request_t)r, "cert_issued", TRUE);
1058 else
1059 kdc_audit_setkv_bool((kdc_request_t)r, "cert_issued", FALSE);
1060 if (r->ac)
1061 krb5_auth_con_free(r->context, r->ac);
1062 if (ticket)
1063 krb5_free_ticket(r->context, ticket);
1064 if (id)
1065 krb5_kt_close(r->context, id);
1066 if (sprincipal)
1067 krb5_free_principal(r->context, sprincipal);
1068 if (cprincipal)
1069 krb5_free_principal(r->context, cprincipal);
1070 if (r->key)
1071 krb5_free_keyblock (r->context, r->key);
1072 hx509_request_free(&r->csr);
1073 free_Kx509CSRPlus(&r->csr_plus);
1074 free_Kx509Response(&rep);
1075 free_Kx509Request(&r->req);
1077 return ret;
1080 #endif /* KX509 */