Allow KDC to always return the salt in the PA-ETYPE-INFO[2]
[heimdal.git] / kdc / kx509.c
blobbc3ca9deca6bed897e23ac6c9068c23fdc4c7266
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 HMAC_Init_ex(&ctx,
161 key->keyvalue.data, key->keyvalue.length,
162 EVP_sha1(), NULL);
163 if (sizeof(digest) != HMAC_size(&ctx))
164 krb5_abortx(context, "runtime error, hmac buffer wrong size in kx509");
165 HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
166 if (req->pk_key.length)
167 HMAC_Update(&ctx, req->pk_key.data, req->pk_key.length);
168 else
169 HMAC_Update(&ctx, req->authenticator.data, req->authenticator.length);
170 HMAC_Final(&ctx, digest, 0);
171 HMAC_CTX_cleanup(&ctx);
173 if (ct_memcmp(req->pk_hash.data, digest, sizeof(digest)) != 0) {
174 krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
175 "kx509 request MAC mismatch");
176 return KRB5KDC_ERR_PREAUTH_FAILED;
178 return 0;
182 * Set the HMAC in the response.
184 static krb5_error_code
185 calculate_reply_hash(krb5_context context,
186 krb5_keyblock *key,
187 Kx509Response *rep)
189 krb5_error_code ret;
190 HMAC_CTX ctx;
192 HMAC_CTX_init(&ctx);
194 HMAC_Init_ex(&ctx, key->keyvalue.data, key->keyvalue.length,
195 EVP_sha1(), NULL);
196 ret = krb5_data_alloc(rep->hash, HMAC_size(&ctx));
197 if (ret) {
198 HMAC_CTX_cleanup(&ctx);
199 return krb5_enomem(context);
202 HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
204 int32_t t = rep->error_code;
205 unsigned char encint[sizeof(t) + 1];
206 size_t k;
209 * RFC6717 says this about how the error-code is included in the HMAC:
211 * o DER representation of the error-code exclusive of the tag and
212 * length, if it is present.
214 * So we use der_put_integer(), which encodes from the right.
216 * RFC6717 does not constrain the error-code's range. We assume it to
217 * be a 32-bit, signed integer, for which we'll need no more than 5
218 * bytes.
220 ret = der_put_integer(&encint[sizeof(encint) - 1],
221 sizeof(encint), &t, &k);
222 if (ret == 0)
223 HMAC_Update(&ctx, &encint[sizeof(encint)] - k, k);
225 if (rep->certificate)
226 HMAC_Update(&ctx, rep->certificate->data, rep->certificate->length);
227 if (rep->e_text)
228 HMAC_Update(&ctx, (unsigned char *)*rep->e_text, strlen(*rep->e_text));
230 HMAC_Final(&ctx, rep->hash->data, 0);
231 HMAC_CTX_cleanup(&ctx);
233 return 0;
236 static void
237 frees(char **s)
239 free(*s);
240 *s = NULL;
243 /* Check that a krbtgt's second component is a local realm */
244 static krb5_error_code
245 is_local_realm(krb5_context context,
246 kx509_req_context reqctx,
247 const char *realm)
249 krb5_error_code ret;
250 krb5_principal tgs;
251 hdb_entry_ex *ent = NULL;
253 ret = krb5_make_principal(context, &tgs, realm, KRB5_TGS_NAME, realm,
254 NULL);
255 if (ret)
256 return ret;
257 if (ret == 0)
258 ret = _kdc_db_fetch(context, reqctx->config, tgs, HDB_F_GET_KRBTGT,
259 NULL, NULL, &ent);
260 if (ent)
261 _kdc_free_ent(context, ent);
262 krb5_free_principal(context, tgs);
263 if (ret == HDB_ERR_NOENTRY || ret == HDB_ERR_NOT_FOUND_HERE)
264 return KRB5KRB_AP_ERR_NOT_US;
265 return ret;
269 * Since we're using the HDB as a keytab we have to check that the client used
270 * an acceptable name for the kx509 service.
272 * We accept two names: kca_service/hostname and krbtgt/REALM.
274 * We allow cross-realm requests.
276 * Maybe x-realm support should be configurable. Requiring INITIAL tickets
277 * does NOT preclude x-realm support! (Cross-realm TGTs can be INITIAL.)
279 * Support for specific client realms is configurable by configuring issuer
280 * credentials and TBS templates on a per-realm basis and configuring no
281 * default. But maybe we should have an explicit configuration parameter
282 * to enable support for clients from different realms than the service.
284 static krb5_error_code
285 kdc_kx509_verify_service_principal(krb5_context context,
286 kx509_req_context reqctx,
287 krb5_principal sprincipal)
289 krb5_error_code ret = 0;
290 krb5_principal principal = NULL;
291 char *expected = NULL;
292 char localhost[MAXHOSTNAMELEN];
294 if (krb5_principal_get_num_comp(context, sprincipal) != 2)
295 goto err;
297 /* Check if sprincipal is a krbtgt/REALM name */
298 if (strcmp(krb5_principal_get_comp_string(context, sprincipal, 0),
299 KRB5_TGS_NAME) == 0) {
300 const char *r = krb5_principal_get_comp_string(context, sprincipal, 1);
301 if ((ret = is_local_realm(context, reqctx, r)))
302 _kdc_audit_addreason((kdc_request_t)reqctx,
303 "Client used wrong krbtgt for kx509");
304 goto out;
307 /* Must be hostbased kca_service name then */
308 ret = gethostname(localhost, sizeof(localhost) - 1);
309 if (ret != 0) {
310 ret = errno;
311 kdc_log(context, reqctx->config, 0, "Failed to get local hostname");
312 _kdc_audit_addreason((kdc_request_t)reqctx,
313 "Failed to get local hostname");
314 return ret;
316 localhost[sizeof(localhost) - 1] = '\0';
318 ret = krb5_make_principal(context, &principal, "", "kca_service",
319 localhost, NULL);
320 if (ret)
321 goto out;
323 if (krb5_principal_compare_any_realm(context, sprincipal, principal))
324 goto out; /* found a match */
326 err:
327 ret = krb5_unparse_name(context, sprincipal, &expected);
328 if (ret)
329 goto out;
331 ret = KRB5KDC_ERR_SERVER_NOMATCH;
332 _kdc_audit_addreason((kdc_request_t)reqctx, "Client used wrong kx509 "
333 "service principal (expected %s)", expected);
335 out:
336 krb5_xfree(expected);
337 krb5_free_principal(context, principal);
339 return ret;
342 static krb5_error_code
343 encode_reply(krb5_context context,
344 kx509_req_context reqctx,
345 Kx509Response *r)
347 krb5_error_code ret;
348 krb5_data data;
349 size_t size;
351 reqctx->reply->data = NULL;
352 reqctx->reply->length = 0;
353 ASN1_MALLOC_ENCODE(Kx509Response, data.data, data.length, r, &size, ret);
354 if (ret)
355 return ret;
356 if (size != data.length)
357 krb5_abortx(context, "ASN1 internal error");
359 ret = krb5_data_alloc(reqctx->reply, data.length + sizeof(version_2_0));
360 if (ret == 0) {
361 memcpy(reqctx->reply->data, version_2_0, sizeof(version_2_0));
362 memcpy(((unsigned char *)reqctx->reply->data) + sizeof(version_2_0),
363 data.data, data.length);
365 free(data.data);
366 return ret;
369 /* Make an error response, and log the error message as well */
370 static krb5_error_code
371 mk_error_response(krb5_context context,
372 kx509_req_context reqctx,
373 int level,
374 int32_t code,
375 const char *fmt,
376 ...)
378 krb5_error_code ret = code;
379 krb5_error_code ret2;
380 Kx509Response rep;
381 const char *msg;
382 char *freeme0 = NULL;
383 char *freeme1 = NULL;
384 va_list ap;
386 if (code != 0) {
387 /* Log errors where _kdc_audit_trail() is not enough */
388 if (code == ENOMEM)
389 level = 0;
390 if (level < 3) {
391 va_start(ap, fmt);
392 kdc_vlog(context, reqctx->config, level, fmt, ap);
393 va_end(ap);
396 va_start(ap, fmt);
397 _kdc_audit_vaddreason((kdc_request_t)reqctx, fmt, ap);
398 va_end(ap);
401 if (!reqctx->config->enable_kx509)
402 code = KRB5KDC_ERR_POLICY;
404 /* Make sure we only send RFC4120 and friends wire protocol error codes */
405 if (code) {
406 if (code == KX509_ERR_NONE) {
407 code = 0;
408 } else if (code > KX509_ERR_NONE && code <= KX509_ERR_SRV_OVERLOADED) {
409 code -= KX509_ERR_NONE;
410 } else {
411 if (code < KRB5KDC_ERR_NONE || code >= KRB5_ERR_RCSID)
412 code = KRB5KRB_ERR_GENERIC;
413 code -= KRB5KDC_ERR_NONE;
414 code += kx509_krb5_error_base;
418 va_start(ap, fmt);
419 if (vasprintf(&freeme0, fmt, ap) == -1 || freeme0 == NULL)
420 msg = "Could not format error message (out of memory)";
421 else
422 msg = freeme0;
423 va_end(ap);
425 if (!reqctx->config->enable_kx509 &&
426 asprintf(&freeme1, "kx509 service is disabled (%s)", msg) > -1 &&
427 freeme1 != NULL) {
428 msg = freeme1;
431 rep.hash = NULL;
432 rep.certificate = NULL;
433 rep.error_code = code;
434 if (ALLOC(rep.e_text))
435 *rep.e_text = (void *)(uintptr_t)msg;
437 if (reqctx->key) {
438 if (ALLOC(rep.hash) != NULL &&
439 calculate_reply_hash(context, reqctx->key, &rep)) {
440 free(rep.hash);
441 rep.hash = NULL;
445 if ((ret2 = encode_reply(context, reqctx, &rep)))
446 ret = ret2;
447 if (rep.hash)
448 krb5_data_free(rep.hash);
449 free(rep.e_text);
450 free(rep.hash);
451 free(freeme0);
452 free(freeme1);
453 return ret;
456 /* Wrap a bare public (RSA) key with a CSR (not signed it, since we can't) */
457 static krb5_error_code
458 make_csr(krb5_context context, kx509_req_context reqctx, krb5_data *key)
460 krb5_error_code ret;
461 SubjectPublicKeyInfo spki;
462 heim_any any;
464 ret = hx509_request_init(context->hx509ctx, &reqctx->csr);
465 if (ret)
466 return ret;
468 memset(&spki, 0, sizeof(spki));
469 spki.subjectPublicKey.data = key->data;
470 spki.subjectPublicKey.length = key->length * 8;
472 ret = der_copy_oid(&asn1_oid_id_pkcs1_rsaEncryption,
473 &spki.algorithm.algorithm);
475 any.data = "\x05\x00";
476 any.length = 2;
477 spki.algorithm.parameters = &any;
479 if (ret == 0)
480 ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
481 reqctx->csr, &spki);
482 der_free_oid(&spki.algorithm.algorithm);
483 if (ret)
484 hx509_request_free(&reqctx->csr);
487 * TODO: Move a lot of the templating stuff here so we can let clients
488 * leave out extensions they don't want.
490 return ret;
493 /* Update a CSR with desired Certificate Extensions */
494 static krb5_error_code
495 update_csr(krb5_context context, kx509_req_context reqctx, Extensions *exts)
497 krb5_error_code ret = 0;
498 size_t i, k;
500 if (exts == NULL)
501 return 0;
503 for (i = 0; ret == 0 && i < exts->len; i++) {
504 Extension *e = &exts->val[i];
506 if (der_heim_oid_cmp(&e->extnID, &asn1_oid_id_x509_ce_keyUsage) == 0) {
507 KeyUsage ku;
509 ret = decode_KeyUsage(e->extnValue.data, e->extnValue.length, &ku,
510 NULL);
511 if (ret)
512 return ret;
513 ret = hx509_request_set_ku(context->hx509ctx, reqctx->csr, ku);
514 } else if (der_heim_oid_cmp(&e->extnID,
515 &asn1_oid_id_x509_ce_extKeyUsage) == 0) {
516 ExtKeyUsage eku;
518 ret = decode_ExtKeyUsage(e->extnValue.data, e->extnValue.length,
519 &eku, NULL);
520 for (k = 0; ret == 0 && k < eku.len; k++) {
521 ret = hx509_request_add_eku(context->hx509ctx, reqctx->csr,
522 &eku.val[k]);
524 free_ExtKeyUsage(&eku);
525 } else if (der_heim_oid_cmp(&e->extnID,
526 &asn1_oid_id_x509_ce_subjectAltName) == 0) {
527 GeneralNames san;
529 ret = decode_GeneralNames(e->extnValue.data, e->extnValue.length,
530 &san, NULL);
531 for (k = 0; ret == 0 && k < san.len; k++) {
532 ret = hx509_request_add_GeneralName(context->hx509ctx,
533 reqctx->csr, &san.val[k]);
535 free_GeneralNames(&san);
538 if (ret) {
539 kdc_log(context, reqctx->config, 1,
540 "Error handling requested extensions: %s",
541 krb5_get_error_message(context, ret));
542 _kdc_audit_addreason((kdc_request_t)reqctx,
543 "Error handling requested extensions: %s",
544 krb5_get_error_message(context, ret));
546 return ret;
551 * Parse the `pk_key' from the request as a CSR or raw public key, and if the
552 * latter, wrap it in a non-signed CSR.
554 static krb5_error_code
555 get_csr(krb5_context context, kx509_req_context reqctx)
557 krb5_error_code ret;
558 RSAPublicKey rsapkey;
559 heim_octet_string pk_key = reqctx->req.pk_key;
560 size_t size;
562 ret = decode_Kx509CSRPlus(pk_key.data, pk_key.length, &reqctx->csr_plus,
563 &size);
564 if (ret == 0) {
565 reqctx->have_csr = 1;
566 reqctx->send_chain = 1;
568 /* Parse CSR */
569 ret = hx509_request_parse_der(context->hx509ctx, &reqctx->csr_plus.csr,
570 &reqctx->csr);
572 * Handle any additional Certificate Extensions requested out of band
573 * of the CSR.
575 if (ret == 0)
576 return update_csr(context, reqctx, reqctx->csr_plus.exts);
577 _kdc_audit_addreason((kdc_request_t)reqctx, "Invalid CSR");
578 return ret;
580 reqctx->send_chain = 0;
581 reqctx->have_csr = 0;
583 /* Check if proof of possession is required by configuration */
584 if (!get_bool_param(context, FALSE, reqctx->realm, "require_csr")) {
585 _kdc_audit_addreason((kdc_request_t)reqctx,
586 "CSRs required but client did not send one");
587 krb5_set_error_message(context, KX509_STATUS_CLIENT_USE_CSR,
588 "CSRs required but kx509 client did not send "
589 "one");
590 return KX509_STATUS_CLIENT_USE_CSR;
593 /* Attempt to decode pk_key as RSAPublicKey */
594 ret = decode_RSAPublicKey(reqctx->req.pk_key.data,
595 reqctx->req.pk_key.length,
596 &rsapkey, &size);
597 free_RSAPublicKey(&rsapkey);
598 if (ret == 0 && size == reqctx->req.pk_key.length)
599 return make_csr(context, reqctx, &pk_key); /* Make pretend CSR */
601 /* Not an RSAPublicKey or garbage follows it */
602 if (ret == 0) {
603 ret = KRB5KDC_ERR_NULL_KEY;
604 _kdc_audit_addreason((kdc_request_t)reqctx,
605 "Request has garbage after key");
606 krb5_set_error_message(context, ret, "Request has garbage after key");
607 return ret;
610 _kdc_audit_addreason((kdc_request_t)reqctx,
611 "Could not decode CSR or RSA subject public key");
612 krb5_set_error_message(context, ret,
613 "Could not decode CSR or RSA subject public key");
614 return ret;
618 * Host-based principal _clients_ might ask for a cert for their host -- but
619 * which services are permitted to do that? This function answers that
620 * question.
622 static int
623 check_authz_svc_ok(krb5_context context, const char *svc)
625 const char *def[] = { "host", "HTTP", 0 };
626 const char * const *svcs;
627 char **strs;
629 strs = krb5_config_get_strings(context, NULL, "kdc",
630 "kx509_permitted_hostbased_services", NULL);
631 for (svcs = strs ? (const char * const *)strs : def; svcs[0]; svcs++) {
632 if (strcmp(svcs[0], svc) == 0) {
633 krb5_config_free_strings(strs);
634 return 1;
637 krb5_config_free_strings(strs);
638 return 0;
641 static krb5_error_code
642 check_authz(krb5_context context,
643 kx509_req_context reqctx,
644 krb5_principal cprincipal)
646 krb5_error_code ret;
647 const char *comp0 = krb5_principal_get_comp_string(context, cprincipal, 0);
648 const char *comp1 = krb5_principal_get_comp_string(context, cprincipal, 1);
649 unsigned int ncomp = krb5_principal_get_num_comp(context, cprincipal);
650 hx509_san_type san_type;
651 KeyUsage ku, ku_allowed;
652 size_t i;
653 const heim_oid *eku_whitelist[] = {
654 &asn1_oid_id_pkix_kp_serverAuth,
655 &asn1_oid_id_pkix_kp_clientAuth,
656 &asn1_oid_id_pkekuoid,
657 &asn1_oid_id_pkinit_ms_eku
659 char *cprinc = NULL;
660 char *s = NULL;
663 * In the no-CSR case we'll derive cert contents from client name and its
664 * HDB entry -- authorization is implied.
666 if (!reqctx->have_csr)
667 return 0;
668 ret = kdc_authorize_csr(context, reqctx->config->app, reqctx->csr,
669 cprincipal);
670 if (ret == 0) {
671 _kdc_audit_addkv((kdc_request_t)reqctx, 0, "authorized", "true");
673 ret = hx509_request_get_san(reqctx->csr, 0, &san_type, &s);
674 if (ret == 0) {
675 const char *san_type_s;
677 /* This should be an hx509 function... */
678 switch (san_type) {
679 case HX509_SAN_TYPE_EMAIL: san_type_s = "rfc822Name"; break;
680 case HX509_SAN_TYPE_DNSNAME: san_type_s = "dNSName"; break;
681 case HX509_SAN_TYPE_DN: san_type_s = "DN"; break;
682 case HX509_SAN_TYPE_REGISTERED_ID: san_type_s = "registeredID"; break;
683 case HX509_SAN_TYPE_XMPP: san_type_s = "xMPPName"; break;
684 case HX509_SAN_TYPE_PKINIT: san_type_s = "krb5PrincipalName"; break;
685 case HX509_SAN_TYPE_MS_UPN: san_type_s = "ms-UPN"; break;
686 default: san_type_s = "unknown"; break;
688 _kdc_audit_addkv((kdc_request_t)reqctx, 0, "san0_type", "%s",
689 san_type_s);
690 _kdc_audit_addkv((kdc_request_t)reqctx, 0, "san0", "%s", s);
691 free(s);
693 ret = hx509_request_get_eku(reqctx->csr, 0, &s);
694 if (ret == 0) {
695 _kdc_audit_addkv((kdc_request_t)reqctx, 0, "eku0", "%s", s);
696 free(s);
698 return 0;
700 if (ret != KRB5_PLUGIN_NO_HANDLE) {
701 _kdc_audit_addreason((kdc_request_t)reqctx,
702 "Requested extensions rejected by plugin");
703 return ret;
706 /* Default authz */
707 if ((ret = krb5_unparse_name(context, cprincipal, &cprinc)))
708 return ret;
710 for (i = 0; ret == 0; i++) {
712 frees(&s);
713 ret = hx509_request_get_san(reqctx->csr, i, &san_type, &s);
714 if (ret)
715 break;
716 switch (san_type) {
717 case HX509_SAN_TYPE_DNSNAME:
718 if (ncomp != 2 || strcasecmp(comp1, s) != 0 ||
719 strchr(s, '.') == NULL ||
720 !check_authz_svc_ok(context, comp0)) {
721 _kdc_audit_addreason((kdc_request_t)reqctx,
722 "Requested extensions rejected by "
723 "default policy (dNSName SAN "
724 "does not match client)");
725 goto eacces;
727 break;
728 case HX509_SAN_TYPE_PKINIT:
729 if (strcmp(cprinc, s) != 0) {
730 _kdc_audit_addreason((kdc_request_t)reqctx,
731 "Requested extensions rejected by "
732 "default policy (PKINIT SAN "
733 "does not match client)");
734 goto eacces;
736 break;
737 default:
738 _kdc_audit_addreason((kdc_request_t)reqctx,
739 "Requested extensions rejected by "
740 "default policy (non-default SAN "
741 "requested)");
742 goto eacces;
745 frees(&s);
746 if (ret == HX509_NO_ITEM)
747 ret = 0;
748 if (ret)
749 goto out;
751 for (i = 0; ret == 0; i++) {
752 heim_oid oid;
753 size_t k;
755 frees(&s);
756 ret = hx509_request_get_eku(reqctx->csr, i, &s);
757 if (ret)
758 break;
760 if ((ret = der_parse_heim_oid(s, ".", &oid))) {
761 goto out;
763 for (k = 0; k < sizeof(eku_whitelist)/sizeof(eku_whitelist[0]); k++) {
764 if (der_heim_oid_cmp(eku_whitelist[k], &oid) == 0)
765 break;
767 der_free_oid(&oid);
768 if (k == sizeof(eku_whitelist)/sizeof(eku_whitelist[0])) {
769 _kdc_audit_addreason((kdc_request_t)reqctx,
770 "Requested EKU rejected by default policy");
771 goto eacces;
774 if (ret == HX509_NO_ITEM)
775 ret = 0;
776 if (ret)
777 goto out;
779 memset(&ku_allowed, 0, sizeof(ku_allowed));
780 ku_allowed.digitalSignature = 1;
781 ku_allowed.nonRepudiation = 1;
782 ret = hx509_request_get_ku(context->hx509ctx, reqctx->csr, &ku);
783 if (ret)
784 goto out;
785 if (KeyUsage2int(ku) != (KeyUsage2int(ku) & KeyUsage2int(ku_allowed)))
786 goto eacces;
788 _kdc_audit_addkv((kdc_request_t)reqctx, 0, "authorized", "true");
789 return 0;
791 eacces:
792 ret = EACCES;
793 goto out2;
795 out:
796 /* XXX Display error code */
797 _kdc_audit_addreason((kdc_request_t)reqctx,
798 "Error handling requested extensions");
799 out2:
800 free(cprinc);
801 free(s);
802 return ret;
805 static int
806 chain_add1_func(hx509_context context, void *d, hx509_cert c)
808 heim_octet_string os;
809 Certificates *cs = d;
810 Certificate c2;
811 int ret;
813 ret = hx509_cert_binary(context, c, &os);
814 if (ret)
815 return ret;
816 ret = decode_Certificate(os.data, os.length, &c2, NULL);
817 der_free_octet_string(&os);
818 if (ret)
819 return ret;
820 ret = add_Certificates(cs, &c2);
821 free_Certificate(&c2);
822 return ret;
825 static krb5_error_code
826 encode_cert_and_chain(hx509_context hx509ctx,
827 hx509_certs certs,
828 krb5_data *out)
830 krb5_error_code ret;
831 Certificates cs;
832 size_t len;
834 cs.len = 0;
835 cs.val = 0;
837 ret = hx509_certs_iter_f(hx509ctx, certs, chain_add1_func, &cs);
838 if (ret == 0)
839 ASN1_MALLOC_ENCODE(Certificates, out->data, out->length,
840 &cs, &len, ret);
841 free_Certificates(&cs);
842 return ret;
846 * Process a request, produce a reply.
849 krb5_error_code
850 _kdc_do_kx509(kx509_req_context r)
852 krb5_error_code ret = 0;
853 krb5_ticket *ticket = NULL;
854 krb5_flags ap_req_options;
855 krb5_principal cprincipal = NULL;
856 krb5_principal sprincipal = NULL;
857 krb5_keytab id = NULL;
858 Kx509Response rep;
859 hx509_certs certs = NULL;
860 int is_probe = 0;
862 r->csr_plus.csr.data = NULL;
863 r->csr_plus.exts = NULL;
864 r->sname = NULL;
865 r->cname = NULL;
866 r->realm = NULL;
867 r->key = NULL;
868 r->csr = NULL;
869 r->ac = NULL;
872 * In order to support authenticated error messages we defer checking
873 * whether the kx509 service is enabled until after accepting the AP-REQ.
876 krb5_data_zero(r->reply);
877 memset(&rep, 0, sizeof(rep));
879 if (r->req.authenticator.length == 0) {
881 * Unauthenticated kx509 service availability probe.
883 * mk_error_response() will check whether the service is enabled and
884 * possibly change the error code and message.
886 is_probe = 1;
887 _kdc_audit_addkv((kdc_request_t)r, 0, "probe", "unauthenticated");
888 ret = mk_error_response(r->context, r, 4, 0,
889 "kx509 service is available");
890 goto out;
893 /* Authenticate the request (consume the AP-REQ) */
894 ret = krb5_kt_resolve(r->context, "HDBGET:", &id);
895 if (ret) {
896 const char *msg = krb5_get_error_message(r->context, ret);
897 ret = mk_error_response(r->context, r, 1,
898 KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN,
899 "Can't open HDB/keytab for kx509: %s",
900 msg);
901 krb5_free_error_message(r->context, msg);
902 goto out;
905 ret = krb5_rd_req(r->context,
906 &r->ac,
907 &r->req.authenticator,
908 NULL,
910 &ap_req_options,
911 &ticket);
912 if (ret == 0)
913 ret = krb5_auth_con_getkey(r->context, r->ac, &r->key);
914 if (ret == 0 && r->key == NULL)
915 ret = KRB5KDC_ERR_NULL_KEY;
917 * Provided we got the session key, errors past this point will be
918 * authenticated.
920 if (ret == 0)
921 ret = krb5_ticket_get_client(r->context, ticket, &cprincipal);
923 /* Optional: check if Ticket is INITIAL */
924 if (ret == 0 &&
925 !ticket->ticket.flags.initial &&
926 !get_bool_param(r->context, TRUE,
927 krb5_principal_get_realm(r->context, cprincipal),
928 "require_initial_kca_tickets")) {
929 ret = mk_error_response(r->context, r, 4, KRB5KDC_ERR_POLICY,
930 "Client used non-INITIAL tickets, but kx509 "
931 "service is configured to require INITIAL "
932 "tickets");
933 goto out;
936 ret = krb5_unparse_name(r->context, cprincipal, &r->cname);
938 /* Check that the service name is a valid kx509 service name */
939 if (ret == 0)
940 ret = krb5_ticket_get_server(r->context, ticket, &sprincipal);
941 if (ret == 0)
942 r->realm = krb5_principal_get_realm(r->context, sprincipal);
943 if (ret == 0)
944 ret = krb5_unparse_name(r->context, sprincipal, &r->sname);
945 if (ret == 0)
946 ret = kdc_kx509_verify_service_principal(r->context, r, sprincipal);
947 if (ret) {
948 ret = mk_error_response(r->context, r, 4, ret,
949 "kx509 client used incorrect service name");
950 goto out;
953 /* Authenticate the rest of the request */
954 ret = verify_req_hash(r->context, &r->req, r->key);
955 if (ret) {
956 ret = mk_error_response(r->context, r, 4, ret,
957 "Incorrect request HMAC on kx509 request");
958 goto out;
961 if (r->req.pk_key.length == 0) {
963 * The request is an authenticated kx509 service availability probe.
965 * mk_error_response() will check whether the service is enabled and
966 * possibly change the error code and message.
968 is_probe = 1;
969 _kdc_audit_addkv((kdc_request_t)r, 0, "probe", "authenticated");
970 ret = mk_error_response(r->context, r, 4, 0,
971 "kx509 authenticated probe request");
972 goto out;
975 /* Extract and parse CSR or a DER-encoded RSA public key */
976 ret = get_csr(r->context, r);
977 if (ret) {
978 const char *msg = krb5_get_error_message(r->context, ret);
979 ret = mk_error_response(r->context, r, 3, ret,
980 "Failed to parse CSR: %s", msg);
981 krb5_free_error_message(r->context, msg);
982 goto out;
985 /* Authorize the request */
986 ret = check_authz(r->context, r, cprincipal);
987 if (ret) {
988 const char *msg = krb5_get_error_message(r->context, ret);
989 ret = mk_error_response(r->context, r, 3, ret,
990 "Rejected by policy: %s", msg);
991 krb5_free_error_message(r->context, msg);
992 goto out;
995 /* Issue the certificate */
996 ALLOC(rep.hash);
997 ALLOC(rep.certificate);
998 if (rep.certificate == NULL || rep.hash == NULL) {
999 ret = mk_error_response(r->context, r, 0, ENOMEM,
1000 "Could allocate memory for response");
1001 goto out;
1004 krb5_data_zero(rep.hash);
1005 krb5_data_zero(rep.certificate);
1006 krb5_ticket_get_times(r->context, ticket, &r->ticket_times);
1007 ret = kdc_issue_certificate(r->context, r->config->app, r->logf, r->csr,
1008 cprincipal, &r->ticket_times, 0 /*req_life*/,
1009 r->send_chain, &certs);
1010 if (ret) {
1011 int level = 1;
1012 const char *msg = krb5_get_error_message(r->context, ret);
1014 if (ret == KRB5KDC_ERR_POLICY)
1015 level = 4; /* _kdc_audit_trail() logs at level 3 */
1016 ret = mk_error_response(r->context, r, level, ret,
1017 "Certificate isuance failed: %s", msg);
1018 krb5_free_error_message(r->context, msg);
1019 goto out;
1022 ret = encode_cert_and_chain(r->context->hx509ctx, certs, rep.certificate);
1023 if (ret) {
1024 const char *msg = krb5_get_error_message(r->context, ret);
1025 ret = mk_error_response(r->context, r, 1, ret,
1026 "Could not encode certificate and chain: %s",
1027 msg);
1028 krb5_free_error_message(r->context, msg);
1029 goto out;
1032 /* Authenticate the response */
1033 ret = calculate_reply_hash(r->context, r->key, &rep);
1034 if (ret) {
1035 ret = mk_error_response(r->context, r, 1, ret,
1036 "Failed to compute response HMAC");
1037 goto out;
1040 /* Encode and output reply */
1041 ret = encode_reply(r->context, r, &rep);
1042 if (ret)
1043 /* Can't send an error message either in this case, surely */
1044 _kdc_audit_addreason((kdc_request_t)r, "Could not encode response");
1046 out:
1047 hx509_certs_free(&certs);
1048 if (ret == 0 && !is_probe)
1049 _kdc_audit_addkv((kdc_request_t)r, 0, "cert_issued", "true");
1050 else
1051 _kdc_audit_addkv((kdc_request_t)r, 0, "cert_issued", "false");
1052 if (r->ac)
1053 krb5_auth_con_free(r->context, r->ac);
1054 if (ticket)
1055 krb5_free_ticket(r->context, ticket);
1056 if (id)
1057 krb5_kt_close(r->context, id);
1058 if (sprincipal)
1059 krb5_free_principal(r->context, sprincipal);
1060 if (cprincipal)
1061 krb5_free_principal(r->context, cprincipal);
1062 if (r->key)
1063 krb5_free_keyblock (r->context, r->key);
1064 hx509_request_free(&r->csr);
1065 free_Kx509CSRPlus(&r->csr_plus);
1066 free_Kx509Response(&rep);
1067 free_Kx509Request(&r->req);
1069 return ret;
1072 #endif /* KX509 */