xref: /openbsd-src/usr.sbin/rpki-client/cms.c (revision c1a45aed656e7d5627c30c92421893a76f370ccb)
1 /*	$OpenBSD: cms.c,v 1.16 2022/03/28 13:04:01 claudio 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 <stdarg.h>
21 #include <stdint.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 
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 /*
36  * Parse and validate a self-signed CMS message, where the signing X509
37  * certificate has been hashed to dgst (optional).
38  * Conforms to RFC 6488.
39  * The eContentType of the message must be an oid object.
40  * Return the eContent as a string and set "rsz" to be its length.
41  */
42 unsigned char *
43 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
44     size_t derlen, const ASN1_OBJECT *oid, size_t *rsz)
45 {
46 	char				 buf[128], obuf[128];
47 	const ASN1_OBJECT		*obj, *octype;
48 	ASN1_OCTET_STRING		**os = NULL, *kid = NULL;
49 	CMS_ContentInfo			*cms;
50 	int				 rc = 0;
51 	STACK_OF(X509)			*certs = NULL;
52 	STACK_OF(X509_CRL)		*crls;
53 	STACK_OF(CMS_SignerInfo)	*sinfos;
54 	CMS_SignerInfo			*si;
55 	X509_ALGOR			*pdig, *psig;
56 	unsigned char			*res = NULL;
57 	int				 i, nattrs, nid;
58 	int				 has_ct = 0, has_md = 0, has_st = 0,
59 					 has_bst = 0;
60 
61 	*rsz = 0;
62 	*xp = NULL;
63 
64 	/* just fail for empty buffers, the warning was printed elsewhere */
65 	if (der == NULL)
66 		return NULL;
67 
68 	if ((cms = d2i_CMS_ContentInfo(NULL, &der, derlen)) == NULL) {
69 		cryptowarnx("%s: RFC 6488: failed CMS parse", fn);
70 		goto out;
71 	}
72 
73 	/*
74 	 * The CMS is self-signed with a signing certifiate.
75 	 * Verify that the self-signage is correct.
76 	 */
77 
78 	if (!CMS_verify(cms, NULL, NULL, NULL, NULL,
79 	    CMS_NO_SIGNER_CERT_VERIFY)) {
80 		cryptowarnx("%s: RFC 6488: CMS not self-signed", fn);
81 		goto out;
82 	}
83 
84 	/* RFC 6488 section 3 verify the CMS */
85 	/* the version of SignedData and SignerInfos can't be verified */
86 
87 	sinfos = CMS_get0_SignerInfos(cms);
88 	assert(sinfos != NULL);
89 	if (sk_CMS_SignerInfo_num(sinfos) != 1) {
90 		cryptowarnx("%s: RFC 6488: CMS has multiple signerInfos", fn);
91 		goto out;
92 	}
93 	si = sk_CMS_SignerInfo_value(sinfos, 0);
94 
95 	nattrs = CMS_signed_get_attr_count(si);
96 	if (nattrs <= 0) {
97 		cryptowarnx("%s: RFC 6488: error extracting signedAttrs", fn);
98 		goto out;
99 	}
100 	for (i = 0; i < nattrs; i++) {
101 		X509_ATTRIBUTE *attr;
102 
103 		attr = CMS_signed_get_attr(si, i);
104 		if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) {
105 			cryptowarnx("%s: RFC 6488: "
106 			    "bad signed attribute encoding", fn);
107 			goto out;
108 		}
109 
110 		obj = X509_ATTRIBUTE_get0_object(attr);
111 		if (obj == NULL) {
112 			cryptowarnx("%s: RFC 6488: bad signed attribute", fn);
113 			goto out;
114 		}
115 		if (OBJ_cmp(obj, cnt_type_oid) == 0) {
116 			if (has_ct++ != 0) {
117 				cryptowarnx("%s: RFC 6488: duplicate "
118 				    "signed attribute", fn);
119 				goto out;
120 			}
121 		} else if (OBJ_cmp(obj, msg_dgst_oid) == 0) {
122 			if (has_md++ != 0) {
123 				cryptowarnx("%s: RFC 6488: duplicate "
124 				    "signed attribute", fn);
125 				goto out;
126 			}
127 		} else if (OBJ_cmp(obj, sign_time_oid) == 0) {
128 			if (has_st++ != 0) {
129 				cryptowarnx("%s: RFC 6488: duplicate "
130 				    "signed attribute", fn);
131 				goto out;
132 			}
133 		} else if (OBJ_cmp(obj, bin_sign_time_oid) == 0) {
134 			if (has_bst++ != 0) {
135 				cryptowarnx("%s: RFC 6488: duplicate "
136 				    "signed attribute", fn);
137 				goto out;
138 			}
139 		} else {
140 			OBJ_obj2txt(buf, sizeof(buf), obj, 1);
141 			cryptowarnx("%s: RFC 6488: "
142 			    "CMS has unexpected signed attribute %s",
143 			    fn, buf);
144 			goto out;
145 		}
146 	}
147 	if (!has_ct || !has_md) {
148 		cryptowarnx("%s: RFC 6488: CMS missing required "
149 		    "signed attribute", fn);
150 		goto out;
151 	}
152 	if (CMS_unsigned_get_attr_count(si) != -1) {
153 		cryptowarnx("%s: RFC 6488: CMS has unsignedAttrs", fn);
154 		goto out;
155 	}
156 
157 	/* Check digest and signature algorithms */
158 	CMS_SignerInfo_get0_algs(si, NULL, NULL, &pdig, &psig);
159 	X509_ALGOR_get0(&obj, NULL, NULL, pdig);
160 	nid = OBJ_obj2nid(obj);
161 	if (nid != NID_sha256) {
162 		warnx("%s: RFC 6488: wrong digest %s, want %s", fn,
163 		    OBJ_nid2ln(nid), OBJ_nid2ln(NID_sha256));
164 		goto out;
165 	}
166 	X509_ALGOR_get0(&obj, NULL, NULL, psig);
167 	nid = OBJ_obj2nid(obj);
168 	/* RFC7395 last paragraph of section 2 specifies the allowed psig */
169 	if (nid != NID_rsaEncryption && nid != NID_sha256WithRSAEncryption) {
170 		warnx("%s: RFC 6488: wrong signature algorithm %s, want %s",
171 		    fn, OBJ_nid2ln(nid), OBJ_nid2ln(NID_rsaEncryption));
172 		goto out;
173 	}
174 
175 	/* RFC 6488 section 2.1.3.1: check the object's eContentType. */
176 
177 	obj = CMS_get0_eContentType(cms);
178 	if (obj == NULL) {
179 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
180 		    "OID object is NULL", fn);
181 		goto out;
182 	}
183 	if (OBJ_cmp(obj, oid) != 0) {
184 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
185 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
186 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
187 		    "unknown OID: %s, want %s", fn, buf, obuf);
188 		goto out;
189 	}
190 
191 	/* Compare content-type with eContentType */
192 	octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid,
193 	    -3, V_ASN1_OBJECT);
194 	assert(octype != NULL);
195 	if (OBJ_cmp(obj, octype) != 0) {
196 		OBJ_obj2txt(buf, sizeof(buf), obj, 1);
197 		OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
198 		warnx("%s: RFC 6488: eContentType does not match Content-Type "
199 		    "OID: %s, want %s", fn, buf, obuf);
200 		goto out;
201 	}
202 
203 	/*
204 	 * Check that there are no CRLS in this CMS message.
205 	 */
206 	crls = CMS_get1_crls(cms);
207 	if (crls != NULL) {
208 		sk_X509_CRL_pop_free(crls, X509_CRL_free);
209 		cryptowarnx("%s: RFC 6488: CMS has CRLs", fn);
210 		goto out;
211 	}
212 
213 	/*
214 	 * The self-signing certificate is further signed by the input
215 	 * signing authority according to RFC 6488, 2.1.4.
216 	 * We extract that certificate now for later verification.
217 	 */
218 
219 	certs = CMS_get0_signers(cms);
220 	if (certs == NULL || sk_X509_num(certs) != 1) {
221 		warnx("%s: RFC 6488 section 2.1.4: eContent: "
222 		    "want 1 signer, have %d", fn, sk_X509_num(certs));
223 		goto out;
224 	}
225 	*xp = X509_dup(sk_X509_value(certs, 0));
226 
227 	if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 ||
228 	    kid == NULL) {
229 		warnx("%s: RFC 6488: could not extract SKI from SID", fn);
230 		goto out;
231 	}
232 	if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) {
233 		warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn);
234 		goto out;
235 	}
236 
237 	/* Verify that we have eContent to disseminate. */
238 
239 	if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) {
240 		warnx("%s: RFC 6488 section 2.1.4: "
241 		    "eContent: zero-length content", fn);
242 		goto out;
243 	}
244 
245 	/*
246 	 * Extract and duplicate the eContent.
247 	 * The CMS framework offers us no other way of easily managing
248 	 * this information; and since we're going to d2i it anyway,
249 	 * simply pass it as the desired underlying types.
250 	 */
251 
252 	if ((res = malloc((*os)->length)) == NULL)
253 		err(1, NULL);
254 	memcpy(res, (*os)->data, (*os)->length);
255 	*rsz = (*os)->length;
256 
257 	rc = 1;
258 out:
259 	sk_X509_free(certs);
260 	CMS_ContentInfo_free(cms);
261 
262 	if (rc == 0) {
263 		X509_free(*xp);
264 		*xp = NULL;
265 	}
266 
267 	return res;
268 }
269 
270 /*
271  * Wrapper around ASN1_get_object() that preserves the current start
272  * state and returns a more meaningful value.
273  * Return zero on failure, non-zero on success.
274  */
275 int
276 ASN1_frame(const char *fn, size_t sz,
277 	const unsigned char **cnt, long *cntsz, int *tag)
278 {
279 	int	 ret, pcls;
280 
281 	ret = ASN1_get_object(cnt, cntsz, tag, &pcls, sz);
282 	if ((ret & 0x80)) {
283 		cryptowarnx("%s: ASN1_get_object", fn);
284 		return 0;
285 	}
286 	return ASN1_object_size((ret & 0x01) ? 2 : 0, *cntsz, *tag);
287 }
288 
289 /*
290  * Check the version field in eContent.
291  * Returns -1 on failure, zero on success.
292  */
293 int
294 cms_econtent_version(const char *fn, const unsigned char **d, size_t dsz,
295 	long *version)
296 {
297 	ASN1_INTEGER	*aint = NULL;
298 	long		 plen;
299 	int		 ptag, rc = -1;
300 
301 	if (!ASN1_frame(fn, dsz, d, &plen, &ptag))
302 		goto out;
303 	if (ptag != 0) {
304 		warnx("%s: eContent version: expected explicit tag [0]", fn);
305 		goto out;
306 	}
307 
308 	aint = d2i_ASN1_INTEGER(NULL, d, plen);
309 	if (aint == NULL) {
310 		cryptowarnx("%s: eContent version: failed d2i_ASN1_INTEGER",
311 		    fn);
312 		goto out;
313 	}
314 
315 	*version = ASN1_INTEGER_get(aint);
316 	if (*version < 0) {
317 		warnx("%s: eContent version: expected positive integer, got:"
318 		    " %ld", fn, *version);
319 		goto out;
320 	}
321 
322 	rc = 0;
323 out:
324 	ASN1_INTEGER_free(aint);
325 	return rc;
326 }
327