xref: /openbsd-src/usr.sbin/rpki-client/x509.c (revision dab7a176b1fe6a293bfdf63916b90c070ac9703b)
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 = &notify_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