aboutsummaryrefslogtreecommitdiff
path: root/lib/hx509/ca.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/hx509/ca.c')
-rw-r--r--lib/hx509/ca.c1889
1 files changed, 1671 insertions, 218 deletions
diff --git a/lib/hx509/ca.c b/lib/hx509/ca.c
index 418a404b4aa9..1ca8d51da39e 100644
--- a/lib/hx509/ca.c
+++ b/lib/hx509/ca.c
@@ -32,7 +32,6 @@
*/
#include "hx_locl.h"
-#include <pkinit_asn1.h>
/**
* @page page_ca Hx509 CA functions
@@ -43,9 +42,11 @@
struct hx509_ca_tbs {
hx509_name subject;
SubjectPublicKeyInfo spki;
+ KeyUsage ku;
ExtKeyUsage eku;
GeneralNames san;
- unsigned key_usage;
+ CertificatePolicies cps;
+ PolicyMappings pms;
heim_integer serial;
struct {
unsigned int proxy:1;
@@ -57,6 +58,7 @@ struct hx509_ca_tbs {
} flags;
time_t notBefore;
time_t notAfter;
+ HeimPkinitPrincMaxLifeSecs pkinitTicketMaxLife;
int pathLenConstraint; /* both for CA and Proxy */
CRLDistributionPoints crldp;
heim_bit_string subjectUniqueID;
@@ -77,7 +79,7 @@ struct hx509_ca_tbs {
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_init(hx509_context context, hx509_ca_tbs *tbs)
{
*tbs = calloc(1, sizeof(**tbs));
@@ -95,20 +97,23 @@ hx509_ca_tbs_init(hx509_context context, hx509_ca_tbs *tbs)
* @ingroup hx509_ca
*/
-void
+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);
- hx509_name_free(&(*tbs)->subject);
+ if ((*tbs)->subject)
+ hx509_name_free(&(*tbs)->subject);
if ((*tbs)->sigalg) {
free_AlgorithmIdentifier((*tbs)->sigalg);
free((*tbs)->sigalg);
@@ -132,7 +137,7 @@ hx509_ca_tbs_free(hx509_ca_tbs *tbs)
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_notBefore(hx509_context context,
hx509_ca_tbs tbs,
time_t t)
@@ -153,7 +158,7 @@ hx509_ca_tbs_set_notBefore(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_notAfter(hx509_context context,
hx509_ca_tbs tbs,
time_t t)
@@ -174,7 +179,7 @@ hx509_ca_tbs_set_notAfter(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_notAfter_lifetime(hx509_context context,
hx509_ca_tbs tbs,
time_t delta)
@@ -182,6 +187,15 @@ hx509_ca_tbs_set_notAfter_lifetime(hx509_context context,
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 },
@@ -190,6 +204,7 @@ static const struct units templatebits[] = {
{ "notBefore", HX509_CA_TEMPLATE_NOTBEFORE },
{ "serial", HX509_CA_TEMPLATE_SERIAL },
{ "subject", HX509_CA_TEMPLATE_SUBJECT },
+ { "pkinitMaxLife", HX509_CA_TEMPLATE_PKINIT_MAX_LIFE },
{ NULL, 0 }
};
@@ -202,19 +217,19 @@ static const struct units templatebits[] = {
* @ingroup hx509_ca
*/
-const struct units *
+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 certifiate.
+ * 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
- * certifiate.
+ * certificate.
* @param cert template certificate.
*
* @return An hx509 error code, see hx509_get_error_string().
@@ -222,7 +237,7 @@ hx509_ca_tbs_template_units(void)
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_template(hx509_context context,
hx509_ca_tbs tbs,
int flags,
@@ -262,11 +277,9 @@ hx509_ca_tbs_set_template(hx509_context context,
return ret;
}
if (flags & HX509_CA_TEMPLATE_KU) {
- KeyUsage ku;
- ret = _hx509_cert_get_keyusage(context, cert, &ku);
+ ret = _hx509_cert_get_keyusage(context, cert, &tbs->ku);
if (ret)
return ret;
- tbs->key_usage = KeyUsage2int(ku);
}
if (flags & HX509_CA_TEMPLATE_EKU) {
ExtKeyUsage eku;
@@ -283,6 +296,12 @@ hx509_ca_tbs_set_template(hx509_context context,
}
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;
}
@@ -300,7 +319,7 @@ hx509_ca_tbs_set_template(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_ca(hx509_context context,
hx509_ca_tbs tbs,
int pathLenConstraint)
@@ -324,7 +343,7 @@ hx509_ca_tbs_set_ca(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_proxy(hx509_context context,
hx509_ca_tbs tbs,
int pathLenConstraint)
@@ -346,7 +365,7 @@ hx509_ca_tbs_set_proxy(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_domaincontroller(hx509_context context,
hx509_ca_tbs tbs)
{
@@ -368,7 +387,7 @@ hx509_ca_tbs_set_domaincontroller(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_spki(hx509_context context,
hx509_ca_tbs tbs,
const SubjectPublicKeyInfo *spki)
@@ -393,7 +412,7 @@ hx509_ca_tbs_set_spki(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_serialnumber(hx509_context context,
hx509_ca_tbs tbs,
const heim_integer *serialNumber)
@@ -406,6 +425,65 @@ hx509_ca_tbs_set_serialnumber(hx509_context context,
}
/**
+ * 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, 0 };
+ KeyUsage ku;
+ size_t i;
+ char *s = NULL;
+ int ret;
+
+ if (hx509_request_count_unauthorized(req)) {
+ hx509_set_error_string(context, 0, ENOMEM, "out of memory");
+ 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.
*
@@ -418,7 +496,29 @@ hx509_ca_tbs_set_serialnumber(hx509_context context,
* @ingroup hx509_ca
*/
-int
+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)
@@ -449,6 +549,127 @@ hx509_ca_tbs_add_eku(hx509_context context,
}
/**
+ * 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.
*
@@ -462,94 +683,49 @@ hx509_ca_tbs_add_eku(hx509_context context,
* @ingroup hx509_ca
*/
-int
+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));
-
- dp.distributionPoint = ecalloc(1, sizeof(*dp.distributionPoint));
-
- {
- DistributionPointName name;
- GeneralName gn;
- size_t size;
-
- name.element = choice_DistributionPointName_fullName;
- name.u.fullName.len = 1;
- name.u.fullName.val = &gn;
-
- gn.element = choice_GeneralName_uniformResourceIdentifier;
- gn.u.uniformResourceIdentifier.data = rk_UNCONST(uri);
- gn.u.uniformResourceIdentifier.length = strlen(uri);
-
- ASN1_MALLOC_ENCODE(DistributionPointName,
- dp.distributionPoint->data,
- dp.distributionPoint->length,
- &name, &size, ret);
- if (ret) {
- hx509_set_error_string(context, 0, ret,
- "Failed to encoded DistributionPointName");
- goto out;
- }
- if (dp.distributionPoint->length != size)
- _hx509_abort("internal ASN.1 encoder error");
- }
+ 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) {
-#if 1
- /**
- * issuername not supported
- */
- hx509_set_error_string(context, 0, EINVAL,
- "CRLDistributionPoints.name.issuername not yet supported");
- return EINVAL;
-#else
- GeneralNames *crlissuer;
- GeneralName gn;
- Name n;
-
- crlissuer = calloc(1, sizeof(*crlissuer));
- if (crlissuer == NULL) {
- return ENOMEM;
- }
- memset(&gn, 0, sizeof(gn));
-
- gn.element = choice_GeneralName_directoryName;
- ret = hx509_name_to_Name(issuername, &n);
- if (ret) {
- hx509_set_error_string(context, 0, ret, "out of memory");
- goto out;
- }
-
- gn.u.directoryName.element = n.element;
- gn.u.directoryName.u.rdnSequence = n.u.rdnSequence;
-
- ret = add_GeneralNames(&crlissuer, &gn);
- free_Name(&n);
+ 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");
- goto out;
+ return ret;
}
-
+ crlissuer.len = 1;
+ crlissuer.val = &ign;
dp.cRLIssuer = &crlissuer;
-#endif
}
ret = add_CRLDistributionPoints(&tbs->crldp, &dp);
- if (ret) {
- hx509_set_error_string(context, 0, ret, "out of memory");
- goto out;
- }
-
-out:
- free_DistributionPoint(&dp);
+ if (issuername)
+ free_Name(&ign.u.directoryName);
+ if (ret)
+ hx509_set_error_string(context, 0, ret, "out of memory");
return ret;
}
@@ -567,7 +743,7 @@ out:
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_add_san_otherName(hx509_context context,
hx509_ca_tbs tbs,
const heim_oid *oid,
@@ -583,52 +759,100 @@ hx509_ca_tbs_add_san_otherName(hx509_context context,
return add_GeneralNames(&tbs->san, &gn);
}
-/**
- * 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
- */
+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_ca_tbs_add_san_pkinit(hx509_context context,
- hx509_ca_tbs tbs,
- const char *principal)
+_hx509_make_pkinit_san(hx509_context context,
+ const char *principal,
+ heim_octet_string *os)
{
- heim_octet_string os;
KRB5PrincipalName p;
size_t size;
int ret;
- char *s = NULL;
+ os->data = NULL;
+ os->length = 0;
memset(&p, 0, sizeof(p));
- /* parse principal */
+ /* Parse principal */
{
- const char *str;
- char *q;
- int n;
+ const char *str, *str_start;
+ size_t n, i;
- /* count number of component */
+ /* Count number of components */
n = 1;
- for(str = principal; *str != '\0' && *str != '@'; str++){
- if(*str=='\\'){
- if(str[1] == '\0' || str[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 == '/')
+ } 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) {
@@ -637,49 +861,136 @@ hx509_ca_tbs_add_san_pkinit(hx509_context context,
goto out;
}
p.principalName.name_string.len = n;
-
p.principalName.name_type = KRB5_NT_PRINCIPAL;
- q = s = strdup(principal);
- if (q == NULL) {
- ret = ENOMEM;
- hx509_set_error_string(context, 0, ret, "malloc: out of memory");
- goto out;
- }
- p.realm = strrchr(q, '@');
- if (p.realm == NULL) {
- ret = HX509_PARSING_NAME_FAILED;
- hx509_set_error_string(context, 0, ret, "Missing @ in principal");
- goto out;
- };
- *p.realm++ = '\0';
-
- n = 0;
- while (q) {
- p.principalName.name_string.val[n++] = q;
- q = strchr(q, '/');
- if (q)
- *q++ = '\0';
+
+ 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);
+ 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,
- &asn1_oid_id_pkinit_san,
- &os);
+ 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);
-out:
- if (p.principalName.name_string.val)
- free (p.principalName.name_string.val);
- if (s)
- free(s);
return ret;
}
@@ -693,7 +1004,7 @@ add_utf8_san(hx509_context context,
const heim_oid *oid,
const char *string)
{
- const PKIXXmppAddr ustring = (const PKIXXmppAddr)(intptr_t)string;
+ const PKIXXmppAddr ustring = (const PKIXXmppAddr)(uintptr_t)string;
heim_octet_string os;
size_t size;
int ret;
@@ -704,17 +1015,13 @@ add_utf8_san(hx509_context context,
ASN1_MALLOC_ENCODE(PKIXXmppAddr, os.data, os.length, &ustring, &size, ret);
if (ret) {
hx509_set_error_string(context, 0, ret, "Out of memory");
- goto out;
+ return ret;
}
if (size != os.length)
_hx509_abort("internal ASN.1 encoder error");
- ret = hx509_ca_tbs_add_san_otherName(context,
- tbs,
- oid,
- &os);
+ ret = hx509_ca_tbs_add_san_otherName(context, tbs, oid, &os);
free(os.data);
-out:
return ret;
}
@@ -731,7 +1038,7 @@ out:
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_add_san_ms_upn(hx509_context context,
hx509_ca_tbs tbs,
const char *principal)
@@ -752,7 +1059,7 @@ hx509_ca_tbs_add_san_ms_upn(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_add_san_jid(hx509_context context,
hx509_ca_tbs tbs,
const char *jid)
@@ -777,7 +1084,7 @@ hx509_ca_tbs_add_san_jid(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_add_san_hostname(hx509_context context,
hx509_ca_tbs tbs,
const char *dnsname)
@@ -805,7 +1112,7 @@ hx509_ca_tbs_add_san_hostname(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_add_san_rfc822name(hx509_context context,
hx509_ca_tbs tbs,
const char *rfc822Name)
@@ -820,6 +1127,295 @@ hx509_ca_tbs_add_san_rfc822name(hx509_context context,
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<public-key>", where the form of <public-key> 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 "[<assigner-oid>]:[<id>]".
+ * @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 \"[<oid>]:[<id>]\")",
+ oidstr);
+ 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<public-key>", where the form of <public-key> 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 "<oid>:<serial>".
+ * @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 "
+ "\"<oid>:<serial>\")",
+ oidstr);
+ 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.
*
@@ -832,7 +1428,7 @@ hx509_ca_tbs_add_san_rfc822name(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_subject(hx509_context context,
hx509_ca_tbs tbs,
hx509_name subject)
@@ -860,7 +1456,7 @@ hx509_ca_tbs_set_subject(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_unique(hx509_context context,
hx509_ca_tbs tbs,
const heim_bit_string *subjectUniqueID,
@@ -900,7 +1496,7 @@ hx509_ca_tbs_set_unique(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_subject_expand(hx509_context context,
hx509_ca_tbs tbs,
hx509_env env)
@@ -909,6 +1505,23 @@ hx509_ca_tbs_subject_expand(hx509_context context,
}
/**
+ * 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.
@@ -920,7 +1533,7 @@ hx509_ca_tbs_subject_expand(hx509_context context,
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_signature_algorithm(hx509_context context,
hx509_ca_tbs tbs,
const AlgorithmIdentifier *sigalg)
@@ -957,16 +1570,7 @@ add_extension(hx509_context context,
memset(&ext, 0, sizeof(ext));
- if (critical_flag) {
- ext.critical = malloc(sizeof(*ext.critical));
- if (ext.critical == NULL) {
- ret = ENOMEM;
- hx509_set_error_string(context, 0, ret, "Out of memory");
- goto out;
- }
- *ext.critical = TRUE;
- }
-
+ ext.critical = critical_flag;
ret = der_copy_oid(oid, &ext.extnID);
if (ret) {
hx509_set_error_string(context, 0, ret, "Out of memory");
@@ -1033,7 +1637,6 @@ ca_sign(hx509_context context,
const AlgorithmIdentifier *sigalg;
time_t notBefore;
time_t notAfter;
- unsigned key_usage;
sigalg = tbs->sigalg;
if (sigalg == NULL)
@@ -1053,21 +1656,12 @@ ca_sign(hx509_context context,
if (notAfter == 0)
notAfter = time(NULL) + 3600 * 24 * 365;
- key_usage = tbs->key_usage;
- if (key_usage == 0) {
- KeyUsage ku;
- memset(&ku, 0, sizeof(ku));
- ku.digitalSignature = 1;
- ku.keyEncipherment = 1;
- key_usage = KeyUsage2int(ku);
- }
-
if (tbs->flags.ca) {
- KeyUsage ku;
- memset(&ku, 0, sizeof(ku));
- ku.keyCertSign = 1;
- ku.cRLSign = 1;
- key_usage |= KeyUsage2int(ku);
+ tbs->ku.keyCertSign = 1;
+ tbs->ku.cRLSign = 1;
+ } else if (KeyUsage2int(tbs->ku) == 0) {
+ tbs->ku.digitalSignature = 1;
+ tbs->ku.keyEncipherment = 1;
}
/*
@@ -1076,6 +1670,12 @@ ca_sign(hx509_context context,
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");
@@ -1086,13 +1686,9 @@ ca_sign(hx509_context context,
* will be generated below.
*/
if (!tbs->flags.proxy) {
- if (tbs->subject == NULL) {
- hx509_set_error_string(context, 0, EINVAL, "No subject name set");
- return EINVAL;
- }
if (hx509_name_is_null_p(tbs->subject) && tbs->san.len == 0) {
hx509_set_error_string(context, 0, EINVAL,
- "NULL subject and no SubjectAltNames");
+ "Empty subject and no SubjectAltNames");
return EINVAL;
}
}
@@ -1146,7 +1742,7 @@ ca_sign(hx509_context context,
/* signature AlgorithmIdentifier, */
ret = copy_AlgorithmIdentifier(sigalg, &tbsc->signature);
if (ret) {
- hx509_set_error_string(context, 0, ret, "Failed to copy sigature alg");
+ hx509_set_error_string(context, 0, ret, "Failed to copy signature alg");
goto out;
}
/* issuer Name, */
@@ -1159,10 +1755,32 @@ ca_sign(hx509_context context,
goto out;
}
/* validity Validity, */
- tbsc->validity.notBefore.element = choice_Time_generalTime;
- tbsc->validity.notBefore.u.generalTime = notBefore;
- tbsc->validity.notAfter.element = choice_Time_generalTime;
- tbsc->validity.notAfter.u.generalTime = notAfter;
+ {
+ /*
+ * 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);
@@ -1236,12 +1854,10 @@ ca_sign(hx509_context context,
goto out;
}
- /* add KeyUsage */
- {
- KeyUsage ku;
-
- ku = int2KeyUsage(key_usage);
- ASN1_MALLOC_ENCODE(KeyUsage, data.data, data.length, &ku, &size, ret);
+ /* 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;
@@ -1255,7 +1871,7 @@ ca_sign(hx509_context context,
goto out;
}
- /* add ExtendedKeyUsage */
+ /* Add ExtendedKeyUsage */
if (tbs->eku.len > 0) {
ASN1_MALLOC_ENCODE(ExtKeyUsage, data.data, data.length,
&tbs->eku, &size, ret);
@@ -1265,14 +1881,14 @@ ca_sign(hx509_context context,
}
if (size != data.length)
_hx509_abort("internal ASN.1 encoder error");
- ret = add_extension(context, tbsc, 0,
+ ret = add_extension(context, tbsc, 1,
&asn1_oid_id_x509_ce_extKeyUsage, &data);
free(data.data);
if (ret)
goto out;
}
- /* add Subject Alternative Name */
+ /* Add Subject Alternative Name */
if (tbs->san.len > 0) {
ASN1_MALLOC_ENCODE(GeneralNames, data.data, data.length,
&tbs->san, &size, ret);
@@ -1282,9 +1898,10 @@ ca_sign(hx509_context context,
}
if (size != data.length)
_hx509_abort("internal ASN.1 encoder error");
- ret = add_extension(context, tbsc, 0,
- &asn1_oid_id_x509_ce_subjectAltName,
- &data);
+
+ /* 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;
@@ -1346,13 +1963,12 @@ ca_sign(hx509_context context,
/* Add BasicConstraints */
{
BasicConstraints bc;
- int aCA = 1;
unsigned int path;
memset(&bc, 0, sizeof(bc));
if (tbs->flags.ca) {
- bc.cA = &aCA;
+ bc.cA = 1;
if (tbs->pathLenConstraint >= 0) {
path = tbs->pathLenConstraint;
bc.pathLenConstraint = &path;
@@ -1376,7 +1992,7 @@ ca_sign(hx509_context context,
goto out;
}
- /* add Proxy */
+ /* Add Proxy */
if (tbs->flags.proxy) {
ProxyCertInfo info;
@@ -1418,8 +2034,8 @@ ca_sign(hx509_context context,
goto out;
}
+ /* Add CRL distribution point */
if (tbs->crldp.len) {
-
ASN1_MALLOC_ENCODE(CRLDistributionPoints, data.data, data.length,
&tbs->crldp, &size, ret);
if (ret) {
@@ -1436,6 +2052,57 @@ ca_sign(hx509_context context,
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");
@@ -1531,8 +2198,7 @@ get_AuthorityKeyIdentifier(hx509_context context,
memset(&gn, 0, sizeof(gn));
gn.element = choice_GeneralName_directoryName;
- gn.u.directoryName.element =
- choice_GeneralName_directoryName_rdnSequence;
+ gn.u.directoryName.element = choice_Name_rdnSequence;
gn.u.directoryName.u.rdnSequence = name.u.rdnSequence;
ret = add_GeneralNames(&gns, &gn);
@@ -1583,7 +2249,7 @@ out:
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_sign(hx509_context context,
hx509_ca_tbs tbs,
hx509_cert signer,
@@ -1627,7 +2293,7 @@ out:
* @ingroup hx509_ca
*/
-int
+HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_sign_self(hx509_context context,
hx509_ca_tbs tbs,
hx509_private_key signer,
@@ -1640,3 +2306,790 @@ hx509_ca_sign_self(hx509_context context,
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, 0 };
+
+ 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;
+}