diff mbox series

[v3] gpg: add optional gpg signing verification

Message ID 20231010180212.1369429-1-amy.fong@siemens.com
State Accepted
Delegated to: Stefano Babic
Headers show
Series [v3] gpg: add optional gpg signing verification | expand

Commit Message

amy.fong.3142@gmail.com Oct. 10, 2023, 6:02 p.m. UTC
From: Amy Fong <amy.fong@siemens.com>

- To enable, configure with CONFIG_SIGALG_GPG
- Runtime configuration requirements:
  - gpg home directory: gpg-home-dir in swupdate.cfg
  - gpgme signing protocol: gpgme-protocol in swupdate.cfg (supported openpgp or cms)

Signed-off-by: Amy Fong <amy.fong@siemens.com>
---
 Kconfig                       |   3 +-
 Makefile.flags                |   4 +
 core/swupdate.c               |  23 ++++-
 corelib/Makefile              |   1 +
 corelib/swupdate_gpg_verify.c | 171 ++++++++++++++++++++++++++++++++++
 corelib/verify_signature.c    |   4 +
 doc/source/signed_images.rst  |  78 +++++++++++++++-
 include/sslapi.h              |   5 +
 include/swupdate.h            |   2 +
 9 files changed, 284 insertions(+), 7 deletions(-)
 create mode 100644 corelib/swupdate_gpg_verify.c
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index 636c4ac..bd4ce04 100644
--- a/Kconfig
+++ b/Kconfig
@@ -430,7 +430,6 @@  choice
 	config SSL_IMPL_MBEDTLS
 		bool "mbedTLS"
 		depends on HAVE_MBEDTLS
-
 endchoice
 
 
@@ -477,6 +476,8 @@  choice
 		bool "Cryptographic Message Syntax (CMS) / PKCS#7"
 		depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL
 
+	config SIGALG_GPG
+		bool "GPG signing"
 endchoice
 
 menu "CMS / PKCS#7 signature verification options"
diff --git a/Makefile.flags b/Makefile.flags
index 2d27a8f..6de9303 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -305,3 +305,7 @@  endif
 # (we stole scripts/checkstack.pl from the kernel... thanks guys!)
 # Reduced from 20k to 16k in 1.9.0.
 FLTFLAGS += -s 16000
+
+ifeq ($(CONFIG_SIGALG_GPG),y)
+LDLIBS += gpgme
+endif
diff --git a/core/swupdate.c b/core/swupdate.c
index 6b536b8..7c9c72b 100644
--- a/core/swupdate.c
+++ b/core/swupdate.c
@@ -150,6 +150,7 @@  static void usage(char *programname)
 		" -l, --loglevel <level>         : logging level\n"
 		" -L, --syslog                   : enable syslog logger\n"
 #ifdef CONFIG_SIGNED_IMAGES
+#ifndef CONFIG_SIGALG_GPG
 		" -k, --key <public key file>    : file with public key to verify images\n"
 		"     --cert-purpose <purpose>   : set expected certificate purpose\n"
 		"                                  [emailProtection|codeSigning] (default: emailProtection)\n"
@@ -158,6 +159,7 @@  static void usage(char *programname)
 #endif
 		"     --ca-path                  : path to the Certificate Authority (PEM)\n"
 #endif
+#endif
 #ifdef CONFIG_ENCRYPTED_IMAGES
 		" -K, --key-aes <key file>       : the file contains the symmetric key to be used\n"
 		"                                  to decrypt images\n"
@@ -338,6 +340,10 @@  static int read_globals_settings(void *elem, void *data)
 
 	char software_select[SWUPDATE_GENERAL_STRING_SIZE] = "";
 	GET_FIELD_STRING(LIBCFG_PARSER, elem, "select", software_select);
+	GET_FIELD_STRING(LIBCFG_PARSER, elem,
+				"gpg-home-dir", sw->gpg_home_directory);
+	GET_FIELD_STRING(LIBCFG_PARSER, elem,
+				"gpgme-protocol", sw->gpgme_protocol);
 	if (software_select[0] != '\0') {
 		/* by convention, errors in a configuration section are ignored */
 		(void)parse_image_selector(software_select, sw);
@@ -470,9 +476,11 @@  int main(int argc, char **argv)
 	strcat(main_options, "H:");
 #endif
 #ifdef CONFIG_SIGNED_IMAGES
+#ifndef CONFIG_SIGALG_GPG
 	strcat(main_options, "k:");
 	public_key_mandatory = 1;
 #endif
+#endif
 #ifdef CONFIG_ENCRYPTED_IMAGES
 	strcat(main_options, "K:");
 #endif
@@ -746,6 +754,19 @@  int main(int argc, char **argv)
 		exit(EXIT_FAILURE);
 	}
 
+#ifdef CONFIG_SIGALG_GPG
+	if (!strlen(swcfg.gpg_home_directory)) {
+		fprintf(stderr,
+			 "Error: SWUpdate is built for signed images, provide a GnuPG home directory.\n");
+		exit(EXIT_FAILURE);
+	}
+	if (!strlen(swcfg.gpgme_protocol)) {
+		fprintf(stderr,
+			"Error: SWUpdate is built for signed images, please specify GnuPG protocol.\n");
+		exit(EXIT_FAILURE);
+	}
+#endif
+
 	if (opt_c && !opt_i) {
 		fprintf(stderr,
 			"Error: Checking local images requires -i <file>.\n");
@@ -767,7 +788,7 @@  int main(int argc, char **argv)
 
 	swupdate_crypto_init();
 
-	if (strlen(swcfg.publickeyfname)) {
+	if (strlen(swcfg.publickeyfname) || strlen(swcfg.gpg_home_directory)) {
 		if (swupdate_dgst_init(&swcfg, swcfg.publickeyfname)) {
 			fprintf(stderr,
 				 "Error: Crypto cannot be initialized.\n");
diff --git a/corelib/Makefile b/corelib/Makefile
index 5f6f8e9..f5dda73 100644
--- a/corelib/Makefile
+++ b/corelib/Makefile
@@ -32,6 +32,7 @@  endif
 lib-$(CONFIG_SIGALG_RAWRSA)	+= swupdate_rsa_verify_mbedtls.o
 lib-$(CONFIG_SIGALG_RSAPSS)	+= swupdate_rsa_verify_mbedtls.o
 endif
+lib-$(CONFIG_SIGALG_GPG)	+= swupdate_gpg_verify.o
 lib-$(CONFIG_LIBCONFIG)		+= swupdate_settings.o \
 				   parsing_library_libconfig.o
 lib-$(CONFIG_JSON)		+= parsing_library_libjson.o server_utils.o
diff --git a/corelib/swupdate_gpg_verify.c b/corelib/swupdate_gpg_verify.c
new file mode 100644
index 0000000..8a00f20
--- /dev/null
+++ b/corelib/swupdate_gpg_verify.c
@@ -0,0 +1,171 @@ 
+/*
+ * Author: Amy Fong
+ * Copyright (C) 2023, Siemens AG
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "swupdate.h"
+#include "sslapi.h"
+#include "util.h"
+
+#include <errno.h>
+#include <locale.h>
+#include <gpgme.h>
+
+static gpg_error_t
+status_cb(void *opaque, const char *keyword, const char *value)
+{
+	(void)opaque;
+	DEBUG("status_cb: %s %s", keyword, value);
+	return 0;
+}
+
+#define MSGBUF_LEN 256
+
+int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile,
+                const char *file, const char *signer_name)
+{
+	gpgme_ctx_t ctx;
+	gpgme_error_t err;
+	gpgme_data_t image_sig, image;
+	FILE *fp_sig = NULL;
+	FILE *fp = NULL;
+	gpgme_signature_t sig;
+	int status = 0;
+	gpgme_protocol_t protocol;
+	gpgme_verify_result_t result;
+	char msg[MSGBUF_LEN];
+	int r;
+	const char *tmp;
+
+	tmp = gpgme_check_version(NULL);
+	if (tmp == NULL) {
+		ERROR("Failed to check gpgme library version");
+		status = -EFAULT;
+		goto out;
+	}
+
+	err = gpgme_new(&ctx);
+	if (err) {
+		ERROR("Failed to create new gpg context");
+		r = gpgme_strerror_r(err, msg, MSGBUF_LEN);
+		if (r == 0) {
+			ERROR("Reason: %s", msg);
+		}
+		status = -EFAULT;
+		goto out;
+	}
+
+	if (dgst->gpgme_protocol != NULL) {
+		DEBUG("gpg: Enabling protocol %s", dgst->gpgme_protocol);
+		if (!strcmp(dgst->gpgme_protocol, "openpgp")) {
+			TRACE("gpg: using protocol OpenPGP");
+			protocol = GPGME_PROTOCOL_OpenPGP;
+		} else if (!strcmp(dgst->gpgme_protocol, "cms")) {
+			TRACE("gpg: using protocol cms");
+			protocol = GPGME_PROTOCOL_CMS;
+		} else {
+			ERROR("gpg: unsupported protocol! %s", dgst->gpgme_protocol);
+			status = -EFAULT;
+			goto out;
+		}
+	} else {
+		ERROR("gpg protocol unspecified!");
+		status = -EFAULT;
+		goto out;
+	}
+
+	gpgme_set_protocol(ctx, protocol);
+	gpgme_set_status_cb(ctx, status_cb, NULL);
+	if (dgst->verbose == 1) {
+		gpgme_set_ctx_flag(ctx, "full-status", "1");
+	}
+	gpgme_set_locale(ctx, LC_ALL, setlocale(LC_ALL, ""));
+
+	if (dgst->gpg_home_directory != NULL) {
+		err = gpgme_ctx_set_engine_info(ctx, protocol, NULL, dgst->gpg_home_directory);
+		if (err) {
+			ERROR("Something went wrong while setting the engine info");
+			r = gpgme_strerror_r(err, msg, MSGBUF_LEN);
+			if (r == 0) {
+				ERROR("Reason: %s", msg);
+			}
+			status = -EFAULT;
+			goto out;
+		}
+	}
+
+	fp_sig = fopen(sigfile, "rb");
+	if (!fp_sig) {
+		ERROR("Failed to open %s", sigfile);
+		status = -EBADF;
+		goto out;
+	}
+	err = gpgme_data_new_from_stream(&image_sig, fp_sig);
+	if (err) {
+		ERROR("error allocating data object");
+		r = gpgme_strerror_r(err, msg, MSGBUF_LEN);
+		if (r == 0) {
+			ERROR("Reason: %s", msg);
+		}
+		status = -ENOMEM;
+		goto out;
+	}
+
+	fp = fopen(file, "rb");
+	if (!fp) {
+		ERROR("Failed to open %s", file);
+		status = -EBADF;
+		goto out;
+	}
+	err = gpgme_data_new_from_stream(&image, fp);
+	if (err) {
+		ERROR("error allocating data object");
+		r = gpgme_strerror_r(err, msg, MSGBUF_LEN);
+		if (r == 0) {
+			ERROR("Reason: %s", msg);
+		}
+		status = -ENOMEM;
+		goto out;
+	}
+
+	err = gpgme_op_verify(ctx, image_sig, image, NULL);
+	result = gpgme_op_verify_result(ctx);
+	if (err) {
+		ERROR("verify failed");
+		r = gpgme_strerror_r(err, msg, MSGBUF_LEN);
+		if (r == 0) {
+			ERROR("Reason: %s", msg);
+		}
+		status = -EBADMSG;
+		goto out;
+	}
+
+	if (result) {
+		for (sig = result->signatures; sig; sig = sig->next) {
+			if (sig->status == GPG_ERR_NO_ERROR) {
+				TRACE("Verified OK");
+				status = 0;
+				goto out;
+			}
+		}
+	}
+	TRACE("Verification failed");
+	status = -EBADMSG;
+
+ out:
+	gpgme_data_release(image);
+	gpgme_data_release(image_sig);
+	gpgme_release(ctx);
+
+	if (fp)
+		fclose(fp);
+	if (fp_sig)
+		fclose(fp_sig);
+
+	return status;
+}
diff --git a/corelib/verify_signature.c b/corelib/verify_signature.c
index 69989fe..227b3b5 100644
--- a/corelib/verify_signature.c
+++ b/corelib/verify_signature.c
@@ -174,6 +174,10 @@  int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile)
 	}
 #endif
 
+#elif defined(CONFIG_SIGALG_GPG)
+	dgst->gpg_home_directory = sw->gpg_home_directory;
+	dgst->gpgme_protocol = sw->gpgme_protocol;
+	dgst->verbose = sw->verbose;
 #else
 	TRACE("public key / cert %s ignored, you need to set SIGALG", keyfile);
 #endif
diff --git a/doc/source/signed_images.rst b/doc/source/signed_images.rst
index 581196d..b17aa3a 100644
--- a/doc/source/signed_images.rst
+++ b/doc/source/signed_images.rst
@@ -53,16 +53,20 @@  selected via menuconfig. Currently, the following mechanisms are implemented:
 - RSA Public / private key. The private key belongs to the build system,
   while the public key must be installed on the target.
 - CMS using certificates
+- GPG key signing
 
-Key or certificate is passed to SWUpdate with the `-k` parameter.
+For RSA and CMS algorithms, key or certificate is passed to SWUpdate
+with the `-k` parameter.
 
 Tool to generate keys / certificates
 ------------------------------------
 
-The `openssl` tool is used to generate the keys. This is part of the
-OpenSSL project. A complete documentation can be found at
+For RSA and CMS signing, the `openssl` tool is used to generate the keys.
+This is part of the OpenSSL project. A complete documentation can be found at
 the `openSSL Website <https://www.openssl.org/docs/manmaster/man1/openssl.html>`_.
 
+For GPG, `gpg` can be used to generate the keys and to sign the images. A complete
+documentation can be found at the `GnuPG Website <https://www.gnupg.org>`_.
 
 Usage with RSA PKCS#1.5 or RSA PSS
 ----------------------------------
@@ -158,6 +162,42 @@  Signing the image is simple as in the previous case:
                 -inkey mycert.key.pem -outform DER -nosmimecap -binary
 
 
+Usage with GNU PG
+-----------------
+
+Generating a new keypair
+........................
+
+
+First, a primary keypair needs to be generated
+
+::
+	gpg --gen-key
+
+The generated keys can be listed as follows
+
+::
+	gpg -k
+
+Check the documentation for more information about parameters.
+
+How to sign with gpg
+.....................
+
+Signing the image is very simple:
+
+::
+	gpg --batch --output sw-description.sig
+		--detach-sig sw-description
+
+For an alternative GnuPG home directory, and if there are multiple keypairs,
+the following can be used to specify. In this example, the GnuPG home directory
+is in GPG_HOMEDIR, while the signing key is found in GPG_KEY.
+
+::
+	gpg --batch --homedir "${GPG_HOMEDIR}" --default-key "${GPG_KEY}" --output sw-description.sig
+		--detach-sig sw-description
+
 Building a signed SWU image
 ---------------------------
 
@@ -187,6 +227,9 @@  A simple script to create a signed image can be:
 	elif if [ x"$MODE" == "xRSA-PSS" ]; then
 	    openssl dgst -sha256 -sign priv.pem -sigopt rsa_padding_mode:pss \
 	        -sigopt rsa_pss_saltlen:-2 sw-description > sw-description.sig
+	elif if [ x"$MODE" == "xGPG" ]; then
+            gpg --batch --homedir "${GPG_HOME_DIR}" --default-key "${GPG_KEY}" \
+                --output sw-description.sig --detach-sig sw-description
         else
             openssl cms -sign -in  sw-description -out sw-description.sig -signer mycert.cert.pem \
                 -inkey mycert.key.pem -outform DER -nosmimecap -binary
@@ -233,5 +276,30 @@  Running SWUpdate with signed images
 
 Verification is activated by setting CONFIG_SIGNED_IMAGES in SWUpdate's configuration.
 If activated, SWUpdate will always check the compound image. For security reasons,
-it is not possible to disable the check at runtime. The -k parameter (public key file)
-is mandatory and the program stops if the public key is not passed.
+it is not possible to disable the check at runtime.
+
+For RSA and CMS signing, the -k parameter (public key file) is mandatory and the program stops 
+if the public key is not passed.
+
+For GPG signing, CONFIG_SIGALG_GPG needs to be enabled. The GPG key will
+need to be imported to the device's GnuPG home directory. To do this, the
+key will need to be exported:
+
+::
+        gpg --export <keyid> --output <public key>
+
+You can then copy it onto the device and import it into your public keyring:
+
+::
+        gpg --import <public key>
+
+To verify that the key has been imported successfully:
+
+::
+        gpg --list-keys
+
+SWUpdate will need need to be configured with the following parameters:
+
+::
+        GnuPG Home directory: gpg-home-dir in swupdate.cfg
+        GPGME Protocol: gpgme-protocol in swupdate.cfg: openpgp or cms
diff --git a/include/sslapi.h b/include/sslapi.h
index a24a152..4668fe7 100644
--- a/include/sslapi.h
+++ b/include/sslapi.h
@@ -156,6 +156,11 @@  struct swupdate_digest {
 #elif defined(CONFIG_ENCRYPTED_IMAGES)
 	mbedtls_cipher_context_t mbedtls_cipher_context;
 #endif /* CONFIG_PKCS11 */
+#ifdef CONFIG_SIGALG_GPG
+	char *gpg_home_directory;
+	int verbose
+	char *gpgme_protocol;
+#endif
 };
 
 #else /* CONFIG_SSL_IMPL */
diff --git a/include/swupdate.h b/include/swupdate.h
index e473e41..83d61c6 100644
--- a/include/swupdate.h
+++ b/include/swupdate.h
@@ -93,6 +93,8 @@  struct swupdate_cfg {
 	void *dgst;	/* Structure for signed images */
 	struct swupdate_parms parms;
 	const char *embscript;
+	char gpg_home_directory[SWUPDATE_GENERAL_STRING_SIZE];
+	char gpgme_protocol[SWUPDATE_GENERAL_STRING_SIZE];
 };
 
 #define SEARCH_FILE(img, list, found, offs) do { \