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
36 #include <rfc2459_asn1.h>
38 #include <hx509_err.h>
43 * This file implements a singular utility function `kdc_issue_certificate()'
44 * for certificate issuance for kx509 and bx509, which takes a principal name,
45 * an `hx509_request' resulting from parsing a CSR and possibly adding
46 * SAN/EKU/KU extensions, the start/end times of request's authentication
47 * method, and whether to include a full certificate chain in the result.
64 static krb5_error_code
65 count_sans(hx509_request req
, size_t *n
)
72 for (i
= 0; ret
== 0; i
++) {
73 hx509_san_type san_type
;
76 ret
= hx509_request_get_san(req
, i
, &san_type
, &s
);
80 case HX509_SAN_TYPE_DNSNAME
:
81 case HX509_SAN_TYPE_EMAIL
:
82 case HX509_SAN_TYPE_XMPP
:
83 case HX509_SAN_TYPE_PKINIT
:
84 case HX509_SAN_TYPE_MS_UPN
:
92 return ret
== HX509_NO_ITEM
? 0 : ret
;
96 has_sans(hx509_request req
)
98 hx509_san_type san_type
;
100 int ret
= hx509_request_get_san(req
, 0, &san_type
, &s
);
103 return ret
== HX509_NO_ITEM
? 0 : 1;
107 characterize_cprinc(krb5_context context
,
108 krb5_principal cprinc
)
110 unsigned int ncomp
= krb5_principal_get_num_comp(context
, cprinc
);
111 const char *comp1
= krb5_principal_get_comp_string(context
, cprinc
, 1);
117 if (strchr(comp1
, '.') == NULL
)
121 if (strchr(comp1
, '.'))
129 /* Characterize request as client or server cert req */
131 characterize(krb5_context context
,
132 krb5_principal cprinc
,
135 krb5_error_code ret
= 0;
136 cert_type res
= CERT_NOTSUP
;
142 return characterize_cprinc(context
, cprinc
);
144 for (i
= 0; ret
== 0; i
++) {
148 ret
= hx509_request_get_eku(req
, i
, &s
);
153 ret
= der_parse_heim_oid(s
, ".", &oid
);
157 * If the client wants only a server certificate, then we'll be
158 * willing to issue one that may be longer-lived than the client's
161 * There may be other server EKUs, but these are the ones we know
164 if (der_heim_oid_cmp(&asn1_oid_id_pkix_kp_serverAuth
, &oid
) &&
165 der_heim_oid_cmp(&asn1_oid_id_pkix_kp_OCSPSigning
, &oid
) &&
166 der_heim_oid_cmp(&asn1_oid_id_pkix_kp_secureShellServer
, &oid
))
173 if (ret
== HX509_NO_ITEM
)
176 for (i
= 0; ret
== 0; i
++) {
177 hx509_san_type san_type
;
180 ret
= hx509_request_get_san(req
, i
, &san_type
, &s
);
184 case HX509_SAN_TYPE_DNSNAME
:
188 case HX509_SAN_TYPE_EMAIL
:
189 case HX509_SAN_TYPE_XMPP
:
190 case HX509_SAN_TYPE_PKINIT
:
191 case HX509_SAN_TYPE_MS_UPN
:
202 if (ret
== HX509_NO_ITEM
)
204 return ret
? CERT_NOTSUP
: res
;
208 * Get a configuration sub-tree for kx509 based on what's being requested and
211 * We have a number of cases:
213 * - default certificate (no CSR used, or no certificate extensions requested)
214 * - for client principals
215 * - for service principals
216 * - client certificate requested (CSR used and client-y SANs/EKUs requested)
217 * - server certificate requested (CSR used and server-y SANs/EKUs requested)
218 * - mixed client/server certificate requested (...)
220 static const krb5_config_binding
*
221 get_cf(krb5_context context
,
222 krb5_kdc_configuration
*config
,
224 krb5_principal cprinc
)
227 const krb5_config_binding
*cf
= NULL
;
228 unsigned int ncomp
= krb5_principal_get_num_comp(context
, cprinc
);
229 const char *realm
= krb5_principal_get_realm(context
, cprinc
);
230 const char *comp0
= krb5_principal_get_comp_string(context
, cprinc
, 0);
231 const char *comp1
= krb5_principal_get_comp_string(context
, cprinc
, 1);
232 const char *label
= NULL
;
233 const char *svc
= NULL
;
234 const char *def
= NULL
;
235 cert_type certtype
= CERT_NOTSUP
;
239 kdc_log(context
, config
, 5, "Client principal has no components!");
240 krb5_set_error_message(context
, ENOTSUP
,
241 "Client principal has no components!");
245 if ((ret
= count_sans(req
, &nsans
)) ||
246 (certtype
= characterize(context
, cprinc
, req
)) == CERT_NOTSUP
) {
247 kdc_log(context
, config
, 5, "Could not characterize CSR");
248 krb5_set_error_message(context
, ret
, "Could not characterize CSR");
254 /* Client requested some certificate extension, a SAN or EKU */
256 case CERT_MIXED
: label
= "mixed"; break;
257 case CERT_CLIENT
: label
= "client"; break;
258 case CERT_SERVER
: label
= "server"; break;
259 default: return NULL
;
263 /* Default certificate desired */
266 } else if (ncomp
== 2 && strcmp(comp1
, "root") == 0) {
268 } else if (ncomp
== 2 && strcmp(comp1
, "admin") == 0) {
269 label
= "admin_user";
270 } else if (strchr(comp1
, '.')) {
271 label
= "hostbased_service";
278 if (strcmp(config
->app
, "kdc") == 0)
279 cf
= krb5_config_get_list(context
, NULL
, config
->app
, "realms", realm
,
280 "kx509", label
, svc
, NULL
);
282 cf
= krb5_config_get_list(context
, NULL
, config
->app
, "realms", realm
,
285 kdc_log(context
, config
, 3,
286 "No %s configuration for %s %s certificates [%s] realm "
287 "-> %s -> kx509 -> %s%s%s",
288 strcmp(config
->app
, "bx509") == 0 ? "bx509" : "kx509",
289 def
, label
, config
->app
, realm
, label
,
290 svc
? " -> " : "", svc
? svc
: "");
291 krb5_set_error_message(context
, KRB5KDC_ERR_POLICY
,
292 "No %s configuration for %s %s certificates [%s] realm "
293 "-> %s -> kx509 -> %s%s%s",
294 strcmp(config
->app
, "bx509") == 0 ? "bx509" : "kx509",
295 def
, label
, config
->app
, realm
, label
,
296 svc
? " -> " : "", svc
? svc
: "");
302 * Find and set a certificate template using a configuration sub-tree
303 * appropriate to the requesting principal.
305 * This allows for the specification of the following in configuration:
307 * - certificates as templates, with ${var} tokens in subjectName attribute
308 * values that will be expanded later
309 * - a plain string with ${var} tokens to use as the subjectName
311 * - whether to include a PKINIT SAN
313 static krb5_error_code
314 set_template(krb5_context context
,
315 krb5_kdc_configuration
*config
,
316 const krb5_config_binding
*cf
,
319 krb5_error_code ret
= 0;
320 const char *cert_template
= NULL
;
321 const char *subj_name
= NULL
;
325 return KRB5KDC_ERR_POLICY
; /* Can't happen */
327 cert_template
= krb5_config_get_string(context
, cf
, "template_cert", NULL
);
328 subj_name
= krb5_config_get_string(context
, cf
, "subject_name", NULL
);
329 ekus
= krb5_config_get_strings(context
, cf
, "ekus", NULL
);
335 ret
= hx509_certs_init(context
->hx509ctx
, cert_template
, 0,
338 ret
= hx509_get_one_cert(context
->hx509ctx
, certs
, &template);
339 hx509_certs_free(&certs
);
341 kdc_log(context
, config
, 1,
342 "Failed to load certificate template from %s",
344 krb5_set_error_message(context
, KRB5KDC_ERR_POLICY
,
345 "Failed to load certificate template from "
346 "%s", cert_template
);
351 * Only take the subjectName, the keyUsage, and EKUs from the template
354 ret
= hx509_ca_tbs_set_template(context
->hx509ctx
, tbs
,
355 HX509_CA_TEMPLATE_SUBJECT
|
356 HX509_CA_TEMPLATE_KU
|
357 HX509_CA_TEMPLATE_EKU
,
359 hx509_cert_free(template);
365 hx509_name dn
= NULL
;
367 ret
= hx509_parse_name(context
->hx509ctx
, subj_name
, &dn
);
369 ret
= hx509_ca_tbs_set_subject(context
->hx509ctx
, tbs
, dn
);
370 hx509_name_free(&dn
);
375 if (cert_template
== NULL
&& subj_name
== NULL
) {
376 hx509_name dn
= NULL
;
378 ret
= hx509_empty_name(context
->hx509ctx
, &dn
);
380 ret
= hx509_ca_tbs_set_subject(context
->hx509ctx
, tbs
, dn
);
381 hx509_name_free(&dn
);
389 for (i
= 0; ret
== 0 && ekus
[i
]; i
++) {
390 heim_oid oid
= { 0, 0 };
392 if ((ret
= der_find_or_parse_heim_oid(ekus
[i
], ".", &oid
)) == 0)
393 ret
= hx509_ca_tbs_add_eku(context
->hx509ctx
, tbs
, &oid
);
396 krb5_config_free_strings(ekus
);
400 * XXX A KeyUsage template would be nice, but it needs some smarts to
401 * remove, e.g., encipherOnly, decipherOnly, keyEncipherment, if the SPKI
402 * algorithm does not support encryption. The same logic should be added
403 * to hx509_ca_tbs_set_template()'s HX509_CA_TEMPLATE_KU functionality.
409 * Find and set a certificate template, set "variables" in `env', and add add
410 * default SANs/EKUs as appropriate.
413 * - lookup a template for the client principal in its HDB entry
414 * - lookup subjectName, SANs for a principal in its HDB entry
415 * - lookup a host-based client principal's HDB entry and add its canonical
416 * name / aliases as dNSName SANs
417 * (this would have to be if requested by the client, perhaps)
419 static krb5_error_code
420 set_tbs(krb5_context context
,
421 krb5_kdc_configuration
*config
,
422 const krb5_config_binding
*cf
,
424 krb5_principal cprinc
,
429 unsigned int ncomp
= krb5_principal_get_num_comp(context
, cprinc
);
430 const char *realm
= krb5_principal_get_realm(context
, cprinc
);
431 const char *comp0
= krb5_principal_get_comp_string(context
, cprinc
, 0);
432 const char *comp1
= krb5_principal_get_comp_string(context
, cprinc
, 1);
433 const char *comp2
= krb5_principal_get_comp_string(context
, cprinc
, 2);
434 char *princ_no_realm
= NULL
;
437 ret
= krb5_unparse_name_flags(context
, cprinc
, 0, &princ
);
439 ret
= krb5_unparse_name_flags(context
, cprinc
,
440 KRB5_PRINCIPAL_UNPARSE_NO_REALM
,
443 ret
= hx509_env_add(context
->hx509ctx
, env
,
444 "principal-name-without-realm", princ_no_realm
);
446 ret
= hx509_env_add(context
->hx509ctx
, env
, "principal-name", princ
);
448 ret
= hx509_env_add(context
->hx509ctx
, env
, "principal-name-realm",
451 /* Populate requested certificate extensions from CSR/CSRPlus if allowed */
452 ret
= hx509_ca_tbs_set_from_csr(context
->hx509ctx
, tbs
, req
);
454 ret
= set_template(context
, config
, cf
, tbs
);
457 * Optionally add PKINIT SAN.
459 * Adding an id-pkinit-san means the client can use the certificate to
460 * initiate PKINIT. That might seem odd, but it enables a sort of PKIX
461 * credential delegation by allowing forwarded Kerberos tickets to be
462 * used to acquire PKIX credentials. Thus this can work:
464 * PKIX (w/ HW token) -> Kerberos ->
465 * PKIX (w/ softtoken) -> Kerberos ->
466 * PKIX (w/ softtoken) -> Kerberos ->
469 * Note that we may not have added the PKINIT EKU -- that depends on the
470 * template, and host-based service templates might well not include it.
472 if (ret
== 0 && !has_sans(req
) &&
473 krb5_config_get_bool_default(context
, cf
, FALSE
, "include_pkinit_san",
475 ret
= hx509_ca_tbs_add_san_pkinit(context
->hx509ctx
, tbs
, princ
);
482 const char *email_domain
;
484 ret
= hx509_env_add(context
->hx509ctx
, env
, "principal-component0",
488 * If configured, include an rfc822Name that's just the client's
489 * principal name sans realm @ configured email domain.
491 if (ret
== 0 && !has_sans(req
) &&
492 (email_domain
= krb5_config_get_string(context
, cf
, "email_domain",
496 if (asprintf(&email
, "%s@%s", princ_no_realm
, email_domain
) == -1 ||
499 ret
= hx509_ca_tbs_add_san_rfc822name(context
->hx509ctx
, tbs
, email
);
502 } else if (ncomp
== 2 || ncomp
== 3) {
504 * 2- and 3-component principal name.
506 * We do not have a reliable name-type indicator. If the second
507 * component has a '.' in it then we'll assume that the name is a
508 * host-based (2-component) or domain-based (3-component) service
509 * principal name. Else we'll assume it's a two-component admin-style
513 ret
= hx509_env_add(context
->hx509ctx
, env
, "principal-component0",
516 ret
= hx509_env_add(context
->hx509ctx
, env
, "principal-component1",
518 if (ret
== 0 && ncomp
== 3)
519 ret
= hx509_env_add(context
->hx509ctx
, env
, "principal-component2",
521 if (ret
== 0 && strchr(comp1
, '.')) {
522 /* Looks like host-based or domain-based service */
523 ret
= hx509_env_add(context
->hx509ctx
, env
,
524 "principal-service-name", comp0
);
526 ret
= hx509_env_add(context
->hx509ctx
, env
, "principal-host-name", comp1
);
527 if (ret
== 0 && ncomp
== 3)
528 ret
= hx509_env_add(context
->hx509ctx
, env
, "principal-domain-name", comp2
);
529 if (ret
== 0 && !has_sans(req
) &&
530 krb5_config_get_bool_default(context
, cf
, FALSE
,
531 "include_dnsname_san", NULL
)) {
532 ret
= hx509_ca_tbs_add_san_hostname(context
->hx509ctx
, tbs
, comp1
);
536 kdc_log(context
, config
, 5, "kx509/bx509 client %s has too many "
537 "components!", princ
);
538 krb5_set_error_message(context
, ret
= KRB5KDC_ERR_POLICY
,
539 "kx509/bx509 client %s has too many "
540 "components!", princ
);
546 krb5_xfree(princ_no_realm
);
551 kdc_log(context
, config
, 0,
552 "Could not set up TBSCertificate: Out of memory");
553 ret
= krb5_enomem(context
);
557 static krb5_error_code
558 tbs_set_times(krb5_context context
,
559 const krb5_config_binding
*cf
,
560 krb5_times
*auth_times
,
564 time_t now
= time(NULL
);
565 time_t endtime
= auth_times
->endtime
;
566 time_t starttime
= auth_times
->starttime
?
567 auth_times
->starttime
: now
- 5 * 60;
569 krb5_config_get_time_default(context
, cf
, 5 * 24 * 3600,
570 "force_cert_lifetime", NULL
);
572 krb5_config_get_time_default(context
, cf
, 0, "max_cert_lifetime",
575 if (fudge
&& now
+ fudge
> endtime
)
576 endtime
= now
+ fudge
;
578 if (req_life
&& req_life
< endtime
- now
)
579 endtime
= now
+ req_life
;
581 if (clamp
&& clamp
< endtime
- now
)
582 endtime
= now
+ clamp
;
584 hx509_ca_tbs_set_notAfter(context
->hx509ctx
, tbs
, endtime
);
585 hx509_ca_tbs_set_notBefore(context
->hx509ctx
, tbs
, starttime
);
590 * Build a certifate for `principal' and its CSR.
593 kdc_issue_certificate(krb5_context context
,
594 krb5_kdc_configuration
*config
,
596 krb5_principal cprinc
,
597 krb5_times
*auth_times
,
601 const krb5_config_binding
*cf
;
604 hx509_ca_tbs tbs
= NULL
;
605 hx509_certs chain
= NULL
;
606 hx509_cert signer
= NULL
;
607 hx509_cert cert
= NULL
;
608 hx509_env env
= NULL
;
613 ku
= int2KeyUsage(0);
614 ku
.digitalSignature
= 1;
615 hx509_request_authorize_ku(req
, ku
);
617 /* Get configuration */
618 if ((cf
= get_cf(context
, config
, req
, cprinc
)) == NULL
)
619 return KRB5KDC_ERR_POLICY
;
620 if ((ca
= krb5_config_get_string(context
, cf
, "ca", NULL
)) == NULL
) {
621 kdc_log(context
, config
, 3, "No kx509 CA issuer credential specified");
622 krb5_set_error_message(context
, ret
= KRB5KDC_ERR_POLICY
,
623 "No kx509 CA issuer credential specified");
627 ret
= hx509_ca_tbs_init(context
->hx509ctx
, &tbs
);
629 kdc_log(context
, config
, 0,
630 "Failed to create certificate: Out of memory");
634 /* Lookup a template and set things in `env' and `tbs' as appropriate */
636 ret
= set_tbs(context
, config
, cf
, req
, cprinc
, &env
, tbs
);
638 /* Populate generic template "env" variables */
641 * The `tbs' and `env' are now complete as to naming and EKUs.
643 * We check that the `tbs' is not name-less, after which all remaining
644 * failures here will not be policy failures. So we also log the intent to
645 * issue a certificate now.
647 if (ret
== 0 && hx509_name_is_null_p(hx509_ca_tbs_get_name(tbs
)) &&
649 kdc_log(context
, config
, 3,
650 "Not issuing certificate because it would have no names");
651 krb5_set_error_message(context
, ret
= KRB5KDC_ERR_POLICY
,
652 "Not issuing certificate because it "
653 "would have no names");
659 * Still to be done below:
661 * - set certificate spki
662 * - set certificate validity
663 * - expand variables in certificate subject name template
665 * - encode certificate and chain
668 /* Load the issuer certificate and private key */
673 ret
= hx509_certs_init(context
->hx509ctx
, ca
, 0, NULL
, &certs
);
675 kdc_log(context
, config
, 1,
676 "Failed to load CA certificate and private key %s", ca
);
677 krb5_set_error_message(context
, ret
, "Failed to load CA "
678 "certificate and private key %s", ca
);
681 ret
= hx509_query_alloc(context
->hx509ctx
, &q
);
683 hx509_certs_free(&certs
);
687 hx509_query_match_option(q
, HX509_QUERY_OPTION_PRIVATE_KEY
);
688 hx509_query_match_option(q
, HX509_QUERY_OPTION_KU_KEYCERTSIGN
);
690 ret
= hx509_certs_find(context
->hx509ctx
, certs
, q
, &signer
);
691 hx509_query_free(context
->hx509ctx
, q
);
692 hx509_certs_free(&certs
);
694 kdc_log(context
, config
, 1,
695 "Failed to find a CA certificate in %s", ca
);
696 krb5_set_error_message(context
, ret
,
697 "Failed to find a CA certificate in %s",
703 /* Populate the subject public key in the TBS context */
705 SubjectPublicKeyInfo spki
;
707 ret
= hx509_request_get_SubjectPublicKeyInfo(context
->hx509ctx
,
710 ret
= hx509_ca_tbs_set_spki(context
->hx509ctx
, tbs
, &spki
);
711 free_SubjectPublicKeyInfo(&spki
);
716 /* Work out cert expiration */
718 ret
= tbs_set_times(context
, cf
, auth_times
, 0 /* XXX req_life */, tbs
);
720 /* Expand the subjectName template in the TBS using the env */
722 ret
= hx509_ca_tbs_subject_expand(context
->hx509ctx
, tbs
, env
);
723 hx509_env_free(&env
);
725 /* All done with the TBS, sign/issue the certificate */
726 ret
= hx509_ca_sign(context
->hx509ctx
, tbs
, signer
, &cert
);
731 * Gather the certificate and chain into a MEMORY store, being careful not
732 * to include private keys in the chain.
734 * We could have specified a separate configuration parameter for an hx509
735 * store meant to have only the chain and no private keys, but expecting
736 * the full chain in the issuer credential store and copying only the certs
737 * (but not the private keys) is safer and easier to configure.
739 ret
= hx509_certs_init(context
->hx509ctx
, "MEMORY:certs",
740 HX509_CERTS_NO_PRIVATE_KEYS
, NULL
, out
);
742 ret
= hx509_certs_add(context
->hx509ctx
, *out
, cert
);
743 if (ret
== 0 && send_chain
) {
744 ret
= hx509_certs_init(context
->hx509ctx
, ca
,
745 HX509_CERTS_NO_PRIVATE_KEYS
, NULL
, &chain
);
747 ret
= hx509_certs_merge(context
->hx509ctx
, *out
, chain
);
751 hx509_certs_free(&chain
);
753 hx509_env_free(&env
);
755 hx509_ca_tbs_free(&tbs
);
757 hx509_cert_free(cert
);
759 hx509_cert_free(signer
);
761 hx509_certs_free(out
);