Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,11 @@ try {

### Validating Trust Marks

Federation tools expose Trust Mark Validator with several methods for validating Trust Marks, with the most common
one being the one to validate Trust Mark for some entity simply based on the Trust Mark Type.
Federation tools expose Trust Mark Validator with several methods for validating
Trust Marks, with the most common one being the one to validate Trust Mark for
some entity simply based on the Trust Mark Type.

If cache is utilized, Trust Mark validation will be cached with cache TTL being the minimum expiration
If cache is used, Trust Mark validation will be cached with cache TTL being the minimum expiration
time of Trust Mark, Leaf Entity Statement or `maxCacheDuration`, whatever is smaller.

```php
Expand All @@ -257,20 +258,22 @@ $leafEntityConfigurationStatement = $trustChain->getResolvedLeaf();
$trustAnchorConfigurationStatement = $trustChain->getResolvedTrustAnchor();

try {
// Example which queries cache for previously validated Trust Mark, and does formal validation if not cached.
// Example which queries cache for previously validated Trust Mark and does formal validation if not cached.
$federationTools->trustMarkValidator()->fromCacheOrDoForTrustMarkType(
$trustMarkType,
$leafEntityConfigurationStatement,
$trustAnchorConfigurationStatement,
$expectedJwtType = \SimpleSAML\OpenID\Codebooks\JwtTypesEnum::TrustMarkJwt,
);

// Example which always does formal validation (does not use cache).
// Example which always does formal validation (does not use cache), and requires usage of Trust Mark
// Status Endpoint for non-expiring Trust Marks.
$federationTools->trustMarkValidator()->doForTrustMarkType(
$trustMarkType,
$leafEntityConfigurationStatement,
$trustAnchorConfigurationStatement,
$expectedJwtType = \SimpleSAML\OpenID\Codebooks\JwtTypesEnum::TrustMarkJwt,
\SimpleSAML\OpenID\Codebooks\TrustMarkStatusEndpointUsagePolicyEnum::RequiredForNonExpiringTrustMarksOnly,
);
} catch (\Throwable $exception) {
$this->logger->error('Trust Mark validation failed. Error was: ' . $exception->getMessage());
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"vendor/bin/phpcbf",
"vendor/bin/phpcs -p",
"composer update web-token/jwt-framework --with web-token/jwt-framework:^3.0",
"vendor/bin/phpstan",
"vendor/bin/phpstan --memory-limit=1024M",
"vendor/bin/phpunit --no-coverage",
"composer update web-token/jwt-framework --with web-token/jwt-framework:^4.0",
"vendor/bin/phpstan",
Expand Down
2 changes: 2 additions & 0 deletions src/Codebooks/ClaimsEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ enum ClaimsEnum: string
case FederationFetchEndpoint = 'federation_fetch_endpoint';
case FederationListEndpoint = 'federation_list_endpoint';
case FederationTrustMarkEndpoint = 'federation_trust_mark_endpoint';
case FederationTrustMarkStatusEndpoint = 'federation_trust_mark_status_endpoint';
case Format = 'format';
case GrantTypes = 'grant_types';
case GrantTypesSupported = 'grant_types_supported';
Expand Down Expand Up @@ -130,6 +131,7 @@ enum ClaimsEnum: string
case ServiceDocumentation = 'service_documentation';
case SignedJwksUri = 'signed_jwks_uri';
case SignedMetadata = 'signed_metadata';
case Status = 'status';
// Subject
case Sub = 'sub';
case SubjectTypesSupported = 'subject_types_supported';
Expand Down
1 change: 1 addition & 0 deletions src/Codebooks/ContentTypesEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ enum ContentTypesEnum: string
case ApplicationJwt = 'application/jwt';
case ApplicationEntityStatementJwt = 'application/entity-statement+jwt';
case ApplicationTrustMarkJwt = 'application/trust-mark+jwt';
case ApplicationTrustMarkStatusResponseJwt = 'application/trust-mark-status-response+jwt';
}
1 change: 1 addition & 0 deletions src/Codebooks/JwtTypesEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ enum JwtTypesEnum: string
case JwkSetJwt = 'jwk-set+jwt';
case TrustMarkJwt = 'trust-mark+jwt';
case TrustMarkDelegationJwt = 'trust-mark-delegation+jwt';
case TrustMarkStatusResponseJwt = 'trust-mark-status-response+jwt';
}
32 changes: 32 additions & 0 deletions src/Codebooks/TrustMarkStatusEndpointUsagePolicyEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\OpenID\Codebooks;

/**
* Trust Mark Status Endpoint Usage Policy for Trust Mark validation.
*/
enum TrustMarkStatusEndpointUsagePolicyEnum
{
// Trust Mark will always be checked using the Trust Mark Status Endpoint.
// Trust Mark Status Endpoint must be exposed by Trust Mark Issuer.
case Required;

// Trust Mark will be checked using the Trust Mark Status Endpoint if the
// Trust Mark does not expire. Trust Mark Status Endpoint must be exposed
// by Trust Mark Issuer.
case RequiredForNonExpiringTrustMarksOnly;

// Trust Mark will be checked using the Trust Mark Status Endpoint if the
// Trust Mark Issuer provides the endpoint.
case RequiredIfEndpointProvided;

// Trust Mark will be checked using the Trust Mark Status Endpoint if the
// Trust Mark does not expire and the Trust Mark Issuer provides the
// endpoint.
case RequiredIfEndpointProvidedForNonExpiringTrustMarksOnly;

// Trust Mark will not be checked using the Trust Mark Status Endpoint.
case NotUsed;
}
25 changes: 25 additions & 0 deletions src/Codebooks/TrustMarkStatusEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\OpenID\Codebooks;

/**
* https://openid.net/specs/openid-federation-1_0.html#name-trust-mark-status-response
*/
enum TrustMarkStatusEnum: string
{
case Active = 'active';
case Expired = 'expired';
case Revoked = 'revoked';
case Invalid = 'invalid';


public function isValid(): bool
{
return match ($this) {
self::Active => true,
default => false,
};
}
}
10 changes: 7 additions & 3 deletions src/Decorators/HttpClientDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ public function __construct(public readonly Client $client = new Client(self::DE


/**
* @param array<string, mixed> $options See https://docs.guzzlephp.org/en/stable/request-options.html
* @throws \SimpleSAML\OpenID\Exceptions\HttpException
*/
public function request(HttpMethodsEnum $httpMethodsEnum, string $uri): ResponseInterface
{
public function request(
HttpMethodsEnum $httpMethodsEnum,
string $uri,
array $options = [],
): ResponseInterface {
try {
$response = $this->client->request($httpMethodsEnum->value, $uri);
$response = $this->client->request($httpMethodsEnum->value, $uri, $options);
} catch (Throwable $throwable) {
$message = sprintf(
'Error sending HTTP request to %s. Error was: %s',
Expand Down
9 changes: 9 additions & 0 deletions src/Exceptions/TrustMarkStatusException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\OpenID\Exceptions;

class TrustMarkStatusException extends JwsException
{
}
33 changes: 33 additions & 0 deletions src/Federation.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
use SimpleSAML\OpenID\Federation\Factories\TrustChainFactory;
use SimpleSAML\OpenID\Federation\Factories\TrustMarkDelegationFactory;
use SimpleSAML\OpenID\Federation\Factories\TrustMarkFactory;
use SimpleSAML\OpenID\Federation\Factories\TrustMarkStatusFactory;
use SimpleSAML\OpenID\Federation\MetadataPolicyApplicator;
use SimpleSAML\OpenID\Federation\MetadataPolicyResolver;
use SimpleSAML\OpenID\Federation\TrustChainResolver;
use SimpleSAML\OpenID\Federation\TrustMarkFetcher;
use SimpleSAML\OpenID\Federation\TrustMarkStatusFetcher;
use SimpleSAML\OpenID\Federation\TrustMarkValidator;
use SimpleSAML\OpenID\Jwks\Factories\JwksFactory;
use SimpleSAML\OpenID\Jws\Factories\JwsParserFactory;
Expand Down Expand Up @@ -101,6 +103,10 @@ class Federation

protected ?TrustMarkFetcher $trustMarkFetcher = null;

protected ?TrustMarkStatusFactory $trustMarkStatusFactory = null;

protected ?TrustMarkStatusFetcher $trustMarkStatusFetcher = null;


public function __construct(
protected readonly SupportedAlgorithms $supportedAlgorithms = new SupportedAlgorithms(),
Expand Down Expand Up @@ -248,12 +254,39 @@ public function trustMarkDelegationFactory(): TrustMarkDelegationFactory
}


public function trustMarkStatusFactory(): TrustMarkStatusFactory
{
return $this->trustMarkStatusFactory ??= new TrustMarkStatusFactory(
$this->jwsParser(),
$this->jwsVerifierDecorator(),
$this->jwksFactory(),
$this->jwsSerializerManagerDecorator(),
$this->timestampValidationLeewayDecorator,
$this->helpers(),
$this->claimFactory(),
);
}


public function trustMarkStatusFetcher(): TrustMarkStatusFetcher
{
return $this->trustMarkStatusFetcher ??= new TrustMarkStatusFetcher(
$this->trustMarkStatusFactory(),
$this->artifactFetcher(),
$this->maxCacheDurationDecorator,
$this->helpers(),
$this->logger,
);
}


public function trustMarkValidator(): TrustMarkValidator
{
return $this->trustMarkValidator ??= new TrustMarkValidator(
$this->trustChainResolver(),
$this->trustMarkFactory(),
$this->trustMarkDelegationFactory(),
$this->trustMarkStatusFetcher(),
$this->maxCacheDurationDecorator,
$this->cacheDecorator(),
$this->logger,
Expand Down
25 changes: 25 additions & 0 deletions src/Federation/EntityStatement.php
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,30 @@ public function getFederationTrustMarkEndpoint(): ?string
}


/**
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
* @throws \SimpleSAML\OpenID\Exceptions\OpenIdException
*
* @return ?non-empty-string
*/
public function getFederationTrustMarkStatusEndpoint(): ?string
{
$federationTrustMarkEndpoint = $this->helpers->arr()->getNestedValue(
$this->getPayload(),
ClaimsEnum::Metadata->value,
EntityTypesEnum::FederationEntity->value,
ClaimsEnum::FederationTrustMarkStatusEndpoint->value,
);

if (is_null($federationTrustMarkEndpoint)) {
return null;
}

return $this->helpers->type()->ensureNonEmptyString($federationTrustMarkEndpoint);
}


/**
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
Expand Down Expand Up @@ -362,6 +386,7 @@ protected function validate(): void
$this->getTrustMarkIssuers(...),
$this->getFederationFetchEndpoint(...),
$this->getFederationTrustMarkEndpoint(...),
$this->getFederationTrustMarkStatusEndpoint(...),
);
}
}
17 changes: 13 additions & 4 deletions src/Federation/EntityStatementFetcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Psr\Log\LoggerInterface;
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
use SimpleSAML\OpenID\Codebooks\ContentTypesEnum;
use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum;
use SimpleSAML\OpenID\Codebooks\WellKnownEnum;
use SimpleSAML\OpenID\Decorators\DateIntervalDecorator;
use SimpleSAML\OpenID\Exceptions\EntityStatementException;
Expand Down Expand Up @@ -133,14 +134,22 @@ public function fromCache(string $uri): ?EntityStatement


/**
* Fetch entity statement from network. Each successful fetch will be cached, with URI being used as a cache key.
* Fetch entity statement from network.
*
* @param array<string, mixed> $options See https://docs.guzzlephp.org/en/stable/request-options.html
* @param bool $shouldCache If true, each successful fetch will be cached, with URI being used as a cache key.
* @param string ...$additionalCacheKeyElements Additional string elements to be used as cache key.
* @throws \SimpleSAML\OpenID\Exceptions\FetchException
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
*/
public function fromNetwork(string $uri): EntityStatement
{
$entityStatement = parent::fromNetwork($uri);
public function fromNetwork(
string $uri,
HttpMethodsEnum $httpMethodsEnum = HttpMethodsEnum::GET,
array $options = [],
bool $shouldCache = true,
string ...$additionalCacheKeyElements,
): EntityStatement {
$entityStatement = parent::fromNetwork($uri, $httpMethodsEnum, $options, $shouldCache);

if ($entityStatement instanceof \SimpleSAML\OpenID\Federation\EntityStatement) {
return $entityStatement;
Expand Down
24 changes: 24 additions & 0 deletions src/Federation/Factories/TrustMarkStatusFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\OpenID\Federation\Factories;

use SimpleSAML\OpenID\Federation\TrustMarkStatus;
use SimpleSAML\OpenID\Jws\Factories\ParsedJwsFactory;

class TrustMarkStatusFactory extends ParsedJwsFactory
{
public function fromToken(string $token): TrustMarkStatus
{
return new TrustMarkStatus(
$this->jwsParser->parse($token),
$this->jwsVerifierDecorator,
$this->jwksFactory,
$this->jwsSerializerManagerDecorator,
$this->timestampValidationLeeway,
$this->helpers,
$this->claimFactory,
);
}
}
17 changes: 13 additions & 4 deletions src/Federation/TrustMarkFetcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Psr\Log\LoggerInterface;
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
use SimpleSAML\OpenID\Codebooks\ContentTypesEnum;
use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum;
use SimpleSAML\OpenID\Decorators\DateIntervalDecorator;
use SimpleSAML\OpenID\Exceptions\EntityStatementException;
use SimpleSAML\OpenID\Exceptions\FetchException;
Expand Down Expand Up @@ -113,14 +114,22 @@ public function fromCache(string $uri): ?TrustMark


/**
* Fetch Trust Mark from network. Each successful fetch will be cached, with URI being used as a cache key.
* Fetch Trust Mark from network.
*
* @param array<string, mixed> $options See https://docs.guzzlephp.org/en/stable/request-options.html
* @param bool $shouldCache If true, each successful fetch will be cached, with URI being used as a cache key.
* @param string ...$additionalCacheKeyElements Additional string elements to be used as cache key.
* @throws \SimpleSAML\OpenID\Exceptions\FetchException
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
*/
public function fromNetwork(string $uri): TrustMark
{
$trustMark = parent::fromNetwork($uri);
public function fromNetwork(
string $uri,
HttpMethodsEnum $httpMethodsEnum = HttpMethodsEnum::GET,
array $options = [],
bool $shouldCache = true,
string ...$additionalCacheKeyElements,
): TrustMark {
$trustMark = parent::fromNetwork($uri, $httpMethodsEnum, $options, $shouldCache);

if ($trustMark instanceof TrustMark) {
return $trustMark;
Expand Down
Loading