xref: /openbsd-src/usr.sbin/rpki-client/cms.c (revision 8550894424f8a4aa4aafb6cd57229dd6ed7cd9dd)
1 /*	$OpenBSD: cms.c,v 1.26 2022/12/28 21:30:18 jmc 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_parse_validate_internal(X509 **xp, const char *fn, const unsigned char *der,
67     size_t derlen, const ASN1_OBJECT *oid, BIO *bio, unsigned char **res,
68     size_t *rsz)
69 {
70 	char				 buf[128], obuf[128];
71 	const ASN1_OBJECT		*obj, *octype;
72 	ASN1_OCTET_STRING		*kid = NULL;
73 	CMS_ContentInfo			*cms;
74 	STACK_OF(X509)			*certs = NULL;
75 	STACK_OF(X509_CRL)		*crls;
76 	STACK_OF(CMS_SignerInfo)	*sinfos;
77 	CMS_SignerInfo			*si;
78 	X509_ALGOR			*pdig, *psig;
79 	int				 i, nattrs, nid;
80 	int				 has_ct = 0, has_md = 0, has_st = 0,
81 					 has_bst = 0;
82 	int				 rc = 0;
83 
84 	*xp = NULL;
85 	if (rsz != NULL)
86 		*rsz = 0;
87 
88 	/* just fail for empty buffers, the warning was printed elsewhere */
89 	if (der == NULL)
90 		return 0;
91 
92 	if ((cms = d2i_CMS_ContentInfo(NULL, &der, derlen)) == NULL) {
93 		cryptowarnx("%s: RFC 6488: failed CMS parse", fn);
94 		goto out;
95 	}
96 
97 	/*
98 	 * The CMS is self-signed with a signing certificate.
99 	 * Verify that the self-signage is correct.
100 	 */
101 	if (!CMS_verify(cms, NULL, NULL, bio, NULL,
102 	    CMS_NO_SIGNER_CERT_VERIFY)) {
103 		cryptowarnx("%s: CMS verification error", fn);
104 		goto out;
105 	}
106 
107 	/* RFC 6488 section 3 verify the CMS */
108 	/* the version of SignedData and SignerInfos can't be verified */
109 
110 	sinfos = CMS_get0_SignerInfos(cms);
111 	assert(sinfos != NULL);
112 	if (sk_CMS_SignerInfo_num(sinfos) != 1) {
113 		cryptowarnx("%s: RFC 6488: CMS has multiple signerInfos", fn);
114 		goto out;
115 	}
116 	si = sk_CMS_SignerInfo_value(sinfos, 0);
117 
118 	nattrs = CMS_signed_get_attr_count(si);
119 	if (nattrs <= 0) {
120 		cryptowarnx("%s: RFC 6488: error extracting signedAttrs", fn);
121 		goto out;
122 	}
123 	for (i = 0; i < nattrs; i++) {
124 		X509_ATTRIBUTE *attr;
125 
126 		attr = CMS_signed_get_attr(si, i);
127 		if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) {
128 			cryptowarnx("%s: RFC 6488: "
129 			    "bad signed attribute encoding", fn);
130 			goto out;
131 		}
132 
133 		obj = X509_ATTRIBUTE_get0_object(attr);
134 		if (obj == NULL) {
135 			cryptowarnx("%s: RFC 6488: bad signed attribute", fn);
136 			goto out;
137 		}
138 		if (OBJ_cmp(obj, cnt_type_oid) == 0) {
139 			if (has_ct++ != 0) {
140 				cryptowarnx("%s: RFC 6488: duplicate "
141 				    "signed attribute", fn);
142 				goto out;
143 			}
144 		} else if (OBJ_cmp(obj, msg_dgst_oid) == 0) {
145 			if (has_md++ != 0) {
146 				cryptowarnx("%s: RFC 6488: duplicate "
147 				    "signed attribute", fn);
148 				goto out;
149 			}
150 		} else if (OBJ_cmp(obj, sign_time_oid) == 0) {
151 			if (has_st++ != 0) {
152 				cryptowarnx("%s: RFC 6488: duplicate "
153 				    "signed attribute", fn);
154 				goto out;
155 			}
156 		} else if (OBJ_cmp(obj, bin_sign_time_oid) == 0) {
157 			if (has_bst++ != 0) {
158 				cryptowarnx("%s: RFC 6488: duplicate "
159 				    "signed attribute", fn);
160 				goto out;
161 			}
162 		} else {
163 			OBJ_obj2txt(buf, sizeof(buf), obj, 1);
164 			cryptowarnx("%s: RFC 6488: "
165 			    "CMS has unexpected signed attribute %s",
166 			    fn, buf);
167 			goto out;
168 		}
169 	}
170 	if (!has_ct || !has_md) {
171 		cryptowarnx("%s: RFC 6488: CMS missing required "
172 		    "signed attribute", fn);
173 		goto out;
174 	}
175 	if (CMS_unsigned_get_attr_count(si) != -1) {
176 		cryptowarnx("%s: RFC 6488: CMS has unsignedAttrs", fn);
177 		goto out;
178 	}
179 
180 	/* Check digest and signature algorithms */
181 	CMS_SignerInfo_get0_algs(si, NULL, NULL, &pdig, &psig);
182 	X509_ALGOR_get0(&obj, NULL, NULL, pdig);
183 	nid = OBJ_obj2nid(obj);
184 	if (nid != NID_sha256) {
185 		warnx("%s: RFC 6488: wrong digest %s, want %s", fn,
186 		    OBJ_nid2ln(nid), OBJ_nid2ln(NID_sha256));
187 		goto out;
188 	}
189 	X509_ALGOR_get0(&obj, NULL, NULL, psig);
190 	nid = OBJ_obj2nid(obj);
191 	/* RFC7395 last paragraph of section 2 specifies the allowed psig */
192 	if (nid != NID_rsaEncryption && nid != NID_sha256WithRSAEncryption) {
193 		warnx("%s: RFC 6488: wrong signature algorithm %s, want %s",
194 		    fn, OBJ_nid2ln(nid), OBJ_nid2ln(NID_rsaEncryption));
195 		goto out;
196 	}
197 
198 	/* RFC 6488 section 2.1.3.1: check the object's eContentType. */
199 
200 	obj = CMS_get0_eContentType(cms);
201 	if (obj == NULL) {
202 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
203 		    "OID object is NULL", fn);
204 		goto out;
205 	}
206 	if (OBJ_cmp(obj, oid) != 0) {
207 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
208 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
209 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
210 		    "unknown OID: %s, want %s", fn, buf, obuf);
211 		goto out;
212 	}
213 
214 	/* Compare content-type with eContentType */
215 	octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid,
216 	    -3, V_ASN1_OBJECT);
217 	assert(octype != NULL);
218 	if (OBJ_cmp(obj, octype) != 0) {
219 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
220 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
221 		warnx("%s: RFC 6488: eContentType does not match Content-Type "
222 		    "OID: %s, want %s", fn, buf, obuf);
223 		goto out;
224 	}
225 
226 	/*
227 	 * Check that there are no CRLS in this CMS message.
228 	 */
229 	crls = CMS_get1_crls(cms);
230 	if (crls != NULL) {
231 		sk_X509_CRL_pop_free(crls, X509_CRL_free);
232 		cryptowarnx("%s: RFC 6488: CMS has CRLs", fn);
233 		goto out;
234 	}
235 
236 	/*
237 	 * The self-signing certificate is further signed by the input
238 	 * signing authority according to RFC 6488, 2.1.4.
239 	 * We extract that certificate now for later verification.
240 	 */
241 
242 	certs = CMS_get0_signers(cms);
243 	if (certs == NULL || sk_X509_num(certs) != 1) {
244 		warnx("%s: RFC 6488 section 2.1.4: eContent: "
245 		    "want 1 signer, have %d", fn, sk_X509_num(certs));
246 		goto out;
247 	}
248 	*xp = sk_X509_value(certs, 0);
249 	if (!X509_up_ref(*xp)) {
250 		*xp = NULL;
251 		goto out;
252 	}
253 
254 	/* Cache X509v3 extensions, see X509_check_ca(3). */
255 	if (X509_check_purpose(*xp, -1, -1) <= 0) {
256 		cryptowarnx("%s: could not cache X509v3 extensions", fn);
257 		goto out;
258 	}
259 
260 	if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 ||
261 	    kid == NULL) {
262 		warnx("%s: RFC 6488: could not extract SKI from SID", fn);
263 		goto out;
264 	}
265 	if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) {
266 		warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn);
267 		goto out;
268 	}
269 
270 	if (!cms_extract_econtent(fn, cms, res, rsz))
271 		goto out;
272 
273 	rc = 1;
274  out:
275 	if (rc == 0) {
276 		X509_free(*xp);
277 		*xp = NULL;
278 	}
279 	sk_X509_free(certs);
280 	CMS_ContentInfo_free(cms);
281 	return rc;
282 }
283 
284 /*
285  * Parse and validate a self-signed CMS message.
286  * Conforms to RFC 6488.
287  * The eContentType of the message must be an oid object.
288  * Return the eContent as a string and set "rsz" to be its length.
289  */
290 unsigned char *
291 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
292     size_t derlen, const ASN1_OBJECT *oid, size_t *rsz)
293 {
294 	unsigned char *res = NULL;
295 
296 	if (!cms_parse_validate_internal(xp, fn, der, derlen, oid, NULL, &res,
297 	    rsz))
298 		return NULL;
299 
300 	return res;
301 }
302 
303 /*
304  * Parse and validate a detached CMS signature.
305  * bio must contain the original message, der must contain the CMS.
306  * Return the 1 on success, 0 on failure.
307  */
308 int
309 cms_parse_validate_detached(X509 **xp, const char *fn, const unsigned char *der,
310     size_t derlen, const ASN1_OBJECT *oid, BIO *bio)
311 {
312 	return cms_parse_validate_internal(xp, fn, der, derlen, oid, bio, NULL,
313 	    NULL);
314 }
315