Commit e2fa67e8fc for strongswan.org

commit e2fa67e8fc794ee5258c26eaa9deb5a69522dfe2
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Tue Mar 24 18:05:01 2026 +0100

    gmp: Avoid crash and timing leaks in PKCS#1 v1.5 decryption padding validation

    This fixes a potential crash due to a null-pointer dereference if rsadp()
    returns NULL (e.g. with an all-zero ciphertext).

    And it also implements the PKCS#1 v1.5 decryption padding check in
    constant time.

    The timing leak caused by the previous implementation was measured at
    ~17.5 μs at 3 GHz, which could allow a Bleichenbacher-like attack in
    LAN environments.  However, because of how RSA encryption is used in
    strongSwan, this is not that much of an issue in practice.  The mechanism
    is only used for two use cases.  One is SCEP/EST via PKCS#7 enveloped
    data.  Fortunately, this can not be triggered in significant numbers by
    an attacker.  The other use case is TLS as used by EAP methods (EAP-TLS,
    EAP-PEAP/TTLS) during the authentication.  While the cipher suites that
    use RSA encryption are still enabled by default, the TLS messages are
    wrapped in EAP and encrypted by IKE, making any kind of attack difficult.

    Note that the gmp plugin isn't enabled anymore by default.  And even
    before that, most setups had the openssl plugin enabled, which has
    priority over the gmp plugin.  So it's unlikely the plugin was used in
    practice.

    Fixes: d615ffdcf3cd ("implement gmp_rsa_private_key.decrypt()")
    Fixes: CVE-2026-35334

diff --git a/src/libstrongswan/plugins/gmp/gmp_rsa_private_key.c b/src/libstrongswan/plugins/gmp/gmp_rsa_private_key.c
index 63e8f54cdd..64f730fdf7 100644
--- a/src/libstrongswan/plugins/gmp/gmp_rsa_private_key.c
+++ b/src/libstrongswan/plugins/gmp/gmp_rsa_private_key.c
@@ -495,8 +495,8 @@ METHOD(private_key_t, decrypt, bool,
 	private_gmp_rsa_private_key_t *this, encryption_scheme_t scheme,
 	void *params, chunk_t crypto, chunk_t *plain)
 {
-	chunk_t em, stripped;
-	bool success = FALSE;
+	chunk_t em;
+	u_int valid, i, j, found_sep = 0, sep_index = 0, m_index;

 	if (scheme != ENCRYPT_RSA_PKCS1)
 	{
@@ -505,33 +505,51 @@ METHOD(private_key_t, decrypt, bool,
 		return FALSE;
 	}
 	/* rsa decryption using PKCS#1 RSADP */
-	stripped = em = rsadp(this, crypto);
+	em = rsadp(this, crypto);
+	if (em.len != this->k)
+	{
+		return FALSE;
+	}

-	/* PKCS#1 v1.5 8.1 encryption-block formatting (EB = 00 || 02 || PS || 00 || D) */
+	/* PKCS#1 v1.5, RFC 8017, section 7.2.2 message structure:
+	 * EM = 00 || 02 || PS || 00 || M */

 	/* check for hex pattern 00 02 in decrypted message */
-	if ((*stripped.ptr++ != 0x00) || (*(stripped.ptr++) != 0x02))
+	valid  = constant_time_eq(em.ptr[0], 0x00);
+	valid &= constant_time_eq(em.ptr[1], 0x02);
+
+	/* the plaintext data starts after first 0x00 byte */
+	for (i = 2; i < em.len; i++)
 	{
-		DBG1(DBG_LIB, "incorrect padding - probably wrong rsa key");
-		goto end;
+		u_int zero = constant_time_eq(em.ptr[i], 0x00);
+
+		sep_index = constant_time_select(i, sep_index, ~found_sep & zero);
+		found_sep |= zero;
 	}
-	stripped.len -= 2;

-	/* the plaintext data starts after first 0x00 byte */
-	while (stripped.len-- > 0 && *stripped.ptr++ != 0x00)
+	/* make sure PS is at least eight bytes long (plus the initial bytes) */
+	valid &= constant_time_ge(sep_index, 10);
+
+	/* instead of copying the message directly, we try not to reveal the message
+	 * length i.e. where the 0x00 byte was. and since clearing a chunk is
+	 * relatively efficient, i.e. doesn't leak much, we always allocate and copy
+	 * a value and then clear it if the structure was invalid */
+	m_index = constant_time_select(sep_index + 1, 11, valid);

-	if (stripped.len == 0)
+	*plain = chunk_alloc(this->k);
+	for (i = 0, j = 0; i < em.len; i++)
 	{
-		DBG1(DBG_LIB, "no plaintext data");
-		goto end;
+		plain->ptr[j] = em.ptr[i];
+		j += constant_time_ge(i, m_index);
 	}
+	plain->len = j;

-	*plain = chunk_clone(stripped);
-	success = TRUE;
-
-end:
+	if (!valid)
+	{
+		chunk_clear(plain);
+	}
 	chunk_clear(&em);
-	return success;
+	return valid;
 }

 METHOD(private_key_t, get_keysize, int,