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