Hi Eddie,

Thanks for the follow-up.

This mail is only feedback for the "we need to fix the OpenSSL engine support breaking builds on OpenSSL 4.x (and some distros with engines disabled in OpenSSL 3.x) but keep OpenSSL engine support for other distros that still support it" part. I'll get to the "support for OpenSSL providers" part in other mails so we don't mix things up.

Patches 2 and 3 are not applicable to "unbreaking-build-on-openssl-4" part of this series, please keep them separate for a v6.

On 10/29/26 9:13 PM, Eddie Kovsky wrote:
Add support for the Provider API while continuing to support the existing
Engine API on distros shipping older releases of OpenSSL.

This is based on similar work contributed by Jan Stancek updating Linux
to use the Provider interface.

     commit 558bdc45dfb2669e1741384a0c80be9c82fa052c
     Author: Jan Stancek <[email protected]>
     Date:   Fri Sep 20 19:52:48 2024 +0300

         sign-file,extract-cert: use pkcs11 provider for OPENSSL MAJOR >= 3

Tested-by: Enric Balletbo i Serra <[email protected]>
Tested-by: Mark Kettenis <[email protected]>
Signed-off-by: Eddie Kovsky <[email protected]>
---
  lib/aes/aes-encrypt.c |   4 +-
  lib/rsa/rsa-sign.c    | 102 +++++++++++++++++++++++++++++++++++++++---
  2 files changed, 100 insertions(+), 6 deletions(-)

diff --git a/lib/aes/aes-encrypt.c b/lib/aes/aes-encrypt.c
index def33a3b3248..0cb2221df800 100644
--- a/lib/aes/aes-encrypt.c
+++ b/lib/aes/aes-encrypt.c
@@ -16,7 +16,9 @@
  #include <openssl/err.h>
  #include <openssl/ssl.h>
  #include <openssl/evp.h>
-#include <openssl/engine.h>
+#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+# include <openssl/engine.h>
+#endif

question: Considering there are no other changes in this file, is this include actually needed? Maybe all we need to do is to unconditionally remove the include (in a separate patch then, to highlight it really isn't needed and not related to unbreaking the build).

  #include <uboot_aes.h>
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
diff --git a/lib/rsa/rsa-sign.c b/lib/rsa/rsa-sign.c
index 0e38c9e802fd..361ed0647bc6 100644
--- a/lib/rsa/rsa-sign.c
+++ b/lib/rsa/rsa-sign.c
@@ -19,7 +19,47 @@
  #include <openssl/err.h>
  #include <openssl/ssl.h>
  #include <openssl/evp.h>
-#include <openssl/engine.h>
+#if OPENSSL_VERSION_MAJOR >= 3
+# define USE_PKCS11_PROVIDER
+# include <err.h>
+# include <openssl/provider.h>
+# include <openssl/store.h>
+#else

issue: remove for the "unbreaking-build-on-openssl-4" patch as that's provider-specific.

+# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+#  define USE_PKCS11_ENGINE
+#  include <openssl/engine.h>
+# endif
+#endif
+

issue: The ifdefery logic is incorrect. This will break distros that are running OpenSSL 3.x but still have engine support. Such is (was, when I reported back in May) the case for Debian Trixie (13) and ArchLinux (they still are both on OpenSSL 3.x). In any case, we still want to support old(er) distros. I could build this code fine on Fedora 44 with OpenSSL 3 and openssl-devel-engine installed, so it should still work the same way now.

So, we know for sure that OpenSSL 4.x doesn't support engine so that's an easy check. For earlier versions, it actually depends on whether OpenSSL is built with engine support and we are allowed to use it. From what I gathered looking at how Fedora handled the "built with engine supports but doesn't want to let you build new software with engines unless you have openssl-devel-engine package installed" part and how Debian allows it, I figured the following (which almost matches your #else).

#if OPENSSL_VERSION_NUMBER < 0x40000000L && !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
# define OSSL_ENGINE_SUPPORTED
# include <openssl/engine.h>
#endif

issue: please rename USE_PKCS11_ENGINE, we actually support other engines so this is misleading and we technically could support both engines AND providers on OpenSSL 3.x. I can suggest OSSL_ENGINE_SUPPORTED.

+#ifdef USE_PKCS11_PROVIDER
+#define ERR(cond, fmt, ...)                            \
+       do {                                            \
+               bool __cond = (cond);                   \
+               drain_openssl_errors(__LINE__, 0);      \
+               if (__cond) {                           \
+                       errx(1, fmt, ## __VA_ARGS__);   \
+               }                                       \
+       } while (0)
+
+static void drain_openssl_errors(int l, int silent)
+{
+       const char *file;
+       char buf[120];
+       int e, line;
+
+       if (ERR_peek_error() == 0)
+               return;
+       if (!silent)
+               fprintf(stderr, "At line:%d:\n", l);
+
+       while ((e = ERR_peek_error_line(&file, &line))) {
+               ERR_error_string(e, buf);
+               if (!silent)
+                       fprintf(stderr, "- SSL %s: %s:%d\n", buf, file, line);
+               ERR_get_error();
+       }
+}
+#endif

issue: remove for the "unbreaking-build-on-openssl-4" patch as that's provider-specific.

  static int rsa_err(const char *msg)
  {
@@ -94,10 +134,11 @@ err_cert:
   *
   * @keydir:   Key prefix
   * @name      Name of key
- * @engine     Engine to use
+ * @engine     Engine to use or NULL when using pkcs11 provider

issue: unnecessary change for the "unbreaking-build-on-openssl-4" patch as that's provider-specific.

   * @evpp      Returns EVP_PKEY object, or NULL on failure
   * Return: 0 if ok, -ve on error (in which case *evpp will be set to NULL)
   */
+#ifdef USE_PKCS11_ENGINE
  static int rsa_engine_get_pub_key(const char *keydir, const char *name,
                                  ENGINE *engine, EVP_PKEY **evpp)
  {
@@ -157,21 +198,24 @@ static int rsa_engine_get_pub_key(const char *keydir, 
const char *name,
return 0;
  }
+#endif
/**
   * rsa_get_pub_key() - read a public key
   *
   * @keydir:   Directory containing the key (PEM file) or key prefix (engine)
   * @name      Name of key file (will have a .crt extension)
- * @engine     Engine to use
+ * @engine     Engine to use or NULL when using pkcs11 provider

issue: unnecessary change for the "unbreaking-build-on-openssl-4" patch as that's provider-specific.

   * @evpp      Returns EVP_PKEY object, or NULL on failure
   * Return: 0 if ok, -ve on error (in which case *evpp will be set to NULL)
   */
  static int rsa_get_pub_key(const char *keydir, const char *name,
                           ENGINE *engine, EVP_PKEY **evpp)
  {
+#ifdef USE_PKCS11_ENGINE
        if (engine)
                return rsa_engine_get_pub_key(keydir, name, engine, evpp);
+#endif

issue: we should fail hard if the user requests to use engines but mkimage/rsa is built without engine support otherwise we may incorrectly sign/verify things with keys the user didn't expect. For context, I've actually managed to build an SPL without a pubkey and "verify" U-Boot FIT. Fortunately enough, I caught this before shipping an update to a secure-boot'ed device but the fewest surprises like those we have for the user, the better.

mkimage will still happily accept the -N argument which sets the engine
parameter, even if engines aren't supported.

I think if you have a file key with the same name as in the engine,
you'll then sign with the wrong key.

Think

mkimage -N myengine -g key

and you have a key.key in the local directory. If you compile with
OpenSSL engines supported, you'll essentially use myengine:key, if not,
you'll use key.key, without mkimage even notifying you by using the same
command.

What I ended up doing is:

static int rsa_engine_get_pub_key(const char *keydir, const char *name,
                                  ENGINE *engine, EVP_PKEY **evpp)
{
#ifdef OSSL_ENGINE_SUPPORTED
[same code as today]
#else
        fprintf(stderr, "rsa-sign compiled without engine support\n");
        return -ENOTSUP;
#endif
}

so we don't need to patch all callers of rsa_engine_get_pub_key() to handle engines being supported or not. The same logic can be applied to rsa_engine_get_priv_key(), rsa_engine_init(), rsa_engine_remove() and then you have to modify much less of the file since you don't have to patch every caller of the function (which you had to do because the function doesn't exist anymore in your implementation).

        return rsa_pem_get_pub_key(keydir, name, evpp);
  }
@@ -207,13 +251,44 @@ static int rsa_pem_get_priv_key(const char *keydir, const char *name,
                return -ENOENT;
        }
+#ifdef USE_PKCS11_PROVIDER
+       EVP_PKEY *private_key = NULL;
+       OSSL_STORE_CTX *store;
+
+       if (!OSSL_PROVIDER_try_load(NULL, "pkcs11", true))
+               ERR(1, "OSSL_PROVIDER_try_load(pkcs11)");
+       if (!OSSL_PROVIDER_try_load(NULL, "default", true))
+               ERR(1, "OSSL_PROVIDER_try_load(default)");
+
+       store = OSSL_STORE_open(path, NULL, NULL, NULL, NULL);
+       ERR(!store, "OSSL_STORE_open");
+
+       while (!OSSL_STORE_eof(store)) {
+               OSSL_STORE_INFO *info = OSSL_STORE_load(store);
+
+               if (!info) {
+                       drain_openssl_errors(__LINE__, 0);
+                       continue;
+               }
+               if (OSSL_STORE_INFO_get_type(info) == OSSL_STORE_INFO_PKEY) {
+                       private_key = OSSL_STORE_INFO_get1_PKEY(info);
+                       ERR(!private_key, "OSSL_STORE_INFO_get1_PKEY");
+               }
+               OSSL_STORE_INFO_free(info);
+               if (private_key)
+                       break;
+       }
+       OSSL_STORE_close(store);
+
+       *evpp = private_key;
+#else

issue: remove for the "unbreaking-build-on-openssl-4" patch as that's provider-specific.

        if (!PEM_read_PrivateKey(f, evpp, NULL, path)) {
                rsa_err("Failure reading private key");
                fclose(f);
                return -EPROTO;
        }
        fclose(f);
-
+#endif
        return 0;
  }
@@ -226,6 +301,7 @@ static int rsa_pem_get_priv_key(const char *keydir, const char *name,
   * @evpp      Returns EVP_PKEY object, or NULL on failure
   * Return: 0 if ok, -ve on error (in which case *evpp will be set to NULL)
   */
+#ifdef USE_PKCS11_ENGINE
  static int rsa_engine_get_priv_key(const char *keydir, const char *name,
                                   const char *keyfile,
                                   ENGINE *engine, EVP_PKEY **evpp)
@@ -293,22 +369,25 @@ static int rsa_engine_get_priv_key(const char *keydir, 
const char *name,
return 0;
  }
+#endif
/**
   * rsa_get_priv_key() - read a private key
   *
   * @keydir:   Directory containing the key (PEM file) or key prefix (engine)
   * @name      Name of key
- * @engine     Engine to use for signing
+ * @engine     Engine to use or NULL when using pkcs11 provider

issue: unnecessary change for the "unbreaking-build-on-openssl-4" patch as that's provider-specific.

chore: (not necessarily you, not necessarily now) we need to fix binman tests when engine support is disabled (e.g. tools/binman/test/fit/dummy-rsa-engine.c won't compile, and we shouldn't run engine-related tests in binman, and CST for NXP stuff doesn't (didn't at least) compile either).

Cheers,
Quentin

Reply via email to