Commit faffa448b7 for strongswan.org

commit faffa448b7728d8e849471800a35d44322f7e36e
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Tue Sep 22 13:17:08 2026 +0200

    asn1: Strip zero-padding when creating an ASN.1 INTEGER

    As DER requires a minimal encoding, this can lead to invalid encodings
    if the zero-padded value is not actually negative and multiple zero-bytes
    would always be wrong.

    The one particular case that triggered this change is the encoding of
    ECDSA signatures in the TPM 2.0 implementations in libtpmtss.  While the
    padding requirements are not clearly specified in the TPM 2.0 Library
    specs (only for ECC points, not for signatures), the reference
    implementation does zero-pad r and s to the size of the curve's order.

    Assuming actual TPMs behave the same (as is recommended by the specs),
    there was a 1 in 256 chance of creating a signature that would not
    successfully validate with a strict validator.  For each integer, there
    is roughly a 1 in 256 chance the first byte is zero and a 1 in 2 chance
    the second byte is < 0x80 (otherwise, the resulting encoding was correct
    by accident).

    A similar case is in the `botan` plugin where ECDSA signature are also
    converted to DER without stripping zero-padding.

    Closes strongswan/strongswan#3149

    Fixes: e74e920bbcec ("libtpmtss: Support for TSS2 v2 libraries")
    Fixes: e8736028e6a2 ("Implement signatures with private keys bound to TPM 2.0")
    Fixes: af26cc4d8542 ("botan: Add Botan plugin to libstrongswan")

diff --git a/src/libstrongswan/asn1/asn1.c b/src/libstrongswan/asn1/asn1.c
index a08ae5aff5..b3c08ffb01 100644
--- a/src/libstrongswan/asn1/asn1.c
+++ b/src/libstrongswan/asn1/asn1.c
@@ -843,19 +843,21 @@ chunk_t asn1_bitstring(const char *mode, chunk_t content)
  */
 chunk_t asn1_integer(const char *mode, chunk_t content)
 {
-	chunk_t zero = chunk_from_chars(0x00), object;
+	chunk_t zero = chunk_from_chars(0x00), object, to_free = chunk_empty;
 	size_t len;
 	u_char *pos;
-	bool move;

-	if (content.len == 0)
-	{	/* make sure 0 is encoded properly */
+	if (*mode == 'm')
+	{
+		to_free = content;
+	}
+	if (!content.len)
+	{	/* make sure empty input ("0") is encoded properly */
 		content = zero;
-		move = FALSE;
 	}
 	else
-	{
-		move = (*mode == 'm');
+	{	/* skip any zero-padding to avoid encoding values incorrectly */
+		content = chunk_skip_zero(content);
 	}

 	/* ASN.1 integers must be positive numbers in two's complement */
@@ -867,9 +869,9 @@ chunk_t asn1_integer(const char *mode, chunk_t content)
 	}
 	memcpy(pos, content.ptr, content.len);

-	if (move)
+	if (to_free.ptr)
 	{
-		free(content.ptr);
+		free(to_free.ptr);
 	}
 	return object;
 }
diff --git a/src/libstrongswan/asn1/asn1.h b/src/libstrongswan/asn1/asn1.h
index a94c82e302..5724cae5eb 100644
--- a/src/libstrongswan/asn1/asn1.h
+++ b/src/libstrongswan/asn1/asn1.h
@@ -294,7 +294,7 @@ chunk_t asn1_bitstring(const char *mode, chunk_t content);
  * Build an ASN.1 INTEGER object
  *
  * @param mode		'c' for copy or 'm' for move
- * @param content	content of the INTEGER
+ * @param content	content of the INTEGER (zero-padding is removed)
  * @return			chunk containing the ASN.1 coded INTEGER
  */
 chunk_t asn1_integer(const char *mode, chunk_t content);
diff --git a/src/libstrongswan/tests/suites/test_asn1.c b/src/libstrongswan/tests/suites/test_asn1.c
index 03a230ae27..58d99cdd56 100644
--- a/src/libstrongswan/tests/suites/test_asn1.c
+++ b/src/libstrongswan/tests/suites/test_asn1.c
@@ -727,16 +727,16 @@ START_TEST(test_asn1_integer)
 	chunk_t b1 = chunk_from_chars(0x02, 0x01, 0x7f);
 	chunk_t b2 = chunk_from_chars(0x02, 0x02, 0x00, 0x80);

-	chunk_t c0 = chunk_empty;
-	chunk_t c1 = chunk_from_chars(0x7f);
-	chunk_t c2 = chunk_from_chars(0x80);
-	chunk_t c3 = chunk_from_chars(0x00, 0x80);
-
 	testdata_t test[] = {
-		{ b0, c0 },
-		{ b1, c1 },
-		{ b2, c2 },
-		{ b2, c3 }
+		{ b0, chunk_empty },
+		{ b0, chunk_from_chars(0x00) },
+		{ b0, chunk_from_chars(0x00, 0x00) },
+		{ b1, chunk_from_chars(0x7f) },
+		{ b1, chunk_from_chars(0x00, 0x7f) },
+		{ b1, chunk_from_chars(0x00, 0x00, 0x7f) },
+		{ b2, chunk_from_chars(0x80) },
+		{ b2, chunk_from_chars(0x00, 0x80) },
+		{ b2, chunk_from_chars(0x00, 0x00, 0x80) },
 	};

 	chunk_t a = chunk_empty;