Skip to content

Commit 2479e92

Browse files
committed
WIP
1 parent f82f65e commit 2479e92

File tree

18 files changed

+366
-364
lines changed

18 files changed

+366
-364
lines changed

config/module_oidc.php.dist

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,25 @@ $config = [
133133
/**
134134
* Token related options.
135135
*/
136-
// Authorization code and tokens TTL (validity duration), with given examples. For duration format info, check
136+
// Authorization code and tokens TTL (validity duration), with given examples.
137+
// For duration format info, check
137138
// https://www.php.net/manual/en/dateinterval.construct.php
138139
ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL => 'PT10M', // 10 minutes
139140
ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL => 'P1M', // 1 month
140141
ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL => 'PT1H', // 1 hour,
141142

143+
/**
144+
* (optional) Timestamp Validation Leeway - additional time tolerance
145+
* allowed for timestamp validation. This is used when validating
146+
* timestamps like Expiration Time (exp), Issued At (iat), Not
147+
* Before (nbf), and similar claims on JWS artifacts.
148+
* If not set, falls back to 'PT1M' (1 minute).
149+
*
150+
* For duration format info, check
151+
* https://www.php.net/manual/en/dateinterval.construct.php
152+
*/
153+
ModuleConfig::OPTION_TIMESTAMP_VALIDATION_LEEWAY => 'PT1M',
154+
142155
/**
143156
* Authentication related options.
144157
*/

docs/6-oidc-upgrade.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ it in production.
1818

1919
New configuration options:
2020

21-
- ModuleConfig::OPTION_PROTOCOL_SIGNATURE_KEY_PAIRS - enables defining multiple
22-
protocol (Connect) related signing algorithms and key pairs.
23-
- ModuleConfig::OPTION_FEDERATION_SIGNATURE_KEY_PAIRS - enables defining
21+
- ModuleConfig::OPTION_PROTOCOL_SIGNATURE_KEY_PAIRS - (required) enables defining
22+
multiple protocol (Connect) related signing algorithms and key pairs.
23+
- ModuleConfig::OPTION_FEDERATION_SIGNATURE_KEY_PAIRS - (required if federation
24+
capabilities are enabled) enables defining multiple key pairs for
25+
Federation purposes like signing Entity Statements, publishing new key for
26+
key roll-ower scenarios, etc.
27+
- ModuleConfig::OPTION_TIMESTAMP_VALIDATION_LEEWAY - optional, used for setting
28+
allowed time tolerance for timestamp validation in artifacts like JWSs.
2429
multiple Federation related signing algorithms and key pairs.
2530
- Several new options regarding experimental support for OpenID4VCI.
2631

routing/services/services.yml

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,30 +75,20 @@ services:
7575
class: League\OAuth2\Server\CryptKey
7676
factory: ['@SimpleSAML\Module\oidc\Factories\CryptKeyFactory', 'buildPublicKey']
7777

78-
SimpleSAML\Module\oidc\Factories\ResourceServerFactory:
79-
arguments:
80-
$publicKey: '@oidc.key.public'
8178
SimpleSAML\Module\oidc\Factories\AuthorizationServerFactory:
8279
arguments:
8380
$privateKey: '@oidc.key.private'
8481
SimpleSAML\Module\oidc\Factories\TokenResponseFactory:
8582
arguments:
8683
$privateKey: '@oidc.key.private'
87-
SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory:
88-
arguments:
89-
$privateKey: '@oidc.key.private'
9084

91-
SimpleSAML\Module\oidc\Server\Validators\BearerTokenValidator:
92-
arguments:
93-
$publicKey: '@oidc.key.public'
85+
SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory: ~
9486

87+
SimpleSAML\Module\oidc\Server\Validators\BearerTokenValidator: ~
88+
SimpleSAML\Module\oidc\Server\ResourceServer: ~
9589
SimpleSAML\Module\oidc\Server\AuthorizationServer:
9690
factory: ['@SimpleSAML\Module\oidc\Factories\AuthorizationServerFactory', 'build']
9791

98-
# OAuth2 Server
99-
League\OAuth2\Server\ResourceServer:
100-
factory: ['@SimpleSAML\Module\oidc\Factories\ResourceServerFactory', 'build']
101-
10292
# Utils
10393
SimpleSAML\Module\oidc\Utils\Debug\ArrayLogger: ~
10494
SimpleSAML\Module\oidc\Utils\FederationParticipationValidator: ~
@@ -134,6 +124,8 @@ services:
134124
factory: [ '@SimpleSAML\Module\oidc\Factories\JwksFactory', 'build' ]
135125
SimpleSAML\OpenID\Jwk: ~
136126
SimpleSAML\OpenID\Did: ~
127+
SimpleSAML\OpenID\Jws:
128+
factory: [ '@SimpleSAML\Module\oidc\Factories\JwsFactory', 'build' ]
137129

138130

139131
# SSP

src/Controllers/UserInfoController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
use Laminas\Diactoros\Response\JsonResponse;
2020
use League\OAuth2\Server\Exception\OAuthServerException;
21-
use League\OAuth2\Server\ResourceServer;
2221
use Psr\Http\Message\ResponseInterface;
2322
use Psr\Http\Message\ServerRequestInterface;
2423
use SimpleSAML\Error;
@@ -29,6 +28,7 @@
2928
use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository;
3029
use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository;
3130
use SimpleSAML\Module\oidc\Repositories\UserRepository;
31+
use SimpleSAML\Module\oidc\Server\ResourceServer;
3232
use SimpleSAML\Module\oidc\Services\ErrorResponder;
3333
use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor;
3434
use Symfony\Component\HttpFoundation\Request;

src/Controllers/VerifiableCredentials/CredentialIssuerCredentialController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace SimpleSAML\Module\oidc\Controllers\VerifiableCredentials;
66

77
use Base64Url\Base64Url;
8-
use League\OAuth2\Server\ResourceServer;
98
use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge;
109
use SimpleSAML\Module\oidc\Codebooks\FlowTypeEnum;
1110
use SimpleSAML\Module\oidc\Entities\AccessTokenEntity;
@@ -14,6 +13,7 @@
1413
use SimpleSAML\Module\oidc\Repositories\IssuerStateRepository;
1514
use SimpleSAML\Module\oidc\Repositories\UserRepository;
1615
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
16+
use SimpleSAML\Module\oidc\Server\ResourceServer;
1717
use SimpleSAML\Module\oidc\Services\LoggerService;
1818
use SimpleSAML\Module\oidc\Utils\RequestParamsResolver;
1919
use SimpleSAML\Module\oidc\Utils\Routes;

src/Entities/AccessTokenEntity.php

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
namespace SimpleSAML\Module\oidc\Entities;
1818

1919
use DateTimeImmutable;
20-
use Lcobucci\JWT\Configuration;
21-
use Lcobucci\JWT\Token;
22-
use League\OAuth2\Server\CryptKey;
2320
use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface;
2421
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
2522
use League\OAuth2\Server\Entities\Traits\EntityTrait;
@@ -29,7 +26,10 @@
2926
use SimpleSAML\Module\oidc\Entities\Interfaces\EntityStringRepresentationInterface;
3027
use SimpleSAML\Module\oidc\Entities\Traits\AssociateWithAuthCodeTrait;
3128
use SimpleSAML\Module\oidc\Entities\Traits\RevokeTokenTrait;
32-
use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService;
29+
use SimpleSAML\Module\oidc\ModuleConfig;
30+
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
31+
use SimpleSAML\OpenID\Jws;
32+
use SimpleSAML\OpenID\Jws\ParsedJws;
3333
use Stringable;
3434

3535
/**
@@ -63,13 +63,12 @@ public function __construct(
6363
OAuth2ClientEntityInterface $clientEntity,
6464
array $scopes,
6565
DateTimeImmutable $expiryDateTime,
66-
CryptKey $privateKey,
67-
protected JsonWebTokenBuilderService $jsonWebTokenBuilderService,
66+
protected readonly Jws $jws,
67+
protected readonly ModuleConfig $moduleConfig,
6868
int|string|null $userIdentifier = null,
6969
?string $authCodeId = null,
7070
?array $requestedClaims = null,
7171
?bool $isRevoked = false,
72-
?Configuration $jwtConfiguration = null,
7372
protected readonly ?FlowTypeEnum $flowTypeEnum = null,
7473
protected readonly ?array $authorizationDetails = null,
7574
protected readonly ?string $boundClientId = null,
@@ -82,14 +81,12 @@ public function __construct(
8281
$this->addScope($scope);
8382
}
8483
$this->setExpiryDateTime($expiryDateTime);
85-
$this->setPrivateKey($privateKey);
8684
$this->setUserIdentifier($userIdentifier);
8785
$this->setAuthCodeId($authCodeId);
8886
$this->setRequestedClaims($requestedClaims ?? []);
8987
if ($isRevoked) {
9088
$this->revoke();
9189
}
92-
$jwtConfiguration !== null ? $this->jwtConfiguration = $jwtConfiguration : $this->initJwtConfiguration();
9390
}
9491

9592
/**
@@ -137,7 +134,7 @@ public function getState(): array
137134
*/
138135
public function __toString(): string
139136
{
140-
return $this->stringRepresentation = $this->convertToJWT()->toString();
137+
return $this->stringRepresentation = $this->convertToJWT()->getToken();
141138
}
142139

143140
/**
@@ -150,29 +147,40 @@ public function toString(): ?string
150147
}
151148

152149
/**
153-
* Implemented instead of original AccessTokenTrait::convertToJWT() method in order to remove microseconds from
154-
* timestamps and to add claims like iss, etc., by using our own JWT builder service.
150+
* Implemented instead of original AccessTokenTrait::convertToJWT() method
151+
* in order to remove microseconds from timestamps and to add claims
152+
* like iss, etc.
155153
*
156-
* @return \Lcobucci\JWT\Token
157154
* @throws \League\OAuth2\Server\Exception\OAuthServerException
158155
* @throws \Exception
159156
*/
160-
protected function convertToJWT(): Token
157+
protected function convertToJWT(): ParsedJws
161158
{
162-
/** @psalm-suppress ArgumentTypeCoercion */
163-
$jwtBuilder = $this->jsonWebTokenBuilderService->getProtocolJwtBuilder()
164-
->permittedFor($this->getClient()->getIdentifier())
165-
->identifiedBy((string)$this->getIdentifier())
166-
->issuedAt(new DateTimeImmutable())
167-
->canOnlyBeUsedAfter(new DateTimeImmutable())
168-
->expiresAt($this->getExpiryDateTime())
169-
->relatedTo((string) $this->getUserIdentifier())
170-
->withClaim('scopes', $this->getScopes());
171-
if ($this->issuerState !== null) {
172-
$jwtBuilder = $jwtBuilder->withClaim('issuer_state', $this->issuerState);
173-
}
159+
$protocolSignatureKeyPair = $this->moduleConfig->getProtocolSignatureKeyPairBag()->getFirstOrFail();
160+
$currentTimestamp = $this->jws->helpers()->dateTime()->getUtc()->getTimestamp();
161+
162+
$payload = array_filter([
163+
ClaimsEnum::Iss->value => $this->moduleConfig->getIssuer(),
164+
ClaimsEnum::Iat->value => $currentTimestamp,
165+
ClaimsEnum::Jti->value => (string)$this->getIdentifier(),
166+
ClaimsEnum::Aud->value => $this->getClient()->getIdentifier(),
167+
ClaimsEnum::Nbf->value => $currentTimestamp,
168+
ClaimsEnum::Exp->value => $this->expiryDateTime->getTimestamp(),
169+
ClaimsEnum::Sub->value => (string)$this->getUserIdentifier(),
170+
'scopes' => $this->getScopes(),
171+
ClaimsEnum::IssuerState->value => $this->issuerState,
172+
]);
173+
174+
$header = [
175+
ClaimsEnum::Kid->value => $protocolSignatureKeyPair->getKeyPair()->getKeyId(),
176+
];
174177

175-
return $this->jsonWebTokenBuilderService->getSignedProtocolJwt($jwtBuilder);
178+
return $this->jws->parsedJwsFactory()->fromData(
179+
$protocolSignatureKeyPair->getKeyPair()->getPrivateKey(),
180+
$protocolSignatureKeyPair->getSignatureAlgorithm(),
181+
$payload,
182+
$header,
183+
);
176184
}
177185

178186
public function getFlowTypeEnum(): ?FlowTypeEnum

src/Factories/CryptKeyFactory.php

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace SimpleSAML\Module\oidc\Factories;
66

77
use League\OAuth2\Server\CryptKey;
8+
use SimpleSAML\Error\ConfigurationError;
89
use SimpleSAML\Module\oidc\ModuleConfig;
910

1011
class CryptKeyFactory
@@ -19,9 +20,14 @@ public function __construct(
1920
*/
2021
public function buildPrivateKey(): CryptKey
2122
{
23+
$defaultSignatureKeyPairConfig = $this->getDefaultProtocolSignatureKeyPairConfig();
24+
25+
$privateKeyFilename = $defaultSignatureKeyPairConfig[ModuleConfig::KEY_PRIVATE_KEY_FILENAME];
26+
$privateKeyPassword = $defaultSignatureKeyPairConfig[ModuleConfig::KEY_PRIVATE_KEY_PASSWORD] ?? null;
27+
2228
return new CryptKey(
23-
$this->moduleConfig->getProtocolPrivateKeyPath(),
24-
$this->moduleConfig->getProtocolPrivateKeyPassPhrase(),
29+
$privateKeyFilename,
30+
$privateKeyPassword,
2531
true,
2632
);
2733
}
@@ -31,6 +37,33 @@ public function buildPrivateKey(): CryptKey
3137
*/
3238
public function buildPublicKey(): CryptKey
3339
{
34-
return new CryptKey($this->moduleConfig->getProtocolCertPath(), null, false);
40+
$defaultSignatureKeyPairConfig = $this->getDefaultProtocolSignatureKeyPairConfig();
41+
$publicKeyFilename = $defaultSignatureKeyPairConfig[ModuleConfig::KEY_PUBLIC_KEY_FILENAME];
42+
return new CryptKey($publicKeyFilename, null, false);
43+
}
44+
45+
/**
46+
* @return array{
47+
* algorithm: \SimpleSAML\OpenID\Algorithms\SignatureAlgorithmEnum,
48+
* private_key_filename: non-empty-string,
49+
* public_key_filename: non-empty-string,
50+
* private_key_password: ?non-empty-string,
51+
* key_id: ?non-empty-string
52+
* }
53+
* @throws ConfigurationError
54+
*
55+
*/
56+
protected function getDefaultProtocolSignatureKeyPairConfig(): array
57+
{
58+
$defaultProtocolKeyPair = $this->moduleConfig->getProtocolSignatureKeyPairs();
59+
60+
/** @psalm-suppress MixedAssignment */
61+
$defaultProtocolKeyPair = $defaultProtocolKeyPair[array_key_first($defaultProtocolKeyPair)];
62+
63+
if (!is_array($defaultProtocolKeyPair)) {
64+
throw new ConfigurationError('Invalid protocol signature key pairs config.');
65+
}
66+
67+
return $this->moduleConfig->getValidatedSignatureKeyPairArray($defaultProtocolKeyPair);
3568
}
3669
}

src/Factories/Entities/AccessTokenEntityFactory.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@
55
namespace SimpleSAML\Module\oidc\Factories\Entities;
66

77
use DateTimeImmutable;
8-
use League\OAuth2\Server\CryptKey;
98
use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface;
109
use SimpleSAML\Module\oidc\Codebooks\FlowTypeEnum;
1110
use SimpleSAML\Module\oidc\Entities\AccessTokenEntity;
1211
use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface;
1312
use SimpleSAML\Module\oidc\Helpers;
13+
use SimpleSAML\Module\oidc\ModuleConfig;
1414
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
15-
use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService;
15+
use SimpleSAML\OpenID\Jws;
1616

1717
class AccessTokenEntityFactory
1818
{
1919
public function __construct(
2020
protected readonly Helpers $helpers,
21-
protected readonly CryptKey $privateKey,
22-
protected readonly JsonWebTokenBuilderService $jsonWebTokenBuilderService,
2321
protected readonly ScopeEntityFactory $scopeEntityFactory,
22+
protected readonly Jws $jws,
23+
protected readonly ModuleConfig $moduleConfig,
2424
) {
2525
}
2626

@@ -47,8 +47,8 @@ public function fromData(
4747
$clientEntity,
4848
$scopes,
4949
$expiryDateTime,
50-
$this->privateKey,
51-
$this->jsonWebTokenBuilderService,
50+
$this->jws,
51+
$this->moduleConfig,
5252
$userIdentifier,
5353
$authCodeId,
5454
$requestedClaims,

src/Factories/FederationFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public function build(): Federation
2727
return new Federation(
2828
supportedAlgorithms: $this->moduleConfig->getSupportedAlgorithms(),
2929
maxCacheDuration: $this->moduleConfig->getFederationCacheMaxDurationForFetched(),
30+
timestampValidationLeeway: $this->moduleConfig->getTimestampValidationLeeway(),
3031
cache: $this->federationCache?->cache,
3132
logger: $this->loggerService,
3233
defaultTrustMarkStatusEndpointUsagePolicyEnum:

src/Factories/JwsFactory.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\oidc\Factories;
6+
7+
use SimpleSAML\Module\oidc\ModuleConfig;
8+
use SimpleSAML\Module\oidc\Services\LoggerService;
9+
use SimpleSAML\OpenID\Jws;
10+
11+
class JwsFactory
12+
{
13+
public function __construct(
14+
protected readonly ModuleConfig $moduleConfig,
15+
protected readonly LoggerService $loggerService,
16+
) {
17+
}
18+
19+
public function build(): Jws
20+
{
21+
return new Jws(
22+
supportedAlgorithms: $this->moduleConfig->getSupportedAlgorithms(),
23+
supportedSerializers: $this->moduleConfig->getSupportedSerializers(),
24+
timestampValidationLeeway: $this->moduleConfig->getTimestampValidationLeeway(),
25+
logger: $this->loggerService,
26+
);
27+
}
28+
}

0 commit comments

Comments
 (0)