1*dab7a176Sjob /* $OpenBSD: x509.c,v 1.105 2024/12/03 14:51:09 job Exp $ */ 29a7e9e7fSjob /* 3740e9a54Stb * Copyright (c) 2022 Theo Buehler <tb@openbsd.org> 4a945dbeeSclaudio * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org> 59a7e9e7fSjob * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 69a7e9e7fSjob * 79a7e9e7fSjob * Permission to use, copy, modify, and distribute this software for any 89a7e9e7fSjob * purpose with or without fee is hereby granted, provided that the above 99a7e9e7fSjob * copyright notice and this permission notice appear in all copies. 109a7e9e7fSjob * 119a7e9e7fSjob * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 129a7e9e7fSjob * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 139a7e9e7fSjob * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 149a7e9e7fSjob * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 159a7e9e7fSjob * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 169a7e9e7fSjob * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 179a7e9e7fSjob * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 189a7e9e7fSjob */ 199a7e9e7fSjob 20e7ecba74Stb #include <assert.h> 219a7e9e7fSjob #include <err.h> 229a7e9e7fSjob #include <stdlib.h> 239a7e9e7fSjob #include <string.h> 249a7e9e7fSjob #include <unistd.h> 259a7e9e7fSjob 266b83d8e3Sjob #include <openssl/evp.h> 2751b3988bSbenno #include <openssl/x509v3.h> 289a7e9e7fSjob 299a7e9e7fSjob #include "extern.h" 309a7e9e7fSjob 3141c63c60Stb ASN1_OBJECT *certpol_oid; /* id-cp-ipAddr-asNumber cert policy */ 32de9b6f5dSclaudio ASN1_OBJECT *carepo_oid; /* 1.3.6.1.5.5.7.48.5 (caRepository) */ 33de9b6f5dSclaudio ASN1_OBJECT *manifest_oid; /* 1.3.6.1.5.5.7.48.10 (rpkiManifest) */ 342cf0e122Sjob ASN1_OBJECT *signedobj_oid; /* 1.3.6.1.5.5.7.48.11 (signedObject) */ 35de9b6f5dSclaudio ASN1_OBJECT *notify_oid; /* 1.3.6.1.5.5.7.48.13 (rpkiNotify) */ 36de9b6f5dSclaudio ASN1_OBJECT *roa_oid; /* id-ct-routeOriginAuthz CMS content type */ 37de9b6f5dSclaudio ASN1_OBJECT *mft_oid; /* id-ct-rpkiManifest CMS content type */ 38de9b6f5dSclaudio ASN1_OBJECT *gbr_oid; /* id-ct-rpkiGhostbusters CMS content type */ 39de9b6f5dSclaudio ASN1_OBJECT *bgpsec_oid; /* id-kp-bgpsec-router Key Purpose */ 409025e295Sclaudio ASN1_OBJECT *cnt_type_oid; /* pkcs-9 id-contentType */ 419025e295Sclaudio ASN1_OBJECT *msg_dgst_oid; /* pkcs-9 id-messageDigest */ 429025e295Sclaudio ASN1_OBJECT *sign_time_oid; /* pkcs-9 id-signingTime */ 433ea6759eStb ASN1_OBJECT *rsc_oid; /* id-ct-signedChecklist */ 44a29ddfd5Sjob ASN1_OBJECT *aspa_oid; /* id-ct-ASPA */ 45ee2a33daSjob ASN1_OBJECT *tak_oid; /* id-ct-SignedTAL */ 46ef3f6f56Sjob ASN1_OBJECT *geofeed_oid; /* id-ct-geofeedCSVwithCRLF */ 47d4be4cdeSjob ASN1_OBJECT *spl_oid; /* id-ct-signedPrefixList */ 48fdfddccfSjob 49c1a8310dStb static const struct { 50c1a8310dStb const char *oid; 51c1a8310dStb ASN1_OBJECT **ptr; 52c1a8310dStb } oid_table[] = { 53c1a8310dStb { 54c1a8310dStb .oid = "1.3.6.1.5.5.7.14.2", 55c1a8310dStb .ptr = &certpol_oid, 56c1a8310dStb }, 57c1a8310dStb { 58c1a8310dStb .oid = "1.3.6.1.5.5.7.48.5", 59c1a8310dStb .ptr = &carepo_oid, 60c1a8310dStb }, 61c1a8310dStb { 62c1a8310dStb .oid = "1.3.6.1.5.5.7.48.10", 63c1a8310dStb .ptr = &manifest_oid, 64c1a8310dStb }, 65c1a8310dStb { 662cf0e122Sjob .oid = "1.3.6.1.5.5.7.48.11", 672cf0e122Sjob .ptr = &signedobj_oid, 682cf0e122Sjob }, 692cf0e122Sjob { 70c1a8310dStb .oid = "1.3.6.1.5.5.7.48.13", 71c1a8310dStb .ptr = ¬ify_oid, 72c1a8310dStb }, 73c1a8310dStb { 74c1a8310dStb .oid = "1.2.840.113549.1.9.16.1.24", 75c1a8310dStb .ptr = &roa_oid, 76c1a8310dStb }, 77c1a8310dStb { 78c1a8310dStb .oid = "1.2.840.113549.1.9.16.1.26", 79c1a8310dStb .ptr = &mft_oid, 80c1a8310dStb }, 81c1a8310dStb { 82c1a8310dStb .oid = "1.2.840.113549.1.9.16.1.35", 83c1a8310dStb .ptr = &gbr_oid, 84c1a8310dStb }, 85c1a8310dStb { 86c1a8310dStb .oid = "1.3.6.1.5.5.7.3.30", 87c1a8310dStb .ptr = &bgpsec_oid, 88c1a8310dStb }, 89c1a8310dStb { 90c1a8310dStb .oid = "1.2.840.113549.1.9.3", 91c1a8310dStb .ptr = &cnt_type_oid, 92c1a8310dStb }, 93c1a8310dStb { 94c1a8310dStb .oid = "1.2.840.113549.1.9.4", 95c1a8310dStb .ptr = &msg_dgst_oid, 96c1a8310dStb }, 97c1a8310dStb { 98c1a8310dStb .oid = "1.2.840.113549.1.9.5", 99c1a8310dStb .ptr = &sign_time_oid, 100c1a8310dStb }, 101c1a8310dStb { 102ef3f6f56Sjob .oid = "1.2.840.113549.1.9.16.1.47", 103ef3f6f56Sjob .ptr = &geofeed_oid, 104ef3f6f56Sjob }, 105ef3f6f56Sjob { 106c1a8310dStb .oid = "1.2.840.113549.1.9.16.1.48", 107c1a8310dStb .ptr = &rsc_oid, 108c1a8310dStb }, 109c1a8310dStb { 110c1a8310dStb .oid = "1.2.840.113549.1.9.16.1.49", 111c1a8310dStb .ptr = &aspa_oid, 112c1a8310dStb }, 113ee2a33daSjob { 114ee2a33daSjob .oid = "1.2.840.113549.1.9.16.1.50", 115ee2a33daSjob .ptr = &tak_oid, 116ee2a33daSjob }, 117d4be4cdeSjob { 118d4be4cdeSjob .oid = "1.2.840.113549.1.9.16.1.51", 119d4be4cdeSjob .ptr = &spl_oid, 120d4be4cdeSjob }, 121c1a8310dStb }; 122c1a8310dStb 123de9b6f5dSclaudio void 124de9b6f5dSclaudio x509_init_oid(void) 125fdfddccfSjob { 126c1a8310dStb size_t i; 127de9b6f5dSclaudio 128c1a8310dStb for (i = 0; i < sizeof(oid_table) / sizeof(oid_table[0]); i++) { 129c1a8310dStb *oid_table[i].ptr = OBJ_txt2obj(oid_table[i].oid, 1); 130c1a8310dStb if (*oid_table[i].ptr == NULL) 131c1a8310dStb errx(1, "OBJ_txt2obj for %s failed", oid_table[i].oid); 132c1a8310dStb } 133fdfddccfSjob } 134fdfddccfSjob 1359a7e9e7fSjob /* 136e891962dStb * A number of critical OpenSSL API functions can't properly indicate failure 137e891962dStb * and are unreliable if the extensions aren't already cached. An old trick is 138e891962dStb * to cache the extensions using an error-checked call to X509_check_purpose() 139e891962dStb * with a purpose of -1. This way functions such as X509_check_ca(), X509_cmp(), 140e891962dStb * X509_get_key_usage(), X509_get_extended_key_usage() won't lie. 141e891962dStb * 142e891962dStb * Should be called right after deserialization and is essentially free to call 143e891962dStb * multiple times. 144e891962dStb */ 145e891962dStb int 146e891962dStb x509_cache_extensions(X509 *x509, const char *fn) 147e891962dStb { 148e891962dStb if (X509_check_purpose(x509, -1, 0) <= 0) { 149e891962dStb warnx("%s: could not cache X509v3 extensions", fn); 150e891962dStb return 0; 151e891962dStb } 152e891962dStb return 1; 153e891962dStb } 154e891962dStb 155e891962dStb /* 1569a7e9e7fSjob * Parse X509v3 authority key identifier (AKI), RFC 6487 sec. 4.8.3. 1579a7e9e7fSjob * Returns the AKI or NULL if it could not be parsed. 1581a998f47Sclaudio * The AKI is formatted as a hex string. 1599a7e9e7fSjob */ 160f999fe57Sclaudio int 161f999fe57Sclaudio x509_get_aki(X509 *x, const char *fn, char **aki) 1629a7e9e7fSjob { 1639a7e9e7fSjob const unsigned char *d; 164356f9aecSclaudio AUTHORITY_KEYID *akid; 165356f9aecSclaudio ASN1_OCTET_STRING *os; 166f999fe57Sclaudio int dsz, crit, rc = 0; 1679a7e9e7fSjob 168f999fe57Sclaudio *aki = NULL; 169356f9aecSclaudio akid = X509_get_ext_d2i(x, NID_authority_key_identifier, &crit, NULL); 1708435fb8dStb if (akid == NULL) { 1718435fb8dStb if (crit != -1) { 1728435fb8dStb warnx("%s: RFC 6487 section 4.8.3: error parsing AKI", 1738435fb8dStb fn); 1748435fb8dStb return 0; 1758435fb8dStb } 176f999fe57Sclaudio return 1; 1778435fb8dStb } 178356f9aecSclaudio if (crit != 0) { 179356f9aecSclaudio warnx("%s: RFC 6487 section 4.8.3: " 180356f9aecSclaudio "AKI: extension not non-critical", fn); 181356f9aecSclaudio goto out; 182356f9aecSclaudio } 183356f9aecSclaudio if (akid->issuer != NULL || akid->serial != NULL) { 184356f9aecSclaudio warnx("%s: RFC 6487 section 4.8.3: AKI: " 185356f9aecSclaudio "authorityCertIssuer or authorityCertSerialNumber present", 186356f9aecSclaudio fn); 187356f9aecSclaudio goto out; 188356f9aecSclaudio } 189356f9aecSclaudio 190356f9aecSclaudio os = akid->keyid; 191356f9aecSclaudio if (os == NULL) { 192356f9aecSclaudio warnx("%s: RFC 6487 section 4.8.3: AKI: " 193356f9aecSclaudio "Key Identifier missing", fn); 194356f9aecSclaudio goto out; 195356f9aecSclaudio } 1969a7e9e7fSjob 1979a7e9e7fSjob d = os->data; 1989a7e9e7fSjob dsz = os->length; 1999a7e9e7fSjob 200356f9aecSclaudio if (dsz != SHA_DIGEST_LENGTH) { 201356f9aecSclaudio warnx("%s: RFC 6487 section 4.8.2: AKI: " 202356f9aecSclaudio "want %d bytes SHA1 hash, have %d bytes", 203356f9aecSclaudio fn, SHA_DIGEST_LENGTH, dsz); 2049a7e9e7fSjob goto out; 2059a7e9e7fSjob } 2069a7e9e7fSjob 207f999fe57Sclaudio *aki = hex_encode(d, dsz); 208f999fe57Sclaudio rc = 1; 2099a7e9e7fSjob out: 210356f9aecSclaudio AUTHORITY_KEYID_free(akid); 211f999fe57Sclaudio return rc; 212356f9aecSclaudio } 213356f9aecSclaudio 214356f9aecSclaudio /* 21533fc45d8Stb * Validate the X509v3 subject key identifier (SKI), RFC 6487 section 4.8.2: 21641ea20e6Sjob * "The SKI is a SHA-1 hash of the value of the DER-encoded ASN.1 BIT STRING of 21741ea20e6Sjob * the Subject Public Key, as described in Section 4.2.1.2 of RFC 5280." 218548c1072Sjob * Returns the SKI formatted as hex string, or NULL if it couldn't be parsed. 219356f9aecSclaudio */ 220f999fe57Sclaudio int 221f999fe57Sclaudio x509_get_ski(X509 *x, const char *fn, char **ski) 222356f9aecSclaudio { 223356f9aecSclaudio ASN1_OCTET_STRING *os; 22433fc45d8Stb unsigned char md[EVP_MAX_MD_SIZE]; 22533fc45d8Stb unsigned int md_len = EVP_MAX_MD_SIZE; 22633fc45d8Stb int crit, rc = 0; 227356f9aecSclaudio 228f999fe57Sclaudio *ski = NULL; 229356f9aecSclaudio os = X509_get_ext_d2i(x, NID_subject_key_identifier, &crit, NULL); 2308435fb8dStb if (os == NULL) { 2318435fb8dStb if (crit != -1) { 2328435fb8dStb warnx("%s: RFC 6487 section 4.8.2: error parsing SKI", 2338435fb8dStb fn); 2348435fb8dStb return 0; 2358435fb8dStb } 236f999fe57Sclaudio return 1; 2378435fb8dStb } 238356f9aecSclaudio if (crit != 0) { 239356f9aecSclaudio warnx("%s: RFC 6487 section 4.8.2: " 240356f9aecSclaudio "SKI: extension not non-critical", fn); 241356f9aecSclaudio goto out; 242356f9aecSclaudio } 243356f9aecSclaudio 24433fc45d8Stb if (!X509_pubkey_digest(x, EVP_sha1(), md, &md_len)) { 24533fc45d8Stb warnx("%s: X509_pubkey_digest", fn); 24633fc45d8Stb goto out; 24733fc45d8Stb } 248356f9aecSclaudio 24933fc45d8Stb if (os->length < 0 || md_len != (size_t)os->length) { 250356f9aecSclaudio warnx("%s: RFC 6487 section 4.8.2: SKI: " 25133fc45d8Stb "want %u bytes SHA1 hash, have %d bytes", 25233fc45d8Stb fn, md_len, os->length); 253356f9aecSclaudio goto out; 254356f9aecSclaudio } 255356f9aecSclaudio 25633fc45d8Stb if (memcmp(os->data, md, md_len) != 0) { 257548c1072Sjob warnx("%s: SKI does not match SHA1 hash of SPK", fn); 258548c1072Sjob goto out; 259548c1072Sjob } 260548c1072Sjob 26133fc45d8Stb *ski = hex_encode(md, md_len); 262f999fe57Sclaudio rc = 1; 263356f9aecSclaudio out: 264356f9aecSclaudio ASN1_OCTET_STRING_free(os); 265f999fe57Sclaudio return rc; 2669a7e9e7fSjob } 2679a7e9e7fSjob 2689a7e9e7fSjob /* 269eb6f3761Stb * Check the cert's purpose: the cA bit in basic constraints distinguishes 27036d6639cStb * between TA/CA and EE/BGPsec router and the key usage bits must match. 27136d6639cStb * TAs are self-signed, CAs not self-issued, EEs have no extended key usage, 27236d6639cStb * BGPsec router have id-kp-bgpsec-router OID. 273fdfddccfSjob */ 274fdfddccfSjob enum cert_purpose 275fdfddccfSjob x509_get_purpose(X509 *x, const char *fn) 276fdfddccfSjob { 27782b39356Sjob BASIC_CONSTRAINTS *bc = NULL; 278fdfddccfSjob EXTENDED_KEY_USAGE *eku = NULL; 27936d6639cStb const X509_EXTENSION *ku; 280204218f7Stb int crit, ext_flags, i, is_ca, ku_idx; 2812f8ca7dcSjob enum cert_purpose purpose = CERT_PURPOSE_INVALID; 282fdfddccfSjob 283eb6f3761Stb if (!x509_cache_extensions(x, fn)) 284eb6f3761Stb goto out; 285eb6f3761Stb 286eb6f3761Stb ext_flags = X509_get_extension_flags(x); 287eb6f3761Stb 28836d6639cStb /* Key usage must be present and critical. KU bits are checked below. */ 28936d6639cStb if ((ku_idx = X509_get_ext_by_NID(x, NID_key_usage, -1)) < 0) { 29036d6639cStb warnx("%s: RFC 6487, section 4.8.4: missing KeyUsage", fn); 29136d6639cStb goto out; 29236d6639cStb } 29336d6639cStb if ((ku = X509_get_ext(x, ku_idx)) == NULL) { 29436d6639cStb warnx("%s: RFC 6487, section 4.8.4: missing KeyUsage", fn); 29536d6639cStb goto out; 29636d6639cStb } 29736d6639cStb if (!X509_EXTENSION_get_critical(ku)) { 29836d6639cStb warnx("%s: RFC 6487, section 4.8.4: KeyUsage not critical", fn); 29936d6639cStb goto out; 30036d6639cStb } 30136d6639cStb 302eb6f3761Stb /* This weird API can return 0, 1, 2, 4, 5 but can't error... */ 303eb6f3761Stb if ((is_ca = X509_check_ca(x)) > 1) { 304eb6f3761Stb if (is_ca == 4) 305eb6f3761Stb warnx("%s: RFC 6487: sections 4.8.1 and 4.8.4: " 306eb6f3761Stb "no basic constraints, but keyCertSign set", fn); 307eb6f3761Stb else 308eb6f3761Stb warnx("%s: unexpected legacy certificate", fn); 309eb6f3761Stb goto out; 310eb6f3761Stb } 311eb6f3761Stb 312eb6f3761Stb if (is_ca) { 31382b39356Sjob bc = X509_get_ext_d2i(x, NID_basic_constraints, &crit, NULL); 3148435fb8dStb if (bc == NULL) { 3158435fb8dStb if (crit != -1) 3168435fb8dStb warnx("%s: RFC 6487 section 4.8.1: " 3178435fb8dStb "error parsing basic constraints", fn); 3188435fb8dStb else 3198435fb8dStb warnx("%s: RFC 6487 section 4.8.1: " 3208435fb8dStb "missing basic constraints", fn); 3218435fb8dStb goto out; 3228435fb8dStb } 3238435fb8dStb if (crit != 1) { 3248435fb8dStb warnx("%s: RFC 6487 section 4.8.1: Basic Constraints " 3258435fb8dStb "must be marked critical", fn); 3268435fb8dStb goto out; 3278435fb8dStb } 32882b39356Sjob if (bc->pathlen != NULL) { 32982b39356Sjob warnx("%s: RFC 6487 section 4.8.1: Path Length " 33082b39356Sjob "Constraint must be absent", fn); 33182b39356Sjob goto out; 33282b39356Sjob } 33336d6639cStb 33436d6639cStb if (X509_get_key_usage(x) != (KU_KEY_CERT_SIGN | KU_CRL_SIGN)) { 33536d6639cStb warnx("%s: RFC 6487 section 4.8.4: key usage violation", 33636d6639cStb fn); 33736d6639cStb goto out; 33836d6639cStb } 33936d6639cStb 34036d6639cStb if (X509_get_extended_key_usage(x) != UINT32_MAX) { 34136d6639cStb warnx("%s: RFC 6487 section 4.8.5: EKU not allowed", 34236d6639cStb fn); 34336d6639cStb goto out; 34436d6639cStb } 34536d6639cStb 346eb6f3761Stb /* 347eb6f3761Stb * EXFLAG_SI means that issuer and subject are identical. 348eb6f3761Stb * EXFLAG_SS is SI plus the AKI is absent or matches the SKI. 349eb6f3761Stb * Thus, exactly the trust anchors should have EXFLAG_SS set 350eb6f3761Stb * and we should never see EXFLAG_SI without EXFLAG_SS. 351eb6f3761Stb */ 352eb6f3761Stb if ((ext_flags & EXFLAG_SS) != 0) 353eb6f3761Stb purpose = CERT_PURPOSE_TA; 354eb6f3761Stb else if ((ext_flags & EXFLAG_SI) == 0) 355fdfddccfSjob purpose = CERT_PURPOSE_CA; 356eb6f3761Stb else 357eb6f3761Stb warnx("%s: RFC 6487, section 4.8.3: " 358eb6f3761Stb "self-issued cert with AKI-SKI mismatch", fn); 359fdfddccfSjob goto out; 360fdfddccfSjob } 361fdfddccfSjob 362eb6f3761Stb if ((ext_flags & EXFLAG_BCONS) != 0) { 3632f8ca7dcSjob warnx("%s: Basic Constraints ext in non-CA cert", fn); 3642f8ca7dcSjob goto out; 3652f8ca7dcSjob } 3662f8ca7dcSjob 36736d6639cStb if (X509_get_key_usage(x) != KU_DIGITAL_SIGNATURE) { 36836d6639cStb warnx("%s: RFC 6487 section 4.8.4: KU must be digitalSignature", 36936d6639cStb fn); 37036d6639cStb goto out; 37136d6639cStb } 37236d6639cStb 373eb6f3761Stb /* 374eb6f3761Stb * EKU is only defined for BGPsec Router certs and must be absent from 375eb6f3761Stb * EE certs. 376eb6f3761Stb */ 377fdfddccfSjob eku = X509_get_ext_d2i(x, NID_ext_key_usage, &crit, NULL); 378fdfddccfSjob if (eku == NULL) { 3798435fb8dStb if (crit != -1) 3808435fb8dStb warnx("%s: error parsing EKU", fn); 3818435fb8dStb else 382eb6f3761Stb purpose = CERT_PURPOSE_EE; /* EKU absent */ 383fdfddccfSjob goto out; 384fdfddccfSjob } 385fdfddccfSjob if (crit != 0) { 386fdfddccfSjob warnx("%s: EKU: extension must not be marked critical", fn); 387fdfddccfSjob goto out; 388fdfddccfSjob } 38937cdae3dStb 39037cdae3dStb /* 391204218f7Stb * Per RFC 8209, section 3.1.3.2 the id-kp-bgpsec-router OID must be 392204218f7Stb * present and others are allowed, which we don't need to recognize. 393204218f7Stb * This matches RFC 5280, section 4.2.1.12. 39437cdae3dStb */ 395204218f7Stb for (i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { 396204218f7Stb if (OBJ_cmp(bgpsec_oid, sk_ASN1_OBJECT_value(eku, i)) == 0) { 397fdfddccfSjob purpose = CERT_PURPOSE_BGPSEC_ROUTER; 398204218f7Stb break; 399204218f7Stb } 400fdfddccfSjob } 401fdfddccfSjob 402fdfddccfSjob out: 40382b39356Sjob BASIC_CONSTRAINTS_free(bc); 404fdfddccfSjob EXTENDED_KEY_USAGE_free(eku); 405fdfddccfSjob return purpose; 406fdfddccfSjob } 407fdfddccfSjob 4086b83d8e3Sjob /* 40917304ed1Sjob * Extract Subject Public Key Info (SPKI) from BGPsec X.509 Certificate. 41017304ed1Sjob * Returns NULL on failure, on success return the SPKI as base64 encoded pubkey 4116b83d8e3Sjob */ 4126b83d8e3Sjob char * 41317304ed1Sjob x509_get_pubkey(X509 *x, const char *fn) 4146b83d8e3Sjob { 41505f213d6Sjob EVP_PKEY *pkey; 4169a67f0c9Stb const EC_KEY *eckey; 417*dab7a176Sjob const EC_GROUP *ecg; 4186b83d8e3Sjob int nid; 4196b83d8e3Sjob const char *cname; 42017304ed1Sjob uint8_t *pubkey = NULL; 4216b83d8e3Sjob char *res = NULL; 42217304ed1Sjob int len; 4236b83d8e3Sjob 42405f213d6Sjob pkey = X509_get0_pubkey(x); 42505f213d6Sjob if (pkey == NULL) { 426ffd2fc2bStb warnx("%s: X509_get0_pubkey failed in %s", fn, __func__); 4276b83d8e3Sjob goto out; 4286b83d8e3Sjob } 42905f213d6Sjob if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { 4306b83d8e3Sjob warnx("%s: Expected EVP_PKEY_EC, got %d", fn, 43105f213d6Sjob EVP_PKEY_base_id(pkey)); 4326b83d8e3Sjob goto out; 4336b83d8e3Sjob } 4346b83d8e3Sjob 43505f213d6Sjob eckey = EVP_PKEY_get0_EC_KEY(pkey); 43605f213d6Sjob if (eckey == NULL) { 4376b83d8e3Sjob warnx("%s: Incorrect key type", fn); 4386b83d8e3Sjob goto out; 4396b83d8e3Sjob } 4406b83d8e3Sjob 441*dab7a176Sjob if ((ecg = EC_KEY_get0_group(eckey)) == NULL) { 442*dab7a176Sjob warnx("%s: EC_KEY_get0_group failed", fn); 443*dab7a176Sjob goto out; 444*dab7a176Sjob } 445*dab7a176Sjob 446*dab7a176Sjob if (EC_GROUP_get_asn1_flag(ecg) != OPENSSL_EC_NAMED_CURVE) { 447*dab7a176Sjob warnx("%s: curve encoding issue", fn); 448*dab7a176Sjob goto out; 449*dab7a176Sjob } 450*dab7a176Sjob 451*dab7a176Sjob if (EC_GROUP_get_point_conversion_form(ecg) != 452*dab7a176Sjob POINT_CONVERSION_UNCOMPRESSED) 453*dab7a176Sjob warnx("%s: unconventional point encoding", fn); 454*dab7a176Sjob 455*dab7a176Sjob nid = EC_GROUP_get_curve_name(ecg); 4566b83d8e3Sjob if (nid != NID_X9_62_prime256v1) { 4576b83d8e3Sjob if ((cname = EC_curve_nid2nist(nid)) == NULL) 45878de3577Stb cname = nid2str(nid); 4596b83d8e3Sjob warnx("%s: Expected P-256, got %s", fn, cname); 4606b83d8e3Sjob goto out; 4616b83d8e3Sjob } 4626b83d8e3Sjob 46305f213d6Sjob if (!EC_KEY_check_key(eckey)) { 4646b83d8e3Sjob warnx("%s: EC_KEY_check_key failed in %s", fn, __func__); 4656b83d8e3Sjob goto out; 4666b83d8e3Sjob } 4676b83d8e3Sjob 46805f213d6Sjob len = i2d_PUBKEY(pkey, &pubkey); 46917304ed1Sjob if (len <= 0) { 47017304ed1Sjob warnx("%s: i2d_PUBKEY failed in %s", fn, __func__); 4716b83d8e3Sjob goto out; 4726b83d8e3Sjob } 4736b83d8e3Sjob 47417304ed1Sjob if (base64_encode(pubkey, len, &res) == -1) 4756b83d8e3Sjob errx(1, "base64_encode failed in %s", __func__); 4766b83d8e3Sjob 4776b83d8e3Sjob out: 47817304ed1Sjob free(pubkey); 4796b83d8e3Sjob return res; 4806b83d8e3Sjob } 481fdfddccfSjob 482fdfddccfSjob /* 48323c6f3a2Stb * Compute the SKI of an RSA public key in an X509_PUBKEY using SHA-1. 48423c6f3a2Stb * Returns allocated hex-encoded SKI on success, NULL on failure. 48523c6f3a2Stb */ 48623c6f3a2Stb char * 48723c6f3a2Stb x509_pubkey_get_ski(X509_PUBKEY *pubkey, const char *fn) 48823c6f3a2Stb { 48923c6f3a2Stb ASN1_OBJECT *obj; 49023c6f3a2Stb const unsigned char *der; 49123c6f3a2Stb int der_len, nid; 49223c6f3a2Stb unsigned char md[EVP_MAX_MD_SIZE]; 49323c6f3a2Stb unsigned int md_len = EVP_MAX_MD_SIZE; 49423c6f3a2Stb 49523c6f3a2Stb if (!X509_PUBKEY_get0_param(&obj, &der, &der_len, NULL, pubkey)) { 49623c6f3a2Stb warnx("%s: X509_PUBKEY_get0_param failed", fn); 49723c6f3a2Stb return NULL; 49823c6f3a2Stb } 49923c6f3a2Stb 5000047c88aStb /* XXX - should allow other keys as well. */ 50123c6f3a2Stb if ((nid = OBJ_obj2nid(obj)) != NID_rsaEncryption) { 50223c6f3a2Stb warnx("%s: RFC 7935: wrong signature algorithm %s, want %s", 50323c6f3a2Stb fn, nid2str(nid), LN_rsaEncryption); 50423c6f3a2Stb return NULL; 50523c6f3a2Stb } 50623c6f3a2Stb 50723c6f3a2Stb if (!EVP_Digest(der, der_len, md, &md_len, EVP_sha1(), NULL)) { 50823c6f3a2Stb warnx("%s: EVP_Digest failed", fn); 50923c6f3a2Stb return NULL; 51023c6f3a2Stb } 51123c6f3a2Stb 51223c6f3a2Stb return hex_encode(md, md_len); 51323c6f3a2Stb } 51423c6f3a2Stb 51523c6f3a2Stb /* 516ebd55816Sjob * Parse the Authority Information Access (AIA) extension 517ebd55816Sjob * See RFC 6487, section 4.8.7 for details. 518ebd55816Sjob * Returns NULL on failure, on success returns the AIA URI 519ebd55816Sjob * (which has to be freed after use). 520ebd55816Sjob */ 521f999fe57Sclaudio int 522e7ecba74Stb x509_get_aia(X509 *x, const char *fn, char **out_aia) 523ebd55816Sjob { 524ebd55816Sjob ACCESS_DESCRIPTION *ad; 525ebd55816Sjob AUTHORITY_INFO_ACCESS *info; 526f999fe57Sclaudio int crit, rc = 0; 527ebd55816Sjob 528e7ecba74Stb assert(*out_aia == NULL); 529e7ecba74Stb 530356f9aecSclaudio info = X509_get_ext_d2i(x, NID_info_access, &crit, NULL); 5318435fb8dStb if (info == NULL) { 5328435fb8dStb if (crit != -1) { 5338435fb8dStb warnx("%s: RFC 6487 section 4.8.7: error parsing AIA", 5348435fb8dStb fn); 5358435fb8dStb return 0; 5368435fb8dStb } 537f999fe57Sclaudio return 1; 538f31ce3c9Stb } 539f31ce3c9Stb 540356f9aecSclaudio if (crit != 0) { 541356f9aecSclaudio warnx("%s: RFC 6487 section 4.8.7: " 542356f9aecSclaudio "AIA: extension not non-critical", fn); 543356f9aecSclaudio goto out; 544356f9aecSclaudio } 545f31ce3c9Stb 5468435fb8dStb if ((X509_get_extension_flags(x) & EXFLAG_SS) != 0) { 5478435fb8dStb warnx("%s: RFC 6487 section 4.8.7: AIA must be absent from " 5488435fb8dStb "a self-signed certificate", fn); 5498435fb8dStb goto out; 5508435fb8dStb } 5518435fb8dStb 552ebd55816Sjob if (sk_ACCESS_DESCRIPTION_num(info) != 1) { 553ebd55816Sjob warnx("%s: RFC 6487 section 4.8.7: AIA: " 554ebd55816Sjob "want 1 element, have %d", fn, 555ebd55816Sjob sk_ACCESS_DESCRIPTION_num(info)); 556ebd55816Sjob goto out; 557ebd55816Sjob } 558ebd55816Sjob 559ebd55816Sjob ad = sk_ACCESS_DESCRIPTION_value(info, 0); 560ebd55816Sjob if (OBJ_obj2nid(ad->method) != NID_ad_ca_issuers) { 561ebd55816Sjob warnx("%s: RFC 6487 section 4.8.7: AIA: " 562ebd55816Sjob "expected caIssuers, have %d", fn, OBJ_obj2nid(ad->method)); 563ebd55816Sjob goto out; 564ebd55816Sjob } 565ebd55816Sjob 566e7ecba74Stb if (!x509_location(fn, "AIA: caIssuers", ad->location, out_aia)) 5671c699626Sbeck goto out; 5681c699626Sbeck 569f999fe57Sclaudio rc = 1; 570ebd55816Sjob 571ebd55816Sjob out: 572ebd55816Sjob AUTHORITY_INFO_ACCESS_free(info); 573f999fe57Sclaudio return rc; 574ebd55816Sjob } 575ebd55816Sjob 576ebd55816Sjob /* 577c5305b1dStb * Parse the Subject Information Access (SIA) extension for an EE cert. 578c5305b1dStb * See RFC 6487, section 4.8.8.2 for details. 5792cf0e122Sjob * Returns NULL on failure, on success returns the SIA signedObject URI 5802cf0e122Sjob * (which has to be freed after use). 5812cf0e122Sjob */ 5822cf0e122Sjob int 583e7ecba74Stb x509_get_sia(X509 *x, const char *fn, char **out_sia) 5842cf0e122Sjob { 5852cf0e122Sjob ACCESS_DESCRIPTION *ad; 5862cf0e122Sjob AUTHORITY_INFO_ACCESS *info; 5872cf0e122Sjob ASN1_OBJECT *oid; 588e7ecba74Stb int i, crit, rc = 0; 5892cf0e122Sjob 590e7ecba74Stb assert(*out_sia == NULL); 5912cf0e122Sjob 5922cf0e122Sjob info = X509_get_ext_d2i(x, NID_sinfo_access, &crit, NULL); 5938435fb8dStb if (info == NULL) { 5948435fb8dStb if (crit != -1) { 5958435fb8dStb warnx("%s: error parsing SIA", fn); 5968435fb8dStb return 0; 5978435fb8dStb } 5982cf0e122Sjob return 1; 5998435fb8dStb } 6002cf0e122Sjob 6012cf0e122Sjob if (crit != 0) { 6022cf0e122Sjob warnx("%s: RFC 6487 section 4.8.8: " 6032cf0e122Sjob "SIA: extension not non-critical", fn); 6042cf0e122Sjob goto out; 6052cf0e122Sjob } 6062cf0e122Sjob 6072cf0e122Sjob for (i = 0; i < sk_ACCESS_DESCRIPTION_num(info); i++) { 608e7ecba74Stb char *sia; 609e7ecba74Stb 6102cf0e122Sjob ad = sk_ACCESS_DESCRIPTION_value(info, i); 6112cf0e122Sjob oid = ad->method; 6122cf0e122Sjob 613c39877f0Stb /* 6145fb296d6Stb * XXX: RFC 6487 4.8.8.2 states that the accessMethod MUST be 6155fb296d6Stb * signedObject. However, rpkiNotify accessMethods currently 6165fb296d6Stb * exist in the wild. Consider removing this special case. 617c39877f0Stb * See also https://www.rfc-editor.org/errata/eid7239. 618c39877f0Stb */ 6195fb296d6Stb if (OBJ_cmp(oid, notify_oid) == 0) { 6205fb296d6Stb if (verbose > 1) 6215fb296d6Stb warnx("%s: RFC 6487 section 4.8.8.2: SIA should" 6225fb296d6Stb " not contain rpkiNotify accessMethod", fn); 6235fb296d6Stb continue; 6245fb296d6Stb } 625c39877f0Stb if (OBJ_cmp(oid, signedobj_oid) != 0) { 626c39877f0Stb char buf[128]; 627c39877f0Stb 628c39877f0Stb OBJ_obj2txt(buf, sizeof(buf), oid, 0); 629c39877f0Stb warnx("%s: RFC 6487 section 4.8.8.2: unexpected" 630c39877f0Stb " accessMethod: %s", fn, buf); 6315fb296d6Stb goto out; 632c39877f0Stb } 6332cf0e122Sjob 634e7ecba74Stb sia = NULL; 635e7ecba74Stb if (!x509_location(fn, "SIA: signedObject", ad->location, &sia)) 6362cf0e122Sjob goto out; 637e6a231f0Stb 638e7ecba74Stb if (*out_sia == NULL && strncasecmp(sia, RSYNC_PROTO, 639e7ecba74Stb RSYNC_PROTO_LEN) == 0) { 640e7ecba74Stb const char *p = sia + RSYNC_PROTO_LEN; 641024ae3e4Sjob size_t fnlen, plen; 642024ae3e4Sjob 643e7ecba74Stb if (filemode) { 644e7ecba74Stb *out_sia = sia; 645024ae3e4Sjob continue; 646e7ecba74Stb } 647024ae3e4Sjob 648024ae3e4Sjob fnlen = strlen(fn); 649024ae3e4Sjob plen = strlen(p); 650024ae3e4Sjob 651024ae3e4Sjob if (fnlen < plen || strcmp(p, fn + fnlen - plen) != 0) { 652024ae3e4Sjob warnx("%s: mismatch between pathname and SIA " 653e7ecba74Stb "(%s)", fn, sia); 654e7ecba74Stb free(sia); 655024ae3e4Sjob goto out; 656024ae3e4Sjob } 657024ae3e4Sjob 658e7ecba74Stb *out_sia = sia; 659e6a231f0Stb continue; 6602cf0e122Sjob } 661e7ecba74Stb if (verbose) 662e7ecba74Stb warnx("%s: RFC 6487 section 4.8.8: SIA: " 663e7ecba74Stb "ignoring location %s", fn, sia); 664e7ecba74Stb free(sia); 665e6a231f0Stb } 666e6a231f0Stb 667e7ecba74Stb if (*out_sia == NULL) { 66849409ac0Sjob warnx("%s: RFC 6487 section 4.8.8.2: " 66949409ac0Sjob "SIA without rsync accessLocation", fn); 6705fb296d6Stb goto out; 67149409ac0Sjob } 6725fb296d6Stb 673e7ecba74Stb rc = 1; 6745fb296d6Stb 6755fb296d6Stb out: 6765fb296d6Stb AUTHORITY_INFO_ACCESS_free(info); 677e7ecba74Stb return rc; 6782cf0e122Sjob } 6792cf0e122Sjob 6802cf0e122Sjob /* 681f5999ddfSjob * Extract the notBefore of a certificate. 682f5999ddfSjob */ 683f5999ddfSjob int 684f5999ddfSjob x509_get_notbefore(X509 *x, const char *fn, time_t *tt) 685f5999ddfSjob { 686f5999ddfSjob const ASN1_TIME *at; 687f5999ddfSjob 688f5999ddfSjob at = X509_get0_notBefore(x); 689f5999ddfSjob if (at == NULL) { 690f5999ddfSjob warnx("%s: X509_get0_notBefore failed", fn); 691f5999ddfSjob return 0; 692f5999ddfSjob } 693f5999ddfSjob if (!x509_get_time(at, tt)) { 6945abefff6Stb warnx("%s: ASN1_TIME_to_tm failed", fn); 695f5999ddfSjob return 0; 696f5999ddfSjob } 697f5999ddfSjob return 1; 698f5999ddfSjob } 699f5999ddfSjob 700f5999ddfSjob /* 7019f544822Sjob * Extract the notAfter from a certificate. 7027fd566d8Sclaudio */ 70361c641a8Sbeck int 7049f544822Sjob x509_get_notafter(X509 *x, const char *fn, time_t *tt) 7057fd566d8Sclaudio { 7067fd566d8Sclaudio const ASN1_TIME *at; 7077fd566d8Sclaudio 7087fd566d8Sclaudio at = X509_get0_notAfter(x); 70961c641a8Sbeck if (at == NULL) { 71061c641a8Sbeck warnx("%s: X509_get0_notafter failed", fn); 71161c641a8Sbeck return 0; 71261c641a8Sbeck } 71310093466Stb if (!x509_get_time(at, tt)) { 7145abefff6Stb warnx("%s: ASN1_TIME_to_tm failed", fn); 71561c641a8Sbeck return 0; 71661c641a8Sbeck } 71761c641a8Sbeck return 1; 7187fd566d8Sclaudio } 7197fd566d8Sclaudio 7207fd566d8Sclaudio /* 721c9e39c95Sjob * Check whether all RFC 3779 extensions are set to inherit. 7223a363cbdSjob * Return 1 if both AS & IP are set to inherit. 7233a363cbdSjob * Return 0 on failure (such as missing extensions or no inheritance). 7243a363cbdSjob */ 7253a363cbdSjob int 7263a363cbdSjob x509_inherits(X509 *x) 7273a363cbdSjob { 7283a363cbdSjob STACK_OF(IPAddressFamily) *addrblk = NULL; 7293a363cbdSjob ASIdentifiers *asidentifiers = NULL; 7303a363cbdSjob const IPAddressFamily *af; 7318435fb8dStb int crit, i, rc = 0; 7323a363cbdSjob 7338435fb8dStb addrblk = X509_get_ext_d2i(x, NID_sbgp_ipAddrBlock, &crit, NULL); 7348435fb8dStb if (addrblk == NULL) { 7358435fb8dStb if (crit != -1) 7368435fb8dStb warnx("error parsing ipAddrBlock"); 7373a363cbdSjob goto out; 7388435fb8dStb } 7393a363cbdSjob 7403a363cbdSjob /* 7413a363cbdSjob * Check by hand, since X509v3_addr_inherits() success only means that 7423a363cbdSjob * at least one address family inherits, not all of them. 7433a363cbdSjob */ 7443a363cbdSjob for (i = 0; i < sk_IPAddressFamily_num(addrblk); i++) { 7453a363cbdSjob af = sk_IPAddressFamily_value(addrblk, i); 7463a363cbdSjob if (af->ipAddressChoice->type != IPAddressChoice_inherit) 7473a363cbdSjob goto out; 7483a363cbdSjob } 7493a363cbdSjob 7503a363cbdSjob asidentifiers = X509_get_ext_d2i(x, NID_sbgp_autonomousSysNum, NULL, 7513a363cbdSjob NULL); 7528435fb8dStb if (asidentifiers == NULL) { 7538435fb8dStb if (crit != -1) 7548435fb8dStb warnx("error parsing asIdentifiers"); 7553a363cbdSjob goto out; 7568435fb8dStb } 7573a363cbdSjob 7583a363cbdSjob /* We need to have AS numbers and don't want RDIs. */ 7593a363cbdSjob if (asidentifiers->asnum == NULL || asidentifiers->rdi != NULL) 7603a363cbdSjob goto out; 7613a363cbdSjob if (!X509v3_asid_inherits(asidentifiers)) 7623a363cbdSjob goto out; 7633a363cbdSjob 7643a363cbdSjob rc = 1; 7653a363cbdSjob out: 7663a363cbdSjob ASIdentifiers_free(asidentifiers); 7673a363cbdSjob sk_IPAddressFamily_pop_free(addrblk, IPAddressFamily_free); 7683a363cbdSjob return rc; 7693a363cbdSjob } 7703a363cbdSjob 7713a363cbdSjob /* 772c9e39c95Sjob * Check whether at least one RFC 3779 extension is set to inherit. 773c9e39c95Sjob * Return 1 if an inherit element is encountered in AS or IP. 774c9e39c95Sjob * Return 0 otherwise. 775c9e39c95Sjob */ 776c9e39c95Sjob int 777c9e39c95Sjob x509_any_inherits(X509 *x) 778c9e39c95Sjob { 779c9e39c95Sjob STACK_OF(IPAddressFamily) *addrblk = NULL; 780c9e39c95Sjob ASIdentifiers *asidentifiers = NULL; 7818435fb8dStb int crit, rc = 0; 782c9e39c95Sjob 7838435fb8dStb addrblk = X509_get_ext_d2i(x, NID_sbgp_ipAddrBlock, &crit, NULL); 7848435fb8dStb if (addrblk == NULL && crit != -1) 7858435fb8dStb warnx("error parsing ipAddrBlock"); 786c9e39c95Sjob if (X509v3_addr_inherits(addrblk)) 787c9e39c95Sjob rc = 1; 788c9e39c95Sjob 7898435fb8dStb asidentifiers = X509_get_ext_d2i(x, NID_sbgp_autonomousSysNum, &crit, 790c9e39c95Sjob NULL); 7918435fb8dStb if (asidentifiers == NULL && crit != -1) 7928435fb8dStb warnx("error parsing asIdentifiers"); 793c9e39c95Sjob if (X509v3_asid_inherits(asidentifiers)) 794c9e39c95Sjob rc = 1; 795c9e39c95Sjob 796c9e39c95Sjob ASIdentifiers_free(asidentifiers); 797c9e39c95Sjob sk_IPAddressFamily_pop_free(addrblk, IPAddressFamily_free); 798c9e39c95Sjob return rc; 799c9e39c95Sjob } 800c9e39c95Sjob 801c9e39c95Sjob /* 802bc19a8c9Sclaudio * Parse the very specific subset of information in the CRL distribution 803bc19a8c9Sclaudio * point extension. 8040c3ba0c1Stb * See RFC 6487, section 4.8.6 for details. 805bc19a8c9Sclaudio * Returns NULL on failure, the crl URI on success which has to be freed 806bc19a8c9Sclaudio * after use. 807bc19a8c9Sclaudio */ 808f999fe57Sclaudio int 809e7ecba74Stb x509_get_crl(X509 *x, const char *fn, char **out_crl) 81051b3988bSbenno { 811356f9aecSclaudio CRL_DIST_POINTS *crldp; 81251b3988bSbenno DIST_POINT *dp; 813f6395eb6Stb GENERAL_NAMES *names; 81451b3988bSbenno GENERAL_NAME *name; 815e7ecba74Stb int i, crit, rc = 0; 81651b3988bSbenno 817e7ecba74Stb assert(*out_crl == NULL); 818e7ecba74Stb 819356f9aecSclaudio crldp = X509_get_ext_d2i(x, NID_crl_distribution_points, &crit, NULL); 8208435fb8dStb if (crldp == NULL) { 8218435fb8dStb if (crit != -1) { 8228435fb8dStb warnx("%s: RFC 6487 section 4.8.6: failed to parse " 8238435fb8dStb "CRL distribution points", fn); 8248435fb8dStb return 0; 8258435fb8dStb } 826f999fe57Sclaudio return 1; 8278435fb8dStb } 828f999fe57Sclaudio 829356f9aecSclaudio if (crit != 0) { 830356f9aecSclaudio warnx("%s: RFC 6487 section 4.8.6: " 831356f9aecSclaudio "CRL distribution point: extension not non-critical", fn); 832356f9aecSclaudio goto out; 833356f9aecSclaudio } 83451b3988bSbenno 83551b3988bSbenno if (sk_DIST_POINT_num(crldp) != 1) { 83651b3988bSbenno warnx("%s: RFC 6487 section 4.8.6: CRL: " 83751b3988bSbenno "want 1 element, have %d", fn, 83851b3988bSbenno sk_DIST_POINT_num(crldp)); 839356f9aecSclaudio goto out; 84051b3988bSbenno } 84151b3988bSbenno 84251b3988bSbenno dp = sk_DIST_POINT_value(crldp, 0); 843c1d2420bSjob if (dp->CRLissuer != NULL) { 844c1d2420bSjob warnx("%s: RFC 6487 section 4.8.6: CRL CRLIssuer field" 845c1d2420bSjob " disallowed", fn); 846c1d2420bSjob goto out; 847c1d2420bSjob } 848c1d2420bSjob if (dp->reasons != NULL) { 849c1d2420bSjob warnx("%s: RFC 6487 section 4.8.6: CRL Reasons field" 850c1d2420bSjob " disallowed", fn); 851c1d2420bSjob goto out; 852c1d2420bSjob } 85351b3988bSbenno if (dp->distpoint == NULL) { 85451b3988bSbenno warnx("%s: RFC 6487 section 4.8.6: CRL: " 85551b3988bSbenno "no distribution point name", fn); 856356f9aecSclaudio goto out; 85751b3988bSbenno } 858c1d2420bSjob if (dp->distpoint->dpname != NULL) { 859c1d2420bSjob warnx("%s: RFC 6487 section 4.8.6: nameRelativeToCRLIssuer" 860c1d2420bSjob " disallowed", fn); 861c1d2420bSjob goto out; 862c1d2420bSjob } 863debb1f3dStb /* Need to hardcode the alternative 0 due to missing macros or enum. */ 864bcf2dfa5Stb if (dp->distpoint->type != 0) { 865debb1f3dStb warnx("%s: RFC 6487 section 4.8.6: CRL DistributionPointName:" 866debb1f3dStb " expected fullName, have %d", fn, dp->distpoint->type); 867356f9aecSclaudio goto out; 86851b3988bSbenno } 86951b3988bSbenno 870f6395eb6Stb names = dp->distpoint->name.fullname; 871f6395eb6Stb for (i = 0; i < sk_GENERAL_NAME_num(names); i++) { 872e7ecba74Stb char *crl = NULL; 873e7ecba74Stb 874f6395eb6Stb name = sk_GENERAL_NAME_value(names, i); 875e6a231f0Stb 876e7ecba74Stb if (!x509_location(fn, "CRL distribution point", name, &crl)) 877f6395eb6Stb goto out; 878e6a231f0Stb 879e7ecba74Stb if (*out_crl == NULL && strncasecmp(crl, RSYNC_PROTO, 880e7ecba74Stb RSYNC_PROTO_LEN) == 0) { 881e7ecba74Stb *out_crl = crl; 882e7ecba74Stb continue; 883e7ecba74Stb } 884e7ecba74Stb if (verbose) 885e7ecba74Stb warnx("%s: ignoring CRL distribution point %s", 886e7ecba74Stb fn, crl); 887e7ecba74Stb free(crl); 88851b3988bSbenno } 889e6a231f0Stb 890e7ecba74Stb if (*out_crl == NULL) { 891f6395eb6Stb warnx("%s: RFC 6487 section 4.8.6: no rsync URI " 892f6395eb6Stb "in CRL distributionPoint", fn); 893e7ecba74Stb goto out; 894e7ecba74Stb } 895e7ecba74Stb 896e7ecba74Stb rc = 1; 89751b3988bSbenno 898356f9aecSclaudio out: 899356f9aecSclaudio CRL_DIST_POINTS_free(crldp); 900e7ecba74Stb return rc; 90151b3988bSbenno } 902e669621fSclaudio 9031a998f47Sclaudio /* 904220c707cSclaudio * Convert passed ASN1_TIME to time_t *t. 905220c707cSclaudio * Returns 1 on success and 0 on failure. 906220c707cSclaudio */ 907220c707cSclaudio int 908220c707cSclaudio x509_get_time(const ASN1_TIME *at, time_t *t) 909220c707cSclaudio { 910220c707cSclaudio struct tm tm; 911220c707cSclaudio 912220c707cSclaudio *t = 0; 913220c707cSclaudio memset(&tm, 0, sizeof(tm)); 9145abefff6Stb /* Fail instead of silently falling back to the current time. */ 9155abefff6Stb if (at == NULL) 9165abefff6Stb return 0; 9175abefff6Stb if (!ASN1_TIME_to_tm(at, &tm)) 918220c707cSclaudio return 0; 91944117f3fSclaudio if ((*t = timegm(&tm)) == -1) 92044117f3fSclaudio errx(1, "timegm failed"); 921220c707cSclaudio return 1; 922220c707cSclaudio } 9237cdd491fSclaudio 9247cdd491fSclaudio /* 925795395baStb * Extract and validate an accessLocation, RFC 6487, 4.8 and RFC 8182, 3.2. 92633e36affStb * Returns 0 on failure and 1 on success. 92733e36affStb */ 92833e36affStb int 9292b872fe6Stb x509_location(const char *fn, const char *descr, GENERAL_NAME *location, 9302b872fe6Stb char **out) 93133e36affStb { 93233e36affStb ASN1_IA5STRING *uri; 93333e36affStb 934e7ecba74Stb assert(*out == NULL); 935e7ecba74Stb 93633e36affStb if (location->type != GEN_URI) { 93733e36affStb warnx("%s: RFC 6487 section 4.8: %s not URI", fn, descr); 93833e36affStb return 0; 93933e36affStb } 94033e36affStb 94133e36affStb uri = location->d.uniformResourceIdentifier; 94233e36affStb 9432b872fe6Stb if (!valid_uri(uri->data, uri->length, NULL)) { 94433e36affStb warnx("%s: RFC 6487 section 4.8: %s bad location", fn, descr); 94533e36affStb return 0; 94633e36affStb } 94733e36affStb 94833e36affStb if ((*out = strndup(uri->data, uri->length)) == NULL) 94933e36affStb err(1, NULL); 95033e36affStb 95133e36affStb return 1; 95233e36affStb } 95333e36affStb 95433e36affStb /* 9550466b83fStb * Check that subject or issuer only contain commonName and serialNumber. 9567cc1142dSjob * Return 0 on failure. 9577cc1142dSjob */ 9587cc1142dSjob int 9590466b83fStb x509_valid_name(const char *fn, const char *descr, const X509_NAME *xn) 9607cc1142dSjob { 9617cc1142dSjob const X509_NAME_ENTRY *ne; 9627cc1142dSjob const ASN1_OBJECT *ao; 9637cc1142dSjob const ASN1_STRING *as; 9647cc1142dSjob int cn = 0, sn = 0; 9657cc1142dSjob int i, nid; 9667cc1142dSjob 9677cc1142dSjob for (i = 0; i < X509_NAME_entry_count(xn); i++) { 9687cc1142dSjob if ((ne = X509_NAME_get_entry(xn, i)) == NULL) { 9697cc1142dSjob warnx("%s: X509_NAME_get_entry", fn); 9707cc1142dSjob return 0; 9717cc1142dSjob } 9727cc1142dSjob if ((ao = X509_NAME_ENTRY_get_object(ne)) == NULL) { 9737cc1142dSjob warnx("%s: X509_NAME_ENTRY_get_object", fn); 9747cc1142dSjob return 0; 9757cc1142dSjob } 9767cc1142dSjob 9777cc1142dSjob nid = OBJ_obj2nid(ao); 9787cc1142dSjob switch (nid) { 9797cc1142dSjob case NID_commonName: 9807cc1142dSjob if (cn++ > 0) { 9810466b83fStb warnx("%s: duplicate commonName in %s", 9820466b83fStb fn, descr); 9837cc1142dSjob return 0; 9847cc1142dSjob } 9857cc1142dSjob if ((as = X509_NAME_ENTRY_get_data(ne)) == NULL) { 9867cc1142dSjob warnx("%s: X509_NAME_ENTRY_get_data failed", 9877cc1142dSjob fn); 9887cc1142dSjob return 0; 9897cc1142dSjob } 9907cc1142dSjob /* 9917cc1142dSjob * The following check can be enabled after AFRINIC re-issues CA certs. 9927cc1142dSjob * https://lists.afrinic.net/pipermail/dbwg/2023-March/000436.html 9937cc1142dSjob */ 9947cc1142dSjob #if 0 99551f875f7Stb /* 99651f875f7Stb * XXX - For some reason RFC 8209, section 3.1.1 decided 99751f875f7Stb * to allow UTF8String for BGPsec Router Certificates. 99851f875f7Stb */ 9997cc1142dSjob if (ASN1_STRING_type(as) != V_ASN1_PRINTABLESTRING) { 10007cc1142dSjob warnx("%s: RFC 6487 section 4.5: commonName is" 10017cc1142dSjob " not PrintableString", fn); 10027cc1142dSjob return 0; 10037cc1142dSjob } 10047cc1142dSjob #endif 10057cc1142dSjob break; 10067cc1142dSjob case NID_serialNumber: 10077cc1142dSjob if (sn++ > 0) { 10080466b83fStb warnx("%s: duplicate serialNumber in %s", 10090466b83fStb fn, descr); 10107cc1142dSjob return 0; 10117cc1142dSjob } 10127cc1142dSjob break; 10137cc1142dSjob case NID_undef: 10147cc1142dSjob warnx("%s: OBJ_obj2nid failed", fn); 10157cc1142dSjob return 0; 10167cc1142dSjob default: 10177cc1142dSjob warnx("%s: RFC 6487 section 4.5: unexpected attribute" 10180466b83fStb " %s in %s", fn, nid2str(nid), descr); 10197cc1142dSjob return 0; 10207cc1142dSjob } 10217cc1142dSjob } 10227cc1142dSjob 10237cc1142dSjob if (cn == 0) { 10240466b83fStb warnx("%s: RFC 6487 section 4.5: %s missing commonName", 10250466b83fStb fn, descr); 10267cc1142dSjob return 0; 10277cc1142dSjob } 10287cc1142dSjob 10297cc1142dSjob return 1; 10307cc1142dSjob } 10317cc1142dSjob 10327cc1142dSjob /* 1033904d9c60Stb * Check ASN1_INTEGER is non-negative and fits in 20 octets. 1034904d9c60Stb * Returns allocated BIGNUM if true, NULL otherwise. 1035904d9c60Stb */ 1036904d9c60Stb static BIGNUM * 1037904d9c60Stb x509_seqnum_to_bn(const char *fn, const char *descr, const ASN1_INTEGER *i) 1038904d9c60Stb { 1039904d9c60Stb BIGNUM *bn = NULL; 1040904d9c60Stb 1041904d9c60Stb if ((bn = ASN1_INTEGER_to_BN(i, NULL)) == NULL) { 1042904d9c60Stb warnx("%s: %s: ASN1_INTEGER_to_BN error", fn, descr); 1043904d9c60Stb goto out; 1044904d9c60Stb } 1045904d9c60Stb 1046904d9c60Stb if (BN_is_negative(bn)) { 1047904d9c60Stb warnx("%s: %s should be non-negative", fn, descr); 1048904d9c60Stb goto out; 1049904d9c60Stb } 1050904d9c60Stb 1051eef0ad36Stb /* Reject values larger than or equal to 2^159. */ 1052c0214ebbStb if (BN_num_bytes(bn) > 20 || BN_is_bit_set(bn, 159)) { 1053c0214ebbStb warnx("%s: %s should fit in 20 octets", fn, descr); 1054c0214ebbStb goto out; 1055c0214ebbStb } 1056c0214ebbStb 1057904d9c60Stb return bn; 1058904d9c60Stb 1059904d9c60Stb out: 1060904d9c60Stb BN_free(bn); 1061904d9c60Stb return NULL; 1062904d9c60Stb } 1063904d9c60Stb 1064904d9c60Stb /* 106509383accStb * Convert an ASN1_INTEGER into a hexstring, enforcing that it is non-negative 106609383accStb * and representable by at most 20 octets (RFC 5280, section 4.1.2.2). 10677cdd491fSclaudio * Returned string needs to be freed by the caller. 10687cdd491fSclaudio */ 10697cdd491fSclaudio char * 1070904d9c60Stb x509_convert_seqnum(const char *fn, const char *descr, const ASN1_INTEGER *i) 10717cdd491fSclaudio { 1072904d9c60Stb BIGNUM *bn = NULL; 10737cdd491fSclaudio char *s = NULL; 10747cdd491fSclaudio 10757cdd491fSclaudio if (i == NULL) 10767cdd491fSclaudio goto out; 10777cdd491fSclaudio 1078904d9c60Stb if ((bn = x509_seqnum_to_bn(fn, descr, i)) == NULL) 1079fee3cdecStb goto out; 1080fee3cdecStb 1081904d9c60Stb if ((s = BN_bn2hex(bn)) == NULL) 1082904d9c60Stb warnx("%s: %s: BN_bn2hex error", fn, descr); 10837cdd491fSclaudio 10847cdd491fSclaudio out: 1085904d9c60Stb BN_free(bn); 10867cdd491fSclaudio return s; 10877cdd491fSclaudio } 1088534b6674Sjob 1089904d9c60Stb int 1090904d9c60Stb x509_valid_seqnum(const char *fn, const char *descr, const ASN1_INTEGER *i) 1091904d9c60Stb { 1092904d9c60Stb BIGNUM *bn; 1093904d9c60Stb 1094904d9c60Stb if ((bn = x509_seqnum_to_bn(fn, descr, i)) == NULL) 1095904d9c60Stb return 0; 1096904d9c60Stb 1097904d9c60Stb BN_free(bn); 1098904d9c60Stb return 1; 1099904d9c60Stb } 1100904d9c60Stb 1101534b6674Sjob /* 1102534b6674Sjob * Find the closest expiry moment by walking the chain of authorities. 1103534b6674Sjob */ 1104534b6674Sjob time_t 1105534b6674Sjob x509_find_expires(time_t notafter, struct auth *a, struct crl_tree *crlt) 1106534b6674Sjob { 1107534b6674Sjob struct crl *crl; 1108534b6674Sjob time_t expires; 1109534b6674Sjob 1110534b6674Sjob expires = notafter; 1111534b6674Sjob 1112335482abStb for (; a != NULL; a = a->issuer) { 1113534b6674Sjob if (expires > a->cert->notafter) 1114534b6674Sjob expires = a->cert->notafter; 1115534b6674Sjob crl = crl_get(crlt, a); 1116534b6674Sjob if (crl != NULL && expires > crl->nextupdate) 1117534b6674Sjob expires = crl->nextupdate; 1118534b6674Sjob } 1119534b6674Sjob 1120534b6674Sjob return expires; 1121534b6674Sjob } 1122