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