Skip to content

Commit 9f6e857

Browse files
author
Simon
committed
fix JWS verifier?
1 parent 08bd31e commit 9f6e857

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"symfony/cache": "^5.4|^6.0|^7.0",
2828
"web-token/jwt-library": "^4.0",
2929
"league/uri": "^7.5",
30-
"league/uri-components": "^7.5"
30+
"league/uri-components": "^7.5",
31+
"phpseclib/phpseclib": "^3.0"
3132
},
3233
"conflict": {
3334
"web-token/jwt-checker": "*",

src/PayconiqCallbackSignatureVerifier.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
use Optios\Payconiq\HeaderChecker\PayconiqJtiChecker;
2727
use Optios\Payconiq\HeaderChecker\PayconiqPathChecker;
2828
use Optios\Payconiq\HeaderChecker\PayconiqSubChecker;
29+
use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as EcdsaAsn1;
30+
use phpseclib3\Crypt\EC\Formats\Signature\IEEE as EcdsaP1363;
2931
use Symfony\Component\Cache\Adapter\AdapterInterface;
3032
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
3133
use Symfony\Contracts\Cache\ItemInterface;
@@ -100,6 +102,7 @@ private function getCertificatesUrl(): string
100102
public function isValid(string $token, ?string $payload = null, ?int $signature = 0): bool
101103
{
102104
try {
105+
$token = self::normalizeEcdsaSigIfNeeded($token, 32);
103106
$this->jwsLoader->loadAndVerifyWithKeySet($token, $this->getJWKSet(), $signature, $payload);
104107
} catch (\Throwable $e) {
105108
return false;
@@ -114,6 +117,7 @@ public function isValid(string $token, ?string $payload = null, ?int $signature
114117
public function loadAndVerifyJWS(string $token, ?string $payload = null, ?int $signature = 0): JWS
115118
{
116119
try {
120+
$token = self::normalizeEcdsaSigIfNeeded($token, 32);
117121
return $this->jwsLoader->loadAndVerifyWithKeySet($token, $this->getJWKSet(), $signature, $payload);
118122
} catch (\Throwable $e) {
119123
throw new PayconiqCallbackSignatureVerificationException(
@@ -156,6 +160,35 @@ private function getJWKSet(): JWKSet
156160
}
157161
}
158162

163+
/**
164+
* If the compact JWS uses a DER-encoded ECDSA signature, convert it to JOSE raw (r||s).
165+
* $partLen: 32 for ES256, 48 for ES384, 66 for ES512.
166+
*/
167+
private static function normalizeEcdsaSigIfNeeded(string $compactJws, int $partLen = 32): string
168+
{
169+
[$h, $p, $sB64u] = explode('.', $compactJws, 3) + [null, null, null];
170+
if ($sB64u === null || $sB64u === '') return $compactJws;
171+
172+
// base64url decode signature
173+
$pad = (4 - strlen($sB64u) % 4) % 4;
174+
$sig = base64_decode(strtr($sB64u, '-_', '+/') . str_repeat('=', $pad), true);
175+
if ($sig === false) return $compactJws;
176+
177+
// already raw r||s of expected length? nothing to do.
178+
if (strlen($sig) === 2 * $partLen) return $compactJws;
179+
180+
// Try DER → raw using phpseclib helpers
181+
try {
182+
$rs = EcdsaAsn1::load($sig); // ['r'=>BigInteger,'s'=>BigInteger]
183+
$raw = EcdsaP1363::save($rs['r'], $rs['s'], null, $partLen); // fixed-length P-1363
184+
$sB64u = rtrim(strtr(base64_encode($raw), '+/', '-_'), '=');
185+
return "$h.$p.$sB64u";
186+
} catch (\Throwable) {
187+
// Not DER / not parseable — leave as-is and let the verifier decide
188+
return $compactJws;
189+
}
190+
}
191+
159192
private function initializeJwsLoader(string $paymentProfileId): JWSLoader
160193
{
161194
return new JWSLoader(

0 commit comments

Comments
 (0)