pkcs7.c revision 1.2
1/*	$NetBSD: pkcs7.c,v 1.2 2017/04/20 13:18:23 joerg Exp $	*/
2#if HAVE_CONFIG_H
3#include "config.h"
4#endif
5#include <nbcompat.h>
6#if HAVE_SYS_CDEFS_H
7#include <sys/cdefs.h>
8#endif
9
10__RCSID("$NetBSD: pkcs7.c,v 1.2 2017/04/20 13:18:23 joerg Exp $");
11
12/*-
13 * Copyright (c) 2004, 2008 The NetBSD Foundation, Inc.
14 * All rights reserved.
15 *
16 * This code is derived from software contributed to The NetBSD Foundation
17 * by Love H�rnquist �strand <lha@it.su.se>
18 *
19 * Redistribution and use in source and binary forms, with or without
20 * modification, are permitted provided that the following conditions
21 * are met:
22 * 1. Redistributions of source code must retain the above copyright
23 *    notice, this list of conditions and the following disclaimer.
24 * 2. Redistributions in binary form must reproduce the above copyright
25 *    notice, this list of conditions and the following disclaimer in the
26 *    documentation and/or other materials provided with the distribution.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
30 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
31 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
32 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38 * POSSIBILITY OF SUCH DAMAGE.
39 */
40
41#if HAVE_ERR_H
42#include <err.h>
43#endif
44
45#include <openssl/pkcs7.h>
46#include <openssl/evp.h>
47#include <openssl/x509.h>
48#include <openssl/x509v3.h>
49#include <openssl/pem.h>
50#include <openssl/err.h>
51
52#include "lib.h"
53
54#ifndef NS_ANY_CA
55#define NS_ANY_CA		(NS_SSL_CA|NS_SMIME_CA|NS_OBJSIGN_CA)
56#endif
57
58static const unsigned int pkg_key_usage = XKU_CODE_SIGN | XKU_SMIME;
59
60static int
61check_ca(X509 *cert)
62{
63	if ((cert->ex_flags & EXFLAG_KUSAGE) != 0 &&
64	    (cert->ex_kusage & KU_KEY_CERT_SIGN) != KU_KEY_CERT_SIGN)
65		return 0;
66	if ((cert->ex_flags & EXFLAG_BCONS) != 0)
67		return (cert->ex_flags & EXFLAG_CA) == EXFLAG_CA;
68	if ((cert->ex_flags & (EXFLAG_V1|EXFLAG_SS)) == (EXFLAG_V1|EXFLAG_SS))
69		return 1;
70	if ((cert->ex_flags & EXFLAG_KUSAGE) != 0)
71		return 1;
72	if ((cert->ex_flags & EXFLAG_NSCERT) != 0 &&
73	    (cert->ex_nscert & NS_ANY_CA) != 0)
74		return 1;
75	return 0;
76}
77
78static STACK_OF(X509) *
79file_to_certs(const char *file)
80{
81	unsigned long ret;
82	STACK_OF(X509) *certs;
83	FILE *f;
84
85	if ((f = fopen(file, "r")) == NULL) {
86		warn("open failed %s", file);
87		return NULL;
88	}
89
90	certs = sk_X509_new_null();
91	for (;;) {
92		X509 *cert;
93
94		cert = PEM_read_X509(f, NULL, NULL, NULL);
95		if (cert == NULL) {
96			ret = ERR_GET_REASON(ERR_peek_error());
97			if (ret == PEM_R_NO_START_LINE) {
98				/* End of file reached. no error */
99				ERR_clear_error();
100				break;
101			}
102			sk_X509_free(certs);
103			warnx("Can't read certificate in file: %s", file);
104			fclose(f);
105			return NULL;
106		}
107		sk_X509_insert(certs, cert, sk_X509_num(certs));
108	}
109
110	fclose(f);
111
112	if (sk_X509_num(certs) == 0) {
113		sk_X509_free(certs);
114		certs = NULL;
115		warnx("No certificate found in file %s", file);
116	}
117
118	return certs;
119}
120
121int
122easy_pkcs7_verify(const char *content, size_t len,
123    const char *signature, size_t signature_len,
124    const char *anchor, int is_pkg)
125{
126	STACK_OF(X509) *cert_chain, *signers;
127	X509_STORE *store;
128	BIO *sig, *in;
129	PKCS7 *p7;
130	int i, status;
131	X509_NAME *name;
132	char *subject;
133
134	OpenSSL_add_all_algorithms();
135	ERR_load_crypto_strings();
136
137	status = -1;
138
139	if (cert_chain_file)
140		cert_chain = file_to_certs(cert_chain_file);
141	else
142		cert_chain = NULL;
143
144	store = X509_STORE_new();
145	if (store == NULL) {
146		sk_X509_free(cert_chain);
147		warnx("Failed to create certificate store");
148		return -1;
149	}
150
151	X509_STORE_load_locations(store, anchor, NULL);
152
153	in = BIO_new_mem_buf(__UNCONST(content), len);
154	sig = BIO_new_mem_buf(__UNCONST(signature), signature_len);
155	signers = NULL;
156
157	p7 = PEM_read_bio_PKCS7(sig, NULL, NULL, NULL);
158	if (p7 == NULL) {
159		warnx("Failed to parse the signature");
160		goto cleanup;
161	}
162
163	if (PKCS7_verify(p7, cert_chain, store, in, NULL, 0) != 1) {
164		warnx("Failed to verify signature");
165		goto cleanup;
166	}
167
168	signers = PKCS7_get0_signers(p7, NULL, 0);
169	if (signers == NULL) {
170		warnx("Failed to get signers");
171		goto cleanup;
172	}
173
174	if (sk_X509_num(signers) == 0) {
175		warnx("No signers found");
176		goto cleanup;
177	}
178
179	for (i = 0; i < sk_X509_num(signers); i++) {
180		/* Compute ex_xkusage */
181		X509_check_purpose(sk_X509_value(signers, i), -1, -1);
182
183		if (check_ca(sk_X509_value(signers, i))) {
184			warnx("CA keys are not valid for signatures");
185			goto cleanup;
186		}
187		if (is_pkg) {
188			if (sk_X509_value(signers, i)->ex_xkusage != pkg_key_usage) {
189				warnx("Certificate must have CODE SIGNING "
190				    "and EMAIL PROTECTION property");
191				goto cleanup;
192			}
193		} else {
194			if (sk_X509_value(signers, i)->ex_xkusage != 0) {
195				warnx("Certificate must not have any property");
196				goto cleanup;
197			}
198		}
199	}
200
201	printf("Sigature ok, signed by:\n");
202
203	for (i = 0; i < sk_X509_num(signers); i++) {
204		name = X509_get_subject_name(sk_X509_value(signers, i));
205		subject = X509_NAME_oneline(name, NULL, 0);
206
207		printf("\t%s\n", subject);
208
209		OPENSSL_free(subject);
210	}
211
212	status = 0;
213
214cleanup:
215	sk_X509_free(cert_chain);
216	sk_X509_free(signers);
217	X509_STORE_free(store);
218
219	PKCS7_free(p7);
220	BIO_free(in);
221	BIO_free(sig);
222
223	return status;
224}
225
226static int
227ssl_pass_cb(char *buf, int size, int rwflag, void *u)
228{
229
230	if (EVP_read_pw_string(buf, size, "Passphrase :", 0)) {
231#if OPENSSL_VERSION >= 0x0090608fL
232		OPENSSL_cleanse(buf, size);
233#else
234		memset(buf, 0, size);
235#endif
236		return 0;
237	}
238	return strlen(buf);
239}
240
241int
242easy_pkcs7_sign(const char *content, size_t len,
243    char **signature, size_t *signature_len,
244    const char *key_file, const char *cert_file)
245{
246	FILE *f;
247	X509 *certificate;
248	STACK_OF(X509) *c, *cert_chain;
249	EVP_PKEY *private_key;
250	char *tmp_sig;
251	BIO *out, *in;
252	PKCS7 *p7;
253	int status;
254
255	OpenSSL_add_all_algorithms();
256	ERR_load_crypto_strings();
257
258	status = -1;
259	private_key = NULL;
260	cert_chain = NULL;
261	in = NULL;
262
263	c = file_to_certs(cert_file);
264
265	if (sk_X509_num(c) != 1) {
266		warnx("More then one certificate in the certificate file");
267		goto cleanup;
268	}
269	certificate = sk_X509_value(c, 0);
270
271	/* Compute ex_kusage */
272	X509_check_purpose(certificate, -1, 0);
273
274	if (check_ca(certificate)) {
275		warnx("CA keys are not valid for signatures");
276		goto cleanup;
277	}
278
279	if (certificate->ex_xkusage != pkg_key_usage) {
280		warnx("Certificate must have CODE SIGNING "
281		    "and EMAIL PROTECTION property");
282		goto cleanup;
283	}
284
285	if (cert_chain_file)
286		cert_chain = file_to_certs(cert_chain_file);
287
288	if ((f = fopen(key_file, "r")) == NULL) {
289		warn("Failed to open private key file %s", key_file);
290		goto cleanup;
291	}
292	private_key = PEM_read_PrivateKey(f, NULL, ssl_pass_cb, NULL);
293	fclose(f);
294	if (private_key == NULL) {
295		warnx("Can't read private key: %s", key_file);
296		goto cleanup;
297	}
298
299	if (X509_check_private_key(certificate, private_key) != 1) {
300		warnx("The private key %s doesn't match the certificate %s",
301		    key_file, cert_file);
302		goto cleanup;
303	}
304
305	in = BIO_new_mem_buf(__UNCONST(content), len);
306
307	p7 = PKCS7_sign(certificate, private_key, cert_chain, in,
308	    PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY);
309	if (p7 == NULL) {
310		warnx("Failed to create signature structure");
311		goto cleanup;
312	}
313
314	out = BIO_new(BIO_s_mem());
315	PEM_write_bio_PKCS7(out, p7);
316	*signature_len = BIO_get_mem_data(out, &tmp_sig);
317	*signature = xmalloc(*signature_len);
318	memcpy(*signature, tmp_sig, *signature_len);
319	BIO_free_all(out);
320
321	PKCS7_free(p7);
322
323	status = 0;
324
325cleanup:
326	sk_X509_free(c);
327	sk_X509_free(cert_chain);
328	EVP_PKEY_free(private_key);
329	BIO_free(in);
330
331	return status;
332}
333