diff mbox series

[1/2] Add PKCS#7 verification for wolfSSL

Message ID 20230321130713.5203-2-bage@debian.org
State Accepted
Headers show
Series Add PKCS#7 verification | expand

Commit Message

Bastian Germann March 21, 2023, 1:07 p.m. UTC
wolfSSL has a PKCS#7 implementation that has mostly the same features like
CMS. Add signature verification based on the existing swupdate_cms_verify
module. It can only deal with one signature and cannot deal with X509
purposes, which is why CMS_SKIP_UNKNOWN_SIGNERS and
CMS_IGNORE_CERTIFICATE_PURPOSE are forcibly set with PKCS#7.

Wire up the new module in the build system.

Signed-off-by: Bastian Germann <bage@debian.org>
---
 Kconfig                           |  13 ++-
 corelib/Makefile                  |   4 +-
 corelib/swupdate_cms_verify.c     |   2 +
 corelib/swupdate_pkcs7_verify.c   | 173 ++++++++++++++++++++++++++++++
 corelib/swupdate_verify_private.h |   2 +
 corelib/verify_signature.c        |   3 +
 include/sslapi.h                  |   3 +-
 7 files changed, 194 insertions(+), 6 deletions(-)
 create mode 100644 corelib/swupdate_pkcs7_verify.c
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index 48325bf..0c497fe 100644
--- a/Kconfig
+++ b/Kconfig
@@ -415,6 +415,8 @@  choice
 	config SSL_IMPL_WOLFSSL
 		bool "wolfSSL (with OpenSSL compatibility layer)"
 		depends on HAVE_WOLFSSL
+		select CMS_IGNORE_CERTIFICATE_PURPOSE
+		select CMS_SKIP_UNKNOWN_SIGNERS
 
 	config SSL_IMPL_MBEDTLS
 		bool "mbedTLS"
@@ -451,7 +453,10 @@  choice
 	default SIGALG_RAWRSA
 	help
 	  Select if the signature algorithm for signed images is a raw RSA signature
-	  (following PKCS#1.5) or if it uses Cryptographic Message Syntax (CMS).
+	  (following PKCS#1.5) or if it uses Cryptographic Message Syntax (CMS) with
+	  OpenSSL/LibreSSL or PKCS#7 with wolfSSL.
+	  wolfSSL's PKCS#7 implementation can only deal with one signature and cannot
+	  deal with X509 purposes.
 
 	config SIGALG_RAWRSA
 		bool "RSA PKCS#1.5"
@@ -460,12 +465,12 @@  choice
 		bool "RSA PSS"
 
 	config SIGALG_CMS
-		bool "Cryptographic Message Syntax (CMS)"
-		depends on SSL_IMPL_OPENSSL
+		bool "Cryptographic Message Syntax (CMS) / PKCS#7"
+		depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL
 
 endchoice
 
-menu "CMS signature verification options"
+menu "CMS / PKCS#7 signature verification options"
 	depends on SIGALG_CMS
 
 config CMS_IGNORE_EXPIRED_CERTIFICATE
diff --git a/corelib/Makefile b/corelib/Makefile
index d6ea6a2..fb0a989 100644
--- a/corelib/Makefile
+++ b/corelib/Makefile
@@ -17,9 +17,11 @@  lib-$(CONFIG_SIGALG_RAWRSA)	+= swupdate_rsa_verify.o
 lib-$(CONFIG_SIGALG_RSAPSS)	+= swupdate_rsa_verify.o
 endif
 ifeq ($(CONFIG_SSL_IMPL_OPENSSL),y)
-# wolfSSL does not support CMS in the compatibility layer yet
 lib-$(CONFIG_SIGALG_CMS)	+= swupdate_cms_verify.o
 endif
+ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y)
+lib-$(CONFIG_SIGALG_CMS)	+= swupdate_pkcs7_verify.o
+endif
 ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y)
 lib-$(CONFIG_HASH_VERIFY)	+= verify_signature_mbedtls.o
 ifeq ($(CONFIG_PKCS11),y)
diff --git a/corelib/swupdate_cms_verify.c b/corelib/swupdate_cms_verify.c
index 2c0ba39..49e3548 100644
--- a/corelib/swupdate_cms_verify.c
+++ b/corelib/swupdate_cms_verify.c
@@ -22,6 +22,7 @@ 
 #define VERIFY_UNKNOWN_SIGNER_FLAGS (0)
 #endif
 
+#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE
 int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca)
 {
 	X509 *x = (X509 *)crt;
@@ -47,6 +48,7 @@  int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca)
 
 	return (ex_flags & EXFLAG_XKUSAGE) && (ex_xkusage & XKU_CODE_SIGN);
 }
+#endif
 
 static int cms_verify_callback(int ok, X509_STORE_CTX *ctx) {
 	int cert_error = X509_STORE_CTX_get_error(ctx);
diff --git a/corelib/swupdate_pkcs7_verify.c b/corelib/swupdate_pkcs7_verify.c
new file mode 100644
index 0000000..3263fcd
--- /dev/null
+++ b/corelib/swupdate_pkcs7_verify.c
@@ -0,0 +1,173 @@ 
+/*
+ * (C) Copyright 2019
+ * Stefano Babic, DENX Software Engineering, sbabic@denx.de.
+ * (C) Copyright 2023
+ * Bastian Germann
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ *
+ * Code mostly taken from openssl examples
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "swupdate.h"
+#include "sslapi.h"
+#include "util.h"
+#include "swupdate_verify_private.h"
+
+static int store_verify_callback(int ok, X509_STORE_CTX *ctx) {
+	int cert_error = X509_STORE_CTX_get_error(ctx);
+
+	if (!ok) {
+		switch (cert_error) {
+#if defined(CONFIG_CMS_IGNORE_EXPIRED_CERTIFICATE)
+		case X509_V_ERR_CERT_HAS_EXPIRED:
+		case X509_V_ERR_CERT_NOT_YET_VALID:
+			ok = 1;
+			break;
+#endif
+#if defined(CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE)
+		case X509_V_ERR_INVALID_PURPOSE:
+			ok = 1;
+			break;
+#endif
+		default:
+			break;
+		}
+	}
+
+	return ok;
+}
+
+X509_STORE *load_cert_chain(const char *file)
+{
+	X509_STORE *castore = X509_STORE_new();
+	if (!castore) {
+		return NULL;
+	}
+
+	/*
+	 * Set error callback function for verification of CRTs and CRLs in order
+	 * to ignore some errors depending on configuration
+	 */
+	X509_STORE_set_verify_cb(castore, store_verify_callback);
+
+	BIO *castore_bio = BIO_new_file(file, "r");
+	if (!castore_bio) {
+		TRACE("failed: BIO_new_file(%s)", file);
+		return NULL;
+	}
+
+	int crt_count = 0;
+	X509 *crt = NULL;
+	do {
+		crt = PEM_read_bio_X509(castore_bio, NULL, 0, NULL);
+		if (crt) {
+			crt_count++;
+			char *subj = X509_NAME_oneline(X509_get_subject_name(crt), NULL, 0);
+			char *issuer = X509_NAME_oneline(X509_get_issuer_name(crt), NULL, 0);
+			TRACE("Read PEM #%d: %s %s", crt_count, issuer, subj);
+			free(subj);
+			free(issuer);
+			if (X509_STORE_add_cert(castore, crt) == 0) {
+				TRACE("Adding certificate to X509_STORE failed");
+				BIO_free(castore_bio);
+				X509_STORE_free(castore);
+				return NULL;
+			}
+		}
+	} while (crt);
+	BIO_free(castore_bio);
+
+	if (crt_count == 0) {
+		X509_STORE_free(castore);
+		return NULL;
+	}
+
+	return castore;
+}
+
+static int check_signer_name(const char *name)
+{
+	// TODO work around wolfSSL's PKCS7_get0_signers always returning NULL
+	if (name)
+		WARN("The X.509 common name might not be equal to %s.", name);
+	return 0;
+}
+
+int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile,
+		const char *file, const char *signer_name)
+{
+	int status = -EFAULT;
+	WOLFSSL_PKCS7* pkcs7 =  (WOLFSSL_PKCS7 *)PKCS7_new();
+	BIO *bio_mem = NULL;
+	BIO *content_bio = NULL;
+
+	/* Open detached signature that needs to be checked */
+	BIO *sigfile_bio = BIO_new_file(sigfile, "rb");
+	if (!sigfile_bio) {
+		ERROR("%s cannot be opened", sigfile);
+		status = -EBADF;
+		goto out;
+	}
+
+	pkcs7->len = wolfSSL_BIO_get_len(sigfile_bio);
+	pkcs7->data = calloc(1, pkcs7->len);
+	BIO_read(sigfile_bio, pkcs7->data, pkcs7->len);
+	if (!pkcs7->data) {
+		ERROR("%s cannot be parsed as DER-encoded PKCS#7 signature blob", sigfile);
+		status = -EFAULT;
+		goto out;
+	}
+
+	/* Open the content file (data which was signed) */
+	content_bio = BIO_new_file(file, "rb");
+	if (!content_bio) {
+		ERROR("%s cannot be opened", file);
+		status = -EBADF;
+		goto out;
+	}
+	long content_len = wolfSSL_BIO_get_len(content_bio);
+	char *content = calloc(1, content_len);
+	BIO_read(content_bio, content, content_len);
+	bio_mem = BIO_new_mem_buf(content, content_len);
+
+	/* Then try to verify signature. The BIO* in parameter has to be a mem BIO.
+           See https://github.com/wolfSSL/wolfssl/issues/6174. */
+	if (!PKCS7_verify((PKCS7 *)pkcs7, NULL, dgst->certs, bio_mem,
+			NULL, PKCS7_BINARY)) {
+		ERR_print_errors_fp(stderr);
+		ERROR("Signature verification failed");
+		status = -EBADMSG;
+		goto out;
+	}
+
+	if (check_signer_name(signer_name)) {
+		ERROR("failed to verify signer name");
+		status = -EFAULT;
+		goto out;
+	}
+
+	TRACE("Verified OK");
+
+	/* Signature is valid */
+	status = 0;
+out:
+
+	if (pkcs7) {
+		PKCS7_free((PKCS7 *)pkcs7);
+	}
+	if (bio_mem) {
+		BIO_free(bio_mem);
+	}
+	if (content_bio) {
+		BIO_free(content_bio);
+	}
+	if (sigfile_bio) {
+		BIO_free(sigfile_bio);
+	}
+	return status;
+}
diff --git a/corelib/swupdate_verify_private.h b/corelib/swupdate_verify_private.h
index db2c4c7..cd3c7e5 100644
--- a/corelib/swupdate_verify_private.h
+++ b/corelib/swupdate_verify_private.h
@@ -16,7 +16,9 @@  EVP_PKEY *load_pubkey(const char *file);
 #endif
 
 #ifdef CONFIG_SIGALG_CMS
+#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE
 int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca);
+#endif
 X509_STORE *load_cert_chain(const char *file);
 #endif
 
diff --git a/corelib/verify_signature.c b/corelib/verify_signature.c
index d91a0bf..69989fe 100644
--- a/corelib/verify_signature.c
+++ b/corelib/verify_signature.c
@@ -153,6 +153,7 @@  int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile)
 		goto dgst_init_error;
 	}
 
+#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE
 	{
 		static char code_sign_name[] = "Code signing";
 		static char code_sign_sname[] = "codesign";
@@ -171,6 +172,8 @@  int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile)
 		ret = -EINVAL;
 		goto dgst_init_error;
 	}
+#endif
+
 #else
 	TRACE("public key / cert %s ignored, you need to set SIGALG", keyfile);
 #endif
diff --git a/include/sslapi.h b/include/sslapi.h
index accf3c4..50f6a96 100644
--- a/include/sslapi.h
+++ b/include/sslapi.h
@@ -38,6 +38,7 @@ 
 #include <openssl/hmac.h>
 #include <openssl/aes.h>
 #include <openssl/opensslv.h>
+#include <openssl/cms.h>
 #elif defined(CONFIG_SSL_IMPL_WOLFSSL)
 #include <wolfssl/options.h>
 #include <wolfssl/openssl/bio.h>
@@ -50,12 +51,12 @@ 
 #include <wolfssl/openssl/hmac.h>
 #include <wolfssl/openssl/aes.h>
 #include <wolfssl/openssl/opensslv.h>
+#include <wolfssl/openssl/pkcs7.h>
 #endif
 
 #if defined(CONFIG_SSL_IMPL_OPENSSL) || defined(CONFIG_SSL_IMPL_WOLFSSL)
 
 #ifdef CONFIG_SIGALG_CMS
-#include <openssl/cms.h>
 
 static inline uint32_t SSL_X509_get_extension_flags(X509 *x)
 {