| Message ID | 20250904115704.58413-9-Michael.Glembotzki@iris-sensing.com |
|---|---|
| State | Changes Requested |
| Delegated to: | Stefano Babic |
| Headers | show |
| Series | Add support for asymmetric decryption | expand |
On 04.09.25 13:49, Michael Glembotzki wrote: > Signed-off-by: Michael Glembotzki <Michael.Glembotzki@iris-sensing.com> > Signed-off-by: Stefano Babic <stefano.babic@swupdate.org> > --- > crypto/Kconfig | 18 +++ > crypto/Makefile | 1 + > crypto/swupdate_decrypt_openssl_cms.c | 202 ++++++++++++++++++++++++++ > crypto/swupdate_openssl.h | 3 + > include/swupdate_aes.h | 1 + > 5 files changed, 225 insertions(+) > create mode 100644 crypto/swupdate_decrypt_openssl_cms.c > > diff --git a/crypto/Kconfig b/crypto/Kconfig > index 93b39778..4b9db821 100644 > --- a/crypto/Kconfig > +++ b/crypto/Kconfig > @@ -94,6 +94,24 @@ config ENCRYPTED_SW_DESCRIPTION > if this is set. It is a compile time option, and mix of plain and > encrypted sw-descriptions is not possible. > > +config ASYM_ENCRYPTED_SW_DESCRIPTION > + bool "Asymmetrical encrypted sw-description" > + depends on ENCRYPTED_SW_DESCRIPTION > + select SIGALG_ASYM_DEC_CMS > + default n > + help > + This option enables support for asymmetrical encrypted sw-description, > + making it possible to decrypt images device specific. > + > +menu "Asymmetric decryption algorithm" > + depends on ASYM_ENCRYPTED_SW_DESCRIPTION > + > + config SIGALG_ASYM_DEC_CMS > + bool "Cryptographic Message Syntax (CMS) / PKCS#7" > + default n > + depends on SSL_IMPL_OPENSSL > +endmenu > + > config ENCRYPTED_IMAGES_HARDEN_LOGGING > bool "Harden logging for encrypted images" > default n > diff --git a/crypto/Makefile b/crypto/Makefile > index 4d06bec0..25ab3ab7 100644 > --- a/crypto/Makefile > +++ b/crypto/Makefile > @@ -8,6 +8,7 @@ obj-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify_openssl.o > obj-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify_openssl.o > obj-$(CONFIG_SIGALG_CMS) += swupdate_cms_verify_openssl.o > obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_openssl.o > +obj-$(CONFIG_SIGALG_ASYM_DEC_CMS) += swupdate_decrypt_openssl_cms.o > endif > > ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) > diff --git a/crypto/swupdate_decrypt_openssl_cms.c b/crypto/swupdate_decrypt_openssl_cms.c > new file mode 100644 > index 00000000..b7e76d26 > --- /dev/null > +++ b/crypto/swupdate_decrypt_openssl_cms.c > @@ -0,0 +1,202 @@ > +/* > + * (C) Copyright 2024-2025 > + * Michael Glembotzki, iris-GmbH infrared & intelligent sensors, michael.glembotzki@iris-sensing.com > + * > + * (C) Copyright 2025 > + * Stefano Babic, stefano.babic@swupdate.org. > + * > + * SPDX-License-Identifier: GPL-2.0-only > + * > + * Code mostly taken from openssl examples > + */ > +#include <sys/stat.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <stdbool.h> > +#include <unistd.h> > +#include "swupdate.h" > +#include "util.h" > +#include "swupdate_crypto.h" > +#include "swupdate_openssl.h" > + > +static void openssl_cms_probe(void); > +static swupdate_decrypt_lib opensslCMS; > + > +//int swupdate_dgst_add_asym_keypair(struct swupdate_cfg *sw, const char *keypair_file) > +static void *openssl_CMS_DECRYPT_init(unsigned char *key, > + __attribute__ ((__unused__)) char keylen, > + __attribute__ ((__unused__)) unsigned char *iv, > + __attribute__ ((__unused__)) cipher_t cipher) > +{ > + struct openssl_digest *dgst = NULL; > + X509 *decrypt_cert = NULL; > + EVP_PKEY *pkey = NULL; > + BIO *tbio = NULL; > + const char *keypair_file = (const char *)key; > + > + if (!keypair_file) { > + ERROR("A PEM private key is not given !"); > + return NULL; > + } > + > + tbio = BIO_new_file(keypair_file, "r"); > + if (!tbio) { > + ERROR("%s cannot be opened", keypair_file); > + goto err; > + } > + > + dgst = calloc(1, sizeof(*dgst)); > + if (!dgst) { > + ERROR("OOM, Cannot create internal crypto structure"); > + goto err; > + } > + > + dgst->cryptbuf = BIO_new(BIO_s_mem()); > + if (!dgst->cryptbuf) { > + ERROR("Cannot create openSSL BIO buffer"); > + goto err; > + } > + > + > + pkey = PEM_read_bio_PrivateKey(tbio, NULL, 0, NULL); > + if (!pkey) { > + ERROR("Decryption key not found"); > + goto err; > + } > + dgst->pkey = pkey; > + BIO_reset(tbio); > + > + /* > + * Cert is optional, but helps OpenSSL find the right key faster in > + * large CMS recipient lists. > + */ > + decrypt_cert = PEM_read_bio_X509(tbio, NULL, 0, NULL); > + if (!decrypt_cert) { > + WARN("Decryption cert not found"); > + } else { > + dgst->decrypt_cert = decrypt_cert; > + } > + BIO_free(tbio); > + > + return (dgst); > + > +err: > + if (tbio) > + BIO_free(tbio); > + if (dgst) { > + if (dgst->cryptbuf) > + BIO_free(dgst->cryptbuf); > + free (dgst); > + } > + > + return NULL; > +} > + > +/* > + * Decrypting CMS requires that the encrypted message is in memory. > + * So store the crypted message, and really do the decryption in the final callback > + */ > +static int openssl_CMS_DECRYPT_update(void *ctx, > + __attribute__ ((__unused__)) unsigned char *buf, > + int *outlen, > + const unsigned char *cryptbuf, > + int inlen) > +{ > + struct openssl_digest *dgst = (struct openssl_digest *)ctx; > + > + if (BIO_write(dgst->cryptbuf, cryptbuf, inlen) < 0) { > + return -EFAULT; > + } > + > + /* > + * important: no plaintext is passed to buf during update() > + */ > + *outlen = 0; > + > + /* > + * Return -EAGAIN instead of 0: > + * 0 would signal EOF and stop the loop, so final() would never be called. > + * -EAGAIN means: no plaintext yet, try again to fill the dgst->cryptbuf. > + */ > + return -EAGAIN; > +} > + > +#define BUFSIZE 16384 > +static int openssl_CMS_DECRYPT_final(void *ctx, unsigned char *buf, > + int *outlen) > +{ > + struct openssl_digest *dgst = (struct openssl_digest *)ctx; > + > + /* Decrypt only once and buffer the result in the internal plain BIO. */ > + if (!dgst->plain) { > + CMS_ContentInfo *cms = NULL; > + BIO *out = BIO_new(BIO_s_mem()); > + if (!out) { > + ERROR("Cannot create openSSL BIO output buffer"); > + return -ENOMEM; > + } > + > + /* Parse message */ > + cms = d2i_CMS_bio(dgst->cryptbuf, NULL); > + if (!cms) { > + ERROR("Cannot parse as DER-encoded CMS blob"); > + BIO_free(out); > + return -EFAULT; > + } > + > + if (!CMS_decrypt(cms, dgst->pkey, dgst->decrypt_cert, NULL, out, 0)) { > + ERROR("Decrypting failed"); > + CMS_ContentInfo_free(cms); > + BIO_free(out); > + return -EFAULT; > + } > + CMS_ContentInfo_free(cms); > + BIO_free(dgst->cryptbuf); > + dgst->cryptbuf = NULL; > + dgst->plain = out; > + } > + > + int n = BIO_read(dgst->plain, buf, BUFSIZE); > + if (n < 0) { > + ERROR("Reading from plain BIO failed"); > + return -EFAULT; > + } > + *outlen = n; > + > + if (BIO_pending(dgst->plain) > 0) { > + /* more plain data is pending, let decrypt_step call final() again */ > + return -EAGAIN; > + } > + > + BIO_free(dgst->plain); > + dgst->plain = NULL; > + return 0; > +} > + > +static void openssl_CMS_DECRYPT_cleanup(void *ctx) > +{ > + struct openssl_digest *dgst = (struct openssl_digest *)ctx; > + if (dgst) { > + EVP_CIPHER_CTX_free(SSL_GET_CTXDEC(dgst)); > + if (dgst->cryptbuf) > + BIO_free(dgst->cryptbuf); > + if (dgst->plain) > + BIO_free(dgst->plain); > + free(dgst); > + } > + > + dgst = NULL; > + > + return; > +} > + > +__attribute__((constructor)) > +static void openssl_cms_probe(void) > +{ > + opensslCMS.DECRYPT_init = openssl_CMS_DECRYPT_init; > + opensslCMS.DECRYPT_update = openssl_CMS_DECRYPT_update; > + opensslCMS.DECRYPT_final = openssl_CMS_DECRYPT_final; > + opensslCMS.DECRYPT_cleanup = openssl_CMS_DECRYPT_cleanup; > + (void)register_cryptolib("opensslCMS", &opensslCMS); > +} > diff --git a/crypto/swupdate_openssl.h b/crypto/swupdate_openssl.h > index 4dc79503..2773cbc8 100644 > --- a/crypto/swupdate_openssl.h > +++ b/crypto/swupdate_openssl.h > @@ -35,12 +35,15 @@ struct openssl_digest { > EVP_PKEY *pkey; /* this is used for RSA key */ > EVP_PKEY_CTX *ckey; /* this is used for RSA key */ > X509_STORE *certs; /* this is used if CMS is set */ > + X509 *decrypt_cert; > EVP_MD_CTX *ctx; > #if OPENSSL_VERSION_NUMBER < 0x10100000L > EVP_CIPHER_CTX ctxdec; > #else > EVP_CIPHER_CTX *ctxdec; > #endif > + BIO *cryptbuf; > + BIO *plain; > }; > > #if OPENSSL_VERSION_NUMBER < 0x10100000L > diff --git a/include/swupdate_aes.h b/include/swupdate_aes.h > index acb69573..b019f8cd 100644 > --- a/include/swupdate_aes.h > +++ b/include/swupdate_aes.h > @@ -25,6 +25,7 @@ typedef enum { > AES_CTR_128, > AES_CTR_192, > AES_CTR_256, > + CMS, > AES_UNKNOWN > } cipher_t; > Reviewed-by: Stefano Babic <stefano.babic@swupdate.org>
diff --git a/crypto/Kconfig b/crypto/Kconfig index 93b39778..4b9db821 100644 --- a/crypto/Kconfig +++ b/crypto/Kconfig @@ -94,6 +94,24 @@ config ENCRYPTED_SW_DESCRIPTION if this is set. It is a compile time option, and mix of plain and encrypted sw-descriptions is not possible. +config ASYM_ENCRYPTED_SW_DESCRIPTION + bool "Asymmetrical encrypted sw-description" + depends on ENCRYPTED_SW_DESCRIPTION + select SIGALG_ASYM_DEC_CMS + default n + help + This option enables support for asymmetrical encrypted sw-description, + making it possible to decrypt images device specific. + +menu "Asymmetric decryption algorithm" + depends on ASYM_ENCRYPTED_SW_DESCRIPTION + + config SIGALG_ASYM_DEC_CMS + bool "Cryptographic Message Syntax (CMS) / PKCS#7" + default n + depends on SSL_IMPL_OPENSSL +endmenu + config ENCRYPTED_IMAGES_HARDEN_LOGGING bool "Harden logging for encrypted images" default n diff --git a/crypto/Makefile b/crypto/Makefile index 4d06bec0..25ab3ab7 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify_openssl.o obj-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify_openssl.o obj-$(CONFIG_SIGALG_CMS) += swupdate_cms_verify_openssl.o obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_openssl.o +obj-$(CONFIG_SIGALG_ASYM_DEC_CMS) += swupdate_decrypt_openssl_cms.o endif ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) diff --git a/crypto/swupdate_decrypt_openssl_cms.c b/crypto/swupdate_decrypt_openssl_cms.c new file mode 100644 index 00000000..b7e76d26 --- /dev/null +++ b/crypto/swupdate_decrypt_openssl_cms.c @@ -0,0 +1,202 @@ +/* + * (C) Copyright 2024-2025 + * Michael Glembotzki, iris-GmbH infrared & intelligent sensors, michael.glembotzki@iris-sensing.com + * + * (C) Copyright 2025 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + */ +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <unistd.h> +#include "swupdate.h" +#include "util.h" +#include "swupdate_crypto.h" +#include "swupdate_openssl.h" + +static void openssl_cms_probe(void); +static swupdate_decrypt_lib opensslCMS; + +//int swupdate_dgst_add_asym_keypair(struct swupdate_cfg *sw, const char *keypair_file) +static void *openssl_CMS_DECRYPT_init(unsigned char *key, + __attribute__ ((__unused__)) char keylen, + __attribute__ ((__unused__)) unsigned char *iv, + __attribute__ ((__unused__)) cipher_t cipher) +{ + struct openssl_digest *dgst = NULL; + X509 *decrypt_cert = NULL; + EVP_PKEY *pkey = NULL; + BIO *tbio = NULL; + const char *keypair_file = (const char *)key; + + if (!keypair_file) { + ERROR("A PEM private key is not given !"); + return NULL; + } + + tbio = BIO_new_file(keypair_file, "r"); + if (!tbio) { + ERROR("%s cannot be opened", keypair_file); + goto err; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + ERROR("OOM, Cannot create internal crypto structure"); + goto err; + } + + dgst->cryptbuf = BIO_new(BIO_s_mem()); + if (!dgst->cryptbuf) { + ERROR("Cannot create openSSL BIO buffer"); + goto err; + } + + + pkey = PEM_read_bio_PrivateKey(tbio, NULL, 0, NULL); + if (!pkey) { + ERROR("Decryption key not found"); + goto err; + } + dgst->pkey = pkey; + BIO_reset(tbio); + + /* + * Cert is optional, but helps OpenSSL find the right key faster in + * large CMS recipient lists. + */ + decrypt_cert = PEM_read_bio_X509(tbio, NULL, 0, NULL); + if (!decrypt_cert) { + WARN("Decryption cert not found"); + } else { + dgst->decrypt_cert = decrypt_cert; + } + BIO_free(tbio); + + return (dgst); + +err: + if (tbio) + BIO_free(tbio); + if (dgst) { + if (dgst->cryptbuf) + BIO_free(dgst->cryptbuf); + free (dgst); + } + + return NULL; +} + +/* + * Decrypting CMS requires that the encrypted message is in memory. + * So store the crypted message, and really do the decryption in the final callback + */ +static int openssl_CMS_DECRYPT_update(void *ctx, + __attribute__ ((__unused__)) unsigned char *buf, + int *outlen, + const unsigned char *cryptbuf, + int inlen) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + + if (BIO_write(dgst->cryptbuf, cryptbuf, inlen) < 0) { + return -EFAULT; + } + + /* + * important: no plaintext is passed to buf during update() + */ + *outlen = 0; + + /* + * Return -EAGAIN instead of 0: + * 0 would signal EOF and stop the loop, so final() would never be called. + * -EAGAIN means: no plaintext yet, try again to fill the dgst->cryptbuf. + */ + return -EAGAIN; +} + +#define BUFSIZE 16384 +static int openssl_CMS_DECRYPT_final(void *ctx, unsigned char *buf, + int *outlen) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + + /* Decrypt only once and buffer the result in the internal plain BIO. */ + if (!dgst->plain) { + CMS_ContentInfo *cms = NULL; + BIO *out = BIO_new(BIO_s_mem()); + if (!out) { + ERROR("Cannot create openSSL BIO output buffer"); + return -ENOMEM; + } + + /* Parse message */ + cms = d2i_CMS_bio(dgst->cryptbuf, NULL); + if (!cms) { + ERROR("Cannot parse as DER-encoded CMS blob"); + BIO_free(out); + return -EFAULT; + } + + if (!CMS_decrypt(cms, dgst->pkey, dgst->decrypt_cert, NULL, out, 0)) { + ERROR("Decrypting failed"); + CMS_ContentInfo_free(cms); + BIO_free(out); + return -EFAULT; + } + CMS_ContentInfo_free(cms); + BIO_free(dgst->cryptbuf); + dgst->cryptbuf = NULL; + dgst->plain = out; + } + + int n = BIO_read(dgst->plain, buf, BUFSIZE); + if (n < 0) { + ERROR("Reading from plain BIO failed"); + return -EFAULT; + } + *outlen = n; + + if (BIO_pending(dgst->plain) > 0) { + /* more plain data is pending, let decrypt_step call final() again */ + return -EAGAIN; + } + + BIO_free(dgst->plain); + dgst->plain = NULL; + return 0; +} + +static void openssl_CMS_DECRYPT_cleanup(void *ctx) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + if (dgst) { + EVP_CIPHER_CTX_free(SSL_GET_CTXDEC(dgst)); + if (dgst->cryptbuf) + BIO_free(dgst->cryptbuf); + if (dgst->plain) + BIO_free(dgst->plain); + free(dgst); + } + + dgst = NULL; + + return; +} + +__attribute__((constructor)) +static void openssl_cms_probe(void) +{ + opensslCMS.DECRYPT_init = openssl_CMS_DECRYPT_init; + opensslCMS.DECRYPT_update = openssl_CMS_DECRYPT_update; + opensslCMS.DECRYPT_final = openssl_CMS_DECRYPT_final; + opensslCMS.DECRYPT_cleanup = openssl_CMS_DECRYPT_cleanup; + (void)register_cryptolib("opensslCMS", &opensslCMS); +} diff --git a/crypto/swupdate_openssl.h b/crypto/swupdate_openssl.h index 4dc79503..2773cbc8 100644 --- a/crypto/swupdate_openssl.h +++ b/crypto/swupdate_openssl.h @@ -35,12 +35,15 @@ struct openssl_digest { EVP_PKEY *pkey; /* this is used for RSA key */ EVP_PKEY_CTX *ckey; /* this is used for RSA key */ X509_STORE *certs; /* this is used if CMS is set */ + X509 *decrypt_cert; EVP_MD_CTX *ctx; #if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_CIPHER_CTX ctxdec; #else EVP_CIPHER_CTX *ctxdec; #endif + BIO *cryptbuf; + BIO *plain; }; #if OPENSSL_VERSION_NUMBER < 0x10100000L diff --git a/include/swupdate_aes.h b/include/swupdate_aes.h index acb69573..b019f8cd 100644 --- a/include/swupdate_aes.h +++ b/include/swupdate_aes.h @@ -25,6 +25,7 @@ typedef enum { AES_CTR_128, AES_CTR_192, AES_CTR_256, + CMS, AES_UNKNOWN } cipher_t;