@@ -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"
@@ -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
@@ -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");
@@ -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
new file mode 100644
@@ -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;
+}
@@ -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
@@ -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
@@ -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 */
@@ -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 { \