diff --git a/README.md b/README.md
index 1512ec4..a7c29f1 100644
--- a/README.md
+++ b/README.md
@@ -74,78 +74,7 @@ Refer to the [examples](https://github.com/bluem-development/bluem-php/tree/mast
Please contact us if you have any questions regarding the examples or the implementation of the library in your project.
## Notes per version
-
-### Version 3.0
-- PHP 8.3 minimum version requirement
-
-### Version 2.4
-- Added Bancontact support
-
-### Version 2.3.2.4 (latest)
-- Added bank 'N26' to ePayments BIC list.
-- Restructured code for Magento compatibility.
-
-### Version 2.3.2.3
-Updated certificates.
-
-### Version 2.3.2.2
-Updated BIC epayments list.
-
-### Version 2.3.2
-Added BIC to mandate request.
-
-### Version 2.3.1
-Added BIC to identity request.
-
-### Version 2.3
-Added PHP 8+ support.
-
-### Version 2.2
-Webhooks and new payment methods
-- Added explicit webhook functionality and relevant documentation
-- Support for PayPal, Creditcards, SOFORT and Carte Bancaire
-
-### Version 2.1
-Major improvement in code style.
-- Added `$bluem->getConfig($key)` method to retrieve a configuration value.
-- Added `$bluem->setConfig($key, $value)` method to set a configuration value.
-- Added several validation steps
-- Added more unit testing coverage
-- Separated more responsibilities for cleaner code
-
-### Versions before 2.1
-
-#### Version 2.0.12
-Allowing the verification if the current IP is based in the Netherlands utilizing a geolocation integration *(IP-API).
-
-```php
-$bluem->VerifyIPIsNetherlands();
-// returns bool true if NL or error, returns false if no error and other country.
-```
-*This feature can be used to determine whether to use iDIN identity checking in any application, as this supports only Dutch banks.*
-
-#### Version 2.0.2:
-
-Triodos Bank, BIC TRIONL2U no longer supported for Identity requests as of 1 june 2021. See: https://www.triodos.nl/veelgestelde-vragen/kan-ik-idin-gebruiken?id=4de127e85eee
-
-- If you use the [Preselection of banks using the DebtorWallet](https://github.com/bluem-development/bluem-php#debtorwallet-preselecting-a-bank-for-mandate-payment-or-identity-request), you will have to update this library to ensure Triodos is no longer an option for iDIN. If you do not do this, customers that select Triodos will be presented with an error.
-
-- If you use the Bluem portal, you don't need to act. This change is already applied within the Bluem portal.
-
-#### Version 2.0.1:
-Major release with more stability, validation and features.
-
-Please note: The main Integration class is called Bluem, so to include it, use:
-```php
-$bluem = new Bluem($config);
-```
-Or use a class alias to ensure code functioning. This is a refactor since version 1.x.
-
-Furthermore, all generally available functions are still available.
-
----
-
-No earlier changelog was recorded. Please refer to the [commit log](https://github.com/bluem-development/bluem-php/commits/master) for more information.
+See [Changelog](changelog.md)
## Testing
For improving future features, unit testing is introduced since november 2021.
diff --git a/changelog.md b/changelog.md
new file mode 100644
index 0000000..cb19d20
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,75 @@
+# Changes per release
+
+## Major version 2
+### 2.5:
+- Migrated away from deprecated XML signature validation package
+- Set minimum PHP to 8.3
+
+### 2.4:
+- Added Bancontact support
+
+### 2.3.2.4
+- Added bank 'N26' to ePayments BIC list.
+- Restructured code for Magento compatibility.
+
+### 2.3.2.3
+Updated certificates.
+
+### 2.3.2.2
+Updated BIC epayments list.
+
+### 2.3.2
+Added BIC to mandate request.
+
+### 2.3.1
+Added BIC to identity request.
+
+### 2.3
+Added PHP 8+ support.
+
+### 2.2
+Webhooks and new payment methods
+- Added explicit webhook functionality and relevant documentation
+- Support for PayPal, Creditcards, SOFORT and Carte Bancaire
+
+### 2.1
+Major improvement in code style.
+- Added `$bluem->getConfig($key)` method to retrieve a configuration value.
+- Added `$bluem->setConfig($key, $value)` method to set a configuration value.
+- Added several validation steps
+- Added more unit testing coverage
+- Separated more responsibilities for cleaner code
+
+###s before 2.1
+
+#### 2.0.12
+Allowing the verification if the current IP is based in the Netherlands utilizing a geolocation integration *(IP-API).
+
+```php
+$bluem->VerifyIPIsNetherlands();
+// returns bool true if NL or error, returns false if no error and other country.
+```
+*This feature can be used to determine whether to use iDIN identity checking in any application, as this supports only Dutch banks.*
+
+#### 2.0.2:
+
+Triodos Bank, BIC TRIONL2U no longer supported for Identity requests as of 1 june 2021. See: https://www.triodos.nl/veelgestelde-vragen/kan-ik-idin-gebruiken?id=4de127e85eee
+
+- If you use the [Preselection of banks using the DebtorWallet](https://github.com/bluem-development/bluem-php#debtorwallet-preselecting-a-bank-for-mandate-payment-or-identity-request), you will have to update this library to ensure Triodos is no longer an option for iDIN. If you do not do this, customers that select Triodos will be presented with an error.
+
+- If you use the Bluem portal, you don't need to act. This change is already applied within the Bluem portal.
+
+#### 2.0.1:
+Major release with more stability, validation and features.
+
+Please note: The main Integration class is called Bluem, so to include it, use:
+```php
+$bluem = new Bluem($config);
+```
+Or use a class alias to ensure code functioning. This is a refactor since version 1.x.
+
+Furthermore, all generally available functions are still available.
+
+---
+
+No earlier changelog was recorded. Please refer to the [commit log](https://github.com/bluem-development/bluem-php/commits/master) for more information.
diff --git a/composer.json b/composer.json
index 9229fc7..05db8f0 100644
--- a/composer.json
+++ b/composer.json
@@ -2,15 +2,25 @@
"name": "bluem-development/bluem-php",
"type": "package",
"description": "Bluem PHP interface",
- "keywords": ["bluem", "php", "interface", "wrapper", "emandate", "epayment", "idin"],
+ "keywords": [
+ "bluem",
+ "php",
+ "interface",
+ "wrapper",
+ "emandate",
+ "epayment",
+ "idin"
+ ],
"homepage": "https://bluem-development.github.io/bluem-php/",
"license": "GPL-3.0-or-later",
- "authors": [{
- "name": "Bluem Plugin Support",
- "email": "pluginsupport@bluem.nl",
- "homepage": "https://bluem.nl/",
- "role": "Developer"
- }],
+ "authors": [
+ {
+ "name": "Bluem Plugin Support",
+ "email": "pluginsupport@bluem.nl",
+ "homepage": "https://bluem.nl/",
+ "role": "Developer"
+ }
+ ],
"support": {
"issues": "https://github.com/bluem-development/bluem-php"
},
@@ -27,7 +37,7 @@
"ext-curl": "*",
"ext-json": "*",
"ext-openssl": "*",
- "selective/xmldsig": "^3.2"
+ "robrichards/xmlseclibs": "^3.1"
},
"autoload": {
"psr-4": {
@@ -43,7 +53,7 @@
"squizlabs/php_codesniffer": "^3.7",
"phpcompatibility/php-compatibility": "^9.3"
},
- "prefer-stable" : true,
+ "prefer-stable": true,
"scripts": {
"post-install-cmd": [
"([ $COMPOSER_DEV_MODE -eq 0 ] || vendor/bin/phpcs --config-set installed_paths ../../magento/magento-coding-standard/)"
@@ -52,4 +62,4 @@
"([ $COMPOSER_DEV_MODE -eq 0 ] || vendor/bin/phpcs --config-set installed_paths ../../magento/magento-coding-standard/,../../phpcompatibility/php-compatibility)"
]
}
-}
+}
\ No newline at end of file
diff --git a/composer.lock b/composer.lock
index a58e7ce..343bcc6 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,59 +4,49 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "b69a5da7ea18e3cd2e4658988add71c5",
+ "content-hash": "2d0694e7db08321b5c6996cb07cb6ad3",
"packages": [
{
- "name": "selective/xmldsig",
- "version": "3.2.0",
+ "name": "robrichards/xmlseclibs",
+ "version": "3.1.5",
"source": {
"type": "git",
- "url": "https://github.com/selective-php/xmldsig.git",
- "reference": "c6fa6b639c2c7781e61f885ac8a8de2fa3e9b6a0"
+ "url": "https://github.com/robrichards/xmlseclibs.git",
+ "reference": "03062be78178cbb5e8f605cd255dc32a14981f92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/selective-php/xmldsig/zipball/c6fa6b639c2c7781e61f885ac8a8de2fa3e9b6a0",
- "reference": "c6fa6b639c2c7781e61f885ac8a8de2fa3e9b6a0",
+ "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/03062be78178cbb5e8f605cd255dc32a14981f92",
+ "reference": "03062be78178cbb5e8f605cd255dc32a14981f92",
"shasum": ""
},
"require": {
- "ext-dom": "*",
"ext-openssl": "*",
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
- },
- "require-dev": {
- "ext-gmp": "*",
- "friendsofphp/php-cs-fixer": "^3",
- "phpstan/phpstan": "^2",
- "phpunit/phpunit": "^10 || ^12",
- "squizlabs/php_codesniffer": "^4",
- "starkbank/ecdsa": "^2.0"
+ "php": ">= 5.4"
},
"type": "library",
"autoload": {
"psr-4": {
- "Selective\\XmlDSig\\": "src/"
+ "RobRichards\\XMLSecLibs\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
- "description": "Sign XML Documents with Digital Signatures",
- "homepage": "https://github.com/selective-php/xmldsig",
+ "description": "A PHP library for XML Security",
+ "homepage": "https://github.com/robrichards/xmlseclibs",
"keywords": [
- "signatures",
- "verify",
+ "security",
+ "signature",
"xml",
"xmldsig"
],
"support": {
- "issues": "https://github.com/selective-php/xmldsig/issues",
- "source": "https://github.com/selective-php/xmldsig/tree/3.2.0"
+ "issues": "https://github.com/robrichards/xmlseclibs/issues",
+ "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.5"
},
- "abandoned": "robrichards/xmlseclibs",
- "time": "2025-11-09T09:15:21+00:00"
+ "time": "2026-03-13T10:31:56+00:00"
}
],
"packages-dev": [
@@ -909,11 +899,11 @@
},
{
"name": "phpstan/phpstan",
- "version": "2.1.50",
+ "version": "2.2.2",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d452086fb4cf648c6b2d8cf3b639351f79e4f3e2",
- "reference": "d452086fb4cf648c6b2d8cf3b639351f79e4f3e2",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e5cc34d491a90e79c216d824f60fe21fd4d93bd6",
+ "reference": "e5cc34d491a90e79c216d824f60fe21fd4d93bd6",
"shasum": ""
},
"require": {
@@ -936,6 +926,17 @@
"license": [
"MIT"
],
+ "authors": [
+ {
+ "name": "Ondřej Mirtes"
+ },
+ {
+ "name": "Markus Staab"
+ },
+ {
+ "name": "Vincent Langlet"
+ }
+ ],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
@@ -958,20 +959,20 @@
"type": "github"
}
],
- "time": "2026-04-17T13:10:32+00:00"
+ "time": "2026-06-05T09:00:01+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "12.5.6",
+ "version": "12.5.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "876099a072646c7745f673d7aeab5382c4439691"
+ "reference": "186dab580576598076de6818596d12b61801880e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/876099a072646c7745f673d7aeab5382c4439691",
- "reference": "876099a072646c7745f673d7aeab5382c4439691",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/186dab580576598076de6818596d12b61801880e",
+ "reference": "186dab580576598076de6818596d12b61801880e",
"shasum": ""
},
"require": {
@@ -982,13 +983,13 @@
"php": ">=8.3",
"phpunit/php-text-template": "^5.0",
"sebastian/complexity": "^5.0",
- "sebastian/environment": "^8.0.3",
- "sebastian/lines-of-code": "^4.0",
+ "sebastian/environment": "^8.1.2",
+ "sebastian/lines-of-code": "^4.0.1",
"sebastian/version": "^6.0",
"theseer/tokenizer": "^2.0.1"
},
"require-dev": {
- "phpunit/phpunit": "^12.5.1"
+ "phpunit/phpunit": "^12.5.28"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
@@ -1026,7 +1027,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.6"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.7"
},
"funding": [
{
@@ -1046,7 +1047,7 @@
"type": "tidelift"
}
],
- "time": "2026-04-15T08:23:17+00:00"
+ "time": "2026-06-01T13:24:19+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -1307,16 +1308,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "12.5.22",
+ "version": "12.5.30",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "e07667405f0f43317a1799d3907c43a123bc5587"
+ "reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e07667405f0f43317a1799d3907c43a123bc5587",
- "reference": "e07667405f0f43317a1799d3907c43a123bc5587",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/900400a5b616d6fb306f9549f6da33ba615d3fbb",
+ "reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb",
"shasum": ""
},
"require": {
@@ -1330,20 +1331,20 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
- "phpunit/php-code-coverage": "^12.5.6",
+ "phpunit/php-code-coverage": "^12.5.7",
"phpunit/php-file-iterator": "^6.0.1",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
"phpunit/php-timer": "^8.0.0",
- "sebastian/cli-parser": "^4.2.0",
- "sebastian/comparator": "^7.1.6",
+ "sebastian/cli-parser": "^4.2.1",
+ "sebastian/comparator": "^7.1.8",
"sebastian/diff": "^7.0.0",
- "sebastian/environment": "^8.1.0",
- "sebastian/exporter": "^7.0.2",
- "sebastian/global-state": "^8.0.2",
+ "sebastian/environment": "^8.1.2",
+ "sebastian/exporter": "^7.0.3",
+ "sebastian/global-state": "^8.0.3",
"sebastian/object-enumerator": "^7.0.0",
"sebastian/recursion-context": "^7.0.1",
- "sebastian/type": "^6.0.3",
+ "sebastian/type": "^6.0.4",
"sebastian/version": "^6.0.0",
"staabm/side-effects-detector": "^1.0.5"
},
@@ -1385,7 +1386,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.22"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.30"
},
"funding": [
{
@@ -1393,25 +1394,25 @@
"type": "other"
}
],
- "time": "2026-04-17T12:51:04+00:00"
+ "time": "2026-06-15T13:12:30+00:00"
},
{
"name": "rector/rector",
- "version": "2.4.2",
+ "version": "2.5.2",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946"
+ "reference": "49ff6339174bdbdf50b0b35ecbcff14a05ac9e24"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/e645b6463c6a88ea5b44b17d3387d35a912c7946",
- "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/49ff6339174bdbdf50b0b35ecbcff14a05ac9e24",
+ "reference": "49ff6339174bdbdf50b0b35ecbcff14a05ac9e24",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0",
- "phpstan/phpstan": "^2.1.48"
+ "phpstan/phpstan": "^2.2.2"
},
"conflict": {
"rector/rector-doctrine": "*",
@@ -1445,7 +1446,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/2.4.2"
+ "source": "https://github.com/rectorphp/rector/tree/2.5.2"
},
"funding": [
{
@@ -1453,27 +1454,27 @@
"type": "github"
}
],
- "time": "2026-04-16T13:07:34+00:00"
+ "time": "2026-06-22T11:39:33+00:00"
},
{
"name": "sebastian/cli-parser",
- "version": "4.2.0",
+ "version": "4.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
- "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04"
+ "reference": "7d05781b13f7dec9043a629a21d086ed74582a15"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04",
- "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15",
+ "reference": "7d05781b13f7dec9043a629a21d086ed74582a15",
"shasum": ""
},
"require": {
"php": ">=8.3"
},
"require-dev": {
- "phpunit/phpunit": "^12.0"
+ "phpunit/phpunit": "^12.5.25"
},
"type": "library",
"extra": {
@@ -1502,7 +1503,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
"security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
- "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0"
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.1"
},
"funding": [
{
@@ -1522,20 +1523,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-14T09:36:45+00:00"
+ "time": "2026-05-17T05:29:34+00:00"
},
{
"name": "sebastian/comparator",
- "version": "7.1.6",
+ "version": "7.1.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "c769009dee98f494e0edc3fd4f4087501688f11e"
+ "reference": "7c65c1e79836812819705b473a90c12399542485"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/c769009dee98f494e0edc3fd4f4087501688f11e",
- "reference": "c769009dee98f494e0edc3fd4f4087501688f11e",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485",
+ "reference": "7c65c1e79836812819705b473a90c12399542485",
"shasum": ""
},
"require": {
@@ -1543,10 +1544,10 @@
"ext-mbstring": "*",
"php": ">=8.3",
"sebastian/diff": "^7.0",
- "sebastian/exporter": "^7.0"
+ "sebastian/exporter": "^7.0.3"
},
"require-dev": {
- "phpunit/phpunit": "^12.2"
+ "phpunit/phpunit": "^12.5.25"
},
"suggest": {
"ext-bcmath": "For comparing BcMath\\Number objects"
@@ -1594,7 +1595,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
- "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.6"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.8"
},
"funding": [
{
@@ -1614,7 +1615,7 @@
"type": "tidelift"
}
],
- "time": "2026-04-14T08:23:15+00:00"
+ "time": "2026-05-21T04:45:25+00:00"
},
{
"name": "sebastian/complexity",
@@ -1743,23 +1744,23 @@
},
{
"name": "sebastian/environment",
- "version": "8.1.0",
+ "version": "8.1.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "b121608b28a13f721e76ffbbd386d08eff58f3f6"
+ "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/b121608b28a13f721e76ffbbd386d08eff58f3f6",
- "reference": "b121608b28a13f721e76ffbbd386d08eff58f3f6",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439",
+ "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439",
"shasum": ""
},
"require": {
"php": ">=8.3"
},
"require-dev": {
- "phpunit/phpunit": "^12.0"
+ "phpunit/phpunit": "^12.5.26"
},
"suggest": {
"ext-posix": "*"
@@ -1795,7 +1796,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
- "source": "https://github.com/sebastianbergmann/environment/tree/8.1.0"
+ "source": "https://github.com/sebastianbergmann/environment/tree/8.1.2"
},
"funding": [
{
@@ -1815,29 +1816,29 @@
"type": "tidelift"
}
],
- "time": "2026-04-15T12:13:01+00:00"
+ "time": "2026-05-25T13:40:20+00:00"
},
{
"name": "sebastian/exporter",
- "version": "7.0.2",
+ "version": "7.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "016951ae10980765e4e7aee491eb288c64e505b7"
+ "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7",
- "reference": "016951ae10980765e4e7aee491eb288c64e505b7",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23",
+ "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=8.3",
- "sebastian/recursion-context": "^7.0"
+ "sebastian/recursion-context": "^7.0.1"
},
"require-dev": {
- "phpunit/phpunit": "^12.0"
+ "phpunit/phpunit": "^12.5.25"
},
"type": "library",
"extra": {
@@ -1885,7 +1886,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"security": "https://github.com/sebastianbergmann/exporter/security/policy",
- "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.3"
},
"funding": [
{
@@ -1905,30 +1906,30 @@
"type": "tidelift"
}
],
- "time": "2025-09-24T06:16:11+00:00"
+ "time": "2026-05-20T04:37:17+00:00"
},
{
"name": "sebastian/global-state",
- "version": "8.0.2",
+ "version": "8.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "ef1377171613d09edd25b7816f05be8313f9115d"
+ "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d",
- "reference": "ef1377171613d09edd25b7816f05be8313f9115d",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9",
+ "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9",
"shasum": ""
},
"require": {
"php": ">=8.3",
"sebastian/object-reflector": "^5.0",
- "sebastian/recursion-context": "^7.0"
+ "sebastian/recursion-context": "^7.0.1"
},
"require-dev": {
"ext-dom": "*",
- "phpunit/phpunit": "^12.0"
+ "phpunit/phpunit": "^12.5.28"
},
"type": "library",
"extra": {
@@ -1959,7 +1960,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"security": "https://github.com/sebastianbergmann/global-state/security/policy",
- "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.3"
},
"funding": [
{
@@ -1979,28 +1980,28 @@
"type": "tidelift"
}
],
- "time": "2025-08-29T11:29:25+00:00"
+ "time": "2026-06-01T15:10:33+00:00"
},
{
"name": "sebastian/lines-of-code",
- "version": "4.0.0",
+ "version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
- "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f"
+ "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f",
- "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e",
+ "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e",
"shasum": ""
},
"require": {
- "nikic/php-parser": "^5.0",
+ "nikic/php-parser": "^5.7.0",
"php": ">=8.3"
},
"require-dev": {
- "phpunit/phpunit": "^12.0"
+ "phpunit/phpunit": "^12.5.25"
},
"type": "library",
"extra": {
@@ -2029,15 +2030,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
- "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0"
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code",
+ "type": "tidelift"
}
],
- "time": "2025-02-07T04:57:28+00:00"
+ "time": "2026-05-19T16:22:07+00:00"
},
{
"name": "sebastian/object-enumerator",
@@ -2231,23 +2244,23 @@
},
{
"name": "sebastian/type",
- "version": "6.0.3",
+ "version": "6.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
- "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d"
+ "reference": "82ff822c2edc46724be9f7411d3163021f602773"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d",
- "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773",
+ "reference": "82ff822c2edc46724be9f7411d3163021f602773",
"shasum": ""
},
"require": {
"php": ">=8.3"
},
"require-dev": {
- "phpunit/phpunit": "^12.0"
+ "phpunit/phpunit": "^12.5.25"
},
"type": "library",
"extra": {
@@ -2276,7 +2289,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"security": "https://github.com/sebastianbergmann/type/security/policy",
- "source": "https://github.com/sebastianbergmann/type/tree/6.0.3"
+ "source": "https://github.com/sebastianbergmann/type/tree/6.0.4"
},
"funding": [
{
@@ -2296,7 +2309,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-09T06:57:12+00:00"
+ "time": "2026-05-20T06:45:45+00:00"
},
{
"name": "sebastian/version",
@@ -2485,16 +2498,16 @@
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.6.0",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
- "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": ""
},
"require": {
@@ -2507,7 +2520,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "3.6-dev"
+ "dev-main": "3.7-dev"
}
},
"autoload": {
@@ -2532,7 +2545,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -2543,16 +2556,20 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-25T14:21:43+00:00"
+ "time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.36.0",
+ "version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -2611,7 +2628,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
},
"funding": [
{
@@ -2635,16 +2652,16 @@
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.33.0",
+ "version": "v1.38.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+ "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
- "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
+ "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
"shasum": ""
},
"require": {
@@ -2696,7 +2713,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2"
},
"funding": [
{
@@ -2716,20 +2733,20 @@
"type": "tidelift"
}
],
- "time": "2024-12-23T08:48:59+00:00"
+ "time": "2026-05-27T06:59:30+00:00"
},
{
"name": "symfony/polyfill-php80",
- "version": "v1.33.0",
+ "version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
- "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
"shasum": ""
},
"require": {
@@ -2780,7 +2797,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
},
"funding": [
{
@@ -2800,7 +2817,7 @@
"type": "tidelift"
}
],
- "time": "2025-01-02T08:10:11+00:00"
+ "time": "2026-04-10T16:19:22+00:00"
},
{
"name": "theseer/tokenizer",
@@ -2938,16 +2955,16 @@
},
{
"name": "webmozart/assert",
- "version": "2.3.0",
+ "version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
- "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4"
+ "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4",
- "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
+ "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
"shasum": ""
},
"require": {
@@ -2963,7 +2980,11 @@
},
"type": "library",
"extra": {
+ "psalm": {
+ "pluginClass": "Webmozart\\Assert\\PsalmPlugin"
+ },
"branch-alias": {
+ "dev-master": "2.0-dev",
"dev-feature/2-0": "2.0-dev"
}
},
@@ -2994,9 +3015,9 @@
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
- "source": "https://github.com/webmozarts/assert/tree/2.3.0"
+ "source": "https://github.com/webmozarts/assert/tree/2.4.1"
},
- "time": "2026-04-11T10:33:05+00:00"
+ "time": "2026-06-15T15:31:57+00:00"
}
],
"aliases": [],
diff --git a/src/Validators/WebhookSignatureValidation.php b/src/Validators/WebhookSignatureValidation.php
index dd7ec4d..342413f 100644
--- a/src/Validators/WebhookSignatureValidation.php
+++ b/src/Validators/WebhookSignatureValidation.php
@@ -10,10 +10,10 @@
namespace Bluem\BluemPHP\Validators;
use Bluem\BluemPHP\Helpers\Now;
+use DOMDocument;
use Exception;
-use Selective\XmlDSig\CryptoVerifier;
-use Selective\XmlDSig\PublicKeyStore;
-use Selective\XmlDSig\XmlSignatureVerifier;
+use RobRichards\XMLSecLibs\XMLSecurityDSig;
+use RobRichards\XMLSecLibs\XMLSecurityKey;
class WebhookSignatureValidation extends WebhookValidator
{
@@ -34,20 +34,41 @@ public function validate(string $data): self
fwrite($temp_file, $data);
$temp_file_path = stream_get_meta_data($temp_file)['uri'];
- $publicKeyStore = new PublicKeyStore();
+ // Load the XML to be verified
+ $doc = new DOMDocument();
+ $doc->load($temp_file_path);
- $public_key_file_path = dirname(__DIR__, 2) . self::KEY_FOLDER . $this->getKeyFileName();
+ // Create a new Security object
+ $objDSig = new XMLSecurityDSig();
+ // Locate the signature within the XML
try {
- $publicKeyStore->loadFromPem(file_get_contents($public_key_file_path));
- $cryptoVerifier = new CryptoVerifier($publicKeyStore);
+ $objDSig->locateSignature($doc);
+ $objDSig->canonicalizeSignedInfo();
+ $objDSig->validateReference();
+ } catch (Exception $e) {
+ $this->addError('Reference Validation Failed: ' . $e->getMessage());
+ fclose($temp_file);
+ return $this;
+ }
- // Create a verifier instance and pass the crypto decoder
- $xmlSignatureVerifier = new XmlSignatureVerifier($cryptoVerifier);
+ try {
+ $objKey = $objDSig->locateKey();
+ if (! $objKey instanceof XMLSecurityKey) {
+ $this->addError('Unable to determine signature key algorithm');
+ fclose($temp_file);
+ return $this;
+ }
+ $objKey->loadKey($this->getPublicKeyFilePath(), true);
+ } catch (Exception $e) {
+ $this->addError('Could not load public key');
+ fclose($temp_file);
+ return $this;
+ }
- // Verify a XML file
- $xmlVerified = $xmlSignatureVerifier->verifyXml(file_get_contents($temp_file_path));
- if (! $xmlVerified) {
+ try {
+ // Check the signature
+ if ($objDSig->verify($objKey) !== 1) {
$this->addError("Invalid signature");
}
} catch (Exception $exception) {
@@ -59,6 +80,14 @@ public function validate(string $data): self
return $this;
}
+ /**
+ * Determine full path to the public key/certificate used for validation.
+ */
+ protected function getPublicKeyFilePath(): string
+ {
+ return dirname(__DIR__, 2) . self::KEY_FOLDER . $this->getKeyFileName();
+ }
+
/**
* Determine filename certificate
*/
diff --git a/tests/Unit/WebhookSignatureValidationTest.php b/tests/Unit/WebhookSignatureValidationTest.php
new file mode 100644
index 0000000..517a334
--- /dev/null
+++ b/tests/Unit/WebhookSignatureValidationTest.php
@@ -0,0 +1,180 @@
+resetWebhookValidatorState();
+ $this->createTemporaryCertificate();
+ }
+
+ protected function tearDown(): void
+ {
+ @unlink($this->privateKeyFilePath);
+ @unlink($this->certificateFilePath);
+
+ parent::tearDown();
+ }
+
+ public function testValidRsaSha256SignaturePasses(): void
+ {
+ $validator = $this->createValidator();
+
+ $validator->validate($this->createSignedXml(XMLSecurityKey::RSA_SHA256, XMLSecurityDSig::SHA256));
+
+ $this->assertTrue($validator::$isValid, $validator->errorMessage());
+ }
+
+ public function testValidRsaSha512SignaturePasses(): void
+ {
+ $validator = $this->createValidator();
+
+ $validator->validate($this->createSignedXml(XMLSecurityKey::RSA_SHA512, XMLSecurityDSig::SHA512));
+
+ $this->assertTrue($validator::$isValid, $validator->errorMessage());
+ }
+
+ public function testTamperedSignedContentFailsReferenceValidation(): void
+ {
+ $validator = $this->createValidator();
+ $signedXml = $this->createSignedXml(XMLSecurityKey::RSA_SHA256, XMLSecurityDSig::SHA256);
+
+ $validator->validate(str_replace('valid', 'tampered', $signedXml));
+
+ $this->assertFalse($validator::$isValid);
+ $this->assertStringContainsString('Reference Validation Failed', $validator->errorMessage());
+ }
+
+ public function testTamperedSignatureValueFailsSignatureValidation(): void
+ {
+ $validator = $this->createValidator();
+
+ $validator->validate($this->tamperSignatureValue(
+ $this->createSignedXml(XMLSecurityKey::RSA_SHA256, XMLSecurityDSig::SHA256)
+ ));
+
+ $this->assertFalse($validator::$isValid);
+ $this->assertStringContainsString('Invalid signature', $validator->errorMessage());
+ }
+
+ public function testMissingSignatureAlgorithmFailsBeforeVerification(): void
+ {
+ $validator = $this->createValidator();
+
+ $validator->validate($this->removeSignatureMethodAlgorithm(
+ $this->createSignedXml(XMLSecurityKey::RSA_SHA256, XMLSecurityDSig::SHA256)
+ ));
+
+ $this->assertFalse($validator::$isValid);
+ $this->assertStringContainsString('Unable to determine signature key algorithm', $validator->errorMessage());
+ }
+
+ private function createTemporaryCertificate(): void
+ {
+ $privateKey = openssl_pkey_new([
+ 'private_key_bits' => 2048,
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ ]);
+
+ $this->assertNotFalse($privateKey);
+ $this->assertTrue(openssl_pkey_export($privateKey, $privateKeyPem));
+
+ $csr = openssl_csr_new([
+ 'commonName' => 'webhook-signature-validation.test',
+ ], $privateKey);
+ $this->assertNotFalse($csr);
+
+ $certificate = openssl_csr_sign($csr, null, $privateKey, 1);
+ $this->assertNotFalse($certificate);
+ $this->assertTrue(openssl_x509_export($certificate, $certificatePem));
+
+ $this->privateKeyFilePath = tempnam(sys_get_temp_dir(), 'bluem-private-key-');
+ $this->certificateFilePath = tempnam(sys_get_temp_dir(), 'bluem-certificate-');
+
+ $this->assertIsString($this->privateKeyFilePath);
+ $this->assertIsString($this->certificateFilePath);
+ $this->assertNotFalse(file_put_contents($this->privateKeyFilePath, $privateKeyPem));
+ $this->assertNotFalse(file_put_contents($this->certificateFilePath, $certificatePem));
+ }
+
+ private function createSignedXml(string $signatureAlgorithm, string $digestAlgorithm): string
+ {
+ $doc = new DOMDocument('1.0', 'UTF-8');
+ $doc->loadXML('valid');
+
+ $signature = new XMLSecurityDSig();
+ $signature->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
+ $signature->addReference($doc, $digestAlgorithm, [XMLSecurityDSig::EXC_C14N], ['force_uri' => true]);
+
+ $privateKey = new XMLSecurityKey($signatureAlgorithm, ['type' => 'private']);
+ $privateKey->loadKey($this->privateKeyFilePath, true);
+
+ $signature->sign($privateKey);
+ $signature->appendSignature($doc->documentElement);
+
+ return $doc->saveXML();
+ }
+
+ private function tamperSignatureValue(string $signedXml): string
+ {
+ $doc = new DOMDocument();
+ $doc->loadXML($signedXml);
+
+ $signatureValue = $doc->getElementsByTagName('SignatureValue')->item(0);
+ $signatureValue->nodeValue = 'A' . substr($signatureValue->nodeValue, 1);
+
+ return $doc->saveXML();
+ }
+
+ private function removeSignatureMethodAlgorithm(string $signedXml): string
+ {
+ $doc = new DOMDocument();
+ $doc->loadXML($signedXml);
+
+ $signatureMethod = $doc->getElementsByTagName('SignatureMethod')->item(0);
+ $signatureMethod->removeAttribute('Algorithm');
+
+ return $doc->saveXML();
+ }
+
+ private function createValidator(): WebhookSignatureValidation
+ {
+ return new class ('test', $this->certificateFilePath) extends WebhookSignatureValidation {
+ public function __construct(string $env, private string $certificateFilePath)
+ {
+ parent::__construct($env);
+ }
+
+ protected function getPublicKeyFilePath(): string
+ {
+ return $this->certificateFilePath;
+ }
+ };
+ }
+
+ private function resetWebhookValidatorState(): void
+ {
+ $isValid = new ReflectionProperty(WebhookValidator::class, 'isValid');
+ $isValid->setAccessible(true);
+ $isValid->setValue(null, true);
+
+ $errors = new ReflectionProperty(WebhookValidator::class, 'errors');
+ $errors->setAccessible(true);
+ $errors->setValue(null, []);
+ }
+}
diff --git a/todos.md b/todos.md
deleted file mode 100644
index e69de29..0000000