From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- third_party/heimdal/lib/hx509/ca.c | 3099 ++++++++++++++++++++++++++++++++++++ 1 file changed, 3099 insertions(+) create mode 100644 third_party/heimdal/lib/hx509/ca.c (limited to 'third_party/heimdal/lib/hx509/ca.c') diff --git a/third_party/heimdal/lib/hx509/ca.c b/third_party/heimdal/lib/hx509/ca.c new file mode 100644 index 0000000..37fb403 --- /dev/null +++ b/third_party/heimdal/lib/hx509/ca.c @@ -0,0 +1,3099 @@ +/* + * Copyright (c) 2006 - 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hx_locl.h" + +/** + * @page page_ca Hx509 CA functions + * + * See the library functions here: @ref hx509_ca + */ + +struct hx509_ca_tbs { + hx509_name subject; + SubjectPublicKeyInfo spki; + KeyUsage ku; + ExtKeyUsage eku; + GeneralNames san; + CertificatePolicies cps; + PolicyMappings pms; + heim_integer serial; + struct { + unsigned int proxy:1; + unsigned int ca:1; + unsigned int key:1; + unsigned int serial:1; + unsigned int domaincontroller:1; + unsigned int xUniqueID:1; + } flags; + time_t notBefore; + time_t notAfter; + HeimPkinitPrincMaxLifeSecs pkinitTicketMaxLife; + int pathLenConstraint; /* both for CA and Proxy */ + CRLDistributionPoints crldp; + heim_bit_string subjectUniqueID; + heim_bit_string issuerUniqueID; + AlgorithmIdentifier *sigalg; +}; + +/** + * Allocate an to-be-signed certificate object that will be converted + * into an certificate. + * + * @param context A hx509 context. + * @param tbs returned to-be-signed certicate object, free with + * hx509_ca_tbs_free(). + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_init(hx509_context context, hx509_ca_tbs *tbs) +{ + *tbs = calloc(1, sizeof(**tbs)); + if (*tbs == NULL) + return ENOMEM; + + return 0; +} + +/** + * Free an To Be Signed object. + * + * @param tbs object to free. + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION void HX509_LIB_CALL +hx509_ca_tbs_free(hx509_ca_tbs *tbs) +{ + if (tbs == NULL || *tbs == NULL) + return; + + free_SubjectPublicKeyInfo(&(*tbs)->spki); + free_CertificatePolicies(&(*tbs)->cps); + free_PolicyMappings(&(*tbs)->pms); + free_GeneralNames(&(*tbs)->san); + free_ExtKeyUsage(&(*tbs)->eku); + der_free_heim_integer(&(*tbs)->serial); + free_CRLDistributionPoints(&(*tbs)->crldp); + der_free_bit_string(&(*tbs)->subjectUniqueID); + der_free_bit_string(&(*tbs)->issuerUniqueID); + if ((*tbs)->subject) + hx509_name_free(&(*tbs)->subject); + if ((*tbs)->sigalg) { + free_AlgorithmIdentifier((*tbs)->sigalg); + free((*tbs)->sigalg); + } + + memset(*tbs, 0, sizeof(**tbs)); + free(*tbs); + *tbs = NULL; +} + +/** + * Set the absolute time when the certificate is valid from. If not + * set the current time will be used. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param t time the certificated will start to be valid + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_notBefore(hx509_context context, + hx509_ca_tbs tbs, + time_t t) +{ + tbs->notBefore = t; + return 0; +} + +/** + * Set the absolute time when the certificate is valid to. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param t time when the certificate will expire + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_notAfter(hx509_context context, + hx509_ca_tbs tbs, + time_t t) +{ + tbs->notAfter = t; + return 0; +} + +/** + * Set the relative time when the certificiate is going to expire. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param delta seconds to the certificate is going to expire. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_notAfter_lifetime(hx509_context context, + hx509_ca_tbs tbs, + time_t delta) +{ + return hx509_ca_tbs_set_notAfter(context, tbs, time(NULL) + delta); +} + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_pkinit_max_life(hx509_context context, + hx509_ca_tbs tbs, + time_t max_life) +{ + tbs->pkinitTicketMaxLife = max_life; + return 0; +} + +static const struct units templatebits[] = { + { "ExtendedKeyUsage", HX509_CA_TEMPLATE_EKU }, + { "KeyUsage", HX509_CA_TEMPLATE_KU }, + { "SPKI", HX509_CA_TEMPLATE_SPKI }, + { "notAfter", HX509_CA_TEMPLATE_NOTAFTER }, + { "notBefore", HX509_CA_TEMPLATE_NOTBEFORE }, + { "serial", HX509_CA_TEMPLATE_SERIAL }, + { "subject", HX509_CA_TEMPLATE_SUBJECT }, + { "pkinitMaxLife", HX509_CA_TEMPLATE_PKINIT_MAX_LIFE }, + { NULL, 0 } +}; + +/** + * Make of template units, use to build flags argument to + * hx509_ca_tbs_set_template() with parse_units(). + * + * @return an units structure. + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION const struct units * HX509_LIB_CALL +hx509_ca_tbs_template_units(void) +{ + return templatebits; +} + +/** + * Initialize the to-be-signed certificate object from a template certificate. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param flags bit field selecting what to copy from the template + * certificate. + * @param cert template certificate. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_template(hx509_context context, + hx509_ca_tbs tbs, + int flags, + hx509_cert cert) +{ + int ret; + + if (flags & HX509_CA_TEMPLATE_SUBJECT) { + if (tbs->subject) + hx509_name_free(&tbs->subject); + ret = hx509_cert_get_subject(cert, &tbs->subject); + if (ret) { + hx509_set_error_string(context, 0, ret, + "Failed to get subject from template"); + return ret; + } + } + if (flags & HX509_CA_TEMPLATE_SERIAL) { + der_free_heim_integer(&tbs->serial); + ret = hx509_cert_get_serialnumber(cert, &tbs->serial); + tbs->flags.serial = !ret; + if (ret) { + hx509_set_error_string(context, 0, ret, + "Failed to copy serial number"); + return ret; + } + } + if (flags & HX509_CA_TEMPLATE_NOTBEFORE) + tbs->notBefore = hx509_cert_get_notBefore(cert); + if (flags & HX509_CA_TEMPLATE_NOTAFTER) + tbs->notAfter = hx509_cert_get_notAfter(cert); + if (flags & HX509_CA_TEMPLATE_SPKI) { + free_SubjectPublicKeyInfo(&tbs->spki); + ret = hx509_cert_get_SPKI(context, cert, &tbs->spki); + tbs->flags.key = !ret; + if (ret) + return ret; + } + if (flags & HX509_CA_TEMPLATE_KU) { + ret = _hx509_cert_get_keyusage(context, cert, &tbs->ku); + if (ret) + return ret; + } + if (flags & HX509_CA_TEMPLATE_EKU) { + ExtKeyUsage eku; + size_t i; + ret = _hx509_cert_get_eku(context, cert, &eku); + if (ret) + return ret; + for (i = 0; i < eku.len; i++) { + ret = hx509_ca_tbs_add_eku(context, tbs, &eku.val[i]); + if (ret) { + free_ExtKeyUsage(&eku); + return ret; + } + } + free_ExtKeyUsage(&eku); + } + if (flags & HX509_CA_TEMPLATE_PKINIT_MAX_LIFE) { + time_t max_life; + + if ((max_life = hx509_cert_get_pkinit_max_life(context, cert, 0)) > 0) + hx509_ca_tbs_set_pkinit_max_life(context, tbs, max_life); + } + return 0; +} + +/** + * Make the to-be-signed certificate object a CA certificate. If the + * pathLenConstraint is negative path length constraint is used. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param pathLenConstraint path length constraint, negative, no + * constraint. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_ca(hx509_context context, + hx509_ca_tbs tbs, + int pathLenConstraint) +{ + tbs->flags.ca = 1; + tbs->pathLenConstraint = pathLenConstraint; + return 0; +} + +/** + * Make the to-be-signed certificate object a proxy certificate. If the + * pathLenConstraint is negative path length constraint is used. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param pathLenConstraint path length constraint, negative, no + * constraint. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_proxy(hx509_context context, + hx509_ca_tbs tbs, + int pathLenConstraint) +{ + tbs->flags.proxy = 1; + tbs->pathLenConstraint = pathLenConstraint; + return 0; +} + + +/** + * Make the to-be-signed certificate object a windows domain controller certificate. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_domaincontroller(hx509_context context, + hx509_ca_tbs tbs) +{ + tbs->flags.domaincontroller = 1; + return 0; +} + +/** + * Set the subject public key info (SPKI) in the to-be-signed certificate + * object. SPKI is the public key and key related parameters in the + * certificate. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param spki subject public key info to use for the to-be-signed certificate object. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_spki(hx509_context context, + hx509_ca_tbs tbs, + const SubjectPublicKeyInfo *spki) +{ + int ret; + free_SubjectPublicKeyInfo(&tbs->spki); + ret = copy_SubjectPublicKeyInfo(spki, &tbs->spki); + tbs->flags.key = !ret; + return ret; +} + +/** + * Set the serial number to use for to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param serialNumber serial number to use for the to-be-signed + * certificate object. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_serialnumber(hx509_context context, + hx509_ca_tbs tbs, + const heim_integer *serialNumber) +{ + int ret; + der_free_heim_integer(&tbs->serial); + ret = der_copy_heim_integer(serialNumber, &tbs->serial); + tbs->flags.serial = !ret; + return ret; +} + +/** + * Copy elements of a CSR into a TBS, but only if all of them are authorized. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param req CSR + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_from_csr(hx509_context context, + hx509_ca_tbs tbs, + hx509_request req) +{ + hx509_san_type san_type; + heim_oid oid = { 0, NULL }; + KeyUsage ku; + size_t i; + char *s = NULL; + int ret; + + if (hx509_request_count_unauthorized(req)) { + hx509_set_error_string(context, 0, EACCES, + "Some certificate features requested in the CSR were not authorized"); + return EACCES; + } + + ret = hx509_request_get_ku(context, req, &ku); + if (ret == 0 && KeyUsage2int(ku)) + ret = hx509_ca_tbs_add_ku(context, tbs, ku); + + for (i = 0; ret == 0; i++) { + free(s); s = NULL; + der_free_oid(&oid); + ret = hx509_request_get_eku(req, i, &s); + if (ret == 0) + ret = der_parse_heim_oid(s, ".", &oid); + if (ret == 0) + ret = hx509_ca_tbs_add_eku(context, tbs, &oid); + } + if (ret == HX509_NO_ITEM) + ret = 0; + + for (i = 0; ret == 0; i++) { + free(s); s = NULL; + ret = hx509_request_get_san(req, i, &san_type, &s); + if (ret == 0) + ret = hx509_ca_tbs_add_san(context, tbs, san_type, s); + } + if (ret == HX509_NO_ITEM) + ret = 0; + + der_free_oid(&oid); + free(s); + return ret; +} + +/** + * An an extended key usage to the to-be-signed certificate object. + * Duplicates will detected and not added. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param oid extended key usage to add. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_ku(hx509_context context, + hx509_ca_tbs tbs, + KeyUsage ku) +{ + tbs->ku = ku; + return 0; +} + +/** + * An an extended key usage to the to-be-signed certificate object. + * Duplicates will detected and not added. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param oid extended key usage to add. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_eku(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *oid) +{ + void *ptr; + int ret; + unsigned i; + + /* search for duplicates */ + for (i = 0; i < tbs->eku.len; i++) { + if (der_heim_oid_cmp(oid, &tbs->eku.val[i]) == 0) + return 0; + } + + ptr = realloc(tbs->eku.val, sizeof(tbs->eku.val[0]) * (tbs->eku.len + 1)); + if (ptr == NULL) { + hx509_set_error_string(context, 0, ENOMEM, "out of memory"); + return ENOMEM; + } + tbs->eku.val = ptr; + ret = der_copy_oid(oid, &tbs->eku.val[tbs->eku.len]); + if (ret) { + hx509_set_error_string(context, 0, ret, "out of memory"); + return ret; + } + tbs->eku.len += 1; + return 0; +} + +/** + * Add a certificate policy to the to-be-signed certificate object. Duplicates + * will detected and not added. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param oid policy OID. + * @param cps_uri CPS URI to qualify policy with. + * @param user_notice user notice display text to qualify policy with. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_pol(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *oid, + const char *cps_uri, + const char *user_notice) +{ + PolicyQualifierInfos pqis; + PolicyQualifierInfo pqi; + PolicyInformation pi; + size_t i, size; + int ret = 0; + + /* search for duplicates */ + for (i = 0; i < tbs->cps.len; i++) { + if (der_heim_oid_cmp(oid, &tbs->cps.val[i].policyIdentifier) == 0) + return 0; + } + + memset(&pi, 0, sizeof(pi)); + memset(&pqi, 0, sizeof(pqi)); + memset(&pqis, 0, sizeof(pqis)); + + pi.policyIdentifier = *oid; + if (cps_uri) { + CPSuri uri; + + uri.length = strlen(cps_uri); + uri.data = (void *)(uintptr_t)cps_uri; + pqi.policyQualifierId = asn1_oid_id_pkix_qt_cps; + + ASN1_MALLOC_ENCODE(CPSuri, + pqi.qualifier.data, + pqi.qualifier.length, + &uri, &size, ret); + if (ret == 0) { + ret = add_PolicyQualifierInfos(&pqis, &pqi); + free_heim_any(&pqi.qualifier); + } + } + if (ret == 0 && user_notice) { + DisplayText dt; + UserNotice un; + + dt.element = choice_DisplayText_utf8String; + dt.u.utf8String = (void *)(uintptr_t)user_notice; + un.explicitText = &dt; + un.noticeRef = 0; + + pqi.policyQualifierId = asn1_oid_id_pkix_qt_unotice; + ASN1_MALLOC_ENCODE(UserNotice, + pqi.qualifier.data, + pqi.qualifier.length, + &un, &size, ret); + if (ret == 0) { + ret = add_PolicyQualifierInfos(&pqis, &pqi); + free_heim_any(&pqi.qualifier); + } + } + + pi.policyQualifiers = pqis.len ? &pqis : 0; + + if (ret == 0) + ret = add_CertificatePolicies(&tbs->cps, &pi); + + free_PolicyQualifierInfos(&pqis); + return ret; +} + +/** + * Add a certificate policy mapping to the to-be-signed certificate object. + * Duplicates will detected and not added. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param issuer issuerDomainPolicy policy OID. + * @param subject subjectDomainPolicy policy OID. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_pol_mapping(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *issuer, + const heim_oid *subject) +{ + PolicyMapping pm; + size_t i; + + /* search for duplicates */ + for (i = 0; i < tbs->pms.len; i++) { + PolicyMapping *pmp = &tbs->pms.val[i]; + if (der_heim_oid_cmp(issuer, &pmp->issuerDomainPolicy) == 0 && + der_heim_oid_cmp(subject, &pmp->subjectDomainPolicy) == 0) + return 0; + } + + memset(&pm, 0, sizeof(pm)); + pm.issuerDomainPolicy = *issuer; + pm.subjectDomainPolicy = *subject; + return add_PolicyMappings(&tbs->pms, &pm); +} + +/** + * Add CRL distribution point URI to the to-be-signed certificate + * object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param uri uri to the CRL. + * @param issuername name of the issuer. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_crl_dp_uri(hx509_context context, + hx509_ca_tbs tbs, + const char *uri, + hx509_name issuername) +{ + DistributionPointName dpn; + DistributionPoint dp; + GeneralNames crlissuer; + GeneralName gn, ign; + Name in; + int ret; + + memset(&dp, 0, sizeof(dp)); + memset(&gn, 0, sizeof(gn)); + memset(&ign, 0, sizeof(ign)); + memset(&in, 0, sizeof(in)); + gn.element = choice_GeneralName_uniformResourceIdentifier; + gn.u.uniformResourceIdentifier.data = rk_UNCONST(uri); + gn.u.uniformResourceIdentifier.length = strlen(uri); + dpn.element = choice_DistributionPointName_fullName; + dpn.u.fullName.len = 1; + dpn.u.fullName.val = &gn; + dp.distributionPoint = &dpn; + + if (issuername) { + ign.element = choice_GeneralName_directoryName; + ret = hx509_name_to_Name(issuername, &ign.u.directoryName); + if (ret) { + hx509_set_error_string(context, 0, ret, "out of memory"); + return ret; + } + crlissuer.len = 1; + crlissuer.val = &ign; + dp.cRLIssuer = &crlissuer; + } + + ret = add_CRLDistributionPoints(&tbs->crldp, &dp); + if (issuername) + free_Name(&ign.u.directoryName); + + if (ret) + hx509_set_error_string(context, 0, ret, "out of memory"); + return ret; +} + +/** + * Add Subject Alternative Name otherName to the to-be-signed + * certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param oid the oid of the OtherName. + * @param os data in the other name. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_otherName(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *oid, + const heim_octet_string *os) +{ + GeneralName gn; + + memset(&gn, 0, sizeof(gn)); + gn.element = choice_GeneralName_otherName; + gn.u.otherName.type_id = *oid; + gn.u.otherName.value = *os; + + return add_GeneralNames(&tbs->san, &gn); +} + +static +int +dequote_strndup(hx509_context context, const char *in, size_t len, char **out) +{ + size_t i, k; + char *s; + + *out = NULL; + if ((s = malloc(len + 1)) == NULL) { + hx509_set_error_string(context, 0, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + + for (k = i = 0; i < len; i++) { + if (in[i] == '\\') { + switch (in[++i]) { + case 't': s[k++] = '\t'; break; + case 'b': s[k++] = '\b'; break; + case 'n': s[k++] = '\n'; break; + case '0': + for (i++; i < len; i++) { + if (in[i] == '\0') + break; + if (in[i++] == '\\' && in[i] == '0') + continue; + hx509_set_error_string(context, 0, + HX509_PARSING_NAME_FAILED, + "embedded NULs not supported in " + "PKINIT SANs"); + free(s); + return HX509_PARSING_NAME_FAILED; + } + break; + case '\0': + hx509_set_error_string(context, 0, + HX509_PARSING_NAME_FAILED, + "trailing unquoted backslashes not " + "allowed in PKINIT SANs"); + free(s); + return HX509_PARSING_NAME_FAILED; + default: s[k++] = in[i]; break; + } + } else { + s[k++] = in[i]; + } + } + s[k] = '\0'; + + *out = s; + return 0; +} + +int +_hx509_make_pkinit_san(hx509_context context, + const char *principal, + heim_octet_string *os) +{ + KRB5PrincipalName p; + size_t size; + int ret; + + os->data = NULL; + os->length = 0; + memset(&p, 0, sizeof(p)); + + /* Parse principal */ + { + const char *str, *str_start; + size_t n, i; + + /* Count number of components */ + n = 1; + for (str = principal; *str != '\0' && *str != '@'; str++) { + if (*str == '\\') { + if (str[1] == '\0') { + ret = HX509_PARSING_NAME_FAILED; + hx509_set_error_string(context, 0, ret, + "trailing \\ in principal name"); + goto out; + } + str++; + } else if(*str == '/') { + n++; + } else if(*str == '@') { + break; + } + } + if (*str != '@') { + /* Note that we allow the realm to be empty */ + ret = HX509_PARSING_NAME_FAILED; + hx509_set_error_string(context, 0, ret, "Missing @ in principal"); + goto out; + }; + + p.principalName.name_string.val = + calloc(n, sizeof(*p.principalName.name_string.val)); + if (p.principalName.name_string.val == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "malloc: out of memory"); + goto out; + } + p.principalName.name_string.len = n; + p.principalName.name_type = KRB5_NT_PRINCIPAL; + + for (i = 0, str_start = str = principal; *str != '\0'; str++) { + if (*str=='\\') { + str++; + } else if(*str == '/') { + /* Note that we allow components to be empty */ + ret = dequote_strndup(context, str_start, str - str_start, + &p.principalName.name_string.val[i++]); + if (ret) + goto out; + str_start = str + 1; + } else if(*str == '@') { + ret = dequote_strndup(context, str_start, str - str_start, + &p.principalName.name_string.val[i++]); + if (ret == 0) + ret = dequote_strndup(context, str + 1, strlen(str + 1), &p.realm); + if (ret) + goto out; + break; + } + } + } + + ASN1_MALLOC_ENCODE(KRB5PrincipalName, os->data, os->length, &p, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != os->length) + _hx509_abort("internal ASN.1 encoder error"); + +out: + free_KRB5PrincipalName(&p); + return ret; +} + +static int +add_ia5string_san(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *oid, + const char *string) +{ + SRVName ustring; + heim_octet_string os; + size_t size; + int ret; + + ustring.data = (void *)(uintptr_t)string; + ustring.length = strlen(string); + + os.length = 0; + os.data = NULL; + + ASN1_MALLOC_ENCODE(SRVName, os.data, os.length, &ustring, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + return ret; + } + if (size != os.length) + _hx509_abort("internal ASN.1 encoder error"); + + ret = hx509_ca_tbs_add_san_otherName(context, tbs, oid, &os); + free(os.data); + return ret; +} + +/** + * Add DNSSRV Subject Alternative Name to the to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param dnssrv An ASCII string of the for _Service.Name. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_dnssrv(hx509_context context, + hx509_ca_tbs tbs, + const char *dnssrv) +{ + size_t i, len; + + /* Minimal DNSSRV input validation */ + if (dnssrv == 0 || dnssrv[0] != '_') { + hx509_set_error_string(context, 0, EINVAL, "Invalid DNSSRV name"); + return EINVAL; + } + for (i = 1, len = strlen(dnssrv); i < len; i++) { + if (dnssrv[i] == '.' && dnssrv[i + 1] != '\0') + break; + } + if (i == len) { + hx509_set_error_string(context, 0, EINVAL, "Invalid DNSSRV name"); + return EINVAL; + } + + return add_ia5string_san(context, tbs, + &asn1_oid_id_pkix_on_dnsSRV, dnssrv); +} + +/** + * Add Kerberos Subject Alternative Name to the to-be-signed + * certificate object. The principal string is a UTF8 string. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param principal Kerberos principal to add to the certificate. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_pkinit(hx509_context context, + hx509_ca_tbs tbs, + const char *principal) +{ + heim_octet_string os; + int ret; + + ret = _hx509_make_pkinit_san(context, principal, &os); + if (ret == 0) + ret = hx509_ca_tbs_add_san_otherName(context, tbs, + &asn1_oid_id_pkinit_san, &os); + free(os.data); + return ret; +} + +/* + * + */ + +static int +add_utf8_san(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *oid, + const char *string) +{ + const PKIXXmppAddr ustring = (const PKIXXmppAddr)(uintptr_t)string; + heim_octet_string os; + size_t size; + int ret; + + os.length = 0; + os.data = NULL; + + ASN1_MALLOC_ENCODE(PKIXXmppAddr, os.data, os.length, &ustring, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + return ret; + } + if (size != os.length) + _hx509_abort("internal ASN.1 encoder error"); + + ret = hx509_ca_tbs_add_san_otherName(context, tbs, oid, &os); + free(os.data); + return ret; +} + +/** + * Add Microsoft UPN Subject Alternative Name to the to-be-signed + * certificate object. The principal string is a UTF8 string. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param principal Microsoft UPN string. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_ms_upn(hx509_context context, + hx509_ca_tbs tbs, + const char *principal) +{ + return add_utf8_san(context, tbs, &asn1_oid_id_pkinit_ms_san, principal); +} + +/** + * Add a Jabber/XMPP jid Subject Alternative Name to the to-be-signed + * certificate object. The jid is an UTF8 string. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param jid string of an a jabber id in UTF8. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_jid(hx509_context context, + hx509_ca_tbs tbs, + const char *jid) +{ + return add_utf8_san(context, tbs, &asn1_oid_id_pkix_on_xmppAddr, jid); +} + + +/** + * Add a Subject Alternative Name hostname to to-be-signed certificate + * object. A domain match starts with ., an exact match does not. + * + * Example of a an domain match: .domain.se matches the hostname + * host.domain.se. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param dnsname a hostame. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_hostname(hx509_context context, + hx509_ca_tbs tbs, + const char *dnsname) +{ + GeneralName gn; + + memset(&gn, 0, sizeof(gn)); + gn.element = choice_GeneralName_dNSName; + gn.u.dNSName.data = rk_UNCONST(dnsname); + gn.u.dNSName.length = strlen(dnsname); + + return add_GeneralNames(&tbs->san, &gn); +} + +/** + * Add a Subject Alternative Name rfc822 (email address) to + * to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param rfc822Name a string to a email address. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_rfc822name(hx509_context context, + hx509_ca_tbs tbs, + const char *rfc822Name) +{ + GeneralName gn; + + memset(&gn, 0, sizeof(gn)); + gn.element = choice_GeneralName_rfc822Name; + gn.u.rfc822Name.data = rk_UNCONST(rfc822Name); + gn.u.rfc822Name.length = strlen(rfc822Name); + + return add_GeneralNames(&tbs->san, &gn); +} + +/* + * PermanentIdentifier is one SAN for naming devices with TPMs after their + * endorsement keys or EK certificates. See TPM 2.0 Keys for Device Identity + * and Attestation, Version 1.00, Revision 2, 9/17/2020 (DRAFT). + * + * The text on the form of permanent identifiers for TPM endorsement keys sans + * certificates is clearly problematic, saying: "When the TPM does not have an + * EK certificate, the identifierValue is a digest of a concatenation of the + * UTF8 string “EkPubkey” (terminating NULL not included) with the binary EK + * public key", but since arbitrary binary is not necessarily valid UTF-8... + * and since NULs embedded in UTF-8 might be OK in some contexts but really + * isn't in C (and Heimdal's ASN.1 compiler does not allow NULs in the + * middle of strings)... That just cannot be correct. Since elsewhere the TCG + * specs use the hex encoding of the SHA-256 digest of the DER encoding of + * public keys, that's what we should support in Heimdal, and maybe send in a + * comment. + * + * Also, even where one should use hex encoding of the SHA-256 digest of the + * DER encoding of public keys, how should the public keys be represented? + * Presumably as SPKIs, with all the required parameters and no more. + */ + +/** + * Add a Subject Alternative Name of PermanentIdentifier type to a to-be-signed + * certificate object. The permanent identifier form for TPM endorsement key + * certificates is the hex encoding of the SHA-256 digest of the DER encoding + * of the certificate. The permanent identifier form for TPM endorsement keys + * are of the form "EkPubkey", where the form of is + * not well specified at this point. It is the caller's responsibility to + * format the identifierValue. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param str permanent identifier name in the form "[]:[]". + * @param assigner The OID of an assigner. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_permanentIdentifier_string(hx509_context context, + hx509_ca_tbs tbs, + const char *str) +{ + const heim_oid *found = NULL; + heim_oid oid; + const char *oidstr, *id; + char *freeme, *p; + int ret; + + memset(&oid, 0, sizeof(oid)); + if ((freeme = strdup(str)) == NULL) + return hx509_enomem(context); + + oidstr = freeme; + p = strchr(freeme, ':'); + if (!p) { + hx509_set_error_string(context, 0, EINVAL, + "Invalid PermanentIdentifier string (should be \"[]:[]\")"); + free(freeme); + return EINVAL; + } + if (p) { + *(p++) = '\0'; + id = p; + } + if (oidstr[0] != '\0') { + ret = der_find_heim_oid_by_name(oidstr, &found); + if (ret) { + ret = der_parse_heim_oid(oidstr, " .", &oid); + if (ret == 0) + found = &oid; + } + } + ret = hx509_ca_tbs_add_san_permanentIdentifier(context, tbs, id, found); + if (found == &oid) + der_free_oid(&oid); + free(freeme); + return ret; +} + +/** + * Add a Subject Alternative Name of PermanentIdentifier type to a to-be-signed + * certificate object. The permanent identifier form for TPM endorsement key + * certificates is the hex encoding of the SHA-256 digest of the DER encoding + * of the certificate. The permanent identifier form for TPM endorsement keys + * are of the form "EkPubkey", where the form of is + * not well specified at this point. It is the caller's responsibility to + * format the identifierValue. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param identifierValue The permanent identifier name. + * @param assigner The OID of an assigner. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_permanentIdentifier(hx509_context context, + hx509_ca_tbs tbs, + const char *identifierValue, + const heim_oid *assigner) +{ + PermanentIdentifier pi; + heim_utf8_string s = (void *)(uintptr_t)identifierValue; + heim_octet_string os; + size_t size; + int ret; + + pi.identifierValue = &s; + pi.assigner = (heim_oid*)(uintptr_t)assigner; + os.length = 0; + os.data = NULL; + + ASN1_MALLOC_ENCODE(PermanentIdentifier, os.data, os.length, &pi, &size, + ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + return ret; + } + if (size != os.length) + _hx509_abort("internal ASN.1 encoder error"); + + ret = hx509_ca_tbs_add_san_otherName(context, tbs, + &asn1_oid_id_pkix_on_permanentIdentifier, + &os); + free(os.data); + return ret; +} + +/** + * Add a Subject Alternative Name of HardwareModuleName type to a to-be-signed + * certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param str a string of the form ":". + * @param hwserial The serial number. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_hardwareModuleName_string(hx509_context context, + hx509_ca_tbs tbs, + const char *str) +{ + const heim_oid *found = NULL; + heim_oid oid; + const char *oidstr, *sno; + char *freeme, *p; + int ret; + + memset(&oid, 0, sizeof(oid)); + if ((freeme = strdup(str)) == NULL) + return hx509_enomem(context); + + oidstr = freeme; + p = strchr(freeme, ':'); + if (!p) { + hx509_set_error_string(context, 0, EINVAL, + "Invalid HardwareModuleName string (should be " + "\":\")"); + free(freeme); + return EINVAL; + } + if (p) { + *(p++) = '\0'; + sno = p; + } + if (oidstr[0] == '\0') { + found = &asn1_oid_tcg_tpm20; + } else { + ret = der_find_heim_oid_by_name(oidstr, &found); + if (ret) { + ret = der_parse_heim_oid(oidstr, " .", &oid); + if (ret == 0) + found = &oid; + } + } + if (!found) { + hx509_set_error_string(context, 0, EINVAL, + "Could not resolve or parse OID \"%s\"", + oidstr); + free(freeme); + return EINVAL; + } + ret = hx509_ca_tbs_add_san_hardwareModuleName(context, tbs, found, sno); + if (found == &oid) + der_free_oid(&oid); + free(freeme); + return ret; +} + +/** + * Add a Subject Alternative Name of HardwareModuleName type to a to-be-signed + * certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param hwtype The hardwar module type (e.g., `&asn1_oid_tcg_tpm20'). + * @param hwserial The serial number. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_hardwareModuleName(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *hwtype, + const char *hwserial) +{ + HardwareModuleName hm; + heim_octet_string os; + size_t size; + int ret; + + hm.hwType = *hwtype; + hm.hwSerialNum.data = (void *)(uintptr_t)hwserial; + hm.hwSerialNum.length = strlen(hwserial); + os.length = 0; + os.data = NULL; + + ASN1_MALLOC_ENCODE(HardwareModuleName, os.data, os.length, &hm, &size, + ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + return ret; + } + if (size != os.length) + _hx509_abort("internal ASN.1 encoder error"); + + ret = hx509_ca_tbs_add_san_otherName(context, tbs, + &asn1_oid_id_on_hardwareModuleName, + &os); + free(os.data); + return ret; +} + +/** + * Add a Subject Alternative Name of the given type to the + * to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param rfc822Name a string to a email address. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san(hx509_context context, + hx509_ca_tbs tbs, + hx509_san_type type, + const char *s) +{ + switch (type) { + case HX509_SAN_TYPE_EMAIL: + return hx509_ca_tbs_add_san_rfc822name(context, tbs, s); + case HX509_SAN_TYPE_DNSNAME: + return hx509_ca_tbs_add_san_hostname(context, tbs, s); + case HX509_SAN_TYPE_DN: + return ENOTSUP; + case HX509_SAN_TYPE_REGISTERED_ID: + return ENOTSUP; + case HX509_SAN_TYPE_XMPP: + return hx509_ca_tbs_add_san_jid(context, tbs, s); + case HX509_SAN_TYPE_PKINIT: + return hx509_ca_tbs_add_san_pkinit(context, tbs, s); + case HX509_SAN_TYPE_MS_UPN: + return hx509_ca_tbs_add_san_ms_upn(context, tbs, s); + default: + return ENOTSUP; + } +} + +/** + * Set the subject name of a to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param subject the name to set a subject. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_subject(hx509_context context, + hx509_ca_tbs tbs, + hx509_name subject) +{ + if (tbs->subject) + hx509_name_free(&tbs->subject); + return hx509_name_copy(context, subject, &tbs->subject); +} + +/** + * Set the issuerUniqueID and subjectUniqueID + * + * These are only supposed to be used considered with version 2 + * certificates, replaced by the two extensions SubjectKeyIdentifier + * and IssuerKeyIdentifier. This function is to allow application + * using legacy protocol to issue them. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param issuerUniqueID to be set + * @param subjectUniqueID to be set + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_unique(hx509_context context, + hx509_ca_tbs tbs, + const heim_bit_string *subjectUniqueID, + const heim_bit_string *issuerUniqueID) +{ + int ret; + + der_free_bit_string(&tbs->subjectUniqueID); + der_free_bit_string(&tbs->issuerUniqueID); + + if (subjectUniqueID) { + ret = der_copy_bit_string(subjectUniqueID, &tbs->subjectUniqueID); + if (ret) + return ret; + } + + if (issuerUniqueID) { + ret = der_copy_bit_string(issuerUniqueID, &tbs->issuerUniqueID); + if (ret) + return ret; + } + + return 0; +} + +/** + * Expand the the subject name in the to-be-signed certificate object + * using hx509_name_expand(). + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param env environment variable to expand variables in the subject + * name, see hx509_env_init(). + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_subject_expand(hx509_context context, + hx509_ca_tbs tbs, + hx509_env env) +{ + return hx509_name_expand(context, tbs->subject, env); +} + +/** + * Get the name of a to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * + * @return An hx509 name. + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION hx509_name HX509_LIB_CALL +hx509_ca_tbs_get_name(hx509_ca_tbs tbs) +{ + return tbs->subject; +} + +/** + * Set signature algorithm on the to be signed certificate + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param sigalg signature algorithm to use + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_signature_algorithm(hx509_context context, + hx509_ca_tbs tbs, + const AlgorithmIdentifier *sigalg) +{ + int ret; + + tbs->sigalg = calloc(1, sizeof(*tbs->sigalg)); + if (tbs->sigalg == NULL) { + hx509_set_error_string(context, 0, ENOMEM, "Out of memory"); + return ENOMEM; + } + ret = copy_AlgorithmIdentifier(sigalg, tbs->sigalg); + if (ret) { + free(tbs->sigalg); + tbs->sigalg = NULL; + return ret; + } + return 0; +} + +/* + * + */ + +static int +add_extension(hx509_context context, + TBSCertificate *tbsc, + int critical_flag, + const heim_oid *oid, + const heim_octet_string *data) +{ + Extension ext; + int ret; + + memset(&ext, 0, sizeof(ext)); + + ext.critical = critical_flag; + ret = der_copy_oid(oid, &ext.extnID); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + ret = der_copy_octet_string(data, &ext.extnValue); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + ret = add_Extensions(tbsc->extensions, &ext); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } +out: + free_Extension(&ext); + return ret; +} + +static int +build_proxy_prefix(hx509_context context, const Name *issuer, Name *subject) +{ + char *tstr; + time_t t; + int ret; + + ret = copy_Name(issuer, subject); + if (ret) { + hx509_set_error_string(context, 0, ret, + "Failed to copy subject name"); + return ret; + } + + t = time(NULL); + ret = asprintf(&tstr, "ts-%lu", (unsigned long)t); + if (ret == -1 || tstr == NULL) { + hx509_set_error_string(context, 0, ENOMEM, + "Failed to copy subject name"); + return ENOMEM; + } + /* prefix with CN=,...*/ + ret = _hx509_name_modify(context, subject, 1, &asn1_oid_id_at_commonName, tstr); + free(tstr); + if (ret) + free_Name(subject); + return ret; +} + +static int +ca_sign(hx509_context context, + hx509_ca_tbs tbs, + hx509_private_key signer, + const AuthorityKeyIdentifier *ai, + const Name *issuername, + hx509_cert *certificate) +{ + heim_error_t error = NULL; + heim_octet_string data; + Certificate c; + TBSCertificate *tbsc; + size_t size; + int ret; + const AlgorithmIdentifier *sigalg; + time_t notBefore; + time_t notAfter; + + sigalg = tbs->sigalg; + if (sigalg == NULL) + sigalg = _hx509_crypto_default_sig_alg; + + memset(&c, 0, sizeof(c)); + + /* + * Default values are: Valid since 24h ago, valid one year into + * the future, KeyUsage digitalSignature and keyEncipherment set, + * and keyCertSign for CA certificates. + */ + notBefore = tbs->notBefore; + if (notBefore == 0) + notBefore = time(NULL) - 3600 * 24; + notAfter = tbs->notAfter; + if (notAfter == 0) + notAfter = time(NULL) + 3600 * 24 * 365; + + if (tbs->flags.ca) { + tbs->ku.keyCertSign = 1; + tbs->ku.cRLSign = 1; + } else if (KeyUsage2int(tbs->ku) == 0) { + tbs->ku.digitalSignature = 1; + tbs->ku.keyEncipherment = 1; + } + + /* + * + */ + + tbsc = &c.tbsCertificate; + + /* Default subject Name to empty */ + if (tbs->subject == NULL && + (ret = hx509_empty_name(context, &tbs->subject))) + return ret; + + /* Sanity checks */ + if (tbs->flags.key == 0) { + ret = EINVAL; + hx509_set_error_string(context, 0, ret, "No public key set"); + return ret; + } + /* + * Don't put restrictions on proxy certificate's subject name, it + * will be generated below. + */ + if (!tbs->flags.proxy) { + if (hx509_name_is_null_p(tbs->subject) && tbs->san.len == 0) { + hx509_set_error_string(context, 0, EINVAL, + "Empty subject and no SubjectAltNames"); + return EINVAL; + } + } + if (tbs->flags.ca && tbs->flags.proxy) { + hx509_set_error_string(context, 0, EINVAL, "Can't be proxy and CA " + "at the same time"); + return EINVAL; + } + if (tbs->flags.proxy) { + if (tbs->san.len > 0) { + hx509_set_error_string(context, 0, EINVAL, + "Proxy certificate is not allowed " + "to have SubjectAltNames"); + return EINVAL; + } + } + + /* version [0] Version OPTIONAL, -- EXPLICIT nnn DEFAULT 1, */ + tbsc->version = calloc(1, sizeof(*tbsc->version)); + if (tbsc->version == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + *tbsc->version = rfc3280_version_3; + /* serialNumber CertificateSerialNumber, */ + if (tbs->flags.serial) { + ret = der_copy_heim_integer(&tbs->serial, &tbsc->serialNumber); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + } else { + /* + * If no explicit serial number is specified, 20 random bytes should be + * sufficiently collision resistant. Since the serial number must be a + * positive integer, ensure minimal ASN.1 DER form by forcing the high + * bit off and the next bit on (thus avoiding an all zero first octet). + */ + tbsc->serialNumber.length = 20; + tbsc->serialNumber.data = malloc(tbsc->serialNumber.length); + if (tbsc->serialNumber.data == NULL){ + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + ret = RAND_bytes(tbsc->serialNumber.data, tbsc->serialNumber.length); + if (ret != 1) { + ret = HX509_CRYPTO_INTERNAL_ERROR; + hx509_set_error_string(context, 0, ret, "Failed to generate random bytes"); + goto out; + } + ((unsigned char *)tbsc->serialNumber.data)[0] &= 0x7f; + ((unsigned char *)tbsc->serialNumber.data)[0] |= 0x40; + } + /* signature AlgorithmIdentifier, */ + ret = copy_AlgorithmIdentifier(sigalg, &tbsc->signature); + if (ret) { + hx509_set_error_string(context, 0, ret, "Failed to copy signature alg"); + goto out; + } + /* issuer Name, */ + if (issuername) + ret = copy_Name(issuername, &tbsc->issuer); + else + ret = hx509_name_to_Name(tbs->subject, &tbsc->issuer); + if (ret) { + hx509_set_error_string(context, 0, ret, "Failed to copy issuer name"); + goto out; + } + /* validity Validity, */ + { + /* + * From RFC 5280, section 4.1.2.5: + * + * CAs conforming to this profile MUST always encode certificate + * validity dates through the year 2049 as UTCTime; certificate validity + * dates in 2050 or later MUST be encoded as GeneralizedTime. + * Conforming applications MUST be able to process validity dates that + * are encoded in either UTCTime or GeneralizedTime. + * + * 2524608000 is seconds since the epoch for 2050-01-01T00:00:00Z. + * + * Both, ...u.generalTime and ...u..utcTime are time_t. + */ + if (notBefore < 1 || (int64_t)notBefore < 2524608000) + tbsc->validity.notBefore.element = choice_Time_utcTime; + else + tbsc->validity.notBefore.element = choice_Time_generalTime; + tbsc->validity.notBefore.u.generalTime = notBefore; + + if (notAfter < 1 || (int64_t)notAfter < 2524608000) + tbsc->validity.notAfter.element = choice_Time_utcTime; + else + tbsc->validity.notAfter.element = choice_Time_generalTime; + tbsc->validity.notAfter.u.generalTime = notAfter; + } + /* subject Name, */ + if (tbs->flags.proxy) { + ret = build_proxy_prefix(context, &tbsc->issuer, &tbsc->subject); + if (ret) + goto out; + } else { + ret = hx509_name_to_Name(tbs->subject, &tbsc->subject); + if (ret) { + hx509_set_error_string(context, 0, ret, + "Failed to copy subject name"); + goto out; + } + } + /* subjectPublicKeyInfo SubjectPublicKeyInfo, */ + ret = copy_SubjectPublicKeyInfo(&tbs->spki, &tbsc->subjectPublicKeyInfo); + if (ret) { + hx509_set_error_string(context, 0, ret, "Failed to copy spki"); + goto out; + } + /* issuerUniqueID [1] IMPLICIT BIT STRING OPTIONAL */ + if (tbs->issuerUniqueID.length) { + tbsc->issuerUniqueID = calloc(1, sizeof(*tbsc->issuerUniqueID)); + if (tbsc->issuerUniqueID == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + ret = der_copy_bit_string(&tbs->issuerUniqueID, tbsc->issuerUniqueID); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + } + /* subjectUniqueID [2] IMPLICIT BIT STRING OPTIONAL */ + if (tbs->subjectUniqueID.length) { + tbsc->subjectUniqueID = calloc(1, sizeof(*tbsc->subjectUniqueID)); + if (tbsc->subjectUniqueID == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + + ret = der_copy_bit_string(&tbs->subjectUniqueID, tbsc->subjectUniqueID); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + } + + /* extensions [3] EXPLICIT Extensions OPTIONAL */ + tbsc->extensions = calloc(1, sizeof(*tbsc->extensions)); + if (tbsc->extensions == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + + /* Add the text BMP string Domaincontroller to the cert */ + if (tbs->flags.domaincontroller) { + data.data = rk_UNCONST("\x1e\x20\x00\x44\x00\x6f\x00\x6d" + "\x00\x61\x00\x69\x00\x6e\x00\x43" + "\x00\x6f\x00\x6e\x00\x74\x00\x72" + "\x00\x6f\x00\x6c\x00\x6c\x00\x65" + "\x00\x72"); + data.length = 34; + + ret = add_extension(context, tbsc, 0, + &asn1_oid_id_ms_cert_enroll_domaincontroller, + &data); + if (ret) + goto out; + } + + /* Add KeyUsage */ + if (KeyUsage2int(tbs->ku) > 0) { + ASN1_MALLOC_ENCODE(KeyUsage, data.data, data.length, + &tbs->ku, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, 1, + &asn1_oid_id_x509_ce_keyUsage, &data); + free(data.data); + if (ret) + goto out; + } + + /* Add ExtendedKeyUsage */ + if (tbs->eku.len > 0) { + ASN1_MALLOC_ENCODE(ExtKeyUsage, data.data, data.length, + &tbs->eku, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, 1, + &asn1_oid_id_x509_ce_extKeyUsage, &data); + free(data.data); + if (ret) + goto out; + } + + /* Add Subject Alternative Name */ + if (tbs->san.len > 0) { + ASN1_MALLOC_ENCODE(GeneralNames, data.data, data.length, + &tbs->san, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + + /* The SAN extension is critical if the subject Name is empty */ + ret = add_extension(context, tbsc, hx509_name_is_null_p(tbs->subject), + &asn1_oid_id_x509_ce_subjectAltName, &data); + free(data.data); + if (ret) + goto out; + } + + /* Add Authority Key Identifier */ + if (ai) { + ASN1_MALLOC_ENCODE(AuthorityKeyIdentifier, data.data, data.length, + ai, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, 0, + &asn1_oid_id_x509_ce_authorityKeyIdentifier, + &data); + free(data.data); + if (ret) + goto out; + } + + /* Add Subject Key Identifier */ + { + SubjectKeyIdentifier si; + unsigned char hash[SHA_DIGEST_LENGTH]; + + { + EVP_MD_CTX *ctx; + + ctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); + EVP_DigestUpdate(ctx, tbs->spki.subjectPublicKey.data, + tbs->spki.subjectPublicKey.length / 8); + EVP_DigestFinal_ex(ctx, hash, NULL); + EVP_MD_CTX_destroy(ctx); + } + + si.data = hash; + si.length = sizeof(hash); + + ASN1_MALLOC_ENCODE(SubjectKeyIdentifier, data.data, data.length, + &si, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, 0, + &asn1_oid_id_x509_ce_subjectKeyIdentifier, + &data); + free(data.data); + if (ret) + goto out; + } + + /* Add BasicConstraints */ + { + BasicConstraints bc; + unsigned int path; + + memset(&bc, 0, sizeof(bc)); + + if (tbs->flags.ca) { + bc.cA = 1; + if (tbs->pathLenConstraint >= 0) { + path = tbs->pathLenConstraint; + bc.pathLenConstraint = &path; + } + } + + ASN1_MALLOC_ENCODE(BasicConstraints, data.data, data.length, + &bc, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + /* Critical if this is a CA */ + ret = add_extension(context, tbsc, tbs->flags.ca, + &asn1_oid_id_x509_ce_basicConstraints, + &data); + free(data.data); + if (ret) + goto out; + } + + /* Add Proxy */ + if (tbs->flags.proxy) { + ProxyCertInfo info; + + memset(&info, 0, sizeof(info)); + + if (tbs->pathLenConstraint >= 0) { + info.pCPathLenConstraint = + malloc(sizeof(*info.pCPathLenConstraint)); + if (info.pCPathLenConstraint == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + *info.pCPathLenConstraint = tbs->pathLenConstraint; + } + + ret = der_copy_oid(&asn1_oid_id_pkix_ppl_inheritAll, + &info.proxyPolicy.policyLanguage); + if (ret) { + free_ProxyCertInfo(&info); + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + + ASN1_MALLOC_ENCODE(ProxyCertInfo, data.data, data.length, + &info, &size, ret); + free_ProxyCertInfo(&info); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, 0, + &asn1_oid_id_pkix_pe_proxyCertInfo, + &data); + free(data.data); + if (ret) + goto out; + } + + /* Add CRL distribution point */ + if (tbs->crldp.len) { + ASN1_MALLOC_ENCODE(CRLDistributionPoints, data.data, data.length, + &tbs->crldp, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, FALSE, + &asn1_oid_id_x509_ce_cRLDistributionPoints, + &data); + free(data.data); + if (ret) + goto out; + } + + /* Add CertificatePolicies */ + if (tbs->cps.len) { + ASN1_MALLOC_ENCODE(CertificatePolicies, data.data, data.length, + &tbs->cps, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, FALSE, + &asn1_oid_id_x509_ce_certificatePolicies, &data); + free(data.data); + if (ret) + goto out; + } + + /* Add PolicyMappings */ + if (tbs->cps.len) { + ASN1_MALLOC_ENCODE(PolicyMappings, data.data, data.length, + &tbs->pms, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, FALSE, + &asn1_oid_id_x509_ce_policyMappings, &data); + free(data.data); + if (ret) + goto out; + } + + /* Add Heimdal PKINIT ticket max life extension */ + if (tbs->pkinitTicketMaxLife > 0) { + ASN1_MALLOC_ENCODE(HeimPkinitPrincMaxLifeSecs, data.data, data.length, + &tbs->pkinitTicketMaxLife, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + if (size != data.length) + _hx509_abort("internal ASN.1 encoder error"); + ret = add_extension(context, tbsc, FALSE, + &asn1_oid_id_heim_ce_pkinit_princ_max_life, &data); + free(data.data); + if (ret) + goto out; + } + + ASN1_MALLOC_ENCODE(TBSCertificate, data.data, data.length,tbsc, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "malloc out of memory"); + goto out; + } + if (data.length != size) + _hx509_abort("internal ASN.1 encoder error"); + + ret = _hx509_create_signature_bitstring(context, + signer, + sigalg, + &data, + &c.signatureAlgorithm, + &c.signatureValue); + free(data.data); + if (ret) + goto out; + + *certificate = hx509_cert_init(context, &c, &error); + if (*certificate == NULL) { + ret = heim_error_get_code(error); + heim_release(error); + goto out; + } + + free_Certificate(&c); + + return 0; + +out: + free_Certificate(&c); + return ret; +} + +static int +get_AuthorityKeyIdentifier(hx509_context context, + const Certificate *certificate, + AuthorityKeyIdentifier *ai) +{ + SubjectKeyIdentifier si; + int ret; + + ret = _hx509_find_extension_subject_key_id(certificate, &si); + if (ret == 0) { + ai->keyIdentifier = calloc(1, sizeof(*ai->keyIdentifier)); + if (ai->keyIdentifier == NULL) { + free_SubjectKeyIdentifier(&si); + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + ret = der_copy_octet_string(&si, ai->keyIdentifier); + free_SubjectKeyIdentifier(&si); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + } else { + GeneralNames gns; + GeneralName gn; + Name name; + + memset(&gn, 0, sizeof(gn)); + memset(&gns, 0, sizeof(gns)); + memset(&name, 0, sizeof(name)); + + ai->authorityCertIssuer = + calloc(1, sizeof(*ai->authorityCertIssuer)); + if (ai->authorityCertIssuer == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + ai->authorityCertSerialNumber = + calloc(1, sizeof(*ai->authorityCertSerialNumber)); + if (ai->authorityCertSerialNumber == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + + /* + * XXX unbreak when asn1 compiler handle IMPLICIT + * + * This is so horrible. + */ + + ret = copy_Name(&certificate->tbsCertificate.subject, &name); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + + memset(&gn, 0, sizeof(gn)); + gn.element = choice_GeneralName_directoryName; + gn.u.directoryName.element = choice_Name_rdnSequence; + gn.u.directoryName.u.rdnSequence = name.u.rdnSequence; + + ret = add_GeneralNames(&gns, &gn); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + + ai->authorityCertIssuer->val = gns.val; + ai->authorityCertIssuer->len = gns.len; + + ret = der_copy_heim_integer(&certificate->tbsCertificate.serialNumber, + ai->authorityCertSerialNumber); + if (ai->authorityCertSerialNumber == NULL) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "Out of memory"); + goto out; + } + } +out: + if (ret) + free_AuthorityKeyIdentifier(ai); + return ret; +} + + +/** + * Sign a to-be-signed certificate object with a issuer certificate. + * + * The caller needs to at least have called the following functions on the + * to-be-signed certificate object: + * - hx509_ca_tbs_init() + * - hx509_ca_tbs_set_subject() + * - hx509_ca_tbs_set_spki() + * + * When done the to-be-signed certificate object should be freed with + * hx509_ca_tbs_free(). + * + * When creating self-signed certificate use hx509_ca_sign_self() instead. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param signer the CA certificate object to sign with (need private key). + * @param certificate return cerificate, free with hx509_cert_free(). + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_sign(hx509_context context, + hx509_ca_tbs tbs, + hx509_cert signer, + hx509_cert *certificate) +{ + const Certificate *signer_cert; + AuthorityKeyIdentifier ai; + int ret; + + memset(&ai, 0, sizeof(ai)); + + signer_cert = _hx509_get_cert(signer); + + ret = get_AuthorityKeyIdentifier(context, signer_cert, &ai); + if (ret) + goto out; + + ret = ca_sign(context, + tbs, + _hx509_cert_private_key(signer), + &ai, + &signer_cert->tbsCertificate.subject, + certificate); + +out: + free_AuthorityKeyIdentifier(&ai); + + return ret; +} + +/** + * Work just like hx509_ca_sign() but signs it-self. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param signer private key to sign with. + * @param certificate return cerificate, free with hx509_cert_free(). + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_sign_self(hx509_context context, + hx509_ca_tbs tbs, + hx509_private_key signer, + hx509_cert *certificate) +{ + return ca_sign(context, + tbs, + signer, + NULL, + NULL, + certificate); +} + +/* + * The following used to be `kdc_issue_certificate()', which was added for + * kx509 support in the kdc, then adapted for bx509d. It now has no + * kdc-specific code and very little krb5-specific code, and is named + * `hx509_ca_issue_certificate()'. + */ + +/* From lib/krb5/principal.c */ +#define princ_num_comp(P) ((P)->principalName.name_string.len) +#define princ_type(P) ((P)->principalName.name_type) +#define princ_comp(P) ((P)->principalName.name_string.val) +#define princ_ncomp(P, N) ((P)->principalName.name_string.val[(N)]) +#define princ_realm(P) ((P)->realm) + +static const char * +princ_get_comp_string(KRB5PrincipalName *principal, unsigned int component) +{ + if (component >= princ_num_comp(principal)) + return NULL; + return princ_ncomp(principal, component); +} +/* XXX Add unparse_name() */ + +typedef enum { + CERT_NOTSUP = 0, + CERT_CLIENT = 1, + CERT_SERVER = 2, + CERT_MIXED = 3 +} cert_type; + +static void +frees(char **s) +{ + free(*s); + *s = NULL; +} + +static heim_error_code +count_sans(hx509_request req, size_t *n) +{ + size_t i; + char *s = NULL; + int ret = 0; + + *n = 0; + for (i = 0; ret == 0; i++) { + hx509_san_type san_type; + + ret = hx509_request_get_san(req, i, &san_type, &s); + if (ret) + break; + switch (san_type) { + case HX509_SAN_TYPE_DNSNAME: + case HX509_SAN_TYPE_EMAIL: + case HX509_SAN_TYPE_XMPP: + case HX509_SAN_TYPE_PKINIT: + case HX509_SAN_TYPE_MS_UPN: + (*n)++; + break; + default: + ret = ENOTSUP; + } + frees(&s); + } + free(s); + return ret == HX509_NO_ITEM ? 0 : ret; +} + +static int +has_sans(hx509_request req) +{ + hx509_san_type san_type; + char *s = NULL; + int ret = hx509_request_get_san(req, 0, &san_type, &s); + + frees(&s); + return ret == HX509_NO_ITEM ? 0 : 1; +} + +static cert_type +characterize_cprinc(hx509_context context, + KRB5PrincipalName *cprinc) +{ + unsigned int ncomp = princ_num_comp(cprinc); + const char *comp1 = princ_get_comp_string(cprinc, 1); + + switch (ncomp) { + case 1: + return CERT_CLIENT; + case 2: + if (strchr(comp1, '.') == NULL) + return CERT_CLIENT; + return CERT_SERVER; + case 3: + if (strchr(comp1, '.')) + return CERT_SERVER; + return CERT_NOTSUP; + default: + return CERT_NOTSUP; + } +} + +/* Characterize request as client or server cert req */ +static cert_type +characterize(hx509_context context, + KRB5PrincipalName *cprinc, + hx509_request req) +{ + heim_error_code ret = 0; + cert_type res = CERT_NOTSUP; + size_t i; + char *s = NULL; + int want_ekus = 0; + + if (!has_sans(req)) + return characterize_cprinc(context, cprinc); + + for (i = 0; ret == 0; i++) { + heim_oid oid; + + frees(&s); + ret = hx509_request_get_eku(req, i, &s); + if (ret) + break; + + want_ekus = 1; + ret = der_parse_heim_oid(s, ".", &oid); + if (ret) + break; + /* + * If the client wants only a server certificate, then we'll be + * willing to issue one that may be longer-lived than the client's + * ticket/token. + * + * There may be other server EKUs, but these are the ones we know + * of. + */ + if (der_heim_oid_cmp(&asn1_oid_id_pkix_kp_serverAuth, &oid) && + der_heim_oid_cmp(&asn1_oid_id_pkix_kp_OCSPSigning, &oid) && + der_heim_oid_cmp(&asn1_oid_id_pkix_kp_secureShellServer, &oid)) + res |= CERT_CLIENT; + else + res |= CERT_SERVER; + der_free_oid(&oid); + } + frees(&s); + if (ret == HX509_NO_ITEM) + ret = 0; + + for (i = 0; ret == 0; i++) { + hx509_san_type san_type; + + frees(&s); + ret = hx509_request_get_san(req, i, &san_type, &s); + if (ret) + break; + switch (san_type) { + case HX509_SAN_TYPE_DNSNAME: + if (!want_ekus) + res |= CERT_SERVER; + break; + case HX509_SAN_TYPE_EMAIL: + case HX509_SAN_TYPE_XMPP: + case HX509_SAN_TYPE_PKINIT: + case HX509_SAN_TYPE_MS_UPN: + if (!want_ekus) + res |= CERT_CLIENT; + break; + default: + ret = ENOTSUP; + } + if (ret) + break; + } + frees(&s); + if (ret == HX509_NO_ITEM) + ret = 0; + return ret ? CERT_NOTSUP : res; +} + +/* + * Get a configuration sub-tree for kx509 based on what's being requested and + * by whom. + * + * We have a number of cases: + * + * - default certificate (no CSR used, or no certificate extensions requested) + * - for client principals + * - for service principals + * - client certificate requested (CSR used and client-y SANs/EKUs requested) + * - server certificate requested (CSR used and server-y SANs/EKUs requested) + * - mixed client/server certificate requested (...) + */ +static heim_error_code +get_cf(hx509_context context, + const heim_config_binding *cf, + heim_log_facility *logf, + hx509_request req, + KRB5PrincipalName *cprinc, + const heim_config_binding **out) +{ + heim_error_code ret; + unsigned int ncomp = princ_num_comp(cprinc); + const char *realm = princ_realm(cprinc); + const char *comp0 = princ_get_comp_string(cprinc, 0); + const char *comp1 = princ_get_comp_string(cprinc, 1); + const char *label = NULL; + const char *svc = NULL; + const char *def = NULL; + cert_type certtype = CERT_NOTSUP; + size_t nsans = 0; + + *out = NULL; + if (ncomp == 0) { + heim_log_msg(context->hcontext, logf, 5, NULL, + "Client principal has no components!"); + hx509_set_error_string(context, 0, ret = ENOTSUP, + "Client principal has no components!"); + return ret; + } + + if ((ret = count_sans(req, &nsans)) || + (certtype = characterize(context, cprinc, req)) == CERT_NOTSUP) { + heim_log_msg(context->hcontext, logf, 5, NULL, + "Could not characterize CSR"); + hx509_set_error_string(context, 0, ret, "Could not characterize CSR"); + return ret; + } + + if (nsans) { + def = "custom"; + /* Client requested some certificate extension, a SAN or EKU */ + switch (certtype) { + case CERT_MIXED: label = "mixed"; break; + case CERT_CLIENT: label = "client"; break; + case CERT_SERVER: label = "server"; break; + default: + hx509_set_error_string(context, 0, ret = ENOTSUP, + "Requested SAN/EKU combination not " + "supported"); + return ret; + } + } else { + def = "default"; + /* Default certificate desired */ + if (ncomp == 1) { + label = "user"; + } else if (ncomp == 2 && strcmp(comp1, "root") == 0) { + label = "root_user"; + } else if (ncomp == 2 && strcmp(comp1, "admin") == 0) { + label = "admin_user"; + } else if (strchr(comp1, '.')) { + label = "hostbased_service"; + svc = comp0; + } else { + label = "other"; + } + } + + *out = heim_config_get_list(context->hcontext, cf, label, svc, NULL); + if (*out) { + ret = 0; + } else { + heim_log_msg(context->hcontext, logf, 3, NULL, + "No configuration for %s %s certificate's realm " + "-> %s -> kx509 -> %s%s%s", def, label, realm, label, + svc ? " -> " : "", svc ? svc : ""); + hx509_set_error_string(context, 0, EACCES, + "No configuration for %s %s certificate's realm " + "-> %s -> kx509 -> %s%s%s", def, label, realm, label, + svc ? " -> " : "", svc ? svc : ""); + } + return ret; +} + + +/* + * Find and set a certificate template using a configuration sub-tree + * appropriate to the requesting principal. + * + * This allows for the specification of the following in configuration: + * + * - certificates as templates, with ${var} tokens in subjectName attribute + * values that will be expanded later + * - a plain string with ${var} tokens to use as the subjectName + * - EKUs + * - whether to include a PKINIT SAN + */ +static heim_error_code +set_template(hx509_context context, + heim_log_facility *logf, + const heim_config_binding *cf, + hx509_ca_tbs tbs) +{ + heim_error_code ret = 0; + const char *cert_template = NULL; + const char *subj_name = NULL; + char **ekus = NULL; + + if (cf == NULL) + return EACCES; /* Can't happen */ + + cert_template = heim_config_get_string(context->hcontext, cf, + "template_cert", NULL); + subj_name = heim_config_get_string(context->hcontext, cf, "subject_name", + NULL); + + if (cert_template) { + hx509_certs certs; + hx509_cert template; + + ret = hx509_certs_init(context, cert_template, 0, NULL, &certs); + if (ret == 0) + ret = hx509_get_one_cert(context, certs, &template); + hx509_certs_free(&certs); + if (ret) { + heim_log_msg(context->hcontext, logf, 1, NULL, + "Failed to load certificate template from %s", + cert_template); + hx509_set_error_string(context, 0, EACCES, + "Failed to load certificate template from " + "%s", cert_template); + return ret; + } + + /* + * Only take the subjectName, the keyUsage, and EKUs from the template + * certificate. + */ + ret = hx509_ca_tbs_set_template(context, tbs, + HX509_CA_TEMPLATE_SUBJECT | + HX509_CA_TEMPLATE_KU | + HX509_CA_TEMPLATE_EKU, + template); + hx509_cert_free(template); + if (ret) + return ret; + } + + if (subj_name) { + hx509_name dn = NULL; + + ret = hx509_parse_name(context, subj_name, &dn); + if (ret == 0) + ret = hx509_ca_tbs_set_subject(context, tbs, dn); + hx509_name_free(&dn); + if (ret) + return ret; + } + + if (cert_template == NULL && subj_name == NULL) { + hx509_name dn = NULL; + + ret = hx509_empty_name(context, &dn); + if (ret == 0) + ret = hx509_ca_tbs_set_subject(context, tbs, dn); + hx509_name_free(&dn); + if (ret) + return ret; + } + + ekus = heim_config_get_strings(context->hcontext, cf, "ekus", NULL); + if (ekus) { + size_t i; + + for (i = 0; ret == 0 && ekus[i]; i++) { + heim_oid oid = { 0, NULL }; + + if ((ret = der_find_or_parse_heim_oid(ekus[i], ".", &oid)) == 0) + ret = hx509_ca_tbs_add_eku(context, tbs, &oid); + der_free_oid(&oid); + } + heim_config_free_strings(ekus); + } + + /* + * XXX A KeyUsage template would be nice, but it needs some smarts to + * remove, e.g., encipherOnly, decipherOnly, keyEncipherment, if the SPKI + * algorithm does not support encryption. The same logic should be added + * to hx509_ca_tbs_set_template()'s HX509_CA_TEMPLATE_KU functionality. + */ + return ret; +} + +/* + * Find and set a certificate template, set "variables" in `env', and add add + * default SANs/EKUs as appropriate. + * + * TODO: + * - lookup a template for the client principal in its HDB entry + * - lookup subjectName, SANs for a principal in its HDB entry + * - lookup a host-based client principal's HDB entry and add its canonical + * name / aliases as dNSName SANs + * (this would have to be if requested by the client, perhaps) + */ +static heim_error_code +set_tbs(hx509_context context, + heim_log_facility *logf, + const heim_config_binding *cf, + hx509_request req, + KRB5PrincipalName *cprinc, + hx509_env *env, + hx509_ca_tbs tbs) +{ + KRB5PrincipalName cprinc_no_realm = *cprinc; + heim_error_code ret; + unsigned int ncomp = princ_num_comp(cprinc); + const char *realm = princ_realm(cprinc); + const char *comp0 = princ_get_comp_string(cprinc, 0); + const char *comp1 = princ_get_comp_string(cprinc, 1); + const char *comp2 = princ_get_comp_string(cprinc, 2); + struct rk_strpool *strpool; + char *princ_no_realm = NULL; + char *princ = NULL; + + strpool = _hx509_unparse_kerberos_name(NULL, cprinc); + if (strpool) + princ = rk_strpoolcollect(strpool); + cprinc_no_realm.realm = NULL; + strpool = _hx509_unparse_kerberos_name(NULL, &cprinc_no_realm); + if (strpool) + princ_no_realm = rk_strpoolcollect(strpool); + if (princ == NULL || princ_no_realm == NULL) { + free(princ); + return hx509_enomem(context); + } + strpool = NULL; + ret = hx509_env_add(context, env, "principal-name-without-realm", + princ_no_realm); + if (ret == 0) + ret = hx509_env_add(context, env, "principal-name", princ); + if (ret == 0) + ret = hx509_env_add(context, env, "principal-name-realm", + realm); + + /* Populate requested certificate extensions from CSR/CSRPlus if allowed */ + if (ret == 0) + ret = hx509_ca_tbs_set_from_csr(context, tbs, req); + if (ret == 0) + ret = set_template(context, logf, cf, tbs); + + /* + * Optionally add PKINIT SAN. + * + * Adding an id-pkinit-san means the client can use the certificate to + * initiate PKINIT. That might seem odd, but it enables a sort of PKIX + * credential delegation by allowing forwarded Kerberos tickets to be + * used to acquire PKIX credentials. Thus this can work: + * + * PKIX (w/ HW token) -> Kerberos -> + * PKIX (w/ softtoken) -> Kerberos -> + * PKIX (w/ softtoken) -> Kerberos -> + * ... + * + * Note that we may not have added the PKINIT EKU -- that depends on the + * template, and host-based service templates might well not include it. + */ + if (ret == 0 && !has_sans(req) && + heim_config_get_bool_default(context->hcontext, cf, FALSE, + "include_pkinit_san", NULL)) { + ret = hx509_ca_tbs_add_san_pkinit(context, tbs, princ); + } + + if (ret) + goto out; + + if (ncomp == 1) { + const char *email_domain; + + ret = hx509_env_add(context, env, "principal-component0", + princ_no_realm); + + /* + * If configured, include an rfc822Name that's just the client's + * principal name sans realm @ configured email domain. + */ + if (ret == 0 && !has_sans(req) && + (email_domain = heim_config_get_string(context->hcontext, cf, + "email_domain", NULL))) { + char *email; + + if (asprintf(&email, "%s@%s", princ_no_realm, email_domain) == -1 || + email == NULL) + goto enomem; + ret = hx509_ca_tbs_add_san_rfc822name(context, tbs, email); + free(email); + } + } else if (ncomp == 2 || ncomp == 3) { + /* + * 2- and 3-component principal name. + * + * We do not have a reliable name-type indicator. If the second + * component has a '.' in it then we'll assume that the name is a + * host-based (2-component) or domain-based (3-component) service + * principal name. Else we'll assume it's a two-component admin-style + * username. + */ + + ret = hx509_env_add(context, env, "principal-component0", comp0); + if (ret == 0) + ret = hx509_env_add(context, env, "principal-component1", comp1); + if (ret == 0 && ncomp == 3) + ret = hx509_env_add(context, env, "principal-component2", comp2); + if (ret == 0 && strchr(comp1, '.')) { + /* Looks like host-based or domain-based service */ + ret = hx509_env_add(context, env, "principal-service-name", comp0); + if (ret == 0) + ret = hx509_env_add(context, env, "principal-host-name", + comp1); + if (ret == 0 && ncomp == 3) + ret = hx509_env_add(context, env, "principal-domain-name", + comp2); + if (ret == 0 && !has_sans(req) && + heim_config_get_bool_default(context->hcontext, cf, FALSE, + "include_dnsname_san", NULL)) { + ret = hx509_ca_tbs_add_san_hostname(context, tbs, comp1); + } + } + } else { + heim_log_msg(context->hcontext, logf, 5, NULL, + "kx509/bx509 client %s has too many components!", princ); + hx509_set_error_string(context, 0, ret = EACCES, + "kx509/bx509 client %s has too many " + "components!", princ); + } + +out: + if (ret == ENOMEM) + goto enomem; + free(princ_no_realm); + free(princ); + return ret; + +enomem: + heim_log_msg(context->hcontext, logf, 0, NULL, + "Could not set up TBSCertificate: Out of memory"); + ret = hx509_enomem(context); + goto out; +} + +/* + * Set the notBefore/notAfter for the certificate to be issued. + * + * Here `starttime' is the supplicant's credentials' notBefore equivalent, + * while `endtime' is the supplicant's credentials' notAfter equivalent. + * + * `req_life' is the lifetime requested by the supplicant. + * + * `endtime' must be larger than the current time. + * + * `starttime' can be zero or negative, in which case the notBefore will be the + * current time minus five minutes. + * + * `endtime', `req_life' and configuration parameters will be used to compute + * the actual notAfter. + */ +static heim_error_code +tbs_set_times(hx509_context context, + const heim_config_binding *cf, + heim_log_facility *logf, + time_t starttime, + time_t endtime, + time_t req_life, + hx509_ca_tbs tbs) +{ + time_t now = time(NULL); + time_t force = heim_config_get_time_default(context->hcontext, + cf, 5 * 24 * 3600, + "force_cert_lifetime", NULL); + time_t clamp = heim_config_get_time_default(context->hcontext, cf, 0, + "max_cert_lifetime", NULL); + int allow_more = heim_config_get_bool_default(context->hcontext, cf, FALSE, + "allow_extra_lifetime", + NULL); + starttime = starttime > 0 ? starttime : now - 5 * 60; + + if (endtime < now) { + heim_log_msg(context->hcontext, logf, 3, NULL, + "Endtime is in the past"); + hx509_set_error_string(context, 0, ERANGE, "Endtime is in the past"); + return ERANGE; + } + + /* Apply requested lifetime if shorter or if allowed more */ + if (req_life > 0 && req_life <= endtime - now) + endtime = now + req_life; + else if (req_life > 0 && allow_more) + endtime = now + req_life; + + /* Apply floor */ + if (force > 0 && force > endtime - now) + endtime = now + force; + + /* Apply ceiling */ + if (clamp > 0 && clamp < endtime - now) + endtime = now + clamp; + + hx509_ca_tbs_set_notAfter(context, tbs, endtime); + hx509_ca_tbs_set_notBefore(context, tbs, starttime); + return 0; +} + +/* + * Build a certifate for `principal' and its CSR. + * + * XXX Make `cprinc' a GeneralName! That's why this is private for now. + */ +heim_error_code +_hx509_ca_issue_certificate(hx509_context context, + const heim_config_binding *cf, + heim_log_facility *logf, + hx509_request req, + KRB5PrincipalName *cprinc, + time_t starttime, + time_t endtime, + time_t req_life, + int send_chain, + hx509_certs *out) +{ + heim_error_code ret; + const char *ca; + hx509_ca_tbs tbs = NULL; + hx509_certs chain = NULL; + hx509_cert signer = NULL; + hx509_cert cert = NULL; + hx509_env env = NULL; + KeyUsage ku; + + *out = NULL; + /* Force KU */ + ku = int2KeyUsage(0); + ku.digitalSignature = 1; + hx509_request_authorize_ku(req, ku); + + ret = get_cf(context, cf, logf, req, cprinc, &cf); + if (ret) + return ret; + + if ((ca = heim_config_get_string(context->hcontext, cf, + "ca", NULL)) == NULL) { + heim_log_msg(context->hcontext, logf, 3, NULL, + "No kx509 CA issuer credential specified"); + hx509_set_error_string(context, 0, ret = EACCES, + "No kx509 CA issuer credential specified"); + return ret; + } + + ret = hx509_ca_tbs_init(context, &tbs); + if (ret) { + heim_log_msg(context->hcontext, logf, 0, NULL, + "Failed to create certificate: Out of memory"); + return ret; + } + + /* Lookup a template and set things in `env' and `tbs' as appropriate */ + if (ret == 0) + ret = set_tbs(context, logf, cf, req, cprinc, &env, tbs); + + /* Populate generic template "env" variables */ + + /* + * The `tbs' and `env' are now complete as to naming and EKUs. + * + * We check that the `tbs' is not name-less, after which all remaining + * failures here will not be policy failures. So we also log the intent to + * issue a certificate now. + */ + if (ret == 0 && hx509_name_is_null_p(hx509_ca_tbs_get_name(tbs)) && + !has_sans(req)) { + heim_log_msg(context->hcontext, logf, 3, NULL, + "Not issuing certificate because it would have no names"); + hx509_set_error_string(context, 0, ret = EACCES, + "Not issuing certificate because it " + "would have no names"); + } + if (ret) + goto out; + + /* + * Still to be done below: + * + * - set certificate spki + * - set certificate validity + * - expand variables in certificate subject name template + * - sign certificate + * - encode certificate and chain + */ + + /* Load the issuer certificate and private key */ + { + hx509_certs certs; + hx509_query *q; + + ret = hx509_certs_init(context, ca, 0, NULL, &certs); + if (ret) { + heim_log_msg(context->hcontext, logf, 1, NULL, + "Failed to load CA certificate and private key %s", + ca); + hx509_set_error_string(context, 0, ret, "Failed to load " + "CA certificate and private key %s", ca); + goto out; + } + ret = hx509_query_alloc(context, &q); + if (ret) { + hx509_certs_free(&certs); + goto out; + } + + hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY); + hx509_query_match_option(q, HX509_QUERY_OPTION_KU_KEYCERTSIGN); + + ret = hx509_certs_find(context, certs, q, &signer); + hx509_query_free(context, q); + hx509_certs_free(&certs); + if (ret) { + heim_log_msg(context->hcontext, logf, 1, NULL, + "Failed to find a CA certificate in %s", ca); + hx509_set_error_string(context, 0, ret, + "Failed to find a CA certificate in %s", + ca); + goto out; + } + } + + /* Populate the subject public key in the TBS context */ + { + SubjectPublicKeyInfo spki; + + ret = hx509_request_get_SubjectPublicKeyInfo(context, + req, &spki); + if (ret == 0) + ret = hx509_ca_tbs_set_spki(context, tbs, &spki); + free_SubjectPublicKeyInfo(&spki); + if (ret) + goto out; + } + + /* Work out cert expiration */ + if (ret == 0) + ret = tbs_set_times(context, cf, logf, starttime, endtime, req_life, + tbs); + + /* Expand the subjectName template in the TBS using the env */ + if (ret == 0) + ret = hx509_ca_tbs_subject_expand(context, tbs, env); + hx509_env_free(&env); + + /* All done with the TBS, sign/issue the certificate */ + if (ret == 0) + ret = hx509_ca_sign(context, tbs, signer, &cert); + + /* + * Gather the certificate and chain into a MEMORY store, being careful not + * to include private keys in the chain. + * + * We could have specified a separate configuration parameter for an hx509 + * store meant to have only the chain and no private keys, but expecting + * the full chain in the issuer credential store and copying only the certs + * (but not the private keys) is safer and easier to configure. + */ + if (ret == 0) + ret = hx509_certs_init(context, "MEMORY:certs", + HX509_CERTS_NO_PRIVATE_KEYS, NULL, out); + if (ret == 0) + ret = hx509_certs_add(context, *out, cert); + if (ret == 0 && send_chain) { + ret = hx509_certs_init(context, ca, + HX509_CERTS_NO_PRIVATE_KEYS, NULL, &chain); + if (ret == 0) + ret = hx509_certs_merge(context, *out, chain); + } + +out: + hx509_certs_free(&chain); + if (env) + hx509_env_free(&env); + if (tbs) + hx509_ca_tbs_free(&tbs); + if (cert) + hx509_cert_free(cert); + if (signer) + hx509_cert_free(signer); + if (ret) + hx509_certs_free(out); + return ret; +} -- cgit v1.2.3