[PATCH RFC] fit: allow to specify RSAPSS padding salt length magic value

Quentin Schulz foss+uboot at 0leil.net
Fri Apr 11 18:32:09 CEST 2025


From: Quentin Schulz <quentin.schulz at cherry.de>

Before commit 1b99c15d73c1 ("lib: rsa: Set conventional salt length
RSA-PSS parameter"), the salt length for PSS padding was openSSL's
default. Prior to openSSL 3.1, this was the maximum allowed value ("max"
when passed from CLI or RSA_PSS_SALTLEN_MAX (-3) if from library).[1]
Starting from 3.1, the default is now "maximized up to the digest
length" ("auto-digestmax" from CLI or RSA_PSS_SALTLEN_AUTO_DIGEST_MAX
(-4) from library).[2]

After commit 1b99c15d73c1 ("lib: rsa: Set conventional salt length
RSA-PSS parameter"), the salt length has been forced to the digest
length, but manually instead of using "digest" from CLI or
RSA_PSS_SALTLEN_DIGEST from lib.

Before commit 81eff51047e2 ("lib: rsa: Update function
padding_pss_verify (any-salt)"), verifying a signature with PSS padding
could only be done when the padding was using the max value for the salt
length. While that is now history and the code should now detect the
appropriate length when verifying, this lack of support in older
versions bring some issue. Indeed, U-Boot is typically split in two
binaries (at least on Rockchip platforms), one for U-Boot FIT (with
U-Boot proper, TF-A, TEE, etc..) which can be signed and one for the
stage(s) before U-Boot proper, who's in charge of verifying the FIT
signature. It would be possible to update only U-Boot FIT and not the
other prior stages binary in which case the device would be bricked if
it has a pre-81eff51047e2 prior stages with a U-Boot FIT signed from
post-1b99c15d73c1 or from pre-1b99c15d73c1 with openSSL >= 3.1.

This introduces a "padding-saltlen" string property to the signature
node of a FIT node which expects the CLI name of the openSSL option for
setting the RSAPSS padding salt length, it can be:
- "max"
- "digest"
- "auto" ("max" alias, but U-Boot internals pass "auto" to openSSL)
- "auto-digestmax" (only available on openSSL >= 3.1)

If it is missing, "digest" is assumed, to match current behavior.

Note that while openSSL allows to pass the length as number, the string
magic values are represented as negative numbers. FDT only allowing
u32/u64 for numbers, we cannot represent those negative numbers in the
same property as the magic string values. Hence the choice to go with a
string property.

[1] https://docs.openssl.org/3.0/man3/EVP_PKEY_CTX_ctrl/#rsa-parameters
[2] https://docs.openssl.org/3.4/man3/EVP_PKEY_CTX_ctrl/#rsa-parameters

Signed-off-by: Quentin Schulz <quentin.schulz at cherry.de>
---
How to test:
- generate keys and certs at the root of U-Boot's source directory:
  - openssl genpkey -algorithm RSA -out dev.key \
	  -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537
  - openssl req -batch -new -x509 -key dev.key -out dev.crt
- apply the following diff for generating signed U-Boot FIT + adding the
  pubkey to U-Boot SPL + making signature verification required:

 diff --git a/arch/arm/dts/rockchip-u-boot.dtsi b/arch/arm/dts/rockchip-u-boot.dtsi
 index c8c928c7e50..e5fd8c73602 100644
 --- a/arch/arm/dts/rockchip-u-boot.dtsi
 +++ b/arch/arm/dts/rockchip-u-boot.dtsi
 @@ -29,7 +29,14 @@
  			u-boot-tpl {
  			};
  #endif
 -			u-boot-spl {
 +			section {
 +				u-boot-spl-nodtb {
 +				};
 +				u-boot-spl-pubkey-dtb {
 +					algo = "sha256,rsa2048";
 +					required = "conf";
 +					key-name-hint = "dev";
 +				};
  			};
  		};
  
 @@ -45,6 +52,7 @@
  			filename = "u-boot.itb";
  			fit,external-offset = <CONFIG_FIT_EXTERNAL_OFFSET>;
  			fit,align = <512>;
 +			fit,sign;
  			offset = <CONFIG_SPL_PAD_TO>;
  			images {
  				u-boot {
 @@ -162,6 +170,13 @@
  					fit,firmware = "op-tee", "u-boot";
  #endif
  					fit,loadables;
 +
 +					signature {
 +						algo = "sha256,rsa2048";
 +						key-name-hint = "dev";
 +						padding = "pss";
 +						sign-images = "fdt", "loadables", "firmware";
 +					};
  				};
  			};
  		};

You need RSA and RSAPSS support in SPL for this to work (it'll compile
just fine but always fail to verify).

It'll successfully boot.

Now revert commit 81eff51047e2 ("lib: rsa: Update function
padding_pss_verify (any-salt)") and regenerate the images.

U-Boot SPL will fail to verify U-Boot FIT signature and refuse to boot.

Now, add `padding-saltlen = "max";` to the signature node and regenerate
the images, U-Boot SPL will now be able to verify U-Boot FIT signature.

Note that I have no security background and I essentially just made sure
it compiles and boots (or refuses to boot).

This is marked as RFC because we would need to add the property to
https://fitspec.osfw.foundation/#configuration-signature-nodes
and
https://fitspec.osfw.foundation/#image-signature-nodes
I assume. Don't want to suggest something to the FIT spec if it's anyway
going to be denied in U-Boot :)

Note that I actually need a patch like this (for now I just hardcoded
the salt length to use MAX instead of digest length) for one of our
products. I don't mind keeping it downstream but I felt like this was a
semi-reasonable approach to making this configurable.

What do you think?
---
 include/image.h    |  1 +
 lib/rsa/rsa-sign.c | 34 +++++++++++++++++++++++++++-------
 tools/image-host.c |  3 +++
 3 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/include/image.h b/include/image.h
index c1db8383459c9d7ce1ecc318ccccb4627a7066e5..785852a92f838b7b89bede1c6e6c9a346a9efd1d 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1511,6 +1511,7 @@ struct image_sign_info {
 	int required_keynode;		/* Node offset of key to use: -1=any */
 	const char *require_keys;	/* Value for 'required' property */
 	const char *engine_id;		/* Engine to use for signing */
+	const char *saltlen_name;	/* OpenSSL RSA PSS salt length magic value */
 	/*
 	 * Note: the following two fields are always valid even w/o
 	 * RSA_VERIFY_WITH_PKEY in order to make sure this structure is
diff --git a/lib/rsa/rsa-sign.c b/lib/rsa/rsa-sign.c
index fa9e143b4ca8cbc50297acdfbb4ccb39cb160f84..10b25094a2fada08eb37c38a5d618b6e07972139 100644
--- a/lib/rsa/rsa-sign.c
+++ b/lib/rsa/rsa-sign.c
@@ -384,7 +384,7 @@ static void rsa_engine_remove(ENGINE *e)
 static int rsa_sign_with_key(EVP_PKEY *pkey, struct padding_algo *padding_algo,
 			     struct checksum_algo *checksum_algo,
 		const struct image_region region[], int region_count,
-		uint8_t **sigp, uint *sig_size)
+		uint8_t **sigp, uint *sig_size, const char *saltlen_name)
 {
 	EVP_PKEY_CTX *ckey;
 	EVP_MD_CTX *context;
@@ -423,17 +423,37 @@ static int rsa_sign_with_key(EVP_PKEY *pkey, struct padding_algo *padding_algo,
 
 	if (CONFIG_IS_ENABLED(FIT_RSASSA_PSS) && padding_algo &&
 	    !strcmp(padding_algo->name, "pss")) {
+		int saltlen;
+
 		if (EVP_PKEY_CTX_set_rsa_padding(ckey,
 						 RSA_PKCS1_PSS_PADDING) <= 0) {
 			ret = rsa_err("Signer padding setup failed");
 			goto err_sign;
 		}
 
-		/* Per RFC 3447 (and convention) the Typical salt length is the
-		 * length of the output of the digest algorithm.
-		 */
-		if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ckey,
-						     checksum_algo->checksum_len) <= 0) {
+		if (saltlen_name && !strcmp(saltlen_name, "max")) {
+			saltlen = RSA_PSS_SALTLEN_MAX;
+		} else if (saltlen_name && !strcmp(saltlen_name, "auto")) {
+			saltlen = RSA_PSS_SALTLEN_AUTO;
+#if OPENSSL_VERSION_NUMBER >= 0x31000000L
+		} else if (saltlen_name && !strcmp(saltlen_name, "auto-digestmax")) {
+			saltlen = RSA_PSS_SALTLEN_AUTO_DIGEST_MAX;
+#endif
+		} else if (saltlen_name && !strcmp(saltlen_name, "digest")) {
+			saltlen = RSA_PSS_SALTLEN_DIGEST;
+		} else if (saltlen_name) {
+			fprintf(stderr, "Invalid salt length '%s'\n", saltlen_name);
+			rsa_err("Invalid salt length");
+			goto err_sign;
+		} else {
+			/* Per RFC 3447 (and convention) the Typical salt length is the
+			 * length of the output of the digest algorithm.
+			 */
+			debug("padding saltlen missing, assuming \"digest\"\n");
+			saltlen = RSA_PSS_SALTLEN_DIGEST;
+		}
+
+		if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ckey, saltlen) <= 0) {
 			ret = rsa_err("Signer salt length setup failed");
 			goto err_sign;
 		}
@@ -491,7 +511,7 @@ int rsa_sign(struct image_sign_info *info,
 	if (ret)
 		goto err_priv;
 	ret = rsa_sign_with_key(pkey, info->padding, info->checksum, region,
-				region_count, sigp, sig_len);
+				region_count, sigp, sig_len, info->saltlen_name);
 	if (ret)
 		goto err_sign;
 
diff --git a/tools/image-host.c b/tools/image-host.c
index a9b8690276386aa21983612c6a306b7897a0af26..63055f2044ac5c320c52dc87e59b275d4bc79e4a 100644
--- a/tools/image-host.c
+++ b/tools/image-host.c
@@ -175,6 +175,7 @@ static int fit_image_setup_sig(struct image_sign_info *info,
 {
 	const char *node_name;
 	const char *padding_name;
+	const char *saltlen_name;
 
 	node_name = fit_get_name(fit, noffset, NULL);
 	if (!algo_name) {
@@ -187,6 +188,7 @@ static int fit_image_setup_sig(struct image_sign_info *info,
 	}
 
 	padding_name = fdt_getprop(fit, noffset, "padding", NULL);
+	saltlen_name = fdt_getprop(fit, noffset, "padding-saltlen", NULL);
 
 	memset(info, '\0', sizeof(*info));
 	info->keydir = keydir;
@@ -198,6 +200,7 @@ static int fit_image_setup_sig(struct image_sign_info *info,
 	info->checksum = image_get_checksum_algo(algo_name);
 	info->crypto = image_get_crypto_algo(algo_name);
 	info->padding = image_get_padding_algo(padding_name);
+	info->saltlen_name = saltlen_name;
 	info->require_keys = require_keys;
 	info->engine_id = engine_id;
 	if (!info->checksum || !info->crypto) {

---
base-commit: cb7555e93075114fe4af0adb806877ac4d4ef80d
change-id: 20250411-rsapss-saltlen-b8d7a676565a

Best regards,
-- 
Quentin Schulz <quentin.schulz at cherry.de>



More information about the U-Boot mailing list