1/* $OpenBSD: crl.c,v 1.42 2024/06/17 18:52:50 tb Exp $ */ 2/* 3 * Copyright (c) 2024 Theo Buehler <tb@openbsd.org> 4 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <err.h> 20#include <stdlib.h> 21#include <string.h> 22#include <unistd.h> 23 24#include <openssl/x509.h> 25 26#include "extern.h" 27 28/* 29 * Check that the CRL number extension is present and that it is non-critical. 30 * Otherwise ignore it per draft-spaghetti-sidrops-rpki-crl-numbers. 31 */ 32static int 33crl_has_crl_number(const char *fn, const X509_CRL *x509_crl) 34{ 35 const X509_EXTENSION *ext; 36 int idx; 37 38 if ((idx = X509_CRL_get_ext_by_NID(x509_crl, NID_crl_number, -1)) < 0) { 39 warnx("%s: RFC 6487, section 5: missing CRL number", fn); 40 return 0; 41 } 42 if ((ext = X509_CRL_get_ext(x509_crl, idx)) == NULL) { 43 warnx("%s: RFC 6487, section 5: failed to get CRL number", fn); 44 return 0; 45 } 46 if (X509_EXTENSION_get_critical(ext) != 0) { 47 warnx("%s: RFC 6487, section 5: CRL number not non-critical", 48 fn); 49 return 0; 50 } 51 52 return 1; 53} 54 55/* 56 * Parse X509v3 authority key identifier (AKI) from the CRL. 57 * Returns the AKI or NULL if it could not be parsed. 58 * The AKI is formatted as a hex string. 59 */ 60static char * 61crl_get_aki(const char *fn, X509_CRL *x509_crl) 62{ 63 AUTHORITY_KEYID *akid = NULL; 64 ASN1_OCTET_STRING *os; 65 const unsigned char *d; 66 int dsz, crit; 67 char *res = NULL; 68 69 if ((akid = X509_CRL_get_ext_d2i(x509_crl, NID_authority_key_identifier, 70 &crit, NULL)) == NULL) { 71 if (crit != -1) 72 warnx("%s: RFC 6487 section 4.8.3: AKI: " 73 "failed to parse CRL extension", fn); 74 else 75 warnx("%s: RFC 6487 section 4.8.3: AKI: " 76 "CRL extension missing", fn); 77 goto out; 78 } 79 if (crit != 0) { 80 warnx("%s: RFC 6487 section 4.8.3: " 81 "AKI: extension not non-critical", fn); 82 goto out; 83 } 84 if (akid->issuer != NULL || akid->serial != NULL) { 85 warnx("%s: RFC 6487 section 4.8.3: AKI: " 86 "authorityCertIssuer or authorityCertSerialNumber present", 87 fn); 88 goto out; 89 } 90 91 os = akid->keyid; 92 if (os == NULL) { 93 warnx("%s: RFC 6487 section 4.8.3: AKI: " 94 "Key Identifier missing", fn); 95 goto out; 96 } 97 98 d = os->data; 99 dsz = os->length; 100 101 if (dsz != SHA_DIGEST_LENGTH) { 102 warnx("%s: RFC 6487 section 4.8.3: AKI: " 103 "want %d bytes SHA1 hash, have %d bytes", 104 fn, SHA_DIGEST_LENGTH, dsz); 105 goto out; 106 } 107 108 res = hex_encode(d, dsz); 109 out: 110 AUTHORITY_KEYID_free(akid); 111 return res; 112} 113 114/* 115 * Check that the list of revoked certificates contains only the specified 116 * two fields, Serial Number and Revocation Date, and that no extensions are 117 * present. 118 */ 119static int 120crl_check_revoked(const char *fn, X509_CRL *x509_crl) 121{ 122 STACK_OF(X509_REVOKED) *list; 123 X509_REVOKED *revoked; 124 int count, i; 125 126 /* If there are no revoked certificates, there's nothing to check. */ 127 if ((list = X509_CRL_get_REVOKED(x509_crl)) == NULL) 128 return 1; 129 130 if ((count = sk_X509_REVOKED_num(list)) <= 0) { 131 /* 132 * XXX - as of May 2024, ~15% of RPKI CRLs fail this check due 133 * to a bug in rpki-rs/Krill. So silently accept this for now. 134 * https://github.com/NLnetLabs/krill/issues/1197 135 * https://github.com/NLnetLabs/rpki-rs/pull/295 136 */ 137 if (verbose > 1) 138 warnx("%s: RFC 5280, section 5.1.2.6: revoked " 139 "certificate list without entries disallowed", fn); 140 return 1; 141 } 142 143 for (i = 0; i < count; i++) { 144 revoked = sk_X509_REVOKED_value(list, i); 145 146 /* 147 * serialNumber and revocationDate are mandatory in the ASN.1 148 * template, so no need to check their presence. 149 * 150 * XXX - due to an old bug in Krill, we can't enforce that 151 * revocationDate is in the past until at least mid-2025: 152 * https://github.com/NLnetLabs/krill/issues/788. 153 */ 154 155 if (X509_REVOKED_get0_extensions(revoked) != NULL) { 156 warnx("%s: RFC 6487, section 5: CRL entry extensions " 157 "disallowed", fn); 158 return 0; 159 } 160 } 161 162 return 1; 163} 164 165struct crl * 166crl_parse(const char *fn, const unsigned char *der, size_t len) 167{ 168 const unsigned char *oder; 169 struct crl *crl; 170 const X509_NAME *name; 171 const ASN1_TIME *at; 172 int count, nid, rc = 0; 173 174 /* just fail for empty buffers, the warning was printed elsewhere */ 175 if (der == NULL) 176 return NULL; 177 178 if ((crl = calloc(1, sizeof(*crl))) == NULL) 179 err(1, NULL); 180 181 oder = der; 182 if ((crl->x509_crl = d2i_X509_CRL(NULL, &der, len)) == NULL) { 183 warnx("%s: d2i_X509_CRL", fn); 184 goto out; 185 } 186 if (der != oder + len) { 187 warnx("%s: %td bytes trailing garbage", fn, oder + len - der); 188 goto out; 189 } 190 191 if (X509_CRL_get_version(crl->x509_crl) != 1) { 192 warnx("%s: RFC 6487 section 5: version 2 expected", fn); 193 goto out; 194 } 195 196 if ((name = X509_CRL_get_issuer(crl->x509_crl)) == NULL) { 197 warnx("%s: X509_CRL_get_issuer", fn); 198 goto out; 199 } 200 if (!x509_valid_name(fn, "issuer", name)) 201 goto out; 202 203 if ((nid = X509_CRL_get_signature_nid(crl->x509_crl)) == NID_undef) { 204 warnx("%s: unknown signature type", fn); 205 goto out; 206 } 207 if (experimental && nid == NID_ecdsa_with_SHA256) { 208 if (verbose) 209 warnx("%s: P-256 support is experimental", fn); 210 } else if (nid != NID_sha256WithRSAEncryption) { 211 warnx("%s: RFC 7935: wrong signature algorithm %s, want %s", 212 fn, nid2str(nid), LN_sha256WithRSAEncryption); 213 goto out; 214 } 215 216 /* 217 * RFC 6487, section 5: AKI and crlNumber MUST be present, no other 218 * CRL extensions are allowed. 219 */ 220 if ((count = X509_CRL_get_ext_count(crl->x509_crl)) != 2) { 221 warnx("%s: RFC 6487 section 5: unexpected number of extensions " 222 "%d != 2", fn, count); 223 goto out; 224 } 225 if (!crl_has_crl_number(fn, crl->x509_crl)) 226 goto out; 227 if ((crl->aki = crl_get_aki(fn, crl->x509_crl)) == NULL) 228 goto out; 229 230 at = X509_CRL_get0_lastUpdate(crl->x509_crl); 231 if (at == NULL) { 232 warnx("%s: X509_CRL_get0_lastUpdate failed", fn); 233 goto out; 234 } 235 if (!x509_get_time(at, &crl->thisupdate)) { 236 warnx("%s: ASN1_TIME_to_tm failed", fn); 237 goto out; 238 } 239 240 at = X509_CRL_get0_nextUpdate(crl->x509_crl); 241 if (at == NULL) { 242 warnx("%s: X509_CRL_get0_nextUpdate failed", fn); 243 goto out; 244 } 245 if (!x509_get_time(at, &crl->nextupdate)) { 246 warnx("%s: ASN1_TIME_to_tm failed", fn); 247 goto out; 248 } 249 250 if (!crl_check_revoked(fn, crl->x509_crl)) 251 goto out; 252 253 rc = 1; 254 out: 255 if (rc == 0) { 256 crl_free(crl); 257 crl = NULL; 258 } 259 return crl; 260} 261 262static inline int 263crlcmp(struct crl *a, struct crl *b) 264{ 265 int cmp; 266 267 cmp = strcmp(a->aki, b->aki); 268 if (cmp > 0) 269 return 1; 270 if (cmp < 0) 271 return -1; 272 273 /* 274 * In filemode the mftpath cannot be determined easily, 275 * but it is always set in normal top-down validation. 276 */ 277 if (a->mftpath == NULL || b->mftpath == NULL) 278 return 0; 279 280 cmp = strcmp(a->mftpath, b->mftpath); 281 if (cmp > 0) 282 return 1; 283 if (cmp < 0) 284 return -1; 285 286 return 0; 287} 288 289RB_GENERATE_STATIC(crl_tree, crl, entry, crlcmp); 290 291/* 292 * Find a CRL based on the auth SKI value. 293 */ 294struct crl * 295crl_get(struct crl_tree *crlt, const struct auth *a) 296{ 297 struct crl find; 298 299 /* XXX - this should be removed, but filemode relies on it. */ 300 if (a == NULL) 301 return NULL; 302 303 find.aki = a->cert->ski; 304 find.mftpath = a->cert->mft; 305 306 return RB_FIND(crl_tree, crlt, &find); 307} 308 309int 310crl_insert(struct crl_tree *crlt, struct crl *crl) 311{ 312 return RB_INSERT(crl_tree, crlt, crl) == NULL; 313} 314 315void 316crl_free(struct crl *crl) 317{ 318 if (crl == NULL) 319 return; 320 free(crl->aki); 321 free(crl->mftpath); 322 X509_CRL_free(crl->x509_crl); 323 free(crl); 324} 325 326void 327crl_tree_free(struct crl_tree *crlt) 328{ 329 struct crl *crl, *tcrl; 330 331 RB_FOREACH_SAFE(crl, crl_tree, crlt, tcrl) { 332 RB_REMOVE(crl_tree, crlt, crl); 333 crl_free(crl); 334 } 335} 336