On Fri, Feb 04, 2022 at 12:03:41PM +0100, Claudio Jeker wrote: > On Fri, Feb 04, 2022 at 10:41:03AM +0100, Theo Buehler wrote: > > It was pointed out to Claudio that rpki-client does not enforce > > certificate policies. > > > > The diff below does that. It has two parts. > > > > In cert.c we check that the certificate policy extension matches the > > specification in RFC 6487, section 4.8.9, as amended by RFC 7318 > > section 2. That's maybe a bit lengthy but completely straightforward. > > If you're curious what's in a CPS URI, it might be this: > > https://www.arin.net/resources/manage/rpki/cps.pdf > > > > The second bit is in parser.c and makes sure that the verifier builds a > > policy tree enforcing that the certification path uses a policy OID of > > id-cp-ipAddr-asNumber (see RFC 6484). This is a bit trickier since it > > involves X509_policy_check() under the hood. In short, we set up a > > policy containing that OID in the verify parameters and set verifier > > flags that ensure that the user set policy is enforced. > > > > If you will, the second part improves the validation. The verifier > > doesn't have a mechanism to enforce things like there's exactly one > > policy identifier, etc. That's what's done in cert.c > > > > This works for me. Please test. > > Looks good to me. OK claudio@
Unfortunately, NID_ipAddr_asNumber is only available in LibreSSL 3.3 and later and didn't make it into OpenSSL 1.1 so I had to do some things a bit differently. I added a certpol_oid to x509_init_oid() and use it instead of NID_ipAddr_asNumber(). Since we have this global already, it's easier to use X509_VERIFY_PARAM_add0_policy(). It also forces one check to use OBJ_cmp() instead OBJ_obj2nid(). > > + policy = sk_POLICYINFO_value(policies, 0); > > + assert(policy != NULL && policy->policyid != NULL); > > Should this be an assert() or a proper error check? > I guess sk_POLICYINFO_value() can not really fail because of the check > above. What about policy->policyid? I gess X509V3_EXT_d2i() sets this > correctly or it failed above. Yes. Both != NULL are guaranteed. You're right about sk_value(). Also your guess is correct: policy->policyid != NULL is guaranteed by the templated ASN.1: In POLICYINFO_seq_tt (lib/libcrypto/x509/x509_cpols.c:147), there is no ASN1_TFLG_OPTIONAL flag to indicate that policy->policyid can be NULL (whereas policy->qualifiers is optional). Therefore if policy != NULL and since X509V3_EXT_d2i didn't fail, it must have deserialized correctly, so policy->policyid != NULL. I left the assert as it is for now, but I can change it into an error check or drop it if you prefer. > > + qualifier = sk_POLICYQUALINFO_value(qualifiers, 0); > > + assert(qualifier != NULL && qualifier->pqualid != NULL); > > Same as above. The answer is the same again, this time arguing with the flags in POLICYQUALINFO_seq_tt. > > + flags |= X509_V_FLAG_EXPLICIT_POLICY; > > + flags |= X509_V_FLAG_INHIBIT_MAP; > > I do not really understand the meaning of X509_V_FLAG_INHIBIT_MAP. Neither > the manpage nor the referenced RFC really help to explain what is > inhibited and if we really want that or not. It seems to be the more > strict option so I think it is the right thing to do but wow this is > complicated. It is super complicated indeed. Policy mappings are explained here: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.5 It's another extension that allows translation between policy OIDs. I'm pretty sure we don't want to allow this. Index: cert.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v retrieving revision 1.53 diff -u -p -r1.53 cert.c --- cert.c 20 Jan 2022 16:36:19 -0000 1.53 +++ cert.c 4 Feb 2022 14:32:50 -0000 @@ -29,6 +29,7 @@ #include <openssl/asn1.h> #include <openssl/x509.h> +#include <openssl/x509v3.h> #include "extern.h" @@ -47,6 +48,7 @@ struct parse { const char *fn; /* currently-parsed file */ }; +extern ASN1_OBJECT *certpol_oid; /* id-cp-ipAddr-asNumber cert policy */ extern ASN1_OBJECT *carepo_oid; /* 1.3.6.1.5.5.7.48.5 (caRepository) */ extern ASN1_OBJECT *manifest_oid; /* 1.3.6.1.5.5.7.48.10 (rpkiManifest) */ extern ASN1_OBJECT *notify_oid; /* 1.3.6.1.5.5.7.48.13 (rpkiNotify) */ @@ -969,6 +971,78 @@ out: return rc; } +static int +certificate_policies(struct parse *p, X509_EXTENSION *ext) +{ + STACK_OF(POLICYINFO) *policies = NULL; + POLICYINFO *policy; + STACK_OF(POLICYQUALINFO) *qualifiers; + POLICYQUALINFO *qualifier; + int nid; + int rc = 0; + + if (!X509_EXTENSION_get_critical(ext)) { + cryptowarnx("%s: RFC 6487 section 4.8.9: certificatePolicies: " + "extension not critical", p->fn); + goto out; + } + + if ((policies = X509V3_EXT_d2i(ext)) == NULL) { + cryptowarnx("%s: RFC 6487 section 4.8.9: certificatePolicies: " + "failed extension parse", p->fn); + goto out; + } + + if (sk_POLICYINFO_num(policies) != 1) { + warnx("%s: RFC 6487 section 4.8.9: certificatePolicies: " + "want 1 policy, got %d", p->fn, + sk_POLICYINFO_num(policies)); + goto out; + } + + policy = sk_POLICYINFO_value(policies, 0); + assert(policy != NULL && policy->policyid != NULL); + + if (OBJ_cmp(policy->policyid, certpol_oid) != 0) { + nid = OBJ_obj2nid(policy->policyid); + warnx("%s: RFC 6487 section 4.8.9: certificatePolicies: " + "unexpected policy identifier %d (%s)", p->fn, nid, + OBJ_nid2sn(nid)); + goto out; + } + + /* Policy qualifiers are optional. If they're absent, we're done. */ + if ((qualifiers = policy->qualifiers) == NULL) { + rc = 1; + goto out; + } + + if (sk_POLICYQUALINFO_num(qualifiers) != 1) { + warnx("%s: RFC 7318 section 2: certificatePolicies: " + "want 1 policy qualifier, got %d", p->fn, + sk_POLICYQUALINFO_num(qualifiers)); + goto out; + } + + qualifier = sk_POLICYQUALINFO_value(qualifiers, 0); + assert(qualifier != NULL && qualifier->pqualid != NULL); + + if ((nid = OBJ_obj2nid(qualifier->pqualid)) != NID_id_qt_cps) { + warnx("%s: RFC 7318 section 2: certificatePolicies: " + "want CPS, got %d (%s)", p->fn, nid, OBJ_nid2sn(nid)); + goto out; + } + + if (verbose > 1) + warnx("%s: CPS %.*s", p->fn, qualifier->d.cpsuri->length, + qualifier->d.cpsuri->data); + + rc = 1; + out: + sk_POLICYINFO_pop_free(policies, POLICYINFO_free); + return rc; +} + /* * Parse and partially validate an RPKI X509 certificate (either a trust * anchor or a certificate) as defined in RFC 6487. @@ -1024,6 +1098,9 @@ cert_parse_inner(const char *fn, const u case NID_sinfo_access: sia_present = 1; c = sbgp_sia(&p, ext); + break; + case NID_certificate_policies: + c = certificate_policies(&p, ext); break; case NID_crl_distribution_points: /* ignored here, handled later */ Index: parser.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v retrieving revision 1.58 diff -u -p -r1.58 parser.c --- parser.c 28 Jan 2022 15:30:23 -0000 1.58 +++ parser.c 4 Feb 2022 14:22:19 -0000 @@ -47,6 +47,8 @@ static X509_STORE_CTX *ctx; static struct auth_tree auths = RB_INITIALIZER(&auths); static struct crl_tree crlt = RB_INITIALIZER(&crlt); +extern ASN1_OBJECT *certpol_oid; + struct parse_repo { RB_ENTRY(parse_repo) entry; char *path; @@ -206,6 +208,8 @@ static int valid_x509(char *file, X509 *x509, struct auth *a, struct crl *crl, unsigned long flags, int nowarn) { + X509_VERIFY_PARAM *params; + ASN1_OBJECT *cp_oid; STACK_OF(X509) *chain; STACK_OF(X509_CRL) *crls = NULL; int c; @@ -217,11 +221,19 @@ valid_x509(char *file, X509 *x509, struc if (!X509_STORE_CTX_init(ctx, NULL, x509, NULL)) cryptoerrx("X509_STORE_CTX_init"); + if ((params = X509_STORE_CTX_get0_param(ctx)) == NULL) + cryptoerrx("X509_STORE_CTX_get0_param"); + if ((cp_oid = OBJ_dup(certpol_oid)) == NULL) + cryptoerrx("OBJ_dup"); + if (!X509_VERIFY_PARAM_add0_policy(params, cp_oid)) + cryptoerrx("X509_VERIFY_PARAM_add0_policy"); + X509_STORE_CTX_set_verify_cb(ctx, verify_cb); if (!X509_STORE_CTX_set_app_data(ctx, file)) cryptoerrx("X509_STORE_CTX_set_app_data"); - if (flags != 0) - X509_STORE_CTX_set_flags(ctx, flags); + flags |= X509_V_FLAG_EXPLICIT_POLICY; + flags |= X509_V_FLAG_INHIBIT_MAP; + X509_STORE_CTX_set_flags(ctx, flags); X509_STORE_CTX_set_depth(ctx, MAX_CERT_DEPTH); X509_STORE_CTX_set0_trusted_stack(ctx, chain); X509_STORE_CTX_set0_crls(ctx, crls); Index: x509.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/x509.c,v retrieving revision 1.33 diff -u -p -r1.33 x509.c --- x509.c 2 Feb 2022 12:10:40 -0000 1.33 +++ x509.c 4 Feb 2022 14:22:19 -0000 @@ -30,6 +30,7 @@ #include "extern.h" +ASN1_OBJECT *certpol_oid; /* id-cp-ipAddr-asNumber cert policy */ ASN1_OBJECT *carepo_oid; /* 1.3.6.1.5.5.7.48.5 (caRepository) */ ASN1_OBJECT *manifest_oid; /* 1.3.6.1.5.5.7.48.10 (rpkiManifest) */ ASN1_OBJECT *notify_oid; /* 1.3.6.1.5.5.7.48.13 (rpkiNotify) */ @@ -42,6 +43,8 @@ void x509_init_oid(void) { + if ((certpol_oid = OBJ_txt2obj("1.3.6.1.5.5.7.14.2", 1)) == NULL) + errx(1, "OBJ_txt2obj for %s failed", "1.3.6.1.5.5.7.14.2"); if ((carepo_oid = OBJ_txt2obj("1.3.6.1.5.5.7.48.5", 1)) == NULL) errx(1, "OBJ_txt2obj for %s failed", "1.3.6.1.5.5.7.48.5"); if ((manifest_oid = OBJ_txt2obj("1.3.6.1.5.5.7.48.10", 1)) == NULL)