1 /* $OpenBSD: cms.c,v 1.13 2022/01/18 16:24:55 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 /*
31 * Parse and validate a self-signed CMS message, where the signing X509
32 * certificate has been hashed to dgst (optional).
33 * Conforms to RFC 6488.
34 * The eContentType of the message must be an oid object.
35 * Return the eContent as a string and set "rsz" to be its length.
36 */
37 unsigned char *
cms_parse_validate(X509 ** xp,const char * fn,const unsigned char * der,size_t derlen,const ASN1_OBJECT * oid,size_t * rsz)38 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
39 size_t derlen, const ASN1_OBJECT *oid, size_t *rsz)
40 {
41 const ASN1_OBJECT *obj;
42 ASN1_OCTET_STRING **os = NULL;
43 CMS_ContentInfo *cms;
44 int rc = 0;
45 STACK_OF(X509) *certs = NULL;
46 unsigned char *res = NULL;
47
48 *rsz = 0;
49 *xp = NULL;
50
51 /* just fail for empty buffers, the warning was printed elsewhere */
52 if (der == NULL)
53 return NULL;
54
55 if ((cms = d2i_CMS_ContentInfo(NULL, &der, derlen)) == NULL) {
56 cryptowarnx("%s: RFC 6488: failed CMS parse", fn);
57 goto out;
58 }
59
60 /*
61 * The CMS is self-signed with a signing certifiate.
62 * Verify that the self-signage is correct.
63 */
64
65 if (!CMS_verify(cms, NULL, NULL, NULL, NULL,
66 CMS_NO_SIGNER_CERT_VERIFY)) {
67 cryptowarnx("%s: RFC 6488: CMS not self-signed", fn);
68 goto out;
69 }
70
71 /* RFC 6488 section 2.1.3.1: check the object's eContentType. */
72
73 obj = CMS_get0_eContentType(cms);
74 if (obj == NULL) {
75 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
76 "OID object is NULL", fn);
77 goto out;
78 }
79 if (OBJ_cmp(obj, oid) != 0) {
80 char buf[128], obuf[128];
81
82 OBJ_obj2txt(buf, sizeof(buf), obj, 1);
83 OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
84 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
85 "unknown OID: %s, want %s", fn, buf, obuf);
86 goto out;
87 }
88
89 /*
90 * The self-signing certificate is further signed by the input
91 * signing authority according to RFC 6488, 2.1.4.
92 * We extract that certificate now for later verification.
93 */
94
95 certs = CMS_get0_signers(cms);
96 if (certs == NULL || sk_X509_num(certs) != 1) {
97 warnx("%s: RFC 6488 section 2.1.4: eContent: "
98 "want 1 signer, have %d", fn, sk_X509_num(certs));
99 goto out;
100 }
101 *xp = X509_dup(sk_X509_value(certs, 0));
102
103 /* Verify that we have eContent to disseminate. */
104
105 if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) {
106 warnx("%s: RFC 6488 section 2.1.4: "
107 "eContent: zero-length content", fn);
108 goto out;
109 }
110
111 /*
112 * Extract and duplicate the eContent.
113 * The CMS framework offers us no other way of easily managing
114 * this information; and since we're going to d2i it anyway,
115 * simply pass it as the desired underlying types.
116 */
117
118 if ((res = malloc((*os)->length)) == NULL)
119 err(1, NULL);
120 memcpy(res, (*os)->data, (*os)->length);
121 *rsz = (*os)->length;
122
123 rc = 1;
124 out:
125 sk_X509_free(certs);
126 CMS_ContentInfo_free(cms);
127
128 if (rc == 0) {
129 X509_free(*xp);
130 *xp = NULL;
131 }
132
133 return res;
134 }
135
136 /*
137 * Wrapper around ASN1_get_object() that preserves the current start
138 * state and returns a more meaningful value.
139 * Return zero on failure, non-zero on success.
140 */
141 int
ASN1_frame(const char * fn,size_t sz,const unsigned char ** cnt,long * cntsz,int * tag)142 ASN1_frame(const char *fn, size_t sz,
143 const unsigned char **cnt, long *cntsz, int *tag)
144 {
145 int ret, pcls;
146
147 ret = ASN1_get_object(cnt, cntsz, tag, &pcls, sz);
148 if ((ret & 0x80)) {
149 cryptowarnx("%s: ASN1_get_object", fn);
150 return 0;
151 }
152 return ASN1_object_size((ret & 0x01) ? 2 : 0, *cntsz, *tag);
153 }
154
155 /*
156 * Check the version field in eContent.
157 * Returns -1 on failure, zero on success.
158 */
159 int
cms_econtent_version(const char * fn,const unsigned char ** d,size_t dsz,long * version)160 cms_econtent_version(const char *fn, const unsigned char **d, size_t dsz,
161 long *version)
162 {
163 ASN1_INTEGER *aint = NULL;
164 long plen;
165 int ptag, rc = -1;
166
167 if (!ASN1_frame(fn, dsz, d, &plen, &ptag))
168 goto out;
169 if (ptag != 0) {
170 warnx("%s: eContent version: expected explicit tag [0]", fn);
171 goto out;
172 }
173
174 aint = d2i_ASN1_INTEGER(NULL, d, plen);
175 if (aint == NULL) {
176 cryptowarnx("%s: eContent version: failed d2i_ASN1_INTEGER",
177 fn);
178 goto out;
179 }
180
181 *version = ASN1_INTEGER_get(aint);
182 if (*version < 0) {
183 warnx("%s: eContent version: expected positive integer, got:"
184 " %ld", fn, *version);
185 goto out;
186 }
187
188 rc = 0;
189 out:
190 ASN1_INTEGER_free(aint);
191 return rc;
192 }
193