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, ¬after))
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