Skip to content

Commit

Permalink
Merge pull request #28 from pdsinterop/feature/jti
Browse files Browse the repository at this point in the history
Add JTI validation
  • Loading branch information
Potherca authored Sep 23, 2022
2 parents ba89585 + 0f45dd7 commit e705275
Show file tree
Hide file tree
Showing 14 changed files with 1,097 additions and 171 deletions.
4 changes: 2 additions & 2 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use Pdsinterop\Solid\Auth\Config\Client;
use Pdsinterop\Solid\Auth\Config\Expiration;
use Pdsinterop\Solid\Auth\Config\Keys;
use Pdsinterop\Solid\Auth\Config\Server;
use Pdsinterop\Solid\Auth\Config\KeysInterface as Keys;
use Pdsinterop\Solid\Auth\Config\ServerInterface as Server;

class Config
{
Expand Down
29 changes: 8 additions & 21 deletions src/Config/Keys.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,35 @@
use Lcobucci\JWT\Signer\Key\InMemory as Key;
use League\OAuth2\Server\CryptKey;

class Keys
class Keys implements KeysInterface
{
////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\

/** @var string|CryptoKey */
private $encryptionKey;
/** @var CryptKey*/
private $privateKey;
/** @var Key */
private $publicKey;
private string|CryptoKey $encryptionKey;
private CryptKey $privateKey;
private Key $publicKey;

//////////////////////////// GETTERS AND SETTERS \\\\\\\\\\\\\\\\\\\\\\\\\\\

/** @return CryptoKey|string */
final public function getEncryptionKey()
final public function getEncryptionKey(): CryptoKey|string
{
return $this->encryptionKey;
}

/** @return CryptKey */
final public function getPrivateKey() : CryptKey
final public function getPrivateKey(): CryptKey
{
return $this->privateKey;
}

/*** @return Key */
public function getPublicKey() : Key
public function getPublicKey(): Key
{
return $this->publicKey;
}

//////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

/**
* Keys constructor.
*
* @param CryptKey $privateKey
* @param string|CryptoKey $encryptionKey
*/
final public function __construct(CryptKey $privateKey, Key $publicKey, $encryptionKey)
final public function __construct(CryptKey $privateKey, Key $publicKey, CryptoKey|string $encryptionKey)
{
// @FIXME: Add type-check for $encryptionKey (or an extending class with different parameter type?)
$this->encryptionKey = $encryptionKey;
$this->privateKey = $privateKey;
$this->publicKey = $publicKey;
Expand Down
16 changes: 16 additions & 0 deletions src/Config/KeysInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Pdsinterop\Solid\Auth\Config;

use Defuse\Crypto\Key as CryptoKey;
use Lcobucci\JWT\Signer\Key\InMemory as Key;
use League\OAuth2\Server\CryptKey;

interface KeysInterface
{
public function getEncryptionKey(): CryptoKey|string;

public function getPrivateKey(): CryptKey;

public function getPublicKey(): Key;
}
2 changes: 1 addition & 1 deletion src/Config/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Pdsinterop\Solid\Auth\Enum\OpenId\OpenIdConnectMetadata as OidcMeta;
use Pdsinterop\Solid\Auth\Exception\LogicException;

class Server implements JsonSerializable
class Server implements ServerInterface
{
////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\

Expand Down
23 changes: 23 additions & 0 deletions src/Config/ServerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Pdsinterop\Solid\Auth\Config;

use Pdsinterop\Solid\Auth\Exception\LogicException;

interface ServerInterface extends \JsonSerializable
{
public function get($key);

public function getRequired(): array;

public function __toString(): string;

/**
* @return array
*
* @throws LogicException for missing required properties
*/
public function jsonSerialize(): array;

public function validate(): bool;
}
9 changes: 7 additions & 2 deletions src/Exceptions.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace Pdsinterop\Solid\Auth\Exception;

use Throwable;

abstract class Exception extends \Exception implements \JsonSerializable
{
final public function jsonSerialize()
final public function jsonSerialize(): array
{
return [
'code' => $this->getCode(),
Expand All @@ -18,5 +20,8 @@ final public function jsonSerialize()
}
}


class LogicException extends Exception {}

class AuthorizationHeaderException extends Exception {}

class InvalidTokenException extends Exception {}
8 changes: 8 additions & 0 deletions src/ReplayDetectorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Pdsinterop\Solid\Auth;

interface ReplayDetectorInterface
{
public function detect(string $jti, string $targetUri): bool;
}
35 changes: 21 additions & 14 deletions src/TokenGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,26 @@

class TokenGenerator
{
use CryptTrait;
////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\

/** @var Config */
public $config;
use CryptTrait;

public Config $config;

private \DateInterval $validFor;

//////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

final public function __construct(
Config $config
Config $config,
\DateInterval $validFor
) {
$this->config = $config;
$this->validFor = $validFor;

$this->setEncryptionKey($this->config->getKeys()->getEncryptionKey());
}

public function generateRegistrationAccessToken($clientId, $privateKey) {
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);

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

return $token->toString();
}
public function generateIdToken($accessToken, $clientId, $subject, $nonce, $privateKey, $dpopKey=null) {

public function generateIdToken($accessToken, $clientId, $subject, $nonce, $privateKey, $dpopKey, $now=null) {
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);

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

// Create JWT
$jwtConfig = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($privateKey));
$now = new DateTimeImmutable();
$now = $now ?? new DateTimeImmutable();
$useAfter = $now->sub(new \DateInterval('PT1S'));
$expire = $now->add(new \DateInterval('PT' . 14*24*60*60 . 'S'));

$expire = $now->add($this->validFor);

$token = $jwtConfig->builder()
->issuedBy($issuer)
Expand All @@ -75,7 +81,7 @@ public function generateIdToken($accessToken, $clientId, $subject, $nonce, $priv
->getToken($jwtConfig->signer(), $jwtConfig->signingKey());
return $token->toString();
}

public function respondToRegistration($registration, $privateKey) {
/*
Expects in $registration:
Expand All @@ -94,10 +100,10 @@ public function respondToRegistration($registration, $privateKey) {
'token_endpoint_auth_method' => 'client_secret_basic',
'registration_access_token' => $registration_access_token,
);

return array_merge($registrationBase, $registration);
}

public function addIdTokenToResponse($response, $clientId, $subject, $nonce, $privateKey, $dpopKey=null) {
if ($response->hasHeader("Location")) {
$value = $response->getHeaderLine("Location");
Expand All @@ -111,7 +117,7 @@ public function addIdTokenToResponse($response, $clientId, $subject, $nonce, $pr
$privateKey,
$dpopKey
);
$value = preg_replace("/#access_token=(.*?)&/", "#access_token=\$1&id_token=$idToken&", $value);
$value = preg_replace("/#access_token=(.*?)&/", "#access_token=\$1&id_token=$idToken&", $value);
$response = $response->withHeader("Location", $value);
} else if (preg_match("/code=(.*?)&/", $value, $matches)) {
$idToken = $this->generateIdToken(
Expand Down Expand Up @@ -153,12 +159,13 @@ public function addIdTokenToResponse($response, $clientId, $subject, $nonce, $pr
public function getCodeInfo($code) {
return json_decode($this->decrypt($code), true);
}

///////////////////////////// HELPER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\

private function generateJti() {
return substr(md5((string)time()), 12); // FIXME: generate unique jti values
}

private function generateTokenHash($accessToken) {
$atHash = hash('sha256', $accessToken);
$atHash = substr($atHash, 0, 32);
Expand Down
Loading

0 comments on commit e705275

Please sign in to comment.