Skip to content

Commit

Permalink
Add initial Trust Mark Validation implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
cicnavi committed Feb 4, 2025
1 parent 927df12 commit 27e60e2
Show file tree
Hide file tree
Showing 6 changed files with 573 additions and 29 deletions.
55 changes: 49 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Test
)
);

// Define the maximum cache TTL for federation artifacts. This will be used together with 'exp'
// Define the maximum cache Time-To-Live (TTL) for federation artifacts. This will be used together with 'exp'
// claim to resolve the maximum cache time for trust chains, entity statements, etc.
$maxCacheDuration = new DateInterval('PT6H');

Expand Down Expand Up @@ -101,7 +101,7 @@ try {
],
);
} catch (\Throwable $exception) {
$this->loggerService->error('Could not resolve trust chain: ' . $exception->getMessage())
$this->logger->error('Could not resolve trust chain: ' . $exception->getMessage())
return;
}

Expand All @@ -126,7 +126,7 @@ try {
'https://trust-achor-id.example.org/',
);
} catch (\Throwable $exception) {
$this->loggerService->error('Could not resolve trust chain: ' . $exception->getMessage())
$this->logger->error('Could not resolve trust chain: ' . $exception->getMessage())
return;
}

Expand All @@ -145,7 +145,7 @@ try {
/** @var \SimpleSAML\OpenID\Federation\TrustChain $trustChain */
$metadata = $trustChain->getResolvedMetadata($entityType);
} catch (\Throwable $exception) {
$this->loggerService->error(
$this->logger->error(
sprintf(
'Error resolving metadata for entity type %s. Error: %s.',
$entityType->value,
Expand All @@ -156,7 +156,7 @@ try {
}

if (is_null($metadata)) {
$this->loggerService->error(
$this->logger->error(
sprintf(
'No metadata available for entity type %s.',
$entityType->value,
Expand Down Expand Up @@ -191,9 +191,52 @@ $trustAnchorEntityId = $trustAnchorConfigurationStatement->getIssuer();
try {
$trustAnchorConfigurationStatement->verifyWithKeySet($trustAnchorJwks);
} catch (\Throwable $exception) {
$this->loggerService->error('Could not verify trust anchor configuration statement signature: ' .
$this->logger->error('Could not verify trust anchor configuration statement signature: ' .
$exception->getMessage());
return;
}

```

### 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 ID.

If cache is utilized, 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
// ...

/** @var \SimpleSAML\OpenID\Federation $federationTools */
/** @var \SimpleSAML\OpenID\Federation\TrustChain $trustChain */


// Trust Mark ID that you want to validate.
$trustMarkId = 'https://example.com/trust-mark/member';
// Leaf for which you want to validate the Trust Mark with ID above.
$leafEntityConfigurationStatement = $trustChain->getResolvedLeaf();
// Trust Anchor under which you want to validate Trust Mark.
$trustAnchorConfigurationStatement = $trustChain->getResolvedTrustAnchor();

try {
// Example which queries cache for previously validated Trust Mark, and does formal validation if not cached.
$federationTools->trustMarkValidator()->fromCacheOrDoForTrustMarkId(
$trustMarkId,
$leafEntityConfigurationStatement,
$trustAnchorConfigurationStatement,
);

// Example which always does formal validation (does not use cache).
$federationTools->trustMarkValidator()->doForTrustMarkId(
$trustMarkId,
$leafEntityConfigurationStatement,
$trustAnchorConfigurationStatement,
);
} catch (\Throwable $exception) {
$this->logger->error('Trust Mark validation failed. Error was: ' . $exception->getMessage());
return;
}

```
54 changes: 36 additions & 18 deletions src/Federation/TrustMarkValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public function isValidationCachedFor(
string $trustAnchorId,
): bool {
if (is_null($this->cacheDecorator)) {
$this->logger?->debug('Cache not available, skipping.');
return false;
}

Expand Down Expand Up @@ -90,7 +91,7 @@ public function isValidationCachedFor(
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function forTrustMarkId(
public function fromCacheOrDoForTrustMarkId(
string $trustMarkId,
EntityStatement $leafEntityConfiguration,
EntityStatement $trustAnchorEntityConfiguration,
Expand All @@ -105,6 +106,21 @@ public function forTrustMarkId(
return;
}

$this->doForTrustMarkId($trustMarkId, $leafEntityConfiguration, $trustAnchorEntityConfiguration);
}

/**
* @param non-empty-string $trustMarkId
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
*/
public function doForTrustMarkId(
string $trustMarkId,
EntityStatement $leafEntityConfiguration,
EntityStatement $trustAnchorEntityConfiguration,
): void {
$this->logger?->debug(
sprintf(
'Validating Trust Mark %s for leaf entity %s under Trust Anchor %s.',
Expand Down Expand Up @@ -201,12 +217,14 @@ public function forTrustMarkId(
}
}

throw new TrustMarkException(sprintf(
'Could not validate Trust Mark %s for leaf entity %s under Trust Anchor %s.',
$trustMarkId,
$leafEntityConfiguration->getIssuer(),
$trustAnchorEntityConfiguration->getIssuer(),
));
throw new TrustMarkException(
sprintf(
'Could not validate Trust Mark %s for leaf entity %s under Trust Anchor %s.',
$trustMarkId,
$leafEntityConfiguration->getIssuer(),
$trustAnchorEntityConfiguration->getIssuer(),
),
);
}

/**
Expand All @@ -218,7 +236,7 @@ public function forTrustMarkId(
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function forTrustMarksClaimValue(
public function fromCacheOrDoForTrustMarksClaimValue(
TrustMarksClaimValue $trustMarksClaimValue,
EntityStatement $leafEntityConfiguration,
EntityStatement $trustAnchorEntityConfiguration,
Expand Down Expand Up @@ -249,7 +267,7 @@ public function forTrustMarksClaimValue(
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkDelegationException
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
*/
protected function doForTrustMarksClaimValue(
public function doForTrustMarksClaimValue(
TrustMarksClaimValue $trustMarksClaimValue,
EntityStatement $leafEntityConfiguration,
EntityStatement $trustAnchorEntityConfiguration,
Expand Down Expand Up @@ -332,7 +350,7 @@ public function validateTrustMarksClaimValue(
* @throws \SimpleSAML\OpenID\Exceptions\JwksException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function forTrustMark(
public function fromCacheOrDoForTrustMark(
TrustMark $trustMark,
EntityStatement $leafEntityConfiguration,
EntityStatement $trustAnchorEntityConfiguration,
Expand Down Expand Up @@ -363,7 +381,7 @@ public function forTrustMark(
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
* @throws \SimpleSAML\OpenID\Exceptions\JwksException
*/
protected function doForTrustMark(
public function doForTrustMark(
TrustMark $trustMark,
EntityStatement $leafEntityConfiguration,
EntityStatement $trustAnchorEntityConfiguration,
Expand All @@ -384,9 +402,9 @@ protected function doForTrustMark(
$trustAnchorEntityConfiguration,
);

$trustMarkIssuerEntityStatement = $trustMarkIssuerTrustChain->getResolvedLeaf();
$trustMarkIssuerEntityConfiguration = $trustMarkIssuerTrustChain->getResolvedLeaf();

$this->validateTrustMarkSignature($trustMark, $trustMarkIssuerEntityStatement);
$this->validateTrustMarkSignature($trustMark, $trustMarkIssuerEntityConfiguration);

$this->validateTrustMarkDelegation($trustMark, $trustAnchorEntityConfiguration);

Expand Down Expand Up @@ -425,7 +443,7 @@ protected function doForTrustMark(
$trustAnchorEntityConfiguration->getIssuer(),
);
} catch (Throwable $exception) {
$this->logger?->debug(sprintf(
$this->logger?->error(sprintf(
'Error caching Trust Mark %s validation for leaf entity %s under Trust Anchor %s with TTL' .
' %s. Error wa: %s.',
$trustMark->getIdentifier(),
Expand Down Expand Up @@ -525,19 +543,19 @@ public function validateTrustChainForTrustMarkIssuer(
*/
public function validateTrustMarkSignature(
TrustMark $trustMark,
EntityStatement $trustMarkIssuerEntityStatement,
EntityStatement $trustMarkIssuerEntityConfiguration,
): void {
$this->logger?->debug('Validating Trust Mark signature.');
try {
$trustMark->verifyWithKeySet($trustMarkIssuerEntityStatement->getJwks()->getValue());
$trustMark->verifyWithKeySet($trustMarkIssuerEntityConfiguration->getJwks()->getValue());
} catch (Throwable $exception) {
$error = sprintf(
'Trust Mark signature validation failed with error: %s',
$exception->getMessage(),
);
$this->logger?->error(
$error,
['trustMarkIssuerJwks' => $trustMarkIssuerEntityStatement->getJwks()],
['trustMarkIssuerJwks' => $trustMarkIssuerEntityConfiguration->getJwks()],
);
throw new TrustMarkException($error);
}
Expand All @@ -563,7 +581,7 @@ public function validateTrustMarkDelegation(
if (is_null($trustMarkOwnersBag)) {
$this->logger?->debug(
sprintf(
'Trust Anchor %s does not define Trust Mark Owners, skipping delegation validation.',
'Trust Anchor %s does not define Trust Mark Owners. Skipping delegation validation.',
$trustAnchorEntityConfiguration->getIssuer(),
),
);
Expand Down
Loading

0 comments on commit 27e60e2

Please sign in to comment.