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