Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions vicephp/Virtue-JWT/src/JWK/Key/ECDSA/PublicKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Virtue\JWK\Key\ECDSA;

use Virtue\JWK\AsymmetricKey;

class PublicKey implements AsymmetricKey
{
/** @var string */
private $alg;

/** @var string */
private $pem;

private function __construct()
{
}

public static function fromPem(string $pem): self
{
$key = new self();
$key->pem = $pem;
$public = \openssl_pkey_get_public($pem);
$details = openssl_pkey_get_details($public);
//TODO remove together with the support of PHP versions < 8.0
if (version_compare(PHP_VERSION, '8.0.0') < 0) {
\openssl_pkey_free($public);
}

if ($details['type'] !== OPENSSL_KEYTYPE_EC) {
throw new \InvalidArgumentException('Key is not ECDSA');
}

$key->alg = 'ES' . $details['bits'];

return $key;
}
/**
* @inheritDoc
*/
public function alg(): string
{
return $this->alg;
}

/**
* @inheritDoc
*/
public function asPem(): string
{
return $this->pem;
}

/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
// TODO: Impelment key serialization for JWKS
return [];
}
}
73 changes: 73 additions & 0 deletions vicephp/Virtue-JWT/src/JWT/Algorithms/ECDSAVerify.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Virtue\JWT\Algorithms;

use Virtue\JWK\Key\ECDSA\PublicKey;
use Virtue\JWT\Algorithm;
use Virtue\JWT\VerificationFailed;
use Virtue\JWT\VerifiesToken;

class ECDSAVerify extends Algorithm implements VerifiesToken
{
private $supported = [
'ES256' => OPENSSL_ALGO_SHA256,
'ES384' => OPENSSL_ALGO_SHA384,
'ES512' => OPENSSL_ALGO_SHA512,
];

private const DER_SEQ = 0x30;
private const DER_INT = 0x02;

/** @var PublicKey */
private $public;

public function __construct(PublicKey $key)
{
parent::__construct($key->alg());
$this->public = $key;
}

/**
* @inheritDoc
*/
public function verify(\Virtue\JWT\Token $token): void
{
if (!isset($this->supported[$this->name])) {
throw new VerificationFailed("Algorithm {$this->name} is not supported");
}

if (!$public = \openssl_pkey_get_public($this->public->asPem())) {
throw new VerificationFailed('Key is invalid.');
}

$msg = $token->withoutSig();
$sig = $token->signature();
$der = join(array_map(
function ($v) {
$str = trim($v[0] > 0x7f ? "\x00$v" : $v, "\x00");
return pack('CCa*', self::DER_INT, strlen($str), $str);
},
str_split($sig, max(1, intdiv(strlen($sig), 2)))
));

$success = openssl_verify(
$msg,
pack('CCa*', self::DER_SEQ, strlen($der), $der),
$public,
$this->supported[$this->name]
);

//TODO remove together with the support of PHP versions < 8.0
if (version_compare(PHP_VERSION, '8.0.0') < 0) {
\openssl_pkey_free($public);
}

if ($success === 1) {
return;
} elseif ($success === 0) {
throw new VerificationFailed();
}

throw new VerificationFailed('OpenSSL error: ' . \openssl_error_string());
}
}
42 changes: 42 additions & 0 deletions vicephp/Virtue-JWT/tests/JWT/Algorithms/ECDSATest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Virtue\JWT\Algorithms;

use PHPUnit\Framework\TestCase;
use Virtue\JWK\Key\ECDSA;
use Virtue\JWT\Token;
use Virtue\JWT\VerificationFailed;

class ECDSATest extends TestCase
{
private const PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" .
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2\n" .
"OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r\n" .
"1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G\n" .
"-----END PRIVATE KEY-----";

private const PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" .
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\n" .
"q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n" .
"-----END PUBLIC KEY-----";

public function testVerify()
{
$token = Token::ofString("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.JQ59_MfvAyMcbIIwfwGtNx05VT_Cd7TU11KiqMZ7SkwhVuz-zrzgwEreiTxilY6FuVfW3bi-qP6ai64pud04ZA");
$ecdsaVerify = new ECDSAVerify(ECDSA\PublicKey::fromPem(self::PUBLIC_KEY));
$ecdsaVerify->verify($token);
$this->addToAssertionCount(1);
}

public function testWrongSignature()
{
echo self::PRIVATE_KEY;
echo "\n";
echo self::PUBLIC_KEY;
$token = Token::ofString("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.daxHOLL2WBVEHejBUIKNNi8rJ_y-5TnCyZw_vHKy1fRafFkX1JLvZs7N0zEr4WQJ0K7jiXbiBFZ_ogRiMBAbnw");
var_dump($token->signature());
$ecdsaVerify = new ECDSAVerify(ECDSA\PublicKey::fromPem(self::PUBLIC_KEY));
$this->expectException(VerificationFailed::class);
$ecdsaVerify->verify($token);
}
}