Skip to content

Commit f74ec00

Browse files
committed
1 parent dfedef8 commit f74ec00

29 files changed

+2801
-56
lines changed

examples/example.php

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,59 @@
11
<?php
22
/**
3-
* example.php
3+
* example-ec.php
44
*
5-
* @created 08.03.2024
5+
* @created 19.08.2024
66
* @author smiley <[email protected]>
77
* @copyright 2024 smiley
88
* @license MIT
99
*/
1010
declare(strict_types=1);
1111

12+
use chillerlan\JOSE\Algorithms\Signature\ECDSA;
13+
use chillerlan\JOSE\JWS;
14+
use chillerlan\JOSE\Key\ECKey;
15+
use chillerlan\JOSE\Util;
16+
1217
require_once __DIR__.'/../vendor/autoload.php';
1318

14-
$example = new \chillerlan\LibraryTemplate\Example;
19+
$algo = 'ES256';
20+
21+
// key examples from: https://datatracker.ietf.org/doc/html/rfc7515#appendix-A
22+
$privateJWK = '{
23+
"kty":"EC",
24+
"crv":"P-256",
25+
"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
26+
"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
27+
"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"
28+
}';
29+
30+
// JWK to PEM conversion
31+
#$privatePEM = (new ECKey)->privateKeyToPEM(Util::jsonDecode($privateJWK));
32+
$privatePEM = '-----BEGIN EC PRIVATE KEY-----
33+
MHcCAQEEII6bEJ5xkJi/mASH3x9dd+nLKWBuvtImO19XwhPfhPSyoAoGCCqGSM49
34+
AwEHoUQDQgAEf83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEXH8UTNG72b
35+
focs3+257rn0s2ldbqkLJK2KRiMohYjlrQ==
36+
-----END EC PRIVATE KEY-----';
37+
38+
#$publicPEM = (new ECKey)->publicKeyToPEM(Util::jsonDecode($privateJWK));
39+
$publicPEM = '-----BEGIN PUBLIC KEY-----
40+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEf83OJ3D2xF1Bg8vub9tLe1gHMzV7
41+
6e8Tus9uPHvRVEXH8UTNG72bfocs3+257rn0s2ldbqkLJK2KRiMohYjlrQ==
42+
-----END PUBLIC KEY-----';
43+
44+
// invoke a key from JWK...
45+
$jwk = ECKey::parse(Util::jsonDecode($privateJWK));
46+
// ...or PEM
47+
$jwk = new ECKey(privateKey: $privatePEM, publicKey: $publicPEM);
48+
// invoke the signature algorithm
49+
$jwa = new ECDSA(jwk: $jwk, algo: $algo);
50+
// invoke the signature
51+
$jws = new JWS($jwa);
52+
// encode and sign (requires private key
53+
$jwt = $jws->encode(['foo' => 'bar']);
54+
var_dump($jwt);
55+
// decode and verify (requires public key)
56+
var_dump($jws->decode($jwt));
1557

16-
echo $example->hello();
58+
#var_dump((new ECKey)->publicKeyToPEM(Util::jsonDecode($privateJWK)));
59+
#var_dump((new ECKey)->pemToPrivateJWK($privatePEM));
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
/**
3+
* Interface EncryptionAlgorithm
4+
*
5+
* @created 17.08.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
declare(strict_types=1);
11+
12+
namespace chillerlan\JOSE\Algorithms\Encryption;
13+
14+
use chillerlan\JOSE\Algorithms\JWA;
15+
16+
/**
17+
* (placeholder - do not use this at all, seriously.)
18+
*
19+
* @link https://datatracker.ietf.org/doc/html/rfc7518#section-4
20+
* @link https://datatracker.ietf.org/doc/html/rfc7518#section-5
21+
*/
22+
interface EncryptionAlgorithm extends JWA{
23+
24+
}

src/Algorithms/JWA.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
* Class JWA
4+
*
5+
* @created 05.08.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
declare(strict_types=1);
11+
12+
namespace chillerlan\JOSE\Algorithms;
13+
14+
use chillerlan\JOSE\Key\JWK;
15+
16+
/**
17+
* JSON Web Algorithms (JWA)
18+
*
19+
* @link https://datatracker.ietf.org/doc/html/rfc7518
20+
* @link https://datatracker.ietf.org/doc/html/rfc8037
21+
*/
22+
interface JWA{
23+
24+
public const SUPPORTED_ALGOS = [];
25+
26+
public function isSupported(string $algo):bool;
27+
public function getJwk():JWK;
28+
public function getName():string;
29+
30+
}

src/Algorithms/JWAAbstract.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Class JWAAbstract
4+
*
5+
* @created 08.08.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
declare(strict_types=1);
11+
12+
namespace chillerlan\JOSE\Algorithms;
13+
14+
use chillerlan\JOSE\Key\JWK;
15+
use RuntimeException;
16+
use function array_key_exists;
17+
18+
/**
19+
* @link https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-crypt_algorithm_identifier
20+
*/
21+
abstract class JWAAbstract implements JWA{
22+
23+
protected JWK $jwk;
24+
protected string $algo;
25+
26+
public function __construct(JWK $jwk, string $algo){
27+
28+
if(!$this->isSupported($algo)){
29+
throw new RuntimeException('invalid algo');
30+
}
31+
32+
$this->jwk = $jwk;
33+
$this->algo = $algo;
34+
35+
$jwkAlgo = $this->jwk->getAlgo();
36+
37+
if($jwkAlgo !== null && $jwkAlgo !== $this->algo){
38+
throw new RuntimeException('key algo does not match the given algo');
39+
}
40+
41+
}
42+
43+
public function isSupported(string $algo):bool{
44+
return array_key_exists($algo, $this::SUPPORTED_ALGOS);
45+
}
46+
47+
public function getJwk():JWK{
48+
return $this->jwk;
49+
}
50+
51+
public function getName():string{
52+
return $this->algo;
53+
}
54+
55+
}

src/Algorithms/OpenSSLAbstract.php

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
/**
3+
* Class OpenSSLAbstract
4+
*
5+
* @created 10.08.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
declare(strict_types=1);
11+
12+
namespace chillerlan\JOSE\Algorithms;
13+
14+
use chillerlan\JOSE\Key\JWK;
15+
use OpenSSLAsymmetricKey;
16+
use RuntimeException;
17+
use function openssl_pkey_get_details;
18+
use function openssl_pkey_get_private;
19+
use function openssl_pkey_get_public;
20+
use function openssl_sign;
21+
use function openssl_verify;
22+
23+
/**
24+
* PHP 8.1 under Windows might get hit with a bunch of OpenSSL errors:
25+
*
26+
* error:25070067:DSO support routines:DSO_load:could not load the shared library
27+
* error:0E07506E:configuration file routines:module_load_dso:error loading dso
28+
* error:0E076071:configuration file routines:module_run:unknown module name
29+
* error:0909006C:PEM routines:get_name:no start line
30+
*
31+
* derived from web-token/jwt-framework
32+
*
33+
* @link https://github.com/web-token/jwt-framework/blob/4af252f28996bfb8ce5eac78037a555dce222829/src/Library/Signature/Algorithm/Util/RSA.php
34+
*/
35+
abstract class OpenSSLAbstract extends JWAAbstract{
36+
37+
protected const KEYTYPE = -1;
38+
39+
private string|null $passphrase;
40+
41+
public function __construct(JWK $jwk, string $algo, string|null $passphrase = null){
42+
parent::__construct($jwk, $algo);
43+
44+
$this->passphrase = $passphrase;
45+
}
46+
47+
abstract protected function checkKeyLength(int $bits):bool;
48+
49+
protected function signMessage(string $message):string{
50+
51+
if(openssl_sign($message, $signature, $this->jwk->getPrivateKey(), $this::SUPPORTED_ALGOS[$this->algo]) === false){
52+
throw new RuntimeException('openssl_sign error');
53+
}
54+
55+
return $signature;
56+
}
57+
58+
protected function verifySignature(string $message, string $signature):bool{
59+
$verified = openssl_verify($message, $signature, $this->getPublicKey(), $this::SUPPORTED_ALGOS[$this->algo]);
60+
61+
return $verified === 1;
62+
}
63+
64+
protected function getPrivateKey():OpenSSLAsymmetricKey{
65+
/** @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal */
66+
return $this->verifyKey(openssl_pkey_get_private($this->jwk->getPrivateKey(), $this->passphrase));
67+
}
68+
69+
/**
70+
* @return array<string, string|array<string, string>>
71+
* @throws \RuntimeException
72+
*/
73+
protected function getPrivateKeyDetails():array{
74+
$details = openssl_pkey_get_details($this->getPrivateKey());
75+
76+
if($details === false){
77+
throw new RuntimeException('could not get private key details');
78+
}
79+
80+
return $details;
81+
}
82+
83+
protected function getPublicKey():OpenSSLAsymmetricKey{
84+
return $this->verifyKey(openssl_pkey_get_public($this->jwk->getPublicKey()));
85+
}
86+
87+
/**
88+
* @return array<string, string|array<string, string>>
89+
* @throws \RuntimeException
90+
*/
91+
protected function getPublicKeyDetails():array{
92+
$details = openssl_pkey_get_details($this->getPublicKey());
93+
94+
if($details === false){
95+
throw new RuntimeException('could not get public key details');
96+
}
97+
98+
return $details;
99+
}
100+
101+
/**
102+
* @throws \RuntimeException
103+
*/
104+
private function verifyKey(OpenSSLAsymmetricKey|false $key):OpenSSLAsymmetricKey{
105+
106+
if($key === false){
107+
throw new RuntimeException('invalid key');
108+
}
109+
110+
$details = openssl_pkey_get_details($key);
111+
112+
if($details === false){
113+
throw new RuntimeException('could not get key details');
114+
}
115+
116+
if($details['type'] !== $this::KEYTYPE){
117+
throw new RuntimeException('invalid key type');
118+
}
119+
120+
if(!$this->checkKeyLength($details['bits'])){
121+
throw new RuntimeException('invalid key length');
122+
}
123+
124+
return $key;
125+
}
126+
127+
}

0 commit comments

Comments
 (0)