xref: /openbsd-src/usr.sbin/rpki-client/cms.c (revision d5abdd01d7a5f24fb6f9b0aab446ef59a9e9067a)
1 /*	$OpenBSD: cms.c,v 1.35 2023/06/12 18:22:02 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 	/* Should only return NULL if cms is not of type SignedData. */
148 	if ((sinfos = CMS_get0_SignerInfos(cms)) == NULL) {
149 		if ((obj = CMS_get0_type(cms)) == NULL) {
150 			warnx("%s: RFC 6488: missing content-type", fn);
151 			goto out;
152 		}
153 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
154 		warnx("%s: RFC 6488: no signerInfo in CMS object of type %s",
155 		    fn, buf);
156 		goto out;
157 	}
158 	if (sk_CMS_SignerInfo_num(sinfos) != 1) {
159 		cryptowarnx("%s: RFC 6488: CMS has multiple signerInfos", fn);
160 		goto out;
161 	}
162 	si = sk_CMS_SignerInfo_value(sinfos, 0);
163 
164 	nattrs = CMS_signed_get_attr_count(si);
165 	if (nattrs <= 0) {
166 		cryptowarnx("%s: RFC 6488: error extracting signedAttrs", fn);
167 		goto out;
168 	}
169 	for (i = 0; i < nattrs; i++) {
170 		X509_ATTRIBUTE *attr;
171 
172 		attr = CMS_signed_get_attr(si, i);
173 		if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) {
174 			cryptowarnx("%s: RFC 6488: "
175 			    "bad signed attribute encoding", fn);
176 			goto out;
177 		}
178 
179 		obj = X509_ATTRIBUTE_get0_object(attr);
180 		if (obj == NULL) {
181 			cryptowarnx("%s: RFC 6488: bad signed attribute", fn);
182 			goto out;
183 		}
184 		if (OBJ_cmp(obj, cnt_type_oid) == 0) {
185 			if (has_ct++ != 0) {
186 				cryptowarnx("%s: RFC 6488: duplicate "
187 				    "signed attribute", fn);
188 				goto out;
189 			}
190 		} else if (OBJ_cmp(obj, msg_dgst_oid) == 0) {
191 			if (has_md++ != 0) {
192 				cryptowarnx("%s: RFC 6488: duplicate "
193 				    "signed attribute", fn);
194 				goto out;
195 			}
196 		} else if (OBJ_cmp(obj, sign_time_oid) == 0) {
197 			if (has_st++ != 0) {
198 				cryptowarnx("%s: RFC 6488: duplicate "
199 				    "signed attribute", fn);
200 				goto out;
201 			}
202 			if (!cms_get_signtime(fn, attr, signtime))
203 				goto out;
204 		} else if (OBJ_cmp(obj, bin_sign_time_oid) == 0) {
205 			if (has_bst++ != 0) {
206 				cryptowarnx("%s: RFC 6488: duplicate "
207 				    "signed attribute", fn);
208 				goto out;
209 			}
210 		} else {
211 			OBJ_obj2txt(buf, sizeof(buf), obj, 1);
212 			cryptowarnx("%s: RFC 6488: "
213 			    "CMS has unexpected signed attribute %s",
214 			    fn, buf);
215 			goto out;
216 		}
217 	}
218 
219 	if (!has_ct || !has_md) {
220 		cryptowarnx("%s: RFC 6488: CMS missing required "
221 		    "signed attribute", fn);
222 		goto out;
223 	}
224 
225 	if (has_bst)
226 		warnx("%s: unsupported CMS signing-time attribute", fn);
227 
228 	if (CMS_unsigned_get_attr_count(si) != -1) {
229 		cryptowarnx("%s: RFC 6488: CMS has unsignedAttrs", fn);
230 		goto out;
231 	}
232 
233 	/* Check digest and signature algorithms (RFC 7935) */
234 	CMS_SignerInfo_get0_algs(si, &pkey, NULL, &pdig, &psig);
235 	if (!valid_ca_pkey(fn, pkey))
236 		goto out;
237 
238 	X509_ALGOR_get0(&obj, NULL, NULL, pdig);
239 	nid = OBJ_obj2nid(obj);
240 	if (nid != NID_sha256) {
241 		warnx("%s: RFC 6488: wrong digest %s, want %s", fn,
242 		    OBJ_nid2ln(nid), OBJ_nid2ln(NID_sha256));
243 		goto out;
244 	}
245 	X509_ALGOR_get0(&obj, NULL, NULL, psig);
246 	nid = OBJ_obj2nid(obj);
247 	/* RFC7935 last paragraph of section 2 specifies the allowed psig */
248 	if (nid != NID_rsaEncryption && nid != NID_sha256WithRSAEncryption) {
249 		warnx("%s: RFC 6488: wrong signature algorithm %s, want %s",
250 		    fn, OBJ_nid2ln(nid), OBJ_nid2ln(NID_rsaEncryption));
251 		goto out;
252 	}
253 
254 	/* RFC 6488 section 2.1.3.1: check the object's eContentType. */
255 
256 	obj = CMS_get0_eContentType(cms);
257 	if (obj == NULL) {
258 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
259 		    "OID object is NULL", fn);
260 		goto out;
261 	}
262 	if (OBJ_cmp(obj, oid) != 0) {
263 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
264 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
265 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
266 		    "unknown OID: %s, want %s", fn, buf, obuf);
267 		goto out;
268 	}
269 
270 	/* Compare content-type with eContentType */
271 	octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid,
272 	    -3, V_ASN1_OBJECT);
273 	assert(octype != NULL);
274 	if (OBJ_cmp(obj, octype) != 0) {
275 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
276 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
277 		warnx("%s: RFC 6488: eContentType does not match Content-Type "
278 		    "OID: %s, want %s", fn, buf, obuf);
279 		goto out;
280 	}
281 
282 	/*
283 	 * Check that there are no CRLS in this CMS message.
284 	 */
285 	crls = CMS_get1_crls(cms);
286 	if (crls != NULL) {
287 		sk_X509_CRL_pop_free(crls, X509_CRL_free);
288 		cryptowarnx("%s: RFC 6488: CMS has CRLs", fn);
289 		goto out;
290 	}
291 
292 	/*
293 	 * The self-signing certificate is further signed by the input
294 	 * signing authority according to RFC 6488, 2.1.4.
295 	 * We extract that certificate now for later verification.
296 	 */
297 
298 	certs = CMS_get0_signers(cms);
299 	if (certs == NULL || sk_X509_num(certs) != 1) {
300 		warnx("%s: RFC 6488 section 2.1.4: eContent: "
301 		    "want 1 signer, have %d", fn, sk_X509_num(certs));
302 		goto out;
303 	}
304 	*xp = sk_X509_value(certs, 0);
305 	if (!X509_up_ref(*xp)) {
306 		*xp = NULL;
307 		goto out;
308 	}
309 
310 	/* Cache X509v3 extensions, see X509_check_ca(3). */
311 	if (X509_check_purpose(*xp, -1, -1) <= 0) {
312 		cryptowarnx("%s: could not cache X509v3 extensions", fn);
313 		goto out;
314 	}
315 
316 	if (!x509_get_notafter(*xp, fn, &notafter))
317 		goto out;
318 	if (*signtime > notafter)
319 		warnx("%s: dating issue: CMS signing-time after X.509 notAfter",
320 		    fn);
321 
322 	if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 ||
323 	    kid == NULL) {
324 		warnx("%s: RFC 6488: could not extract SKI from SID", fn);
325 		goto out;
326 	}
327 	if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) {
328 		warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn);
329 		goto out;
330 	}
331 
332 	if (!cms_extract_econtent(fn, cms, res, rsz))
333 		goto out;
334 
335 	rc = 1;
336  out:
337 	if (rc == 0) {
338 		X509_free(*xp);
339 		*xp = NULL;
340 	}
341 	sk_X509_free(certs);
342 	CMS_ContentInfo_free(cms);
343 	return rc;
344 }
345 
346 /*
347  * Parse and validate a self-signed CMS message.
348  * Conforms to RFC 6488.
349  * The eContentType of the message must be an oid object.
350  * Return the eContent as a string and set "rsz" to be its length.
351  */
352 unsigned char *
353 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
354     size_t derlen, const ASN1_OBJECT *oid, size_t *rsz, time_t *st)
355 {
356 	unsigned char *res = NULL;
357 
358 	if (!cms_parse_validate_internal(xp, fn, der, derlen, oid, NULL, &res,
359 	    rsz, st))
360 		return NULL;
361 
362 	return res;
363 }
364 
365 /*
366  * Parse and validate a detached CMS signature.
367  * bio must contain the original message, der must contain the CMS.
368  * Return the 1 on success, 0 on failure.
369  */
370 int
371 cms_parse_validate_detached(X509 **xp, const char *fn, const unsigned char *der,
372     size_t derlen, const ASN1_OBJECT *oid, BIO *bio, time_t *st)
373 {
374 	return cms_parse_validate_internal(xp, fn, der, derlen, oid, bio, NULL,
375 	    NULL, st);
376 }
377