xref: /openbsd/usr.sbin/rpki-client/cms.c (revision c026bd36)
1 /*	$OpenBSD: cms.c,v 1.48 2024/06/11 13:09:02 tb 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 <err.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 
24 #include <openssl/bio.h>
25 #include <openssl/cms.h>
26 
27 #include "extern.h"
28 
29 extern ASN1_OBJECT	*cnt_type_oid;
30 extern ASN1_OBJECT	*msg_dgst_oid;
31 extern ASN1_OBJECT	*sign_time_oid;
32 
33 static int
cms_extract_econtent(const char * fn,CMS_ContentInfo * cms,unsigned char ** res,size_t * rsz)34 cms_extract_econtent(const char *fn, CMS_ContentInfo *cms, unsigned char **res,
35     size_t *rsz)
36 {
37 	ASN1_OCTET_STRING		**os = NULL;
38 
39 	/* Detached signature case: no eContent to extract, so do nothing. */
40 	if (res == NULL || rsz == NULL)
41 		return 1;
42 
43 	if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) {
44 		warnx("%s: RFC 6488 section 2.1.4: "
45 		    "eContent: zero-length content", fn);
46 		return 0;
47 	}
48 
49 	/*
50 	 * Extract and duplicate the eContent.
51 	 * The CMS framework offers us no other way of easily managing
52 	 * this information; and since we're going to d2i it anyway,
53 	 * simply pass it as the desired underlying types.
54 	 */
55 	if ((*res = malloc((*os)->length)) == NULL)
56 		err(1, NULL);
57 	memcpy(*res, (*os)->data, (*os)->length);
58 	*rsz = (*os)->length;
59 
60 	return 1;
61 }
62 
63 static int
cms_get_signtime(const char * fn,X509_ATTRIBUTE * attr,time_t * signtime)64 cms_get_signtime(const char *fn, X509_ATTRIBUTE *attr, time_t *signtime)
65 {
66 	const ASN1_TIME		*at;
67 	const char		*time_str = "UTCtime";
68 	int			 time_type = V_ASN1_UTCTIME;
69 
70 	*signtime = 0;
71 	at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL);
72 	if (at == NULL) {
73 		time_str = "GeneralizedTime";
74 		time_type = V_ASN1_GENERALIZEDTIME;
75 		at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL);
76 		if (at == NULL) {
77 			warnx("%s: CMS signing-time issue", fn);
78 			return 0;
79 		}
80 		warnx("%s: GeneralizedTime instead of UTCtime", fn);
81 	}
82 
83 	if (!x509_get_time(at, signtime)) {
84 		warnx("%s: failed to convert %s", fn, time_str);
85 		return 0;
86 	}
87 
88 	return 1;
89 }
90 
91 static int
cms_parse_validate_internal(X509 ** xp,const char * fn,const unsigned char * der,size_t len,const ASN1_OBJECT * oid,BIO * bio,unsigned char ** res,size_t * rsz,time_t * signtime)92 cms_parse_validate_internal(X509 **xp, const char *fn, const unsigned char *der,
93     size_t len, const ASN1_OBJECT *oid, BIO *bio, unsigned char **res,
94     size_t *rsz, time_t *signtime)
95 {
96 	const unsigned char		*oder;
97 	char				 buf[128], obuf[128];
98 	const ASN1_OBJECT		*obj, *octype;
99 	ASN1_OCTET_STRING		*kid = NULL;
100 	CMS_ContentInfo			*cms;
101 	long				 version;
102 	STACK_OF(X509)			*certs = NULL;
103 	STACK_OF(X509_CRL)		*crls;
104 	STACK_OF(CMS_SignerInfo)	*sinfos;
105 	CMS_SignerInfo			*si;
106 	EVP_PKEY			*pkey;
107 	X509_ALGOR			*pdig, *psig;
108 	int				 i, nattrs, nid;
109 	int				 has_ct = 0, has_md = 0, has_st = 0;
110 	time_t				 notafter;
111 	int				 rc = 0;
112 
113 	*xp = NULL;
114 	if (rsz != NULL)
115 		*rsz = 0;
116 	*signtime = 0;
117 
118 	/* just fail for empty buffers, the warning was printed elsewhere */
119 	if (der == NULL)
120 		return 0;
121 
122 	oder = der;
123 	if ((cms = d2i_CMS_ContentInfo(NULL, &der, len)) == NULL) {
124 		warnx("%s: RFC 6488: failed CMS parse", fn);
125 		goto out;
126 	}
127 	if (der != oder + len) {
128 		warnx("%s: %td bytes trailing garbage", fn, oder + len - der);
129 		goto out;
130 	}
131 
132 	/*
133 	 * The CMS is self-signed with a signing certificate.
134 	 * Verify that the self-signage is correct.
135 	 */
136 	if (!CMS_verify(cms, NULL, NULL, bio, NULL,
137 	    CMS_NO_SIGNER_CERT_VERIFY)) {
138 		warnx("%s: CMS verification error", fn);
139 		goto out;
140 	}
141 
142 	/* RFC 6488 section 3 verify the CMS */
143 
144 	/* Should only return NULL if cms is not of type SignedData. */
145 	if ((sinfos = CMS_get0_SignerInfos(cms)) == NULL) {
146 		if ((obj = CMS_get0_type(cms)) == NULL) {
147 			warnx("%s: RFC 6488: missing content-type", fn);
148 			goto out;
149 		}
150 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
151 		warnx("%s: RFC 6488: no signerInfo in CMS object of type %s",
152 		    fn, buf);
153 		goto out;
154 	}
155 	if (sk_CMS_SignerInfo_num(sinfos) != 1) {
156 		warnx("%s: RFC 6488: CMS has multiple signerInfos", fn);
157 		goto out;
158 	}
159 	si = sk_CMS_SignerInfo_value(sinfos, 0);
160 
161 	if (!CMS_get_version(cms, &version)) {
162 		warnx("%s: Failed to retrieve SignedData version", fn);
163 		goto out;
164 	}
165 	if (version != 3) {
166 		warnx("%s: SignedData version %ld != 3", fn, version);
167 		goto out;
168 	}
169 	if (!CMS_SignerInfo_get_version(si, &version)) {
170 		warnx("%s: Failed to retrieve SignerInfo version", fn);
171 		goto out;
172 	}
173 	if (version != 3) {
174 		warnx("%s: SignerInfo version %ld != 3", fn, version);
175 		goto out;
176 	}
177 
178 	nattrs = CMS_signed_get_attr_count(si);
179 	if (nattrs <= 0) {
180 		warnx("%s: RFC 6488: error extracting signedAttrs", fn);
181 		goto out;
182 	}
183 	for (i = 0; i < nattrs; i++) {
184 		X509_ATTRIBUTE *attr;
185 
186 		attr = CMS_signed_get_attr(si, i);
187 		if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) {
188 			warnx("%s: RFC 6488: bad signed attribute encoding",
189 			    fn);
190 			goto out;
191 		}
192 
193 		obj = X509_ATTRIBUTE_get0_object(attr);
194 		if (obj == NULL) {
195 			warnx("%s: RFC 6488: bad signed attribute", fn);
196 			goto out;
197 		}
198 		if (OBJ_cmp(obj, cnt_type_oid) == 0) {
199 			if (has_ct++ != 0) {
200 				warnx("%s: RFC 6488: duplicate "
201 				    "signed attribute", fn);
202 				goto out;
203 			}
204 		} else if (OBJ_cmp(obj, msg_dgst_oid) == 0) {
205 			if (has_md++ != 0) {
206 				warnx("%s: RFC 6488: duplicate "
207 				    "signed attribute", fn);
208 				goto out;
209 			}
210 		} else if (OBJ_cmp(obj, sign_time_oid) == 0) {
211 			if (has_st++ != 0) {
212 				warnx("%s: RFC 6488: duplicate "
213 				    "signed attribute", fn);
214 				goto out;
215 			}
216 			if (!cms_get_signtime(fn, attr, signtime))
217 				goto out;
218 		} else {
219 			OBJ_obj2txt(buf, sizeof(buf), obj, 1);
220 			warnx("%s: RFC 6488: "
221 			    "CMS has unexpected signed attribute %s",
222 			    fn, buf);
223 			goto out;
224 		}
225 	}
226 
227 	if (!has_ct || !has_md) {
228 		/* RFC 9589, section 4 */
229 		warnx("%s: RFC 6488: CMS missing required "
230 		    "signed attribute", fn);
231 		goto out;
232 	}
233 
234 	if (!has_st) {
235 		/* RFC 9589, section 4 */
236 		warnx("%s: missing CMS signing-time attribute", fn);
237 		goto out;
238 	}
239 
240 	if (CMS_unsigned_get_attr_count(si) != -1) {
241 		warnx("%s: RFC 6488: CMS has unsignedAttrs", fn);
242 		goto out;
243 	}
244 
245 	/* Check digest and signature algorithms (RFC 7935) */
246 	CMS_SignerInfo_get0_algs(si, &pkey, NULL, &pdig, &psig);
247 	if (!valid_ca_pkey(fn, pkey))
248 		goto out;
249 
250 	X509_ALGOR_get0(&obj, NULL, NULL, pdig);
251 	nid = OBJ_obj2nid(obj);
252 	if (nid != NID_sha256) {
253 		warnx("%s: RFC 6488: wrong digest %s, want %s", fn,
254 		    nid2str(nid), LN_sha256);
255 		goto out;
256 	}
257 	X509_ALGOR_get0(&obj, NULL, NULL, psig);
258 	nid = OBJ_obj2nid(obj);
259 	/* RFC7935 last paragraph of section 2 specifies the allowed psig */
260 	if (experimental && nid == NID_ecdsa_with_SHA256) {
261 		if (verbose)
262 			warnx("%s: P-256 support is experimental", fn);
263 	} else if (nid != NID_rsaEncryption &&
264 	    nid != NID_sha256WithRSAEncryption) {
265 		warnx("%s: RFC 6488: wrong signature algorithm %s, want %s",
266 		    fn, nid2str(nid), LN_rsaEncryption);
267 		goto out;
268 	}
269 
270 	/* RFC 6488 section 2.1.3.1: check the object's eContentType. */
271 
272 	obj = CMS_get0_eContentType(cms);
273 	if (obj == NULL) {
274 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
275 		    "OID object is NULL", fn);
276 		goto out;
277 	}
278 	if (OBJ_cmp(obj, oid) != 0) {
279 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
280 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
281 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
282 		    "unknown OID: %s, want %s", fn, buf, obuf);
283 		goto out;
284 	}
285 
286 	/* Compare content-type with eContentType */
287 	octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid,
288 	    -3, V_ASN1_OBJECT);
289 	/*
290 	 * Since lastpos == -3, octype can be NULL for 4 reasons:
291 	 * 1. requested attribute OID is missing
292 	 * 2. signedAttrs contains multiple attributes with requested OID
293 	 * 3. attribute with requested OID has multiple values (malformed)
294 	 * 4. X509_ATTRIBUTE_get0_data() returned NULL. This is also malformed,
295 	 *    but libcrypto will create, sign, and verify such objects.
296 	 * Reasons 1 and 2 are excluded because has_ct == 1. We don't know which
297 	 * one of 3 or 4 we hit. Doesn't matter, drop the garbage on the floor.
298 	 */
299 	if (octype == NULL) {
300 		warnx("%s: RFC 6488, section 2.1.6.4.1: malformed value "
301 		    "for content-type attribute", fn);
302 		goto out;
303 	}
304 	if (OBJ_cmp(obj, octype) != 0) {
305 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
306 		OBJ_obj2txt(obuf, sizeof(obuf), octype, 1);
307 		warnx("%s: RFC 6488: eContentType does not match Content-Type "
308 		    "OID: %s, want %s", fn, buf, obuf);
309 		goto out;
310 	}
311 
312 	/*
313 	 * Check that there are no CRLS in this CMS message.
314 	 */
315 	crls = CMS_get1_crls(cms);
316 	if (crls != NULL) {
317 		sk_X509_CRL_pop_free(crls, X509_CRL_free);
318 		warnx("%s: RFC 6488: CMS has CRLs", fn);
319 		goto out;
320 	}
321 
322 	/*
323 	 * The self-signing certificate is further signed by the input
324 	 * signing authority according to RFC 6488, 2.1.4.
325 	 * We extract that certificate now for later verification.
326 	 */
327 
328 	certs = CMS_get0_signers(cms);
329 	if (certs == NULL || sk_X509_num(certs) != 1) {
330 		warnx("%s: RFC 6488 section 2.1.4: eContent: "
331 		    "want 1 signer, have %d", fn, sk_X509_num(certs));
332 		goto out;
333 	}
334 	*xp = sk_X509_value(certs, 0);
335 	if (!X509_up_ref(*xp)) {
336 		*xp = NULL;
337 		goto out;
338 	}
339 
340 	if (!x509_cache_extensions(*xp, fn))
341 		goto out;
342 
343 	if (!x509_get_notafter(*xp, fn, &notafter))
344 		goto out;
345 	if (*signtime > notafter)
346 		warnx("%s: dating issue: CMS signing-time after X.509 notAfter",
347 		    fn);
348 
349 	if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 ||
350 	    kid == NULL) {
351 		warnx("%s: RFC 6488: could not extract SKI from SID", fn);
352 		goto out;
353 	}
354 	if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) {
355 		warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn);
356 		goto out;
357 	}
358 
359 	if (!cms_extract_econtent(fn, cms, res, rsz))
360 		goto out;
361 
362 	rc = 1;
363  out:
364 	if (rc == 0) {
365 		X509_free(*xp);
366 		*xp = NULL;
367 	}
368 	sk_X509_free(certs);
369 	CMS_ContentInfo_free(cms);
370 	return rc;
371 }
372 
373 /*
374  * Parse and validate a self-signed CMS message.
375  * Conforms to RFC 6488.
376  * The eContentType of the message must be an oid object.
377  * Return the eContent as a string and set "rsz" to be its length.
378  */
379 unsigned char *
cms_parse_validate(X509 ** xp,const char * fn,const unsigned char * der,size_t derlen,const ASN1_OBJECT * oid,size_t * rsz,time_t * st)380 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
381     size_t derlen, const ASN1_OBJECT *oid, size_t *rsz, time_t *st)
382 {
383 	unsigned char *res = NULL;
384 
385 	if (!cms_parse_validate_internal(xp, fn, der, derlen, oid, NULL, &res,
386 	    rsz, st))
387 		return NULL;
388 
389 	return res;
390 }
391 
392 /*
393  * Parse and validate a detached CMS signature.
394  * bio must contain the original message, der must contain the CMS.
395  * Return the 1 on success, 0 on failure.
396  */
397 int
cms_parse_validate_detached(X509 ** xp,const char * fn,const unsigned char * der,size_t derlen,const ASN1_OBJECT * oid,BIO * bio,time_t * st)398 cms_parse_validate_detached(X509 **xp, const char *fn, const unsigned char *der,
399     size_t derlen, const ASN1_OBJECT *oid, BIO *bio, time_t *st)
400 {
401 	return cms_parse_validate_internal(xp, fn, der, derlen, oid, bio, NULL,
402 	    NULL, st);
403 }
404