diff mbox series

[1/3] Add support for asymmetric decryption

Message ID 20231126132417.107606-1-Michael.Glembotzki@iris-sensing.com
State Changes Requested
Headers show
Series [1/3] Add support for asymmetric decryption | expand

Commit Message

Michael Glembotzki Nov. 26, 2023, 1:24 p.m. UTC
This feature enables asymmetric image decryption. The asymmetrically encrypted
AES file is decrypted and loaded from a Cryptographic Message Syntax file
(DER). The AES file can be encrypted for any number of recipient devices.

Signed-off-by: Michael Glembotzki <Michael.Glembotzki@iris-sensing.com>
---
 Kconfig                        |  12 ++++
 core/installer.c               |  12 ++++
 core/stream_interface.c        |  70 ++++++++++++++++++++-
 core/swupdate.c                |  44 +++++++++++--
 corelib/Makefile               |   3 +
 corelib/swupdate_cms_decrypt.c | 112 +++++++++++++++++++++++++++++++++
 include/parsers.h              |   9 +++
 include/sslapi.h               |   9 +++
 include/swupdate.h             |   1 +
 9 files changed, 267 insertions(+), 5 deletions(-)
 create mode 100644 corelib/swupdate_cms_decrypt.c

Comments

Stefano Babic Nov. 27, 2023, 8:45 a.m. UTC | #1
Hi Michael,

On 26.11.23 14:24, Michael Glembotzki wrote:
> This feature enables asymmetric image decryption. The asymmetrically encrypted
> AES file is decrypted and loaded from a Cryptographic Message Syntax file
> (DER). The AES file can be encrypted for any number of recipient devices.
> 

This feature is clear after reading thwe code but not so much by reading 
the patch for the documentation. This should be enhanced.

I think the use case should be added, and better explained as:

- all artifaczts are encrypted with the same key
- the way to exchange the key is together with a new software (is this 
what you intent with "rotate" ?)
- if one device is compromised, it can get even later releases an obtain 
the new keys.

And this feature will add a revocation for a single device.

Nevertheless, I disagree with this implementation. This changes the 
structure of a SWU just to add a file that just contains a key. I will 
suggest another way.

We have already support for encrypted sw-description 
(CONFIG_ENCRYPTED_SW_DESCRIPTION). Currently, it is encrypted with the 
symmetric key, too.

If sw-description will be encrypted with asymmetric key, the key can 
simply added as new attribute in the global section, like
	aes-key = .....
	ivt = ....

This makes the structure compatible with the past because stil 
lsw-description remains the first file in the SWU. Size of 
sw-description (as size of the aes-key) will increase with the amount of 
devices, like the size of the aes-key file here.

What do you think ?

Best regards,
Stefano Babic


> Signed-off-by: Michael Glembotzki <Michael.Glembotzki@iris-sensing.com>
> ---
>   Kconfig                        |  12 ++++
>   core/installer.c               |  12 ++++
>   core/stream_interface.c        |  70 ++++++++++++++++++++-
>   core/swupdate.c                |  44 +++++++++++--
>   corelib/Makefile               |   3 +
>   corelib/swupdate_cms_decrypt.c | 112 +++++++++++++++++++++++++++++++++
>   include/parsers.h              |   9 +++
>   include/sslapi.h               |   9 +++
>   include/swupdate.h             |   1 +
>   9 files changed, 267 insertions(+), 5 deletions(-)
>   create mode 100644 corelib/swupdate_cms_decrypt.c
> 
> diff --git a/Kconfig b/Kconfig
> index 2ae2e4b..a0ae5db 100644
> --- a/Kconfig
> +++ b/Kconfig
> @@ -499,6 +499,18 @@ config ENCRYPTED_IMAGES
>   comment "Image encryption needs an SSL implementation"
>   	depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS
>   
> +config ASYM_ENCRYPTED_AESFILE
> +	bool "Enable asymmetrically encrypted AES file with CMS / PKCS#7"
> +	default n
> +	depends on ENCRYPTED_IMAGES && SSL_IMPL_OPENSSL
> +	help
> +	  This option enables asymmetric image decryption. The asymmetrically
> +	  encrypted AES file is decrypted and loaded from a Cryptographic Message
> +	  Syntax file (DER). The AES file can be encrypted for any number of
> +	  recipient devices. For security reasons the AES file should be rotated
> +	  regularly and the feature should be used together with signature
> +	  verification: SIGNED_IMAGES.
> +
>   config ENCRYPTED_SW_DESCRIPTION
>   	bool "Even sw-description is encrypted"
>   	depends on ENCRYPTED_IMAGES
> diff --git a/core/installer.c b/core/installer.c
> index 20b5b51..7349779 100644
> --- a/core/installer.c
> +++ b/core/installer.c
> @@ -497,6 +497,18 @@ void cleanup_files(struct swupdate_cfg *software) {
>   		free(fn);
>   	}
>   #endif
> +
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	if (asprintf(&fn, "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME) != ENOMEM_ASPRINTF) {
> +		remove_sw_file(fn);
> +		free(fn);
> +	}
> +	if (asprintf(&fn, "%s%s", TMPDIR, AES_FILENAME) != ENOMEM_ASPRINTF) {
> +		remove_sw_file(fn);
> +		free(fn);
> +	}
> +#endif
> +
>   }
>   
>   int preupdatecmd(struct swupdate_cfg *swcfg)
> diff --git a/core/stream_interface.c b/core/stream_interface.c
> index 0b78329..25f9457 100644
> --- a/core/stream_interface.c
> +++ b/core/stream_interface.c
> @@ -45,11 +45,13 @@
>   #include "state.h"
>   #include "bootloader.h"
>   #include "hw-compatibility.h"
> +#include "sslapi.h"
>   
>   #define BUFF_SIZE	 4096
>   #define PERCENT_LB_INDEX	4
>   
>   enum {
> +	STREAM_WAIT_ENC_AESFILE,
>   	STREAM_WAIT_DESCRIPTION,
>   	STREAM_WAIT_SIGNATURE,
>   	STREAM_DATA,
> @@ -85,7 +87,7 @@ static int extract_file_to_tmp(int fd, const char *fname, unsigned long *poffs,
>   		return -1;
>   	}
>   	if (strcmp(fdh.filename, fname)) {
> -		TRACE("description file name not the first of the list: %s instead of %s",
> +		TRACE("file: %s instead of %s next element in the list",
>   			fdh.filename,
>   			fname);
>   		return -1;
> @@ -148,6 +150,11 @@ static int extract_files(int fd, struct swupdate_cfg *software)
>   	bool installed_directly = false;
>   	bool encrypted_sw_desc = false;
>   
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	char aes_file[MAX_IMAGE_FNAME];
> +	status = STREAM_WAIT_ENC_AESFILE;
> +#endif
> +
>   #ifdef CONFIG_ENCRYPTED_SW_DESCRIPTION
>   	encrypted_sw_desc = true;
>   #endif
> @@ -164,6 +171,28 @@ static int extract_files(int fd, struct swupdate_cfg *software)
>   	for (;;) {
>   		switch (status) {
>   		/* Waiting for the first Header */
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +		case STREAM_WAIT_ENC_AESFILE:
> +			if (extract_file_to_tmp(fd, ENCRYPTED_AES_FILENAME, &offset, false) < 0) {
> +				return -1;
> +			}
> +
> +			snprintf(output_file, sizeof(output_file), "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME);
> +			snprintf(aes_file, sizeof(aes_file), "%s%s", TMPDIR, AES_FILENAME);
> +			if (swupdate_decrypt_file(software->dgst, output_file, aes_file)) {
> +				ERROR("Decrypting AES file");
> +				return -1;
> +			}
> +
> +			if (load_decryption_key(aes_file)) {
> +				ERROR("Key file does not contain a valid AES key");
> +				return -1;
> +			}
> +
> +			status = STREAM_WAIT_DESCRIPTION;
> +			break;
> +#endif
> +
>   		case STREAM_WAIT_DESCRIPTION:
>   			if (extract_file_to_tmp(fd, SW_DESCRIPTION_FILENAME, &offset, encrypted_sw_desc) < 0 )
>   				return -1;
> @@ -422,6 +451,7 @@ static int save_stream(int fdin, struct swupdate_cfg *software)
>   		goto no_copy_output;
>   	}
>   
> +#ifndef CONFIG_ASYM_ENCRYPTED_AESFILE
>   	/*
>   	 * Make an estimation for sw-description and signature.
>   	 * Signature cannot be very big - if it is, it is an attack.
> @@ -430,6 +460,14 @@ static int save_stream(int fdin, struct swupdate_cfg *software)
>   	 */
>   	tmpsize = SWUPDATE_ALIGN(fdh.size + fdh.namesize + sizeof(struct new_ascii_header) + bufsize - len,
>   			bufsize);
> +#else
> +	/*
> +	 * tmpsize has enough space for the encrypted-aesfile, sw-description and
> +	 * sw-description.sig
> +	 */
> +	tmpsize = SWUPDATE_ALIGN(fdh.size + fdh.namesize + sizeof(struct new_ascii_header) + 2 * bufsize - len,
> +			bufsize);
> +#endif
>   	ret = copy_write(&tmpfd, buf, len);  /* copy the first buffer */
>   	if (ret < 0) {
>   		ret =  -EIO;
> @@ -447,6 +485,28 @@ static int save_stream(int fdin, struct swupdate_cfg *software)
>   	lseek(tmpfd, 0, SEEK_SET);
>   	offset = 0;
>   
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	if (extract_file_to_tmp(tmpfd, ENCRYPTED_AES_FILENAME, &offset, false) < 0) {
> +		ret = -EINVAL;
> +		goto no_copy_output;
> +	}
> +
> +	snprintf(output_file, sizeof(output_file), "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME);
> +	char aes_file[MAX_IMAGE_FNAME];
> +	snprintf(aes_file, sizeof(aes_file), "%s%s", TMPDIR, AES_FILENAME);
> +	if (swupdate_decrypt_file(software->dgst, output_file, aes_file)) {
> +		ERROR("Decrypting AES Key");
> +		ret = -1;
> +		goto no_copy_output;
> +	}
> +
> +	if (load_decryption_key(aes_file)) {
> +		ERROR("Key file does not contain a valid AES key");
> +		ret = -1;
> +		goto no_copy_output;
> +	}
> +#endif
> +
>   	if (extract_file_to_tmp(tmpfd, SW_DESCRIPTION_FILENAME, &offset, encrypted_sw_desc) < 0) {
>   		ERROR("%s cannot be extracted", SW_DESCRIPTION_FILENAME);
>   		ret = -EINVAL;
> @@ -507,6 +567,10 @@ no_copy_output:
>   
>   	cleanup_files(software);
>   
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	clear_aes_key();
> +#endif
> +
>   	return ret;
>   }
>   
> @@ -703,6 +767,10 @@ void *network_initializer(void *data)
>   		/* release temp files we may have created */
>   		cleanup_files(software);
>   
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +		clear_aes_key();
> +#endif
> +
>   #ifndef CONFIG_NOCLEANUP
>   		swupdate_remove_directory(SCRIPTS_DIR_SUFFIX);
>   		swupdate_remove_directory(DATADST_DIR_SUFFIX);
> diff --git a/core/swupdate.c b/core/swupdate.c
> index 6f9938e..9532d0a 100644
> --- a/core/swupdate.c
> +++ b/core/swupdate.c
> @@ -101,8 +101,11 @@ static struct option long_options[] = {
>   	{"forced-signer-name", required_argument, NULL, '2'},
>   #endif
>   #endif
> -#ifdef CONFIG_ENCRYPTED_IMAGES
> +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
>   	{"key-aes", required_argument, NULL, 'K'},
> +#endif
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	{"recip-keypair", required_argument, NULL, 'r'},
>   #endif
>   	{"loglevel", required_argument, NULL, 'l'},
>   	{"max-version", required_argument, NULL, '3'},
> @@ -162,9 +165,12 @@ static void usage(char *programname)
>   		"     --ca-path                  : path to the Certificate Authority (PEM)\n"
>   #endif
>   #endif
> -#ifdef CONFIG_ENCRYPTED_IMAGES
> +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
>   		" -K, --key-aes <key file>       : the file contains the symmetric key to be used\n"
>   		"                                  to decrypt images\n"
> +#endif
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +		" -r, --recip-keypair <key file> : path to the recipient keypair (PEM)\n"
>   #endif
>   		" -n, --dry-run                  : run SWUpdate without installing the software\n"
>   		" -N, --no-downgrading <version> : not install a release older as <version>\n"
> @@ -310,8 +316,14 @@ static int read_globals_settings(void *elem, void *data)
>   				"public-key-file", sw->publickeyfname);
>   	GET_FIELD_STRING(LIBCFG_PARSER, elem,
>   				"ca-path", sw->publickeyfname);
> +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
>   	GET_FIELD_STRING(LIBCFG_PARSER, elem,
>   				"aes-key-file", sw->aeskeyfname);
> +#endif
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	GET_FIELD_STRING(LIBCFG_PARSER, elem,
> +				"recip-keypair", sw->recipkeypairfname);
> +#endif
>   	GET_FIELD_STRING(LIBCFG_PARSER, elem,
>   				"mtd-blacklist", sw->mtdblacklist);
>   	GET_FIELD_STRING(LIBCFG_PARSER, elem,
> @@ -497,9 +509,12 @@ int main(int argc, char **argv)
>   	public_key_mandatory = 1;
>   #endif
>   #endif
> -#ifdef CONFIG_ENCRYPTED_IMAGES
> +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
>   	strcat(main_options, "K:");
>   #endif
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	strcat(main_options, "r:");
> +#endif
>   
>   	memset(fname, 0, sizeof(fname));
>   
> @@ -656,12 +671,19 @@ int main(int argc, char **argv)
>   			strlcpy(swcfg.maximum_version, optarg,
>   				sizeof(swcfg.maximum_version));
>   			break;
> -#ifdef CONFIG_ENCRYPTED_IMAGES
> +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
>   		case 'K':
>   			strlcpy(swcfg.aeskeyfname,
>   				optarg,
>   			       	sizeof(swcfg.aeskeyfname));
>   			break;
> +#endif
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +		case 'r':
> +			strlcpy(swcfg.recipkeypairfname,
> +				optarg,
> +				sizeof(swcfg.recipkeypairfname));
> +			break;
>   #endif
>   		case 'N':
>   			swcfg.no_downgrading = true;
> @@ -842,6 +864,19 @@ int main(int argc, char **argv)
>   		mtd_set_ubiblacklist(swcfg.mtdblacklist);
>   #endif
>   
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	if (strlen(swcfg.recipkeypairfname)) {
> +		if (swupdate_dgst_add_recipient_keypair(&swcfg, swcfg.recipkeypairfname)) {
> +			fprintf(stderr,
> +				"Error: Recipient keypair cannot be initialized.\n");
> +			exit(EXIT_FAILURE);
> +		}
> +	} else {
> +		fprintf(stderr,
> +			 "Error: SWUpdate is built for asym encrypted images, provide a recipient key pair.\n");
> +		exit(EXIT_FAILURE);
> +	}
> +#else
>   	/*
>   	 * If an AES key is passed, load it to allow
>   	 * to decrypt images
> @@ -853,6 +888,7 @@ int main(int argc, char **argv)
>   			exit(EXIT_FAILURE);
>   		}
>   	}
> +#endif
>   
>   	lua_handlers_init();
>   
> diff --git a/corelib/Makefile b/corelib/Makefile
> index c9ca4aa..36d32ec 100644
> --- a/corelib/Makefile
> +++ b/corelib/Makefile
> @@ -18,6 +18,9 @@ endif
>   lib-$(CONFIG_SIGALG_RAWRSA)	+= swupdate_rsa_verify.o
>   lib-$(CONFIG_SIGALG_RSAPSS)	+= swupdate_rsa_verify.o
>   endif
> +ifeq ($(CONFIG_ASYM_ENCRYPTED_AESFILE),y)
> +lib-$(CONFIG_ENCRYPTED_IMAGES)	+= swupdate_cms_decrypt.o
> +endif
>   ifeq ($(CONFIG_SSL_IMPL_OPENSSL),y)
>   lib-$(CONFIG_SIGALG_CMS)	+= swupdate_cms_verify.o
>   endif
> diff --git a/corelib/swupdate_cms_decrypt.c b/corelib/swupdate_cms_decrypt.c
> new file mode 100644
> index 0000000..5af2508
> --- /dev/null
> +++ b/corelib/swupdate_cms_decrypt.c
> @@ -0,0 +1,112 @@
> +/*
> + * (C) Copyright 2023
> + * Michael Glembotzki, iris-GmbH infrared & intelligent sensors, michael.glembotzki@iris-sensing.com.
> + *
> + * SPDX-License-Identifier:     GPL-2.0-only
> + *
> + * Code mostly taken from openssl examples
> + */
> +#include <sys/stat.h>
> +#include "swupdate.h"
> +#include "sslapi.h"
> +#include "util.h"
> +
> +int swupdate_dgst_add_recipient_keypair(struct swupdate_cfg *sw, const char *keypair_file) {
> +	X509 *rcert = NULL;
> +	EVP_PKEY *rkey = NULL;
> +	struct swupdate_digest *dgst = sw->dgst;
> +	int ret = 0;
> +
> +	if (!dgst) {
> +		dgst = calloc(1, sizeof(*dgst));
> +		if (!dgst) {
> +			ret = 1;
> +			goto err;
> +		}
> +	}
> +
> +	BIO *tbio = BIO_new_file(keypair_file, "r");
> +	if (!tbio) {
> +		ERROR("%s cannot be opened", keypair_file);
> +		ret = 1;
> +		goto err;
> +	}
> +
> +	rcert = PEM_read_bio_X509(tbio, NULL, 0, NULL);
> +	if (!rcert) {
> +		WARN("Recipient cert not found");
> +	}
> +	BIO_reset(tbio);
> +
> +	rkey = PEM_read_bio_PrivateKey(tbio, NULL, 0, NULL);
> +	BIO_free(tbio);
> +	if (!rkey) {
> +		ERROR("Recipient private key not found");
> +		ret = 1;
> +		goto err;
> +	}
> +
> +	dgst->rcert = rcert;
> +	dgst->rkey = rkey;
> +
> +	return ret;
> +
> +err:
> +	if (dgst) {
> +		free(dgst);
> +	}
> +	return ret;
> +}
> +
> +int swupdate_decrypt_file(struct swupdate_digest *dgst, const char *infile, const char *outfile) {
> +	BIO *in = NULL, *out = NULL;
> +	CMS_ContentInfo *cms = NULL;
> +	int ret = 0;
> +
> +	if (!dgst || !infile || !outfile) {
> +		return 1;
> +	}
> +
> +	/* Open CMS message to decrypt */
> +	in = BIO_new_file(infile, "rb");
> +	if (!in) {
> +		ERROR("%s cannot be opened", infile);
> +		ret = 1;
> +		goto err;
> +	}
> +
> +	/* Parse message */
> +	cms = d2i_CMS_bio(in, NULL);
> +	if (!cms) {
> +		ERROR("%s cannot be parsed as DER-encoded CMS blob", infile);
> +		ret = 1;
> +		goto err;
> +	}
> +
> +	out = BIO_new_file(outfile, "wb");
> +	if (!out) {
> +		ERROR("%s cannot be opened", outfile);
> +		ret = 1;
> +		goto err;
> +	}
> +
> +	if (chmod(outfile, S_IRUSR | S_IWUSR)) {
> +		ERROR("Setting file permissions");
> +		ret = 1;
> +		goto err;
> +	}
> +
> +	/* Decrypt CMS message */
> +	if (!CMS_decrypt(cms, dgst->rkey, dgst->rcert, NULL, out, 0)) {
> +		ERR_print_errors_fp(stderr);
> +		ERROR("Decrypting %s failed", infile);
> +		ret = 1;
> +		goto err;
> +	}
> +
> +err:
> +	BIO_free(in);
> +	BIO_free(out);
> +	CMS_ContentInfo_free(cms);
> +	return ret;
> +}
> diff --git a/include/parsers.h b/include/parsers.h
> index 0e94c2b..53c3ee0 100644
> --- a/include/parsers.h
> +++ b/include/parsers.h
> @@ -15,6 +15,15 @@
>   #define SW_DESCRIPTION_FILENAME	CONFIG_SWDESCRIPTION
>   #endif
>   
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +#define AES_FILENAME	"aes-file"
> +#ifndef CONFIG_SET_ENCRYPTED_AES_FILENAME
> +#define ENCRYPTED_AES_FILENAME	"encrypted-aesfile"
> +#else
> +#define ENCRYPTED_AES_FILENAME	CONFIG_SET_ENCRYPTED_AES_FILENAME
> +#endif
> +#endif
> +
>   typedef int (*parser_fn)(struct swupdate_cfg *swcfg, const char *filename);
>   
>   int parse(struct swupdate_cfg *swcfg, const char *filename);
> diff --git a/include/sslapi.h b/include/sslapi.h
> index 9f5b061..0ccb672 100644
> --- a/include/sslapi.h
> +++ b/include/sslapi.h
> @@ -106,6 +106,10 @@ struct swupdate_digest {
>   #else
>   	EVP_CIPHER_CTX *ctxdec;
>   #endif
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +	EVP_PKEY *rkey; /* recipient private key */
> +	X509 *rcert;    /* recipient private cert */
> +#endif
>   };
>   
>   #if OPENSSL_VERSION_NUMBER < 0x10100000L
> @@ -215,6 +219,11 @@ UNUSED static inline struct swupdate_digest *swupdate_DECRYPT_init(
>   #define swupdate_DECRYPT_cleanup(p)
>   #endif
>   
> +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> +int swupdate_dgst_add_recipient_keypair(struct swupdate_cfg *sw, const char *keypair_file);
> +int swupdate_decrypt_file(struct swupdate_digest *dgst, const char *infile, const char *outfile);
> +#endif
> +
>   #ifndef SSL_PURPOSE_DEFAULT
>   #define SSL_PURPOSE_EMAIL_PROT -1
>   #define SSL_PURPOSE_CODE_SIGN  -1
> diff --git a/include/swupdate.h b/include/swupdate.h
> index c1f86b3..cdfb971 100644
> --- a/include/swupdate.h
> +++ b/include/swupdate.h
> @@ -57,6 +57,7 @@ struct swupdate_cfg {
>   	char output[SWUPDATE_GENERAL_STRING_SIZE];
>   	char publickeyfname[SWUPDATE_GENERAL_STRING_SIZE];
>   	char aeskeyfname[SWUPDATE_GENERAL_STRING_SIZE];
> +	char recipkeypairfname[SWUPDATE_GENERAL_STRING_SIZE];
>   	char postupdatecmd[SWUPDATE_GENERAL_STRING_SIZE];
>   	char preupdatecmd[SWUPDATE_GENERAL_STRING_SIZE];
>   	char minimum_version[SWUPDATE_GENERAL_STRING_SIZE];
Michael Glembotzki Nov. 27, 2023, 9:35 a.m. UTC | #2
Hi Stefano,

I understand the advantages of your suggestion and will prepare a V2.
Thanks for the feedback!

Best regards,
Michael


Am Mo., 27. Nov. 2023 um 09:45 Uhr schrieb Stefano Babic
<stefano.babic@swupdate.org>:
>
> Hi Michael,
>
> On 26.11.23 14:24, Michael Glembotzki wrote:
> > This feature enables asymmetric image decryption. The asymmetrically encrypted
> > AES file is decrypted and loaded from a Cryptographic Message Syntax file
> > (DER). The AES file can be encrypted for any number of recipient devices.
> >
>
> This feature is clear after reading thwe code but not so much by reading
> the patch for the documentation. This should be enhanced.
>
> I think the use case should be added, and better explained as:
>
> - all artifaczts are encrypted with the same key
> - the way to exchange the key is together with a new software (is this
> what you intent with "rotate" ?)
> - if one device is compromised, it can get even later releases an obtain
> the new keys.
>
> And this feature will add a revocation for a single device.
>
> Nevertheless, I disagree with this implementation. This changes the
> structure of a SWU just to add a file that just contains a key. I will
> suggest another way.
>
> We have already support for encrypted sw-description
> (CONFIG_ENCRYPTED_SW_DESCRIPTION). Currently, it is encrypted with the
> symmetric key, too.
>
> If sw-description will be encrypted with asymmetric key, the key can
> simply added as new attribute in the global section, like
>         aes-key = .....
>         ivt = ....
>
> This makes the structure compatible with the past because stil
> lsw-description remains the first file in the SWU. Size of
> sw-description (as size of the aes-key) will increase with the amount of
> devices, like the size of the aes-key file here.
>
> What do you think ?
>
> Best regards,
> Stefano Babic
>
>
> > Signed-off-by: Michael Glembotzki <Michael.Glembotzki@iris-sensing.com>
> > ---
> >   Kconfig                        |  12 ++++
> >   core/installer.c               |  12 ++++
> >   core/stream_interface.c        |  70 ++++++++++++++++++++-
> >   core/swupdate.c                |  44 +++++++++++--
> >   corelib/Makefile               |   3 +
> >   corelib/swupdate_cms_decrypt.c | 112 +++++++++++++++++++++++++++++++++
> >   include/parsers.h              |   9 +++
> >   include/sslapi.h               |   9 +++
> >   include/swupdate.h             |   1 +
> >   9 files changed, 267 insertions(+), 5 deletions(-)
> >   create mode 100644 corelib/swupdate_cms_decrypt.c
> >
> > diff --git a/Kconfig b/Kconfig
> > index 2ae2e4b..a0ae5db 100644
> > --- a/Kconfig
> > +++ b/Kconfig
> > @@ -499,6 +499,18 @@ config ENCRYPTED_IMAGES
> >   comment "Image encryption needs an SSL implementation"
> >       depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS
> >
> > +config ASYM_ENCRYPTED_AESFILE
> > +     bool "Enable asymmetrically encrypted AES file with CMS / PKCS#7"
> > +     default n
> > +     depends on ENCRYPTED_IMAGES && SSL_IMPL_OPENSSL
> > +     help
> > +       This option enables asymmetric image decryption. The asymmetrically
> > +       encrypted AES file is decrypted and loaded from a Cryptographic Message
> > +       Syntax file (DER). The AES file can be encrypted for any number of
> > +       recipient devices. For security reasons the AES file should be rotated
> > +       regularly and the feature should be used together with signature
> > +       verification: SIGNED_IMAGES.
> > +
> >   config ENCRYPTED_SW_DESCRIPTION
> >       bool "Even sw-description is encrypted"
> >       depends on ENCRYPTED_IMAGES
> > diff --git a/core/installer.c b/core/installer.c
> > index 20b5b51..7349779 100644
> > --- a/core/installer.c
> > +++ b/core/installer.c
> > @@ -497,6 +497,18 @@ void cleanup_files(struct swupdate_cfg *software) {
> >               free(fn);
> >       }
> >   #endif
> > +
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     if (asprintf(&fn, "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME) != ENOMEM_ASPRINTF) {
> > +             remove_sw_file(fn);
> > +             free(fn);
> > +     }
> > +     if (asprintf(&fn, "%s%s", TMPDIR, AES_FILENAME) != ENOMEM_ASPRINTF) {
> > +             remove_sw_file(fn);
> > +             free(fn);
> > +     }
> > +#endif
> > +
> >   }
> >
> >   int preupdatecmd(struct swupdate_cfg *swcfg)
> > diff --git a/core/stream_interface.c b/core/stream_interface.c
> > index 0b78329..25f9457 100644
> > --- a/core/stream_interface.c
> > +++ b/core/stream_interface.c
> > @@ -45,11 +45,13 @@
> >   #include "state.h"
> >   #include "bootloader.h"
> >   #include "hw-compatibility.h"
> > +#include "sslapi.h"
> >
> >   #define BUFF_SIZE    4096
> >   #define PERCENT_LB_INDEX    4
> >
> >   enum {
> > +     STREAM_WAIT_ENC_AESFILE,
> >       STREAM_WAIT_DESCRIPTION,
> >       STREAM_WAIT_SIGNATURE,
> >       STREAM_DATA,
> > @@ -85,7 +87,7 @@ static int extract_file_to_tmp(int fd, const char *fname, unsigned long *poffs,
> >               return -1;
> >       }
> >       if (strcmp(fdh.filename, fname)) {
> > -             TRACE("description file name not the first of the list: %s instead of %s",
> > +             TRACE("file: %s instead of %s next element in the list",
> >                       fdh.filename,
> >                       fname);
> >               return -1;
> > @@ -148,6 +150,11 @@ static int extract_files(int fd, struct swupdate_cfg *software)
> >       bool installed_directly = false;
> >       bool encrypted_sw_desc = false;
> >
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     char aes_file[MAX_IMAGE_FNAME];
> > +     status = STREAM_WAIT_ENC_AESFILE;
> > +#endif
> > +
> >   #ifdef CONFIG_ENCRYPTED_SW_DESCRIPTION
> >       encrypted_sw_desc = true;
> >   #endif
> > @@ -164,6 +171,28 @@ static int extract_files(int fd, struct swupdate_cfg *software)
> >       for (;;) {
> >               switch (status) {
> >               /* Waiting for the first Header */
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +             case STREAM_WAIT_ENC_AESFILE:
> > +                     if (extract_file_to_tmp(fd, ENCRYPTED_AES_FILENAME, &offset, false) < 0) {
> > +                             return -1;
> > +                     }
> > +
> > +                     snprintf(output_file, sizeof(output_file), "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME);
> > +                     snprintf(aes_file, sizeof(aes_file), "%s%s", TMPDIR, AES_FILENAME);
> > +                     if (swupdate_decrypt_file(software->dgst, output_file, aes_file)) {
> > +                             ERROR("Decrypting AES file");
> > +                             return -1;
> > +                     }
> > +
> > +                     if (load_decryption_key(aes_file)) {
> > +                             ERROR("Key file does not contain a valid AES key");
> > +                             return -1;
> > +                     }
> > +
> > +                     status = STREAM_WAIT_DESCRIPTION;
> > +                     break;
> > +#endif
> > +
> >               case STREAM_WAIT_DESCRIPTION:
> >                       if (extract_file_to_tmp(fd, SW_DESCRIPTION_FILENAME, &offset, encrypted_sw_desc) < 0 )
> >                               return -1;
> > @@ -422,6 +451,7 @@ static int save_stream(int fdin, struct swupdate_cfg *software)
> >               goto no_copy_output;
> >       }
> >
> > +#ifndef CONFIG_ASYM_ENCRYPTED_AESFILE
> >       /*
> >        * Make an estimation for sw-description and signature.
> >        * Signature cannot be very big - if it is, it is an attack.
> > @@ -430,6 +460,14 @@ static int save_stream(int fdin, struct swupdate_cfg *software)
> >        */
> >       tmpsize = SWUPDATE_ALIGN(fdh.size + fdh.namesize + sizeof(struct new_ascii_header) + bufsize - len,
> >                       bufsize);
> > +#else
> > +     /*
> > +      * tmpsize has enough space for the encrypted-aesfile, sw-description and
> > +      * sw-description.sig
> > +      */
> > +     tmpsize = SWUPDATE_ALIGN(fdh.size + fdh.namesize + sizeof(struct new_ascii_header) + 2 * bufsize - len,
> > +                     bufsize);
> > +#endif
> >       ret = copy_write(&tmpfd, buf, len);  /* copy the first buffer */
> >       if (ret < 0) {
> >               ret =  -EIO;
> > @@ -447,6 +485,28 @@ static int save_stream(int fdin, struct swupdate_cfg *software)
> >       lseek(tmpfd, 0, SEEK_SET);
> >       offset = 0;
> >
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     if (extract_file_to_tmp(tmpfd, ENCRYPTED_AES_FILENAME, &offset, false) < 0) {
> > +             ret = -EINVAL;
> > +             goto no_copy_output;
> > +     }
> > +
> > +     snprintf(output_file, sizeof(output_file), "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME);
> > +     char aes_file[MAX_IMAGE_FNAME];
> > +     snprintf(aes_file, sizeof(aes_file), "%s%s", TMPDIR, AES_FILENAME);
> > +     if (swupdate_decrypt_file(software->dgst, output_file, aes_file)) {
> > +             ERROR("Decrypting AES Key");
> > +             ret = -1;
> > +             goto no_copy_output;
> > +     }
> > +
> > +     if (load_decryption_key(aes_file)) {
> > +             ERROR("Key file does not contain a valid AES key");
> > +             ret = -1;
> > +             goto no_copy_output;
> > +     }
> > +#endif
> > +
> >       if (extract_file_to_tmp(tmpfd, SW_DESCRIPTION_FILENAME, &offset, encrypted_sw_desc) < 0) {
> >               ERROR("%s cannot be extracted", SW_DESCRIPTION_FILENAME);
> >               ret = -EINVAL;
> > @@ -507,6 +567,10 @@ no_copy_output:
> >
> >       cleanup_files(software);
> >
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     clear_aes_key();
> > +#endif
> > +
> >       return ret;
> >   }
> >
> > @@ -703,6 +767,10 @@ void *network_initializer(void *data)
> >               /* release temp files we may have created */
> >               cleanup_files(software);
> >
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +             clear_aes_key();
> > +#endif
> > +
> >   #ifndef CONFIG_NOCLEANUP
> >               swupdate_remove_directory(SCRIPTS_DIR_SUFFIX);
> >               swupdate_remove_directory(DATADST_DIR_SUFFIX);
> > diff --git a/core/swupdate.c b/core/swupdate.c
> > index 6f9938e..9532d0a 100644
> > --- a/core/swupdate.c
> > +++ b/core/swupdate.c
> > @@ -101,8 +101,11 @@ static struct option long_options[] = {
> >       {"forced-signer-name", required_argument, NULL, '2'},
> >   #endif
> >   #endif
> > -#ifdef CONFIG_ENCRYPTED_IMAGES
> > +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
> >       {"key-aes", required_argument, NULL, 'K'},
> > +#endif
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     {"recip-keypair", required_argument, NULL, 'r'},
> >   #endif
> >       {"loglevel", required_argument, NULL, 'l'},
> >       {"max-version", required_argument, NULL, '3'},
> > @@ -162,9 +165,12 @@ static void usage(char *programname)
> >               "     --ca-path                  : path to the Certificate Authority (PEM)\n"
> >   #endif
> >   #endif
> > -#ifdef CONFIG_ENCRYPTED_IMAGES
> > +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
> >               " -K, --key-aes <key file>       : the file contains the symmetric key to be used\n"
> >               "                                  to decrypt images\n"
> > +#endif
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +             " -r, --recip-keypair <key file> : path to the recipient keypair (PEM)\n"
> >   #endif
> >               " -n, --dry-run                  : run SWUpdate without installing the software\n"
> >               " -N, --no-downgrading <version> : not install a release older as <version>\n"
> > @@ -310,8 +316,14 @@ static int read_globals_settings(void *elem, void *data)
> >                               "public-key-file", sw->publickeyfname);
> >       GET_FIELD_STRING(LIBCFG_PARSER, elem,
> >                               "ca-path", sw->publickeyfname);
> > +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
> >       GET_FIELD_STRING(LIBCFG_PARSER, elem,
> >                               "aes-key-file", sw->aeskeyfname);
> > +#endif
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     GET_FIELD_STRING(LIBCFG_PARSER, elem,
> > +                             "recip-keypair", sw->recipkeypairfname);
> > +#endif
> >       GET_FIELD_STRING(LIBCFG_PARSER, elem,
> >                               "mtd-blacklist", sw->mtdblacklist);
> >       GET_FIELD_STRING(LIBCFG_PARSER, elem,
> > @@ -497,9 +509,12 @@ int main(int argc, char **argv)
> >       public_key_mandatory = 1;
> >   #endif
> >   #endif
> > -#ifdef CONFIG_ENCRYPTED_IMAGES
> > +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
> >       strcat(main_options, "K:");
> >   #endif
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     strcat(main_options, "r:");
> > +#endif
> >
> >       memset(fname, 0, sizeof(fname));
> >
> > @@ -656,12 +671,19 @@ int main(int argc, char **argv)
> >                       strlcpy(swcfg.maximum_version, optarg,
> >                               sizeof(swcfg.maximum_version));
> >                       break;
> > -#ifdef CONFIG_ENCRYPTED_IMAGES
> > +#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
> >               case 'K':
> >                       strlcpy(swcfg.aeskeyfname,
> >                               optarg,
> >                               sizeof(swcfg.aeskeyfname));
> >                       break;
> > +#endif
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +             case 'r':
> > +                     strlcpy(swcfg.recipkeypairfname,
> > +                             optarg,
> > +                             sizeof(swcfg.recipkeypairfname));
> > +                     break;
> >   #endif
> >               case 'N':
> >                       swcfg.no_downgrading = true;
> > @@ -842,6 +864,19 @@ int main(int argc, char **argv)
> >               mtd_set_ubiblacklist(swcfg.mtdblacklist);
> >   #endif
> >
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     if (strlen(swcfg.recipkeypairfname)) {
> > +             if (swupdate_dgst_add_recipient_keypair(&swcfg, swcfg.recipkeypairfname)) {
> > +                     fprintf(stderr,
> > +                             "Error: Recipient keypair cannot be initialized.\n");
> > +                     exit(EXIT_FAILURE);
> > +             }
> > +     } else {
> > +             fprintf(stderr,
> > +                      "Error: SWUpdate is built for asym encrypted images, provide a recipient key pair.\n");
> > +             exit(EXIT_FAILURE);
> > +     }
> > +#else
> >       /*
> >        * If an AES key is passed, load it to allow
> >        * to decrypt images
> > @@ -853,6 +888,7 @@ int main(int argc, char **argv)
> >                       exit(EXIT_FAILURE);
> >               }
> >       }
> > +#endif
> >
> >       lua_handlers_init();
> >
> > diff --git a/corelib/Makefile b/corelib/Makefile
> > index c9ca4aa..36d32ec 100644
> > --- a/corelib/Makefile
> > +++ b/corelib/Makefile
> > @@ -18,6 +18,9 @@ endif
> >   lib-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify.o
> >   lib-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify.o
> >   endif
> > +ifeq ($(CONFIG_ASYM_ENCRYPTED_AESFILE),y)
> > +lib-$(CONFIG_ENCRYPTED_IMAGES)       += swupdate_cms_decrypt.o
> > +endif
> >   ifeq ($(CONFIG_SSL_IMPL_OPENSSL),y)
> >   lib-$(CONFIG_SIGALG_CMS)    += swupdate_cms_verify.o
> >   endif
> > diff --git a/corelib/swupdate_cms_decrypt.c b/corelib/swupdate_cms_decrypt.c
> > new file mode 100644
> > index 0000000..5af2508
> > --- /dev/null
> > +++ b/corelib/swupdate_cms_decrypt.c
> > @@ -0,0 +1,112 @@
> > +/*
> > + * (C) Copyright 2023
> > + * Michael Glembotzki, iris-GmbH infrared & intelligent sensors, michael.glembotzki@iris-sensing.com.
> > + *
> > + * SPDX-License-Identifier:     GPL-2.0-only
> > + *
> > + * Code mostly taken from openssl examples
> > + */
> > +#include <sys/stat.h>
> > +#include "swupdate.h"
> > +#include "sslapi.h"
> > +#include "util.h"
> > +
> > +int swupdate_dgst_add_recipient_keypair(struct swupdate_cfg *sw, const char *keypair_file) {
> > +     X509 *rcert = NULL;
> > +     EVP_PKEY *rkey = NULL;
> > +     struct swupdate_digest *dgst = sw->dgst;
> > +     int ret = 0;
> > +
> > +     if (!dgst) {
> > +             dgst = calloc(1, sizeof(*dgst));
> > +             if (!dgst) {
> > +                     ret = 1;
> > +                     goto err;
> > +             }
> > +     }
> > +
> > +     BIO *tbio = BIO_new_file(keypair_file, "r");
> > +     if (!tbio) {
> > +             ERROR("%s cannot be opened", keypair_file);
> > +             ret = 1;
> > +             goto err;
> > +     }
> > +
> > +     rcert = PEM_read_bio_X509(tbio, NULL, 0, NULL);
> > +     if (!rcert) {
> > +             WARN("Recipient cert not found");
> > +     }
> > +     BIO_reset(tbio);
> > +
> > +     rkey = PEM_read_bio_PrivateKey(tbio, NULL, 0, NULL);
> > +     BIO_free(tbio);
> > +     if (!rkey) {
> > +             ERROR("Recipient private key not found");
> > +             ret = 1;
> > +             goto err;
> > +     }
> > +
> > +     dgst->rcert = rcert;
> > +     dgst->rkey = rkey;
> > +
> > +     return ret;
> > +
> > +err:
> > +     if (dgst) {
> > +             free(dgst);
> > +     }
> > +     return ret;
> > +}
> > +
> > +int swupdate_decrypt_file(struct swupdate_digest *dgst, const char *infile, const char *outfile) {
> > +     BIO *in = NULL, *out = NULL;
> > +     CMS_ContentInfo *cms = NULL;
> > +     int ret = 0;
> > +
> > +     if (!dgst || !infile || !outfile) {
> > +             return 1;
> > +     }
> > +
> > +     /* Open CMS message to decrypt */
> > +     in = BIO_new_file(infile, "rb");
> > +     if (!in) {
> > +             ERROR("%s cannot be opened", infile);
> > +             ret = 1;
> > +             goto err;
> > +     }
> > +
> > +     /* Parse message */
> > +     cms = d2i_CMS_bio(in, NULL);
> > +     if (!cms) {
> > +             ERROR("%s cannot be parsed as DER-encoded CMS blob", infile);
> > +             ret = 1;
> > +             goto err;
> > +     }
> > +
> > +     out = BIO_new_file(outfile, "wb");
> > +     if (!out) {
> > +             ERROR("%s cannot be opened", outfile);
> > +             ret = 1;
> > +             goto err;
> > +     }
> > +
> > +     if (chmod(outfile, S_IRUSR | S_IWUSR)) {
> > +             ERROR("Setting file permissions");
> > +             ret = 1;
> > +             goto err;
> > +     }
> > +
> > +     /* Decrypt CMS message */
> > +     if (!CMS_decrypt(cms, dgst->rkey, dgst->rcert, NULL, out, 0)) {
> > +             ERR_print_errors_fp(stderr);
> > +             ERROR("Decrypting %s failed", infile);
> > +             ret = 1;
> > +             goto err;
> > +     }
> > +
> > +err:
> > +     BIO_free(in);
> > +     BIO_free(out);
> > +     CMS_ContentInfo_free(cms);
> > +     return ret;
> > +}
> > diff --git a/include/parsers.h b/include/parsers.h
> > index 0e94c2b..53c3ee0 100644
> > --- a/include/parsers.h
> > +++ b/include/parsers.h
> > @@ -15,6 +15,15 @@
> >   #define SW_DESCRIPTION_FILENAME     CONFIG_SWDESCRIPTION
> >   #endif
> >
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +#define AES_FILENAME "aes-file"
> > +#ifndef CONFIG_SET_ENCRYPTED_AES_FILENAME
> > +#define ENCRYPTED_AES_FILENAME       "encrypted-aesfile"
> > +#else
> > +#define ENCRYPTED_AES_FILENAME       CONFIG_SET_ENCRYPTED_AES_FILENAME
> > +#endif
> > +#endif
> > +
> >   typedef int (*parser_fn)(struct swupdate_cfg *swcfg, const char *filename);
> >
> >   int parse(struct swupdate_cfg *swcfg, const char *filename);
> > diff --git a/include/sslapi.h b/include/sslapi.h
> > index 9f5b061..0ccb672 100644
> > --- a/include/sslapi.h
> > +++ b/include/sslapi.h
> > @@ -106,6 +106,10 @@ struct swupdate_digest {
> >   #else
> >       EVP_CIPHER_CTX *ctxdec;
> >   #endif
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +     EVP_PKEY *rkey; /* recipient private key */
> > +     X509 *rcert;    /* recipient private cert */
> > +#endif
> >   };
> >
> >   #if OPENSSL_VERSION_NUMBER < 0x10100000L
> > @@ -215,6 +219,11 @@ UNUSED static inline struct swupdate_digest *swupdate_DECRYPT_init(
> >   #define swupdate_DECRYPT_cleanup(p)
> >   #endif
> >
> > +#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
> > +int swupdate_dgst_add_recipient_keypair(struct swupdate_cfg *sw, const char *keypair_file);
> > +int swupdate_decrypt_file(struct swupdate_digest *dgst, const char *infile, const char *outfile);
> > +#endif
> > +
> >   #ifndef SSL_PURPOSE_DEFAULT
> >   #define SSL_PURPOSE_EMAIL_PROT -1
> >   #define SSL_PURPOSE_CODE_SIGN  -1
> > diff --git a/include/swupdate.h b/include/swupdate.h
> > index c1f86b3..cdfb971 100644
> > --- a/include/swupdate.h
> > +++ b/include/swupdate.h
> > @@ -57,6 +57,7 @@ struct swupdate_cfg {
> >       char output[SWUPDATE_GENERAL_STRING_SIZE];
> >       char publickeyfname[SWUPDATE_GENERAL_STRING_SIZE];
> >       char aeskeyfname[SWUPDATE_GENERAL_STRING_SIZE];
> > +     char recipkeypairfname[SWUPDATE_GENERAL_STRING_SIZE];
> >       char postupdatecmd[SWUPDATE_GENERAL_STRING_SIZE];
> >       char preupdatecmd[SWUPDATE_GENERAL_STRING_SIZE];
> >       char minimum_version[SWUPDATE_GENERAL_STRING_SIZE];
>
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index 2ae2e4b..a0ae5db 100644
--- a/Kconfig
+++ b/Kconfig
@@ -499,6 +499,18 @@  config ENCRYPTED_IMAGES
 comment "Image encryption needs an SSL implementation"
 	depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS
 
+config ASYM_ENCRYPTED_AESFILE
+	bool "Enable asymmetrically encrypted AES file with CMS / PKCS#7"
+	default n
+	depends on ENCRYPTED_IMAGES && SSL_IMPL_OPENSSL
+	help
+	  This option enables asymmetric image decryption. The asymmetrically
+	  encrypted AES file is decrypted and loaded from a Cryptographic Message
+	  Syntax file (DER). The AES file can be encrypted for any number of
+	  recipient devices. For security reasons the AES file should be rotated
+	  regularly and the feature should be used together with signature
+	  verification: SIGNED_IMAGES.
+
 config ENCRYPTED_SW_DESCRIPTION
 	bool "Even sw-description is encrypted"
 	depends on ENCRYPTED_IMAGES
diff --git a/core/installer.c b/core/installer.c
index 20b5b51..7349779 100644
--- a/core/installer.c
+++ b/core/installer.c
@@ -497,6 +497,18 @@  void cleanup_files(struct swupdate_cfg *software) {
 		free(fn);
 	}
 #endif
+
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	if (asprintf(&fn, "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME) != ENOMEM_ASPRINTF) {
+		remove_sw_file(fn);
+		free(fn);
+	}
+	if (asprintf(&fn, "%s%s", TMPDIR, AES_FILENAME) != ENOMEM_ASPRINTF) {
+		remove_sw_file(fn);
+		free(fn);
+	}
+#endif
+
 }
 
 int preupdatecmd(struct swupdate_cfg *swcfg)
diff --git a/core/stream_interface.c b/core/stream_interface.c
index 0b78329..25f9457 100644
--- a/core/stream_interface.c
+++ b/core/stream_interface.c
@@ -45,11 +45,13 @@ 
 #include "state.h"
 #include "bootloader.h"
 #include "hw-compatibility.h"
+#include "sslapi.h"
 
 #define BUFF_SIZE	 4096
 #define PERCENT_LB_INDEX	4
 
 enum {
+	STREAM_WAIT_ENC_AESFILE,
 	STREAM_WAIT_DESCRIPTION,
 	STREAM_WAIT_SIGNATURE,
 	STREAM_DATA,
@@ -85,7 +87,7 @@  static int extract_file_to_tmp(int fd, const char *fname, unsigned long *poffs,
 		return -1;
 	}
 	if (strcmp(fdh.filename, fname)) {
-		TRACE("description file name not the first of the list: %s instead of %s",
+		TRACE("file: %s instead of %s next element in the list",
 			fdh.filename,
 			fname);
 		return -1;
@@ -148,6 +150,11 @@  static int extract_files(int fd, struct swupdate_cfg *software)
 	bool installed_directly = false;
 	bool encrypted_sw_desc = false;
 
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	char aes_file[MAX_IMAGE_FNAME];
+	status = STREAM_WAIT_ENC_AESFILE;
+#endif
+
 #ifdef CONFIG_ENCRYPTED_SW_DESCRIPTION
 	encrypted_sw_desc = true;
 #endif
@@ -164,6 +171,28 @@  static int extract_files(int fd, struct swupdate_cfg *software)
 	for (;;) {
 		switch (status) {
 		/* Waiting for the first Header */
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+		case STREAM_WAIT_ENC_AESFILE:
+			if (extract_file_to_tmp(fd, ENCRYPTED_AES_FILENAME, &offset, false) < 0) {
+				return -1;
+			}
+
+			snprintf(output_file, sizeof(output_file), "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME);
+			snprintf(aes_file, sizeof(aes_file), "%s%s", TMPDIR, AES_FILENAME);
+			if (swupdate_decrypt_file(software->dgst, output_file, aes_file)) {
+				ERROR("Decrypting AES file");
+				return -1;
+			}
+
+			if (load_decryption_key(aes_file)) {
+				ERROR("Key file does not contain a valid AES key");
+				return -1;
+			}
+
+			status = STREAM_WAIT_DESCRIPTION;
+			break;
+#endif
+
 		case STREAM_WAIT_DESCRIPTION:
 			if (extract_file_to_tmp(fd, SW_DESCRIPTION_FILENAME, &offset, encrypted_sw_desc) < 0 )
 				return -1;
@@ -422,6 +451,7 @@  static int save_stream(int fdin, struct swupdate_cfg *software)
 		goto no_copy_output;
 	}
 
+#ifndef CONFIG_ASYM_ENCRYPTED_AESFILE
 	/*
 	 * Make an estimation for sw-description and signature.
 	 * Signature cannot be very big - if it is, it is an attack.
@@ -430,6 +460,14 @@  static int save_stream(int fdin, struct swupdate_cfg *software)
 	 */
 	tmpsize = SWUPDATE_ALIGN(fdh.size + fdh.namesize + sizeof(struct new_ascii_header) + bufsize - len,
 			bufsize);
+#else
+	/*
+	 * tmpsize has enough space for the encrypted-aesfile, sw-description and
+	 * sw-description.sig
+	 */
+	tmpsize = SWUPDATE_ALIGN(fdh.size + fdh.namesize + sizeof(struct new_ascii_header) + 2 * bufsize - len,
+			bufsize);
+#endif
 	ret = copy_write(&tmpfd, buf, len);  /* copy the first buffer */
 	if (ret < 0) {
 		ret =  -EIO;
@@ -447,6 +485,28 @@  static int save_stream(int fdin, struct swupdate_cfg *software)
 	lseek(tmpfd, 0, SEEK_SET);
 	offset = 0;
 
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	if (extract_file_to_tmp(tmpfd, ENCRYPTED_AES_FILENAME, &offset, false) < 0) {
+		ret = -EINVAL;
+		goto no_copy_output;
+	}
+
+	snprintf(output_file, sizeof(output_file), "%s%s", TMPDIR, ENCRYPTED_AES_FILENAME);
+	char aes_file[MAX_IMAGE_FNAME];
+	snprintf(aes_file, sizeof(aes_file), "%s%s", TMPDIR, AES_FILENAME);
+	if (swupdate_decrypt_file(software->dgst, output_file, aes_file)) {
+		ERROR("Decrypting AES Key");
+		ret = -1;
+		goto no_copy_output;
+	}
+
+	if (load_decryption_key(aes_file)) {
+		ERROR("Key file does not contain a valid AES key");
+		ret = -1;
+		goto no_copy_output;
+	}
+#endif
+
 	if (extract_file_to_tmp(tmpfd, SW_DESCRIPTION_FILENAME, &offset, encrypted_sw_desc) < 0) {
 		ERROR("%s cannot be extracted", SW_DESCRIPTION_FILENAME);
 		ret = -EINVAL;
@@ -507,6 +567,10 @@  no_copy_output:
 
 	cleanup_files(software);
 
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	clear_aes_key();
+#endif
+
 	return ret;
 }
 
@@ -703,6 +767,10 @@  void *network_initializer(void *data)
 		/* release temp files we may have created */
 		cleanup_files(software);
 
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+		clear_aes_key();
+#endif
+
 #ifndef CONFIG_NOCLEANUP
 		swupdate_remove_directory(SCRIPTS_DIR_SUFFIX);
 		swupdate_remove_directory(DATADST_DIR_SUFFIX);
diff --git a/core/swupdate.c b/core/swupdate.c
index 6f9938e..9532d0a 100644
--- a/core/swupdate.c
+++ b/core/swupdate.c
@@ -101,8 +101,11 @@  static struct option long_options[] = {
 	{"forced-signer-name", required_argument, NULL, '2'},
 #endif
 #endif
-#ifdef CONFIG_ENCRYPTED_IMAGES
+#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
 	{"key-aes", required_argument, NULL, 'K'},
+#endif
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	{"recip-keypair", required_argument, NULL, 'r'},
 #endif
 	{"loglevel", required_argument, NULL, 'l'},
 	{"max-version", required_argument, NULL, '3'},
@@ -162,9 +165,12 @@  static void usage(char *programname)
 		"     --ca-path                  : path to the Certificate Authority (PEM)\n"
 #endif
 #endif
-#ifdef CONFIG_ENCRYPTED_IMAGES
+#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
 		" -K, --key-aes <key file>       : the file contains the symmetric key to be used\n"
 		"                                  to decrypt images\n"
+#endif
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+		" -r, --recip-keypair <key file> : path to the recipient keypair (PEM)\n"
 #endif
 		" -n, --dry-run                  : run SWUpdate without installing the software\n"
 		" -N, --no-downgrading <version> : not install a release older as <version>\n"
@@ -310,8 +316,14 @@  static int read_globals_settings(void *elem, void *data)
 				"public-key-file", sw->publickeyfname);
 	GET_FIELD_STRING(LIBCFG_PARSER, elem,
 				"ca-path", sw->publickeyfname);
+#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
 	GET_FIELD_STRING(LIBCFG_PARSER, elem,
 				"aes-key-file", sw->aeskeyfname);
+#endif
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	GET_FIELD_STRING(LIBCFG_PARSER, elem,
+				"recip-keypair", sw->recipkeypairfname);
+#endif
 	GET_FIELD_STRING(LIBCFG_PARSER, elem,
 				"mtd-blacklist", sw->mtdblacklist);
 	GET_FIELD_STRING(LIBCFG_PARSER, elem,
@@ -497,9 +509,12 @@  int main(int argc, char **argv)
 	public_key_mandatory = 1;
 #endif
 #endif
-#ifdef CONFIG_ENCRYPTED_IMAGES
+#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
 	strcat(main_options, "K:");
 #endif
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	strcat(main_options, "r:");
+#endif
 
 	memset(fname, 0, sizeof(fname));
 
@@ -656,12 +671,19 @@  int main(int argc, char **argv)
 			strlcpy(swcfg.maximum_version, optarg,
 				sizeof(swcfg.maximum_version));
 			break;
-#ifdef CONFIG_ENCRYPTED_IMAGES
+#if defined(CONFIG_ENCRYPTED_IMAGES) && !defined(CONFIG_ASYM_ENCRYPTED_AESFILE)
 		case 'K':
 			strlcpy(swcfg.aeskeyfname,
 				optarg,
 			       	sizeof(swcfg.aeskeyfname));
 			break;
+#endif
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+		case 'r':
+			strlcpy(swcfg.recipkeypairfname,
+				optarg,
+				sizeof(swcfg.recipkeypairfname));
+			break;
 #endif
 		case 'N':
 			swcfg.no_downgrading = true;
@@ -842,6 +864,19 @@  int main(int argc, char **argv)
 		mtd_set_ubiblacklist(swcfg.mtdblacklist);
 #endif
 
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	if (strlen(swcfg.recipkeypairfname)) {
+		if (swupdate_dgst_add_recipient_keypair(&swcfg, swcfg.recipkeypairfname)) {
+			fprintf(stderr,
+				"Error: Recipient keypair cannot be initialized.\n");
+			exit(EXIT_FAILURE);
+		}
+	} else {
+		fprintf(stderr,
+			 "Error: SWUpdate is built for asym encrypted images, provide a recipient key pair.\n");
+		exit(EXIT_FAILURE);
+	}
+#else
 	/*
 	 * If an AES key is passed, load it to allow
 	 * to decrypt images
@@ -853,6 +888,7 @@  int main(int argc, char **argv)
 			exit(EXIT_FAILURE);
 		}
 	}
+#endif
 
 	lua_handlers_init();
 
diff --git a/corelib/Makefile b/corelib/Makefile
index c9ca4aa..36d32ec 100644
--- a/corelib/Makefile
+++ b/corelib/Makefile
@@ -18,6 +18,9 @@  endif
 lib-$(CONFIG_SIGALG_RAWRSA)	+= swupdate_rsa_verify.o
 lib-$(CONFIG_SIGALG_RSAPSS)	+= swupdate_rsa_verify.o
 endif
+ifeq ($(CONFIG_ASYM_ENCRYPTED_AESFILE),y)
+lib-$(CONFIG_ENCRYPTED_IMAGES)	+= swupdate_cms_decrypt.o
+endif
 ifeq ($(CONFIG_SSL_IMPL_OPENSSL),y)
 lib-$(CONFIG_SIGALG_CMS)	+= swupdate_cms_verify.o
 endif
diff --git a/corelib/swupdate_cms_decrypt.c b/corelib/swupdate_cms_decrypt.c
new file mode 100644
index 0000000..5af2508
--- /dev/null
+++ b/corelib/swupdate_cms_decrypt.c
@@ -0,0 +1,112 @@ 
+/*
+ * (C) Copyright 2023
+ * Michael Glembotzki, iris-GmbH infrared & intelligent sensors, michael.glembotzki@iris-sensing.com.
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ *
+ * Code mostly taken from openssl examples
+ */
+#include <sys/stat.h>
+#include "swupdate.h"
+#include "sslapi.h"
+#include "util.h"
+
+int swupdate_dgst_add_recipient_keypair(struct swupdate_cfg *sw, const char *keypair_file) {
+	X509 *rcert = NULL;
+	EVP_PKEY *rkey = NULL;
+	struct swupdate_digest *dgst = sw->dgst;
+	int ret = 0;
+
+	if (!dgst) {
+		dgst = calloc(1, sizeof(*dgst));
+		if (!dgst) {
+			ret = 1;
+			goto err;
+		}
+	}
+
+	BIO *tbio = BIO_new_file(keypair_file, "r");
+	if (!tbio) {
+		ERROR("%s cannot be opened", keypair_file);
+		ret = 1;
+		goto err;
+	}
+
+	rcert = PEM_read_bio_X509(tbio, NULL, 0, NULL);
+	if (!rcert) {
+		WARN("Recipient cert not found");
+	}
+	BIO_reset(tbio);
+
+	rkey = PEM_read_bio_PrivateKey(tbio, NULL, 0, NULL);
+	BIO_free(tbio);
+	if (!rkey) {
+		ERROR("Recipient private key not found");
+		ret = 1;
+		goto err;
+	}
+
+	dgst->rcert = rcert;
+	dgst->rkey = rkey;
+
+	return ret;
+
+err:
+	if (dgst) {
+		free(dgst);
+	}
+	return ret;
+}
+
+int swupdate_decrypt_file(struct swupdate_digest *dgst, const char *infile, const char *outfile) {
+	BIO *in = NULL, *out = NULL;
+	CMS_ContentInfo *cms = NULL;
+	int ret = 0;
+
+	if (!dgst || !infile || !outfile) {
+		return 1;
+	}
+
+	/* Open CMS message to decrypt */
+	in = BIO_new_file(infile, "rb");
+	if (!in) {
+		ERROR("%s cannot be opened", infile);
+		ret = 1;
+		goto err;
+	}
+
+	/* Parse message */
+	cms = d2i_CMS_bio(in, NULL);
+	if (!cms) {
+		ERROR("%s cannot be parsed as DER-encoded CMS blob", infile);
+		ret = 1;
+		goto err;
+	}
+
+	out = BIO_new_file(outfile, "wb");
+	if (!out) {
+		ERROR("%s cannot be opened", outfile);
+		ret = 1;
+		goto err;
+	}
+
+	if (chmod(outfile, S_IRUSR | S_IWUSR)) {
+		ERROR("Setting file permissions");
+		ret = 1;
+		goto err;
+	}
+
+	/* Decrypt CMS message */
+	if (!CMS_decrypt(cms, dgst->rkey, dgst->rcert, NULL, out, 0)) {
+		ERR_print_errors_fp(stderr);
+		ERROR("Decrypting %s failed", infile);
+		ret = 1;
+		goto err;
+	}
+
+err:
+	BIO_free(in);
+	BIO_free(out);
+	CMS_ContentInfo_free(cms);
+	return ret;
+}
diff --git a/include/parsers.h b/include/parsers.h
index 0e94c2b..53c3ee0 100644
--- a/include/parsers.h
+++ b/include/parsers.h
@@ -15,6 +15,15 @@ 
 #define SW_DESCRIPTION_FILENAME	CONFIG_SWDESCRIPTION
 #endif
 
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+#define AES_FILENAME	"aes-file"
+#ifndef CONFIG_SET_ENCRYPTED_AES_FILENAME
+#define ENCRYPTED_AES_FILENAME	"encrypted-aesfile"
+#else
+#define ENCRYPTED_AES_FILENAME	CONFIG_SET_ENCRYPTED_AES_FILENAME
+#endif
+#endif
+
 typedef int (*parser_fn)(struct swupdate_cfg *swcfg, const char *filename);
 
 int parse(struct swupdate_cfg *swcfg, const char *filename);
diff --git a/include/sslapi.h b/include/sslapi.h
index 9f5b061..0ccb672 100644
--- a/include/sslapi.h
+++ b/include/sslapi.h
@@ -106,6 +106,10 @@  struct swupdate_digest {
 #else
 	EVP_CIPHER_CTX *ctxdec;
 #endif
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+	EVP_PKEY *rkey; /* recipient private key */
+	X509 *rcert;    /* recipient private cert */
+#endif
 };
 
 #if OPENSSL_VERSION_NUMBER < 0x10100000L
@@ -215,6 +219,11 @@  UNUSED static inline struct swupdate_digest *swupdate_DECRYPT_init(
 #define swupdate_DECRYPT_cleanup(p)
 #endif
 
+#ifdef CONFIG_ASYM_ENCRYPTED_AESFILE
+int swupdate_dgst_add_recipient_keypair(struct swupdate_cfg *sw, const char *keypair_file);
+int swupdate_decrypt_file(struct swupdate_digest *dgst, const char *infile, const char *outfile);
+#endif
+
 #ifndef SSL_PURPOSE_DEFAULT
 #define SSL_PURPOSE_EMAIL_PROT -1
 #define SSL_PURPOSE_CODE_SIGN  -1
diff --git a/include/swupdate.h b/include/swupdate.h
index c1f86b3..cdfb971 100644
--- a/include/swupdate.h
+++ b/include/swupdate.h
@@ -57,6 +57,7 @@  struct swupdate_cfg {
 	char output[SWUPDATE_GENERAL_STRING_SIZE];
 	char publickeyfname[SWUPDATE_GENERAL_STRING_SIZE];
 	char aeskeyfname[SWUPDATE_GENERAL_STRING_SIZE];
+	char recipkeypairfname[SWUPDATE_GENERAL_STRING_SIZE];
 	char postupdatecmd[SWUPDATE_GENERAL_STRING_SIZE];
 	char preupdatecmd[SWUPDATE_GENERAL_STRING_SIZE];
 	char minimum_version[SWUPDATE_GENERAL_STRING_SIZE];