xref: /openbsd-src/usr.sbin/rpki-client/cms.c (revision fc405d53b73a2d73393cb97f684863d17b583e38)
1 /*	$OpenBSD: cms.c,v 1.33 2023/03/13 19:46:55 job Exp $ */
2 /*
3  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <assert.h>
19 #include <err.h>
20 #include <stdint.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 
25 #include <openssl/bio.h>
26 #include <openssl/cms.h>
27 
28 #include "extern.h"
29 
30 extern ASN1_OBJECT	*cnt_type_oid;
31 extern ASN1_OBJECT	*msg_dgst_oid;
32 extern ASN1_OBJECT	*sign_time_oid;
33 extern ASN1_OBJECT	*bin_sign_time_oid;
34 
35 static int
36 cms_extract_econtent(const char *fn, CMS_ContentInfo *cms, unsigned char **res,
37     size_t *rsz)
38 {
39 	ASN1_OCTET_STRING		**os = NULL;
40 
41 	/* Detached signature case: no eContent to extract, so do nothing. */
42 	if (res == NULL || rsz == NULL)
43 		return 1;
44 
45 	if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) {
46 		warnx("%s: RFC 6488 section 2.1.4: "
47 		    "eContent: zero-length content", fn);
48 		return 0;
49 	}
50 
51 	/*
52 	 * Extract and duplicate the eContent.
53 	 * The CMS framework offers us no other way of easily managing
54 	 * this information; and since we're going to d2i it anyway,
55 	 * simply pass it as the desired underlying types.
56 	 */
57 	if ((*res = malloc((*os)->length)) == NULL)
58 		err(1, NULL);
59 	memcpy(*res, (*os)->data, (*os)->length);
60 	*rsz = (*os)->length;
61 
62 	return 1;
63 }
64 
65 static int
66 cms_get_signtime(const char *fn, X509_ATTRIBUTE *attr, time_t *signtime)
67 {
68 	const ASN1_TIME		*at;
69 	const char		*time_str = "UTCtime";
70 	int			 time_type = V_ASN1_UTCTIME;
71 
72 	*signtime = 0;
73 	at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL);
74 	if (at == NULL) {
75 		time_str = "GeneralizedTime";
76 		time_type = V_ASN1_GENERALIZEDTIME;
77 		at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL);
78 		if (at == NULL) {
79 			warnx("%s: CMS signing-time issue", fn);
80 			return 0;
81 		}
82 		warnx("%s: GeneralizedTime instead of UTCtime", fn);
83 	}
84 
85 	if (!x509_get_time(at, signtime)) {
86 		warnx("%s: failed to convert %s", fn, time_str);
87 		return 0;
88 	}
89 
90 	return 1;
91 }
92 
93 static int
94 cms_parse_validate_internal(X509 **xp, const char *fn, const unsigned char *der,
95     size_t len, const ASN1_OBJECT *oid, BIO *bio, unsigned char **res,
96     size_t *rsz, time_t *signtime)
97 {
98 	const unsigned char		*oder;
99 	char				 buf[128], obuf[128];
100 	const ASN1_OBJECT		*obj, *octype;
101 	ASN1_OCTET_STRING		*kid = NULL;
102 	CMS_ContentInfo			*cms;
103 	STACK_OF(X509)			*certs = NULL;
104 	STACK_OF(X509_CRL)		*crls;
105 	STACK_OF(CMS_SignerInfo)	*sinfos;
106 	CMS_SignerInfo			*si;
107 	EVP_PKEY			*pkey;
108 	X509_ALGOR			*pdig, *psig;
109 	int				 i, nattrs, nid;
110 	int				 has_ct = 0, has_md = 0, has_st = 0,
111 					 has_bst = 0;
112 	time_t				 notafter;
113 	int				 rc = 0;
114 
115 	*xp = NULL;
116 	if (rsz != NULL)
117 		*rsz = 0;
118 	*signtime = 0;
119 
120 	/* just fail for empty buffers, the warning was printed elsewhere */
121 	if (der == NULL)
122 		return 0;
123 
124 	oder = der;
125 	if ((cms = d2i_CMS_ContentInfo(NULL, &der, len)) == NULL) {
126 		cryptowarnx("%s: RFC 6488: failed CMS parse", fn);
127 		goto out;
128 	}
129 	if (der != oder + len) {
130 		warnx("%s: %td bytes trailing garbage", fn, oder + len - der);
131 		goto out;
132 	}
133 
134 	/*
135 	 * The CMS is self-signed with a signing certificate.
136 	 * Verify that the self-signage is correct.
137 	 */
138 	if (!CMS_verify(cms, NULL, NULL, bio, NULL,
139 	    CMS_NO_SIGNER_CERT_VERIFY)) {
140 		cryptowarnx("%s: CMS verification error", fn);
141 		goto out;
142 	}
143 
144 	/* RFC 6488 section 3 verify the CMS */
145 	/* the version of SignedData and SignerInfos can't be verified */
146 
147 	sinfos = CMS_get0_SignerInfos(cms);
148 	assert(sinfos != NULL);
149 	if (sk_CMS_SignerInfo_num(sinfos) != 1) {
150 		cryptowarnx("%s: RFC 6488: CMS has multiple signerInfos", fn);
151 		goto out;
152 	}
153 	si = sk_CMS_SignerInfo_value(sinfos, 0);
154 
155 	nattrs = CMS_signed_get_attr_count(si);
156 	if (nattrs <= 0) {
157 		cryptowarnx("%s: RFC 6488: error extracting signedAttrs", fn);
158 		goto out;
159 	}
160 	for (i = 0; i < nattrs; i++) {
161 		X509_ATTRIBUTE *attr;
162 
163 		attr = CMS_signed_get_attr(si, i);
164 		if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) {
165 			cryptowarnx("%s: RFC 6488: "
166 			    "bad signed attribute encoding", fn);
167 			goto out;
168 		}
169 
170 		obj = X509_ATTRIBUTE_get0_object(attr);
171 		if (obj == NULL) {
172 			cryptowarnx("%s: RFC 6488: bad signed attribute", fn);
173 			goto out;
174 		}
175 		if (OBJ_cmp(obj, cnt_type_oid) == 0) {
176 			if (has_ct++ != 0) {
177 				cryptowarnx("%s: RFC 6488: duplicate "
178 				    "signed attribute", fn);
179 				goto out;
180 			}
181 		} else if (OBJ_cmp(obj, msg_dgst_oid) == 0) {
182 			if (has_md++ != 0) {
183 				cryptowarnx("%s: RFC 6488: duplicate "
184 				    "signed attribute", fn);
185 				goto out;
186 			}
187 		} else if (OBJ_cmp(obj, sign_time_oid) == 0) {
188 			if (has_st++ != 0) {
189 				cryptowarnx("%s: RFC 6488: duplicate "
190 				    "signed attribute", fn);
191 				goto out;
192 			}
193 			if (!cms_get_signtime(fn, attr, signtime))
194 				goto out;
195 		} else if (OBJ_cmp(obj, bin_sign_time_oid) == 0) {
196 			if (has_bst++ != 0) {
197 				cryptowarnx("%s: RFC 6488: duplicate "
198 				    "signed attribute", fn);
199 				goto out;
200 			}
201 		} else {
202 			OBJ_obj2txt(buf, sizeof(buf), obj, 1);
203 			cryptowarnx("%s: RFC 6488: "
204 			    "CMS has unexpected signed attribute %s",
205 			    fn, buf);
206 			goto out;
207 		}
208 	}
209 
210 	if (!has_ct || !has_md) {
211 		cryptowarnx("%s: RFC 6488: CMS missing required "
212 		    "signed attribute", fn);
213 		goto out;
214 	}
215 
216 	if (has_bst)
217 		warnx("%s: unsupported CMS signing-time attribute", fn);
218 
219 	if (CMS_unsigned_get_attr_count(si) != -1) {
220 		cryptowarnx("%s: RFC 6488: CMS has unsignedAttrs", fn);
221 		goto out;
222 	}
223 
224 	/* Check digest and signature algorithms (RFC 7935) */
225 	CMS_SignerInfo_get0_algs(si, &pkey, NULL, &pdig, &psig);
226 	if (!valid_ca_pkey(fn, pkey))
227 		goto out;
228 
229 	X509_ALGOR_get0(&obj, NULL, NULL, pdig);
230 	nid = OBJ_obj2nid(obj);
231 	if (nid != NID_sha256) {
232 		warnx("%s: RFC 6488: wrong digest %s, want %s", fn,
233 		    OBJ_nid2ln(nid), OBJ_nid2ln(NID_sha256));
234 		goto out;
235 	}
236 	X509_ALGOR_get0(&obj, NULL, NULL, psig);
237 	nid = OBJ_obj2nid(obj);
238 	/* RFC7935 last paragraph of section 2 specifies the allowed psig */
239 	if (nid != NID_rsaEncryption && nid != NID_sha256WithRSAEncryption) {
240 		warnx("%s: RFC 6488: wrong signature algorithm %s, want %s",
241 		    fn, OBJ_nid2ln(nid), OBJ_nid2ln(NID_rsaEncryption));
242 		goto out;
243 	}
244 
245 	/* RFC 6488 section 2.1.3.1: check the object's eContentType. */
246 
247 	obj = CMS_get0_eContentType(cms);
248 	if (obj == NULL) {
249 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
250 		    "OID object is NULL", fn);
251 		goto out;
252 	}
253 	if (OBJ_cmp(obj, oid) != 0) {
254 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
255 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
256 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
257 		    "unknown OID: %s, want %s", fn, buf, obuf);
258 		goto out;
259 	}
260 
261 	/* Compare content-type with eContentType */
262 	octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid,
263 	    -3, V_ASN1_OBJECT);
264 	assert(octype != NULL);
265 	if (OBJ_cmp(obj, octype) != 0) {
266 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
267 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
268 		warnx("%s: RFC 6488: eContentType does not match Content-Type "
269 		    "OID: %s, want %s", fn, buf, obuf);
270 		goto out;
271 	}
272 
273 	/*
274 	 * Check that there are no CRLS in this CMS message.
275 	 */
276 	crls = CMS_get1_crls(cms);
277 	if (crls != NULL) {
278 		sk_X509_CRL_pop_free(crls, X509_CRL_free);
279 		cryptowarnx("%s: RFC 6488: CMS has CRLs", fn);
280 		goto out;
281 	}
282 
283 	/*
284 	 * The self-signing certificate is further signed by the input
285 	 * signing authority according to RFC 6488, 2.1.4.
286 	 * We extract that certificate now for later verification.
287 	 */
288 
289 	certs = CMS_get0_signers(cms);
290 	if (certs == NULL || sk_X509_num(certs) != 1) {
291 		warnx("%s: RFC 6488 section 2.1.4: eContent: "
292 		    "want 1 signer, have %d", fn, sk_X509_num(certs));
293 		goto out;
294 	}
295 	*xp = sk_X509_value(certs, 0);
296 	if (!X509_up_ref(*xp)) {
297 		*xp = NULL;
298 		goto out;
299 	}
300 
301 	/* Cache X509v3 extensions, see X509_check_ca(3). */
302 	if (X509_check_purpose(*xp, -1, -1) <= 0) {
303 		cryptowarnx("%s: could not cache X509v3 extensions", fn);
304 		goto out;
305 	}
306 
307 	if (!x509_get_notafter(*xp, fn, &notafter))
308 		goto out;
309 	if (*signtime > notafter) {
310 		warnx("%s: dating issue: CMS signing-time after X.509 notAfter",
311 		    fn);
312 		goto out;
313 	}
314 
315 	if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 ||
316 	    kid == NULL) {
317 		warnx("%s: RFC 6488: could not extract SKI from SID", fn);
318 		goto out;
319 	}
320 	if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) {
321 		warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn);
322 		goto out;
323 	}
324 
325 	if (!cms_extract_econtent(fn, cms, res, rsz))
326 		goto out;
327 
328 	rc = 1;
329  out:
330 	if (rc == 0) {
331 		X509_free(*xp);
332 		*xp = NULL;
333 	}
334 	sk_X509_free(certs);
335 	CMS_ContentInfo_free(cms);
336 	return rc;
337 }
338 
339 /*
340  * Parse and validate a self-signed CMS message.
341  * Conforms to RFC 6488.
342  * The eContentType of the message must be an oid object.
343  * Return the eContent as a string and set "rsz" to be its length.
344  */
345 unsigned char *
346 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
347     size_t derlen, const ASN1_OBJECT *oid, size_t *rsz, time_t *st)
348 {
349 	unsigned char *res = NULL;
350 
351 	if (!cms_parse_validate_internal(xp, fn, der, derlen, oid, NULL, &res,
352 	    rsz, st))
353 		return NULL;
354 
355 	return res;
356 }
357 
358 /*
359  * Parse and validate a detached CMS signature.
360  * bio must contain the original message, der must contain the CMS.
361  * Return the 1 on success, 0 on failure.
362  */
363 int
364 cms_parse_validate_detached(X509 **xp, const char *fn, const unsigned char *der,
365     size_t derlen, const ASN1_OBJECT *oid, BIO *bio, time_t *st)
366 {
367 	return cms_parse_validate_internal(xp, fn, der, derlen, oid, bio, NULL,
368 	    NULL, st);
369 }
370