hxtool: Add "acert" (assert cert contents) command

This will prove useful in testing kx509.
This commit is contained in:
Nicolas Williams
2019-10-28 18:17:09 -05:00
parent 6612090ba0
commit 427751a204
2 changed files with 685 additions and 0 deletions

View File

@@ -858,6 +858,150 @@ command = {
argument="certificates..." argument="certificates..."
help = "Create a CRL" help = "Create a CRL"
} }
command = {
option = {
long = "verbose"
short = "v"
type = "flag"
help = "verbose"
}
option = {
long = "end-entity"
type = "flag"
help = "check the first EE certificate in the store"
}
option = {
long = "ca"
type = "flag"
help = "check the first CA certificate in the store"
}
option = {
long = "cert-num"
type = "integer"
default = "-1"
help = "check the nth certificate in the store"
}
option = {
long = "expr"
type = "string"
argument = "expression"
help = "test the first certificate matching expression"
}
option = {
long = "has-email-san"
short = "M"
type = "strings"
argument = "email-address"
help = "check that cert has email SAN"
}
option = {
long = "has-xmpp-san"
type = "strings"
short = "X"
argument = "jabber address"
help = "check that cert has XMPP SAN"
}
option = {
long = "has-ms-upn-san"
short = "U"
type = "strings"
argument = "UPN"
help = "check that cert has UPN SAN"
}
option = {
long = "has-dnsname-san"
short = "D"
type = "strings"
argument = "domainname"
help = "check that cert has domainname SAN"
}
option = {
long = "has-pkinit-san"
short = "P"
type = "strings"
argument = "Kerberos principal name"
help = "check that cert has PKINIT SAN"
}
option = {
long = "has-registeredID-san"
short = "R"
type = "strings"
argument = "OID"
help = "check that cert has registeredID SAN"
}
option = {
long = "has-eku"
short = "E"
type = "strings"
argument = "OID"
help = "check that cert has EKU"
}
option = {
long = "has-ku"
short = "K"
type = "strings"
argument = "key usage element"
help = "check that cert has key usage"
}
option = {
long = "exact"
type = "flag"
help = "check that cert has only given SANs/EKUs/KUs"
}
option = {
long = "valid-now"
short = "n"
type = "flag"
help = "check that current time is in certicate's validity period"
}
option = {
long = "valid-at"
type = "string"
argument = "datetime"
help = "check that the certificate is valid at given time"
}
option = {
long = "not-after-eq"
type = "string"
argument = "datetime"
help = "check that the certificate's notAfter is as given"
}
option = {
long = "not-after-lt"
type = "string"
argument = "datetime"
help = "check that the certificate's notAfter is before the given time"
}
option = {
long = "not-after-gt"
type = "string"
argument = "datetime"
help = "check that the certificate's notAfter is after the given time"
}
option = {
long = "not-before-eq"
type = "string"
argument = "datetime"
help = "check that the certificate's notBefore is as given"
}
option = {
long = "not-before-lt"
type = "string"
argument = "datetime"
help = "check that the certificate's notBefore is before the given time"
}
option = {
long = "not-before-gt"
type = "string"
argument = "datetime"
help = "check that the certificate's notBefore is after the given time"
}
name = "acert"
min_args = "1"
max_args = "1"
argument = "certificate-store"
help = "Assert certificate content"
}
command = { command = {
name = "help" name = "help"
name = "?" name = "?"

View File

@@ -2364,6 +2364,547 @@ hxtool_list_oids(void *opt, int argc, char **argv)
return 0; return 0;
} }
static int
acert1_sans_utf8_other(struct acert_options *opt,
struct getarg_strings *wanted,
const char *type,
heim_any *san,
size_t *count)
{
size_t k, len;
if (!wanted->num_strings)
return 0;
for (k = 0; k < wanted->num_strings; k++) {
len = strlen(wanted->strings[k]);
if (len == san->length &&
strncmp(san->data, wanted->strings[k], len) == 0) {
if (opt->verbose_flag)
fprintf(stderr, "Matched OtherName SAN %s (%s)\n",
wanted->strings[k], type);
(*count)++;
return 0;
}
}
if (opt->verbose_flag)
fprintf(stderr, "Did not match OtherName SAN %s (%s)\n",
wanted->strings[k], type);
return -1;
}
static int
acert1_sans_other(struct acert_options *opt,
heim_oid *type_id,
heim_any *value,
size_t *count)
{
heim_any pkinit;
size_t k, match;
const char *type_str = NULL;
char *s = NULL;
int ret;
(void) der_print_heim_oid_sym(type_id, '.', &s);
type_str = s ? s : "<unknown>";
if (der_heim_oid_cmp(type_id, &asn1_oid_id_pkix_on_xmppAddr) == 0) {
ret = acert1_sans_utf8_other(opt, &opt->has_xmpp_san_strings,
s ? s : "xmpp", value, count);
free(s);
return ret;
}
if (der_heim_oid_cmp(type_id, &asn1_oid_id_pkinit_san) != 0) {
if (opt->verbose_flag)
fprintf(stderr, "Ignoring OtherName SAN of type %s\n", type_str);
free(s);
return -1;
}
free(s);
type_str = s = NULL;
if (opt->has_pkinit_san_strings.num_strings == 0)
return 0;
for (k = 0; k < opt->has_pkinit_san_strings.num_strings; k++) {
const char *s2 = opt->has_pkinit_san_strings.strings[k];
if ((ret = _hx509_make_pkinit_san(context, s2, &pkinit)))
return ret;
match = (pkinit.length == value->length &&
memcmp(pkinit.data, value->data, pkinit.length) == 0);
free(pkinit.data);
if (match) {
if (opt->verbose_flag)
fprintf(stderr, "Matched PKINIT SAN %s\n", s2);
(*count)++;
return 0;
}
}
if (opt->verbose_flag)
fprintf(stderr, "Unexpected PKINIT SAN\n");
return -1;
}
static int
acert1_sans(struct acert_options *opt,
Extension *e,
size_t *count,
size_t *found)
{
heim_printable_string hps;
GeneralNames gns;
size_t i, k, sz;
size_t unwanted = 0;
int ret = 0;
memset(&gns, 0, sizeof(gns));
decode_GeneralNames(e->extnValue.data, e->extnValue.length, &gns, &sz);
for (i = 0; (ret == -1 || ret == 0) && i < gns.len; i++) {
GeneralName *gn = &gns.val[i];
const char *s;
(*found)++;
if (gn->element == choice_GeneralName_rfc822Name) {
for (k = 0; k < opt->has_email_san_strings.num_strings; k++) {
s = opt->has_email_san_strings.strings[k];
hps.data = rk_UNCONST(s);
hps.length = strlen(s);
if (der_printable_string_cmp(&gn->u.rfc822Name, &hps) == 0) {
if (opt->verbose_flag)
fprintf(stderr, "Matched e-mail address SAN %s\n", s);
(*count)++;
break;
}
}
if (k && k == opt->has_email_san_strings.num_strings) {
if (opt->verbose_flag)
fprintf(stderr, "Unexpected e-mail address SAN %.*s\n",
(int)gn->u.rfc822Name.length,
(const char *)gn->u.rfc822Name.data);
unwanted++;
}
} else if (gn->element == choice_GeneralName_dNSName) {
for (k = 0; k < opt->has_dnsname_san_strings.num_strings; k++) {
s = opt->has_dnsname_san_strings.strings[k];
hps.data = rk_UNCONST(s);
hps.length = strlen(s);
if (der_printable_string_cmp(&gn->u.dNSName, &hps) == 0) {
if (opt->verbose_flag)
fprintf(stderr, "Matched dNSName SAN %s\n", s);
(*count)++;
break;
}
}
if (k && k == opt->has_dnsname_san_strings.num_strings) {
if (opt->verbose_flag)
fprintf(stderr, "Unexpected e-mail address SAN %.*s\n",
(int)gn->u.dNSName.length,
(const char *)gn->u.dNSName.data);
unwanted++;
}
} else if (gn->element == choice_GeneralName_registeredID) {
for (k = 0; k < opt->has_registeredID_san_strings.num_strings; k++) {
s = opt->has_registeredID_san_strings.strings[k];
heim_oid oid;
memset(&oid, 0, sizeof(oid));
if ((ret = der_parse_heim_oid(s, NULL, &oid)))
break;
if (der_heim_oid_cmp(&gn->u.registeredID, &oid) == 0) {
der_free_oid(&oid);
if (opt->verbose_flag)
fprintf(stderr, "Matched registeredID SAN %s\n", s);
(*count)++;
break;
}
der_free_oid(&oid);
}
if (k && k == opt->has_dnsname_san_strings.num_strings) {
if (opt->verbose_flag)
fprintf(stderr, "Unexpected registeredID SAN\n");
unwanted++;
}
} else if (gn->element == choice_GeneralName_otherName) {
ret = acert1_sans_other(opt, &gn->u.otherName.type_id,
&gn->u.otherName.value, count);
} else if (opt->verbose_flag) {
fprintf(stderr, "Unexpected unsupported SAN\n");
unwanted++;
}
}
free_GeneralNames(&gns);
if (ret == 0 && unwanted && opt->exact_flag)
return -1;
return ret;
}
static int
acert1_ekus(struct acert_options *opt,
Extension *e,
size_t *count,
size_t *found)
{
ExtKeyUsage eku;
size_t i, k, sz;
size_t unwanted = 0;
int ret = 0;
memset(&eku, 0, sizeof(eku));
decode_ExtKeyUsage(e->extnValue.data, e->extnValue.length, &eku, &sz);
for (i = 0; (ret == -1 || ret == 0) && i < eku.len; i++) {
(*found)++;
for (k = 0; k < opt->has_eku_strings.num_strings; k++) {
const char *s = opt->has_eku_strings.strings[k];
heim_oid oid;
memset(&oid, 0, sizeof(oid));
if ((ret = der_parse_heim_oid(s, NULL, &oid)))
break;
if (der_heim_oid_cmp(&eku.val[i], &oid) == 0) {
der_free_oid(&oid);
if (opt->verbose_flag)
fprintf(stderr, "Matched EKU OID %s\n", s);
(*count)++;
break;
}
der_free_oid(&oid);
}
if (k && k == opt->has_eku_strings.num_strings) {
char *oids = NULL;
(void) der_print_heim_oid_sym(&eku.val[i], '.', &oids);
if (opt->verbose_flag)
fprintf(stderr, "Unexpected EKU OID %s\n",
oids ? oids : "<could-not-format-OID>");
unwanted++;
}
}
free_ExtKeyUsage(&eku);
if (ret == 0 && unwanted && opt->exact_flag)
return -1;
return ret;
}
static int
acert1_kus(struct acert_options *opt,
Extension *e,
size_t *count,
size_t *found)
{
const struct units *u = asn1_KeyUsage_units();
uint64_t ku_num;
KeyUsage ku;
size_t unwanted = 0;
size_t wanted = opt->has_ku_strings.num_strings;
size_t i, k, sz;
memset(&ku, 0, sizeof(ku));
decode_KeyUsage(e->extnValue.data, e->extnValue.length, &ku, &sz);
ku_num = KeyUsage2int(ku);
/* Validate requested key usage values */
for (k = 0; k < wanted; k++) {
const char *s = opt->has_ku_strings.strings[k];
for (i = 0; u[i].name; i++)
if (strcmp(s, u[i].name) == 0)
break;
if (u[i].name == NULL)
warnx("Warning: requested key usage %s unknown", s);
}
for (i = 0; u[i].name; i++) {
if ((u[i].mult & ku_num))
(*found)++;
for (k = 0; k < wanted; k++) {
const char *s = opt->has_ku_strings.strings[k];
if (!(u[i].mult & ku_num) || strcmp(s, u[i].name) != 0)
continue;
if (opt->verbose_flag)
fprintf(stderr, "Matched key usage %s\n", s);
(*count)++;
break;
}
if ((u[i].mult & ku_num) && k == wanted) {
if (opt->verbose_flag)
fprintf(stderr, "Unexpected key usage %s\n", u[i].name);
unwanted++;
}
}
return (unwanted && opt->exact_flag) ? -1 : 0;
}
static time_t
ptime(const char *s)
{
struct tm at_tm;
char *rest;
int at_s;
if ((rest = strptime(s, "%Y-%m-%dT%H:%M:%S", &at_tm)) != NULL &&
rest[0] == '\0')
return mktime(&at_tm);
if ((rest = strptime(s, "%Y%m%d%H%M%S", &at_tm)) != NULL && rest[0] == '\0')
return mktime(&at_tm);
if ((at_s = parse_time(s, "s")) != -1)
return time(NULL) + at_s;
errx(1, "Could not parse time spec %s", s);
}
static int
acert1_validity(struct acert_options *opt, hx509_cert cert)
{
time_t not_before_eq = 0;
time_t not_before_lt = 0;
time_t not_before_gt = 0;
time_t not_after_eq = 0;
time_t not_after_lt = 0;
time_t not_after_gt = 0;
if (opt->valid_now_flag) {
time_t now = time(NULL);
if (hx509_cert_get_notBefore(cert) > now) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate not valid yet\n");
return -1;
}
if (hx509_cert_get_notAfter(cert) < now) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate currently expired\n");
return -1;
}
}
if (opt->valid_at_string) {
time_t at = ptime(opt->valid_at_string);
if (hx509_cert_get_notBefore(cert) > at) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate not valid yet at %s\n",
opt->valid_at_string);
return -1;
}
if (hx509_cert_get_notAfter(cert) < at) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate expired before %s\n",
opt->valid_at_string);
return -1;
}
}
if (opt->not_before_eq_string)
not_before_eq = ptime(opt->not_before_eq_string);
if (opt->not_before_lt_string)
not_before_lt = ptime(opt->not_before_lt_string);
if (opt->not_before_gt_string)
not_before_gt = ptime(opt->not_before_gt_string);
if (opt->not_after_eq_string)
not_after_eq = ptime(opt->not_after_eq_string);
if (opt->not_after_lt_string)
not_after_lt = ptime(opt->not_after_lt_string);
if (opt->not_after_gt_string)
not_after_gt = ptime(opt->not_after_gt_string);
if ((not_before_eq && hx509_cert_get_notBefore(cert) != not_before_eq) ||
(not_before_lt && hx509_cert_get_notBefore(cert) >= not_before_lt) ||
(not_before_gt && hx509_cert_get_notBefore(cert) <= not_before_gt)) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate notBefore not as requested\n");
return -1;
}
if ((not_after_eq && hx509_cert_get_notAfter(cert) != not_after_eq) ||
(not_after_lt && hx509_cert_get_notAfter(cert) >= not_after_lt) ||
(not_after_gt && hx509_cert_get_notAfter(cert) <= not_after_gt)) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate notAfter not as requested\n");
return -1;
}
return 0;
}
static int
acert1(struct acert_options *opt, size_t cert_num, hx509_cert cert, int *matched)
{
const heim_oid *misc_exts [] = {
&asn1_oid_id_x509_ce_authorityKeyIdentifier,
&asn1_oid_id_x509_ce_subjectKeyIdentifier,
&asn1_oid_id_x509_ce_basicConstraints,
&asn1_oid_id_x509_ce_nameConstraints,
&asn1_oid_id_x509_ce_certificatePolicies,
&asn1_oid_id_x509_ce_policyMappings,
&asn1_oid_id_x509_ce_issuerAltName,
&asn1_oid_id_x509_ce_subjectDirectoryAttributes,
&asn1_oid_id_x509_ce_policyConstraints,
&asn1_oid_id_x509_ce_cRLDistributionPoints,
&asn1_oid_id_x509_ce_deltaCRLIndicator,
&asn1_oid_id_x509_ce_issuingDistributionPoint,
&asn1_oid_id_x509_ce_inhibitAnyPolicy,
&asn1_oid_id_x509_ce_cRLNumber,
&asn1_oid_id_x509_ce_freshestCRL,
NULL
};
const Certificate *c;
const Extensions *e;
KeyUsage ku;
size_t matched_elements = 0;
size_t wanted, sans_wanted, ekus_wanted, kus_wanted;
size_t found, sans_found, ekus_found, kus_found;
size_t i, k;
int ret;
if ((c = _hx509_get_cert(cert)) == NULL)
errx(1, "Could not get Certificate");
e = c->tbsCertificate.extensions;
ret = _hx509_cert_get_keyusage(context, cert, &ku);
if (ret && ret != HX509_KU_CERT_MISSING)
hx509_err(context, 1, ret, "Could not get key usage of certificate");
if (ret == HX509_KU_CERT_MISSING && opt->ca_flag)
return 0; /* want CA cert; this isn't it */
if (ret == 0 && opt->ca_flag && !ku.keyCertSign)
return 0; /* want CA cert; this isn't it */
if (ret == 0 && opt->end_entity_flag && ku.keyCertSign)
return 0; /* want EE cert; this isn't it */
if (opt->cert_num_integer != -1 && cert_num <= INT_MAX &&
opt->cert_num_integer != (int)cert_num)
return 0;
if (opt->cert_num_integer == -1 || opt->cert_num_integer == (int)cert_num)
*matched = 1;
if (_hx509_cert_get_version(c) < 3) {
warnx("Certificate with version %d < 3 ignored",
_hx509_cert_get_version(c));
return 0;
}
sans_wanted = opt->has_email_san_strings.num_strings
+ opt->has_xmpp_san_strings.num_strings
+ opt->has_ms_upn_san_strings.num_strings
+ opt->has_dnsname_san_strings.num_strings
+ opt->has_pkinit_san_strings.num_strings
+ opt->has_registeredID_san_strings.num_strings;
ekus_wanted = opt->has_eku_strings.num_strings;
kus_wanted = opt->has_ku_strings.num_strings;
wanted = sans_wanted + ekus_wanted + kus_wanted;
found = sans_found = ekus_found = kus_found = 0;
if (e == NULL) {
if (wanted)
return -1;
return acert1_validity(opt, cert);;
}
for (i = 0; i < e->len; i++) {
if (der_heim_oid_cmp(&e->val[i].extnID,
&asn1_oid_id_x509_ce_subjectAltName) == 0) {
ret = acert1_sans(opt, &e->val[i], &matched_elements, &sans_found);
if (ret == -1 && sans_wanted == 0 &&
(!opt->exact_flag || sans_found == 0))
ret = 0;
} else if (der_heim_oid_cmp(&e->val[i].extnID,
&asn1_oid_id_x509_ce_extKeyUsage) == 0) {
ret = acert1_ekus(opt, &e->val[i], &matched_elements, &ekus_found);
if (ret == -1 && ekus_wanted == 0 &&
(!opt->exact_flag || ekus_found == 0))
ret = 0;
} else if (der_heim_oid_cmp(&e->val[i].extnID,
&asn1_oid_id_x509_ce_keyUsage) == 0) {
ret = acert1_kus(opt, &e->val[i], &matched_elements, &kus_found);
if (ret == -1 && kus_wanted == 0 &&
(!opt->exact_flag || kus_found == 0))
ret = 0;
} else {
char *oids = NULL;
for (k = 0; misc_exts[k]; k++) {
if (der_heim_oid_cmp(&e->val[i].extnID, misc_exts[k]) == 0)
break;
}
if (misc_exts[k])
continue;
(void) der_print_heim_oid(&e->val[i].extnID, '.', &oids);
warnx("Matching certificate has unexpected certificate "
"extension %s", oids ? oids : "<could not display OID>");
free(oids);
ret = -1;
}
if (ret && ret != -1)
hx509_err(context, 1, ret, "Error checking matching certificate");
if (ret == -1)
break;
}
if (matched_elements != wanted)
return -1;
found = sans_found + ekus_found + kus_found;
if (matched_elements != found && opt->exact_flag)
return -1;
if (ret)
return ret;
return acert1_validity(opt, cert);
}
int
acert(struct acert_options *opt, int argc, char **argv)
{
hx509_cursor cursor = NULL;
hx509_certs certs = NULL;
hx509_cert cert = NULL;
size_t n = 0;
int matched = 0;
int ret;
if (opt->not_after_eq_string &&
(opt->not_after_lt_string || opt->not_after_gt_string))
errx(1, "--not-after-eq should not be given with --not-after-lt/gt");
if (opt->not_before_eq_string &&
(opt->not_before_lt_string || opt->not_before_gt_string))
errx(1, "--not-before-eq should not be given with --not-before-lt/gt");
if ((ret = hx509_certs_init(context, argv[0], 0, NULL, &certs)))
hx509_err(context, 1, ret, "Could not load certificates from %s",
argv[0]);
hx509_query *q = NULL;
if (opt->expr_string) {
if ((ret = hx509_query_alloc(context, &q)))
hx509_err(context, 1, ret, "Could not initialize query");
hx509_query_match_expr(context, q, opt->expr_string);
if ((ret = hx509_certs_find(context, certs, q, &cert)) || !cert)
hx509_err(context, 1, ret, "No matching certificate");
ret = acert1(opt, -1, cert, &matched);
matched = 1;
} else {
ret = hx509_certs_start_seq(context, certs, &cursor);
while (ret == 0 &&
(ret = hx509_certs_next_cert(context, certs,
cursor, &cert)) == 0 &&
cert) {
ret = acert1(opt, n++, cert, &matched);
if (matched)
break;
}
if (cursor)
(void) hx509_certs_end_seq(context, certs, cursor);
}
if (!matched && ret)
hx509_err(context, 1, ret, "Could not find certificate");
if (!matched)
errx(1, "Could not find certificate");
if (ret == -1)
errx(1, "Matching certificate did not meet requirements");
if (ret)
hx509_err(context, 1, ret, "Matching certificate did not meet "
"requirements");
return 0;
}
/* /*
* *
*/ */