From 25a93ca5887b8833c89783695642762fe3a5addc Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Fri, 30 Oct 2026 21:27:15 +0100 Subject: [PATCH 1/7] Add support for ECDSA signatures --- src/Alg/Signature/ECDSA.php | 39 +++++++++++++++++++ .../Signature/SignatureAlgorithmFactory.php | 4 +- src/Constants.php | 22 +++++++++++ src/XML/ds/SignatureMethod.php | 1 + tests/XML/SignedElementTest.php | 35 +++++++++++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/Alg/Signature/ECDSA.php diff --git a/src/Alg/Signature/ECDSA.php b/src/Alg/Signature/ECDSA.php new file mode 100644 index 00000000..d8c308f4 --- /dev/null +++ b/src/Alg/Signature/ECDSA.php @@ -0,0 +1,39 @@ + 16, ]; + /** @var array */ + public static array $ECDSA_DIGESTS = [ + self::SIG_ECDSA_SHA1 => self::DIGEST_SHA1, + self::SIG_ECDSA_SHA224 => self::DIGEST_SHA224, + self::SIG_ECDSA_SHA256 => self::DIGEST_SHA256, + self::SIG_ECDSA_SHA384 => self::DIGEST_SHA384, + self::SIG_ECDSA_SHA512 => self::DIGEST_SHA512, + ]; + /** @var array */ public static array $RSA_DIGESTS = [ self::SIG_RSA_SHA1 => self::DIGEST_SHA1, diff --git a/src/XML/ds/SignatureMethod.php b/src/XML/ds/SignatureMethod.php index d94eff6e..845b3dda 100644 --- a/src/XML/ds/SignatureMethod.php +++ b/src/XML/ds/SignatureMethod.php @@ -51,6 +51,7 @@ public function __construct( Assert::oneOf( $Algorithm->getValue(), array_merge( + array_keys(C::$ECDSA_DIGESTS), array_keys(C::$RSA_DIGESTS), array_keys(C::$HMAC_DIGESTS), ), diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php index 166e07f3..fd727c79 100644 --- a/tests/XML/SignedElementTest.php +++ b/tests/XML/SignedElementTest.php @@ -328,6 +328,41 @@ public function testSuccessfulVerifyingRsaPssSignature(): void } + /** + * Verify an ECDSA signature + */ + public function testSuccessfulVerifyingEcdsaSignature(): void + { + // sign using ECDSA_SHA256 + $customSignable = $this->sign(C::SIG_ECDSA_SHA256); + // No-op to _actually_ sign the element and set the signature-property. + $customSignable = CustomSignable::fromXML($customSignable->toXML()); + + $signature = $customSignable->getSignature(); + + $this->assertEquals( + C::SIG_ECDSA_SHA256, + $signature + ->getSignedInfo() + ->getSignatureMethod() + ->getAlgorithm() + ->getValue(), + ); + + $certificate = new X509Certificate($this->certificate); + + $factory = new SignatureAlgorithmFactory(); + $verifier = $factory->getAlgorithm( + C::SIG_ECDSA_SHA256, + $certificate->getPublicKey(), + ); + + $verified = $customSignable->verify($verifier); + + $this->assertInstanceOf(CustomSignable::class, $verified); + } + + /** * Negative: verify a RSA-PSS signature using PKCS#1 v1.5 */ From a286358626f1e37478f12dece2209e16f1652219 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Fri, 30 Oct 2026 22:31:56 +0100 Subject: [PATCH 2/7] Add ECDSA test-keys --- resources/certificates/ecdsa_p256.crt | 11 +++++++++++ resources/keys/ecdsa_p256.key | 5 +++++ resources/keys/ecdsa_p256.pub | 4 ++++ src/TestUtils/PEMCertificatesMock.php | 6 ++++++ 4 files changed, 26 insertions(+) create mode 100644 resources/certificates/ecdsa_p256.crt create mode 100644 resources/keys/ecdsa_p256.key create mode 100644 resources/keys/ecdsa_p256.pub diff --git a/resources/certificates/ecdsa_p256.crt b/resources/certificates/ecdsa_p256.crt new file mode 100644 index 00000000..21e1e5b0 --- /dev/null +++ b/resources/certificates/ecdsa_p256.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlTCCATugAwIBAgIUWHvNlHco/kXqoHF43TZjwf4F2QwwCgYIKoZIzj0EAwIw +IDEeMBwGA1UEAwwVWW91ciBTQU1MIEVudGl0eSBOYW1lMB4XDTI2MTAzMDIwNTY0 +NloXDTM2MTAyNzIwNTY0NlowIDEeMBwGA1UEAwwVWW91ciBTQU1MIEVudGl0eSBO +YW1lMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBm8ZNWwOcn0uQC0FygoZh6JD +jDDVQIzWiXtlL7Hh8RdiVxdPGelxSuSxickteg6ARgFkA/ITHy3T3grfgR/pL6NT +MFEwHQYDVR0OBBYEFGcpb3FbXgL0mMVfTS/fvIg9h+ibMB8GA1UdIwQYMBaAFGcp +b3FbXgL0mMVfTS/fvIg9h+ibMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID +SAAwRQIgPPpZwGxAgjE18UnxZuYG9gGRBqVN/ZIws3b6yxaSVGoCIQCwapQL096j ++OfTgLNl3u7qppz6k+gJiPA+lTmPxoEdKg== +-----END CERTIFICATE----- diff --git a/resources/keys/ecdsa_p256.key b/resources/keys/ecdsa_p256.key new file mode 100644 index 00000000..ee529448 --- /dev/null +++ b/resources/keys/ecdsa_p256.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgr4ZWUPx/u4A/pnZA +ys8xMPj8pW2SZt9Cp3rrOK6fKkOhRANCAAQGbxk1bA5yfS5ALQXKChmHokOMMNVA +jNaJe2UvseHxF2JXF08Z6XFK5LGJyS16DoBGAWQD8hMfLdPeCt+BH+kv +-----END PRIVATE KEY----- diff --git a/resources/keys/ecdsa_p256.pub b/resources/keys/ecdsa_p256.pub new file mode 100644 index 00000000..8644db1c --- /dev/null +++ b/resources/keys/ecdsa_p256.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBm8ZNWwOcn0uQC0FygoZh6JDjDDV +QIzWiXtlL7Hh8RdiVxdPGelxSuSxickteg6ARgFkA/ITHy3T3grfgR/pLw== +-----END PUBLIC KEY----- diff --git a/src/TestUtils/PEMCertificatesMock.php b/src/TestUtils/PEMCertificatesMock.php index 7f602c04..42ee36aa 100644 --- a/src/TestUtils/PEMCertificatesMock.php +++ b/src/TestUtils/PEMCertificatesMock.php @@ -42,6 +42,12 @@ class PEMCertificatesMock public const string SELFSIGNED_PRIVATE_KEY = 'selfsigned.simplesamlphp.org.key'; + public const string ECDSA_CERTIFICATE = 'ecdsa_p256.crt'; + + public const string ECDSA_PUBLIC_KEY = 'ecdsa_p256.pub'; + + public const string ECDSA_PRIVATE_KEY = 'ecdsa_p256.key'; + public const string BROKEN_CERTIFICATE = 'broken.simplesamlphp.org.crt'; public const string BROKEN_PUBLIC_KEY = 'broken.simplesamlphp.org.pub'; From 8380bc31934dbc34ea9d51fdb1044c9c4a28bb08 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Fri, 30 Oct 2026 22:38:07 +0100 Subject: [PATCH 3/7] Do not pass padding-parameter for ECDSA-keys --- src/Backend/OpenSSL.php | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Backend/OpenSSL.php b/src/Backend/OpenSSL.php index 00da5770..d1b3bccd 100644 --- a/src/Backend/OpenSSL.php +++ b/src/Backend/OpenSSL.php @@ -41,7 +41,7 @@ final class OpenSSL implements EncryptionBackend, OAEPParametersAware, Signature protected string $digest; // signature padding method - protected int $sig_padding = OPENSSL_PKCS1_PADDING; + protected ?int $sig_padding = OPENSSL_PKCS1_PADDING; // asymmetric encryption options protected int $enc_padding = OPENSSL_PKCS1_OAEP_PADDING; @@ -201,9 +201,16 @@ public function sign( KeyInterface $key, string $plaintext, ): string { - if (!openssl_sign($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding)) { - throw new OpenSSLException('Cannot sign data'); + if ($this->sig_padding === null) { + if (!openssl_sign($plaintext, $signature, $key->getMaterial(), $this->digest)) { + throw new OpenSSLException('Cannot sign data'); + } + } else { + if (!openssl_sign($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding)) { + throw new OpenSSLException('Cannot sign data'); + } } + return $signature; } @@ -223,7 +230,11 @@ public function verify( string $plaintext, string $signature, ): bool { - return openssl_verify($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding) === 1; + if ($this->sig_padding === null) { + return openssl_verify($plaintext, $signature, $key->getMaterial(), $this->digest) === 1; + } else { + return openssl_verify($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding) === 1; + } } @@ -308,6 +319,12 @@ public function setOAEPParams(?string $digestAlg = null, ?string $mgf = null): v public function setSignaturePadding(string $algId): void { $padding = match ($algId) { + C::SIG_ECDSA_SHA1 => null, + C::SIG_ECDSA_SHA224 => null, + C::SIG_ECDSA_SHA256 => null, + C::SIG_ECDSA_SHA384 => null, + C::SIG_ECDSA_SHA512 => null, + C::SIG_RSA_PSS_SHA1 => OPENSSL_PKCS1_PSS_PADDING, C::SIG_RSA_PSS_SHA224 => OPENSSL_PKCS1_PSS_PADDING, C::SIG_RSA_PSS_SHA256 => OPENSSL_PKCS1_PSS_PADDING, From a5843fdffa05c303f18e1e06e0f42e3c6ba4ed6f Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Fri, 30 Oct 2026 22:42:27 +0100 Subject: [PATCH 4/7] Sign using the correct ECDSA-keys --- tests/XML/SignedElementTest.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php index fd727c79..0ad84180 100644 --- a/tests/XML/SignedElementTest.php +++ b/tests/XML/SignedElementTest.php @@ -22,9 +22,11 @@ use SimpleSAML\XMLSecurity\XML\ds\X509Certificate as X509; use SimpleSAML\XMLSecurity\XML\ds\X509Data; +use function array_keys; use function base64_encode; use function chunk_split; use function dirname; +use function in_array; use function strval; use function trim; @@ -417,9 +419,15 @@ private function sign(string $algorithm): CustomSignable $customSignable = CustomSignable::fromXML($xml->documentElement); - $private = PEMCertificatesMock::getPrivateKey( - PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY, - ); + if (in_array($algorithm, array_keys(C::$ECDSA_DIGESTS))) { + $private = PEMCertificatesMock::getPrivateKey( + PEMCertificatesMock::ECDSA_PRIVATE_KEY, + ); + } else { + $private = PEMCertificatesMock::getPrivateKey( + PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY, + ); + } $factory = new SignatureAlgorithmFactory(); $signer = $factory->getAlgorithm( From cca24b3dcaa3bfd8817bccc21c2b29038e1a90a0 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Fri, 30 Oct 2026 22:52:34 +0100 Subject: [PATCH 5/7] Use the correct key for verification --- tests/XML/SignedElementTest.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php index 0ad84180..5dd8eefd 100644 --- a/tests/XML/SignedElementTest.php +++ b/tests/XML/SignedElementTest.php @@ -40,6 +40,9 @@ final class SignedElementTest extends TestCase /** @var \SimpleSAML\XMLSecurity\CryptoEncoding\PEM */ private PEM $certificate; + /** @var \SimpleSAML\XMLSecurity\CryptoEncoding\PEM */ + private PEM $ecdsa_certificate; + /** @var \SimpleSAML\XMLSecurity\CryptoEncoding\PEM */ private PEM $wrong_certificate; @@ -70,6 +73,10 @@ public function setUp(): void PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE), ); + $this->ecdsa_certificate = PEM::fromString( + PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::ECDSA_CERTIFICATE), + ); + $this->wrong_certificate = PEM::fromString( PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::OTHER_CERTIFICATE), ); @@ -351,7 +358,7 @@ public function testSuccessfulVerifyingEcdsaSignature(): void ->getValue(), ); - $certificate = new X509Certificate($this->certificate); + $certificate = new X509Certificate($this->ecdsa_certificate); $factory = new SignatureAlgorithmFactory(); $verifier = $factory->getAlgorithm( From 66f8b2a0b9f8646f56e4ad161a197c3210f2f2c3 Mon Sep 17 00:00:00 2001 From: Ben Martin Date: Tue, 3 Nov 2026 10:26:09 +1000 Subject: [PATCH 6/7] tiny update --- src/Alg/Signature/ECDSA.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Alg/Signature/ECDSA.php b/src/Alg/Signature/ECDSA.php index d8c308f4..dee16a6c 100644 --- a/src/Alg/Signature/ECDSA.php +++ b/src/Alg/Signature/ECDSA.php @@ -15,7 +15,7 @@ final class ECDSA extends AbstractSigner implements SignatureAlgorithmInterface { /** - * RSA constructor. + * ECDSA constructor. * * @param \SimpleSAML\XMLSecurity\Key\AsymmetricKey $key The asymmetric key (either public or private) to use. * @param string $algId The identifier of this algorithm. From c129980f1d1f6791a587dcde273a86e210a462f1 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 3 Nov 2026 09:57:55 +0100 Subject: [PATCH 7/7] Refactor --- src/Backend/OpenSSL.php | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/Backend/OpenSSL.php b/src/Backend/OpenSSL.php index d1b3bccd..cd4b8d4d 100644 --- a/src/Backend/OpenSSL.php +++ b/src/Backend/OpenSSL.php @@ -41,7 +41,7 @@ final class OpenSSL implements EncryptionBackend, OAEPParametersAware, Signature protected string $digest; // signature padding method - protected ?int $sig_padding = OPENSSL_PKCS1_PADDING; + protected int $sig_padding = OPENSSL_PKCS1_PADDING; // asymmetric encryption options protected int $enc_padding = OPENSSL_PKCS1_OAEP_PADDING; @@ -201,14 +201,8 @@ public function sign( KeyInterface $key, string $plaintext, ): string { - if ($this->sig_padding === null) { - if (!openssl_sign($plaintext, $signature, $key->getMaterial(), $this->digest)) { - throw new OpenSSLException('Cannot sign data'); - } - } else { - if (!openssl_sign($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding)) { - throw new OpenSSLException('Cannot sign data'); - } + if (!openssl_sign($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding)) { + throw new OpenSSLException('Cannot sign data'); } return $signature; @@ -230,11 +224,7 @@ public function verify( string $plaintext, string $signature, ): bool { - if ($this->sig_padding === null) { - return openssl_verify($plaintext, $signature, $key->getMaterial(), $this->digest) === 1; - } else { - return openssl_verify($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding) === 1; - } + return openssl_verify($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding) === 1; } @@ -319,11 +309,11 @@ public function setOAEPParams(?string $digestAlg = null, ?string $mgf = null): v public function setSignaturePadding(string $algId): void { $padding = match ($algId) { - C::SIG_ECDSA_SHA1 => null, - C::SIG_ECDSA_SHA224 => null, - C::SIG_ECDSA_SHA256 => null, - C::SIG_ECDSA_SHA384 => null, - C::SIG_ECDSA_SHA512 => null, + C::SIG_ECDSA_SHA1 => 0, + C::SIG_ECDSA_SHA224 => 0, + C::SIG_ECDSA_SHA256 => 0, + C::SIG_ECDSA_SHA384 => 0, + C::SIG_ECDSA_SHA512 => 0, C::SIG_RSA_PSS_SHA1 => OPENSSL_PKCS1_PSS_PADDING, C::SIG_RSA_PSS_SHA224 => OPENSSL_PKCS1_PSS_PADDING,