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