Skip to content

Commit e705275

Browse files
authored
Merge pull request #28 from pdsinterop/feature/jti
Add JTI validation
2 parents ba89585 + 0f45dd7 commit e705275

File tree

14 files changed

+1097
-171
lines changed

14 files changed

+1097
-171
lines changed

src/Config.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
use Pdsinterop\Solid\Auth\Config\Client;
66
use Pdsinterop\Solid\Auth\Config\Expiration;
7-
use Pdsinterop\Solid\Auth\Config\Keys;
8-
use Pdsinterop\Solid\Auth\Config\Server;
7+
use Pdsinterop\Solid\Auth\Config\KeysInterface as Keys;
8+
use Pdsinterop\Solid\Auth\Config\ServerInterface as Server;
99

1010
class Config
1111
{

src/Config/Keys.php

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,35 @@
66
use Lcobucci\JWT\Signer\Key\InMemory as Key;
77
use League\OAuth2\Server\CryptKey;
88

9-
class Keys
9+
class Keys implements KeysInterface
1010
{
1111
////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
1212

13-
/** @var string|CryptoKey */
14-
private $encryptionKey;
15-
/** @var CryptKey*/
16-
private $privateKey;
17-
/** @var Key */
18-
private $publicKey;
13+
private string|CryptoKey $encryptionKey;
14+
private CryptKey $privateKey;
15+
private Key $publicKey;
1916

2017
//////////////////////////// GETTERS AND SETTERS \\\\\\\\\\\\\\\\\\\\\\\\\\\
2118

22-
/** @return CryptoKey|string */
23-
final public function getEncryptionKey()
19+
final public function getEncryptionKey(): CryptoKey|string
2420
{
2521
return $this->encryptionKey;
2622
}
2723

28-
/** @return CryptKey */
29-
final public function getPrivateKey() : CryptKey
24+
final public function getPrivateKey(): CryptKey
3025
{
3126
return $this->privateKey;
3227
}
3328

34-
/*** @return Key */
35-
public function getPublicKey() : Key
29+
public function getPublicKey(): Key
3630
{
3731
return $this->publicKey;
3832
}
3933

4034
//////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
4135

42-
/**
43-
* Keys constructor.
44-
*
45-
* @param CryptKey $privateKey
46-
* @param string|CryptoKey $encryptionKey
47-
*/
48-
final public function __construct(CryptKey $privateKey, Key $publicKey, $encryptionKey)
36+
final public function __construct(CryptKey $privateKey, Key $publicKey, CryptoKey|string $encryptionKey)
4937
{
50-
// @FIXME: Add type-check for $encryptionKey (or an extending class with different parameter type?)
5138
$this->encryptionKey = $encryptionKey;
5239
$this->privateKey = $privateKey;
5340
$this->publicKey = $publicKey;

src/Config/KeysInterface.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Pdsinterop\Solid\Auth\Config;
4+
5+
use Defuse\Crypto\Key as CryptoKey;
6+
use Lcobucci\JWT\Signer\Key\InMemory as Key;
7+
use League\OAuth2\Server\CryptKey;
8+
9+
interface KeysInterface
10+
{
11+
public function getEncryptionKey(): CryptoKey|string;
12+
13+
public function getPrivateKey(): CryptKey;
14+
15+
public function getPublicKey(): Key;
16+
}

src/Config/Server.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use Pdsinterop\Solid\Auth\Enum\OpenId\OpenIdConnectMetadata as OidcMeta;
77
use Pdsinterop\Solid\Auth\Exception\LogicException;
88

9-
class Server implements JsonSerializable
9+
class Server implements ServerInterface
1010
{
1111
////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
1212

src/Config/ServerInterface.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Pdsinterop\Solid\Auth\Config;
4+
5+
use Pdsinterop\Solid\Auth\Exception\LogicException;
6+
7+
interface ServerInterface extends \JsonSerializable
8+
{
9+
public function get($key);
10+
11+
public function getRequired(): array;
12+
13+
public function __toString(): string;
14+
15+
/**
16+
* @return array
17+
*
18+
* @throws LogicException for missing required properties
19+
*/
20+
public function jsonSerialize(): array;
21+
22+
public function validate(): bool;
23+
}

src/Exceptions.inc.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace Pdsinterop\Solid\Auth\Exception;
44

5+
use Throwable;
6+
57
abstract class Exception extends \Exception implements \JsonSerializable
68
{
7-
final public function jsonSerialize()
9+
final public function jsonSerialize(): array
810
{
911
return [
1012
'code' => $this->getCode(),
@@ -18,5 +20,8 @@ final public function jsonSerialize()
1820
}
1921
}
2022

21-
2223
class LogicException extends Exception {}
24+
25+
class AuthorizationHeaderException extends Exception {}
26+
27+
class InvalidTokenException extends Exception {}

src/ReplayDetectorInterface.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Pdsinterop\Solid\Auth;
4+
5+
interface ReplayDetectorInterface
6+
{
7+
public function detect(string $jti, string $targetUri): bool;
8+
}

src/TokenGenerator.php

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,26 @@
1414

1515
class TokenGenerator
1616
{
17-
use CryptTrait;
1817
////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
1918

20-
/** @var Config */
21-
public $config;
19+
use CryptTrait;
20+
21+
public Config $config;
22+
23+
private \DateInterval $validFor;
2224

2325
//////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
2426

2527
final public function __construct(
26-
Config $config
28+
Config $config,
29+
\DateInterval $validFor
2730
) {
2831
$this->config = $config;
32+
$this->validFor = $validFor;
33+
2934
$this->setEncryptionKey($this->config->getKeys()->getEncryptionKey());
3035
}
31-
36+
3237
public function generateRegistrationAccessToken($clientId, $privateKey) {
3338
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);
3439

@@ -42,18 +47,19 @@ public function generateRegistrationAccessToken($clientId, $privateKey) {
4247

4348
return $token->toString();
4449
}
45-
46-
public function generateIdToken($accessToken, $clientId, $subject, $nonce, $privateKey, $dpopKey=null) {
50+
51+
public function generateIdToken($accessToken, $clientId, $subject, $nonce, $privateKey, $dpopKey, $now=null) {
4752
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);
4853

4954
$jwks = $this->getJwks();
5055
$tokenHash = $this->generateTokenHash($accessToken);
5156

5257
// Create JWT
5358
$jwtConfig = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($privateKey));
54-
$now = new DateTimeImmutable();
59+
$now = $now ?? new DateTimeImmutable();
5560
$useAfter = $now->sub(new \DateInterval('PT1S'));
56-
$expire = $now->add(new \DateInterval('PT' . 14*24*60*60 . 'S'));
61+
62+
$expire = $now->add($this->validFor);
5763

5864
$token = $jwtConfig->builder()
5965
->issuedBy($issuer)
@@ -75,7 +81,7 @@ public function generateIdToken($accessToken, $clientId, $subject, $nonce, $priv
7581
->getToken($jwtConfig->signer(), $jwtConfig->signingKey());
7682
return $token->toString();
7783
}
78-
84+
7985
public function respondToRegistration($registration, $privateKey) {
8086
/*
8187
Expects in $registration:
@@ -94,10 +100,10 @@ public function respondToRegistration($registration, $privateKey) {
94100
'token_endpoint_auth_method' => 'client_secret_basic',
95101
'registration_access_token' => $registration_access_token,
96102
);
97-
103+
98104
return array_merge($registrationBase, $registration);
99105
}
100-
106+
101107
public function addIdTokenToResponse($response, $clientId, $subject, $nonce, $privateKey, $dpopKey=null) {
102108
if ($response->hasHeader("Location")) {
103109
$value = $response->getHeaderLine("Location");
@@ -111,7 +117,7 @@ public function addIdTokenToResponse($response, $clientId, $subject, $nonce, $pr
111117
$privateKey,
112118
$dpopKey
113119
);
114-
$value = preg_replace("/#access_token=(.*?)&/", "#access_token=\$1&id_token=$idToken&", $value);
120+
$value = preg_replace("/#access_token=(.*?)&/", "#access_token=\$1&id_token=$idToken&", $value);
115121
$response = $response->withHeader("Location", $value);
116122
} else if (preg_match("/code=(.*?)&/", $value, $matches)) {
117123
$idToken = $this->generateIdToken(
@@ -153,12 +159,13 @@ public function addIdTokenToResponse($response, $clientId, $subject, $nonce, $pr
153159
public function getCodeInfo($code) {
154160
return json_decode($this->decrypt($code), true);
155161
}
162+
156163
///////////////////////////// HELPER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
157164

158165
private function generateJti() {
159166
return substr(md5((string)time()), 12); // FIXME: generate unique jti values
160167
}
161-
168+
162169
private function generateTokenHash($accessToken) {
163170
$atHash = hash('sha256', $accessToken);
164171
$atHash = substr($atHash, 0, 32);

0 commit comments

Comments
 (0)