diff --git a/lib/hx509/hxtool-commands.in b/lib/hx509/hxtool-commands.in index 1bd0119ad..40df936da 100644 --- a/lib/hx509/hxtool-commands.in +++ b/lib/hx509/hxtool-commands.in @@ -475,6 +475,22 @@ command = { } command = { name = "request-create" + option = { + long = "ca" + type = "flag" + help = "Request CA certificate" + } + option = { + long = "ca-path-length" + type = "integer" + help = "Path length constraint for CA certificate" + default = "-1" + } + option = { + long = "ee" + type = "flag" + help = "Include BasicConstraints w/ cA set to false" + } option = { long = "subject" type = "string" diff --git a/lib/hx509/hxtool.c b/lib/hx509/hxtool.c index f61187163..e4e9a8021 100644 --- a/lib/hx509/hxtool.c +++ b/lib/hx509/hxtool.c @@ -1498,6 +1498,25 @@ request_create(struct request_create_options *opt, int argc, char **argv) if (ret) hx509_err(context, 1, ret, "Could not initialize CSR context"); + if (opt->ca_flag && opt->ee_flag) + errx(1, "request-create --ca and --ee are mutually exclusive"); + + if (opt->ca_flag) { + unsigned pathLenConstraint = 0; + unsigned *pathLenConstraintPtr = NULL; + + if (opt->ca_path_length_integer > 0 && + opt->ca_path_length_integer < INT_MAX) { + pathLenConstraint = opt->ca_path_length_integer; + pathLenConstraintPtr = &pathLenConstraint; + } + ret = hx509_request_set_cA(context, req, pathLenConstraintPtr); + if (ret) + errx(1, "hx509_request_set_cA: %d\n", ret); + } else if (opt->ee_flag) { + hx509_request_set_eE(context, req); + } + if (opt->subject_string) { hx509_name name = NULL; diff --git a/lib/hx509/libhx509-exports.def b/lib/hx509/libhx509-exports.def index 81783ff7c..12c45daf7 100644 --- a/lib/hx509/libhx509-exports.def +++ b/lib/hx509/libhx509-exports.def @@ -36,6 +36,7 @@ EXPORTS hx509_request_add_pkinit hx509_request_add_registered hx509_request_add_xmpp_name + hx509_request_authorize_cA hx509_request_authorize_ku hx509_request_authorize_eku hx509_request_authorize_san @@ -48,6 +49,8 @@ EXPORTS _hx509_private_key_ref hx509_request_eku_authorized_p hx509_request_free + hx509_request_get_cA + hx509_request_get_cA_pathLenConstraint hx509_request_get_eku hx509_request_get_exts hx509_request_get_ku @@ -63,6 +66,8 @@ EXPORTS hx509_request_add_email hx509_request_reject_eku hx509_request_reject_san + hx509_request_set_cA + hx509_request_set_eE hx509_request_set_name hx509_request_set_ku hx509_request_san_authorized_p diff --git a/lib/hx509/req.c b/lib/hx509/req.c index d0bfe91a9..3cbe4a39d 100644 --- a/lib/hx509/req.c +++ b/lib/hx509/req.c @@ -46,11 +46,15 @@ struct hx509_request_data { KeyUsage ku; ExtKeyUsage eku; GeneralNames san; + BasicConstraints bc; struct abitstring_s authorized_EKUs; struct abitstring_s authorized_SANs; - uint32_t nunsupported; /* Count of unsupported features requested */ - uint32_t nauthorized; /* Count of supported features authorized */ + uint32_t nunsupported_crit; /* Count of unsupported critical features requested */ + uint32_t nunsupported_opt; /* Count of unsupported optional features requested */ + uint32_t nauthorized; /* Count of supported features authorized */ + uint32_t ca_is_authorized:1; uint32_t ku_are_authorized:1; + uint32_t include_BasicConstraints:1; }; /** @@ -97,10 +101,55 @@ hx509_request_free(hx509_request *reqp) free_SubjectPublicKeyInfo(&req->key); free_ExtKeyUsage(&req->eku); free_GeneralNames(&req->san); + free_BasicConstraints(&req->bc); memset(req, 0, sizeof(*req)); free(req); } +/** + * Make the CSR request a CA certificate + * + * @param context An hx509 context. + * @param req The hx509_request to alter. + * @param pathLenConstraint the pathLenConstraint for the BasicConstraints (optional) + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_set_cA(hx509_context context, + hx509_request req, + unsigned *pathLenConstraint) +{ + req->bc.cA = 1; + if (pathLenConstraint) { + if (req->bc.pathLenConstraint == NULL) + req->bc.pathLenConstraint = malloc(sizeof(*pathLenConstraint)); + if (req->bc.pathLenConstraint == NULL) + return ENOMEM; + *req->bc.pathLenConstraint = *pathLenConstraint; + } + return 0; +} + +/** + * Make the CSR request an EE (end-entity, i.e., not a CA) certificate + * + * @param context An hx509 context. + * @param req The hx509_request to alter. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION void HX509_LIB_CALL +hx509_request_set_eE(hx509_context context, hx509_request req) +{ + req->bc.cA = 0; + free(req->bc.pathLenConstraint); + req->include_BasicConstraints = 1; + req->ca_is_authorized = 0; +} + /** * Set the subjectName of the CSR. * @@ -524,6 +573,29 @@ get_exts(hx509_context context, exts->val = NULL; exts->len = 0; + if (req->bc.cA || req->include_BasicConstraints) { + Extension e; + + /* + * If `!req->bc.cA' then we don't include BasicConstraints unless + * hx509_request_set_eE() was called on this request, and in that case + * we make this extension non-critical. We do this to emulate Dell + * iDRAC CSR-making software. + * + * If `req->bc.cA' then we make the BasicConstraints critical, + * obviously. + */ + memset(&e, 0, sizeof(e)); + e.critical = req->bc.cA ? 1 : 0; + if (ret == 0) + ASN1_MALLOC_ENCODE(BasicConstraints, e.extnValue.data, e.extnValue.length, + &req->bc, &size, ret); + if (ret == 0) + ret = der_copy_oid(&asn1_oid_id_x509_ce_basicConstraints, &e.extnID); + if (ret == 0) + ret = add_Extensions(exts, &e); + free_Extension(&e); + } if (KeyUsage2int(req->ku)) { Extension e; @@ -851,8 +923,12 @@ hx509_request_parse_der(hx509_context context, * Count all KUs as one requested extension to be authorized, * though the caller will have to check the KU values individually. */ - if (KeyUsage2int((*req)->ku) & ~KeyUsage2int(int2KeyUsage(~0))) - (*req)->nunsupported++; + if (KeyUsage2int((*req)->ku) & ~KeyUsage2int(int2KeyUsage(~0ULL))) { + if (e->critical) + (*req)->nunsupported_crit++; + else + (*req)->nunsupported_opt++; + } } else if (der_heim_oid_cmp(&e->extnID, &asn1_oid_id_x509_ce_extKeyUsage) == 0) { ret = decode_ExtKeyUsage(e->extnValue.data, e->extnValue.length, @@ -873,10 +949,18 @@ hx509_request_parse_der(hx509_context context, * Count each SAN as a separate requested extension to be * authorized. */ + } else if (der_heim_oid_cmp(&e->extnID, + &asn1_oid_id_x509_ce_basicConstraints) == 0) { + (*req)->include_BasicConstraints = 1; + ret = decode_BasicConstraints(e->extnValue.data, e->extnValue.length, + &(*req)->bc, NULL); } else { char *oidstr = NULL; - (*req)->nunsupported++; + if (e->critical) + (*req)->nunsupported_crit++; + else + (*req)->nunsupported_opt++; /* * We need an HX509_TRACE facility for this sort of warning. @@ -1073,6 +1157,26 @@ reject_feat(hx509_request req, abitstring a, size_t n, int idx) } } +/** + * Authorize issuance of a CA certificate as requested. + * + * @param req The hx509_request object. + * @param pathLenConstraint the pathLenConstraint for the BasicConstraints (optional) + * + * @return an hx509 or system error. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_authorize_cA(hx509_request req, unsigned *pathLenConstraint) +{ + int ret; + + ret = hx509_request_set_cA(NULL, req, pathLenConstraint); + req->ca_is_authorized++; + return ret; +} + /** * Filter the requested KeyUsage and mark it authorized. * @@ -1201,7 +1305,7 @@ hx509_request_san_authorized_p(hx509_request req, size_t idx) HX509_LIB_FUNCTION size_t HX509_LIB_CALL hx509_request_count_unsupported(hx509_request req) { - return req->nunsupported; + return req->nunsupported_crit; } /** @@ -1216,9 +1320,10 @@ HX509_LIB_FUNCTION size_t HX509_LIB_CALL hx509_request_count_unauthorized(hx509_request req) { size_t nrequested = req->eku.len + req->san.len + - (KeyUsage2int(req->ku) ? 1 : 0) + req->nunsupported; + (KeyUsage2int(req->ku) ? 1 : 0) + !!req->bc.cA + + req->nunsupported_crit; - return nrequested - (req->nauthorized + req->ku_are_authorized); + return nrequested - (req->nauthorized + req->ku_are_authorized + req->ca_is_authorized); } static hx509_san_type @@ -1390,6 +1495,45 @@ hx509_request_get_san(hx509_request req, return 0; } +/** + * Indicate if a CSR requested a CA certificate. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * + * @return 1 if the CSR requested CA certificate, 0 otherwise. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_get_cA(hx509_context context, + hx509_request req) +{ + return req->bc.cA; +} + +/** + * Return the CSR's requested BasicConstraints pathLenConstraint. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * + * @return -1 if no pathLenConstraint was requested (or the BasicConstraints + * does not request a CA certificate), or the actual requested + * pathLenConstraint. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_get_cA_pathLenConstraint(hx509_context context, + hx509_request req) +{ + if (req->bc.cA && req->bc.pathLenConstraint && + *req->bc.pathLenConstraint < INT_MAX) + return *req->bc.pathLenConstraint; + return -1; +} + /** * Display a CSR. * @@ -1437,6 +1581,13 @@ hx509_request_print(hx509_context context, hx509_request req, FILE *f) */ fprintf(f, "PKCS#10 CertificationRequest:\n"); + if (req->include_BasicConstraints) { + fprintf(f, " cA: %s\n", req->bc.cA ? "yes" : "no"); + if (req->bc.pathLenConstraint) + fprintf(f, " pathLenConstraint: %u\n", *req->bc.pathLenConstraint); + else + fprintf(f, " pathLenConstraint: unspecified\n"); + } if (req->name) { char *subject; ret = hx509_name_to_string(req->name, &subject); @@ -1515,6 +1666,14 @@ hx509_request_print(hx509_context context, hx509_request req, FILE *f) break; } } + if (req->nunsupported_crit) { + fprintf(f, " unsupported_critical_extensions_count: %u\n", + (unsigned)req->nunsupported_crit); + } + if (req->nunsupported_crit) { + fprintf(f, " unsupported_optional_extensions_count: %u\n", + (unsigned)req->nunsupported_opt); + } free(s); s = NULL; if (ret == HX509_NO_ITEM) ret = 0; diff --git a/lib/hx509/test_req.in b/lib/hx509/test_req.in index 9288df673..1d553dbaf 100644 --- a/lib/hx509/test_req.in +++ b/lib/hx509/test_req.in @@ -161,3 +161,51 @@ Certificate Request: EOF fi fi + +${hxtool} request-create \ + --ca \ + --ca-path-length=3 \ + --subject="cn=ca-cert" \ + --key=FILE:$srcdir/data/key.der \ + pkcs10-request.der || exit 1 +${hxtool} request-print PKCS10:pkcs10-request.der > "${objdir}/actual"|| exit 1 +cat > "$objdir/expected" < "${objdir}/actual"|| exit 1 +cat > "$objdir/expected" < "${objdir}/actual" || exit 1 +cat > "$objdir/expected" <