xref: /openbsd-src/usr.sbin/rpki-client/cms.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /*	$OpenBSD: cms.c,v 1.6 2019/11/29 05:14:11 benno 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,
39     const char *oid, const unsigned char *dgst, size_t *rsz)
40 {
41 	const ASN1_OBJECT	*obj;
42 	ASN1_OCTET_STRING	**os = NULL;
43 	BIO			*bio = NULL, *shamd;
44 	CMS_ContentInfo		*cms;
45 	char			 buf[128], mdbuf[EVP_MAX_MD_SIZE];
46 	int			 rc = 0, sz;
47 	STACK_OF(X509)		*certs = NULL;
48 	EVP_MD			*md;
49 	unsigned char		*res = NULL;
50 
51 	*rsz = 0;
52 	*xp = NULL;
53 
54 	/*
55 	 * This is usually fopen() failure, so let it pass through to
56 	 * the handler, which will in turn ignore the entity.
57 	 */
58 
59 	if ((bio = BIO_new_file(fn, "rb")) == NULL) {
60 		if (verbose > 0)
61 			cryptowarnx("%s: BIO_new_file", fn);
62 		return NULL;
63 	}
64 
65 	/*
66 	 * If we have a digest specified, create an MD chain that will
67 	 * automatically compute a digest during the CMS creation.
68 	 */
69 
70 	if (dgst != NULL) {
71 		if ((shamd = BIO_new(BIO_f_md())) == NULL)
72 			cryptoerrx("BIO_new");
73 		if (!BIO_set_md(shamd, EVP_sha256()))
74 			cryptoerrx("BIO_set_md");
75 		if ((bio = BIO_push(shamd, bio)) == NULL)
76 			cryptoerrx("BIO_push");
77 	}
78 
79 	if ((cms = d2i_CMS_bio(bio, NULL)) == NULL) {
80 		cryptowarnx("%s: RFC 6488: failed CMS parse", fn);
81 		goto out;
82 	}
83 
84 	/*
85 	 * If we have a digest, find it in the chain (we'll already have
86 	 * made it, so assert otherwise) and verify it.
87 	 */
88 
89 	if (dgst != NULL) {
90 		shamd = BIO_find_type(bio, BIO_TYPE_MD);
91 		assert(shamd != NULL);
92 
93 		if (!BIO_get_md(shamd, &md))
94 			cryptoerrx("BIO_get_md");
95 		assert(EVP_MD_type(md) == NID_sha256);
96 
97 		if ((sz = BIO_gets(shamd, mdbuf, EVP_MAX_MD_SIZE)) < 0)
98 			cryptoerrx("BIO_gets");
99 		assert(sz == SHA256_DIGEST_LENGTH);
100 
101 		if (memcmp(mdbuf, dgst, SHA256_DIGEST_LENGTH)) {
102 			warnx("%s: RFC 6488: bad message digest", fn);
103 			goto out;
104 		}
105 	}
106 
107 	/*
108 	 * The CMS is self-signed with a signing certifiate.
109 	 * Verify that the self-signage is correct.
110 	 */
111 
112 	if (!CMS_verify(cms, NULL, NULL,
113 	    NULL, NULL, CMS_NO_SIGNER_CERT_VERIFY)) {
114 		cryptowarnx("%s: RFC 6488: CMS not self-signed", fn);
115 		goto out;
116 	}
117 
118 	/* RFC 6488 section 2.1.3.1: check the object's eContentType. */
119 
120 	obj = CMS_get0_eContentType(cms);
121 	if ((sz = OBJ_obj2txt(buf, sizeof(buf), obj, 1)) < 0)
122 		cryptoerrx("OBJ_obj2txt");
123 
124 	if ((size_t)sz >= sizeof(buf)) {
125 		warnx("%s: RFC 6488 section 2.1.3.1: "
126 		    "eContentType: OID too long", fn);
127 		goto out;
128 	} else if (strcmp(buf, oid)) {
129 		warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
130 		    "unknown OID: %s, want %s", fn, buf, oid);
131 		goto out;
132 	}
133 
134 	/*
135 	 * The self-signing certificate is further signed by the input
136 	 * signing authority according to RFC 6488, 2.1.4.
137 	 * We extract that certificate now for later verification.
138 	 */
139 
140 	certs = CMS_get0_signers(cms);
141 	if (certs == NULL || sk_X509_num(certs) != 1) {
142 		warnx("%s: RFC 6488 section 2.1.4: eContent: "
143 		    "want 1 signer, have %d", fn, sk_X509_num(certs));
144 		goto out;
145 	}
146 	*xp = X509_dup(sk_X509_value(certs, 0));
147 
148 	/* Verify that we have eContent to disseminate. */
149 
150 	if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) {
151 		warnx("%s: RFC 6488 section 2.1.4: "
152 		    "eContent: zero-length content", fn);
153 		goto out;
154 	}
155 
156 	/*
157 	 * Extract and duplicate the eContent.
158 	 * The CMS framework offers us no other way of easily managing
159 	 * this information; and since we're going to d2i it anyway,
160 	 * simply pass it as the desired underlying types.
161 	 */
162 
163 	if ((res = malloc((*os)->length)) == NULL)
164 		err(1, NULL);
165 	memcpy(res, (*os)->data, (*os)->length);
166 	*rsz = (*os)->length;
167 
168 	rc = 1;
169 out:
170 	BIO_free_all(bio);
171 	sk_X509_free(certs);
172 	CMS_ContentInfo_free(cms);
173 
174 	if (rc == 0) {
175 		X509_free(*xp);
176 		*xp = NULL;
177 	}
178 
179 	return res;
180 }
181