Skip to content

Commit b46dba0

Browse files
authored
Validate Trust Mark Issuers TA claim on TM validation (#12)
* Introduce TrustMarkIssuers claim * Start validating trust mark issuers advertized by Trust Anchor when validating Trust Marks
1 parent 9f14f25 commit b46dba0

14 files changed

Lines changed: 545 additions & 4 deletions

rector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// naming: true,
2121
instanceOf: true,
2222
earlyReturn: true,
23-
strictBooleans: true,
23+
// strictBooleans: true,
2424
// carbon: true,
2525
rectorPreset: true,
2626
phpunitCodeQuality: true,

src/Codebooks/ClaimsEnum.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ enum ClaimsEnum: string
143143
case Type = 'type';
144144
case TrustChain = 'trust_chain';
145145
case TrustMark = 'trust_mark';
146+
case TrustMarkIssuers = 'trust_mark_issuers';
146147
case TrustMarkOwners = 'trust_mark_owners';
147148
case TrustMarkType = 'trust_mark_type';
148149
case TrustMarks = 'trust_marks';
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\OpenID\Federation\Claims;
6+
7+
use JsonSerializable;
8+
9+
class TrustMarkIssuersClaimBag implements JsonSerializable
10+
{
11+
/** @var array<non-empty-string,\SimpleSAML\OpenID\Federation\Claims\TrustMarkIssuersClaimValue> */
12+
protected array $trustMarkIssuersClaimValues = [];
13+
14+
15+
public function __construct(TrustMarkIssuersClaimValue ...$trustMarkIssuersClaimValues)
16+
{
17+
$this->add(...$trustMarkIssuersClaimValues);
18+
}
19+
20+
21+
public function add(TrustMarkIssuersClaimValue ...$trustMarkIssuersClaimValues): void
22+
{
23+
foreach ($trustMarkIssuersClaimValues as $trustMarkIssuersClaimValue) {
24+
$this->trustMarkIssuersClaimValues[$trustMarkIssuersClaimValue->getTrustMarkType()] =
25+
$trustMarkIssuersClaimValue;
26+
}
27+
}
28+
29+
30+
public function has(string $trustMarkType): bool
31+
{
32+
return isset($this->trustMarkIssuersClaimValues[$trustMarkType]);
33+
}
34+
35+
36+
public function get(string $trustMarkType): ?TrustMarkIssuersClaimValue
37+
{
38+
return $this->trustMarkIssuersClaimValues[$trustMarkType] ?? null;
39+
}
40+
41+
42+
/**
43+
* @return \SimpleSAML\OpenID\Federation\Claims\TrustMarkIssuersClaimValue[]
44+
*/
45+
public function getAll(): array
46+
{
47+
return $this->trustMarkIssuersClaimValues;
48+
}
49+
50+
51+
/**
52+
* @return array<non-empty-string,array<non-empty-string>>
53+
*/
54+
public function jsonSerialize(): array
55+
{
56+
return array_combine(
57+
array_keys($this->trustMarkIssuersClaimValues),
58+
array_map(
59+
fn(TrustMarkIssuersClaimValue $tMICValue): array => $tMICValue->getTrustMarkIssuers(),
60+
$this->trustMarkIssuersClaimValues,
61+
),
62+
);
63+
}
64+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\OpenID\Federation\Claims;
6+
7+
use JsonSerializable;
8+
9+
class TrustMarkIssuersClaimValue implements JsonSerializable
10+
{
11+
/**
12+
* @param non-empty-string $trustMarkType
13+
* @param array<non-empty-string> $trustMarkIssuers
14+
*/
15+
public function __construct(
16+
protected readonly string $trustMarkType,
17+
protected readonly array $trustMarkIssuers,
18+
) {
19+
}
20+
21+
22+
/**
23+
* @return non-empty-string
24+
*/
25+
public function getTrustMarkType(): string
26+
{
27+
return $this->trustMarkType;
28+
}
29+
30+
31+
/**
32+
* @return array<non-empty-string>
33+
*/
34+
public function getTrustMarkIssuers(): array
35+
{
36+
return $this->trustMarkIssuers;
37+
}
38+
39+
40+
/**
41+
* @return array<non-empty-string,array<non-empty-string>>
42+
*/
43+
public function jsonSerialize(): array
44+
{
45+
return [
46+
$this->trustMarkType => $this->getTrustMarkIssuers(),
47+
];
48+
}
49+
}

src/Federation/EntityStatement.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use SimpleSAML\OpenID\Codebooks\EntityTypesEnum;
1010
use SimpleSAML\OpenID\Codebooks\JwtTypesEnum;
1111
use SimpleSAML\OpenID\Exceptions\EntityStatementException;
12+
use SimpleSAML\OpenID\Federation\Claims\TrustMarkIssuersClaimBag;
1213
use SimpleSAML\OpenID\Federation\Claims\TrustMarkOwnersClaimBag;
1314
use SimpleSAML\OpenID\Federation\Claims\TrustMarksClaimBag;
1415
use SimpleSAML\OpenID\Jws\ParsedJws;
@@ -234,6 +235,25 @@ public function getTrustMarkOwners(): ?TrustMarkOwnersClaimBag
234235
}
235236

236237

238+
public function getTrustMarkIssuers(): ?TrustMarkIssuersClaimBag
239+
{
240+
// trust_mark_issuers
241+
// OPTIONAL. A Trust Anchor MAY use this claim to tell which combination of Trust Mark type identifiers and
242+
// issuers are trusted by the federation. It is a JSON object with member names that are Trust Mark type
243+
// identifiers, and each corresponding value being an array of Entity Identifiers that are trusted to
244+
// represent the accreditation authority for Trust Marks with that identifier.
245+
246+
$claimKey = ClaimsEnum::TrustMarkIssuers->value;
247+
$trustMarkIssuersClaimData = $this->getPayloadClaim($claimKey);
248+
249+
if (is_null($trustMarkIssuersClaimData)) {
250+
return null;
251+
}
252+
253+
return $this->claimFactory->forFederation()->buildTrustMarkIssuersClaimBagFrom($trustMarkIssuersClaimData);
254+
}
255+
256+
237257
/**
238258
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
239259
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
@@ -339,6 +359,7 @@ protected function validate(): void
339359
$this->getMetadataPolicy(...),
340360
$this->getTrustMarks(...),
341361
$this->getTrustMarkOwners(...),
362+
$this->getTrustMarkIssuers(...),
342363
$this->getFederationFetchEndpoint(...),
343364
$this->getFederationTrustMarkEndpoint(...),
344365
);

src/Federation/Factories/FederationClaimFactory.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
88
use SimpleSAML\OpenID\Exceptions\TrustMarkException;
99
use SimpleSAML\OpenID\Factories\ClaimFactory;
10+
use SimpleSAML\OpenID\Federation\Claims\TrustMarkIssuersClaimBag;
11+
use SimpleSAML\OpenID\Federation\Claims\TrustMarkIssuersClaimValue;
1012
use SimpleSAML\OpenID\Federation\Claims\TrustMarkOwnersClaimBag;
1113
use SimpleSAML\OpenID\Federation\Claims\TrustMarkOwnersClaimValue;
1214
use SimpleSAML\OpenID\Federation\Claims\TrustMarksClaimBag;
@@ -152,4 +154,48 @@ public function buildTrustMarkOwnersClaimBag(
152154
): TrustMarkOwnersClaimBag {
153155
return new TrustMarkOwnersClaimBag(...$trustMarkOwnersClaimValues);
154156
}
157+
158+
159+
public function buildTrustMarkIssuersClaimBagFrom(mixed $trustMarkIssuersClaimData): TrustMarkIssuersClaimBag
160+
{
161+
$trustMarkIssuersClaimData = $this->helpers->type()->ensureArrayWithKeysAsNonEmptyStrings(
162+
$trustMarkIssuersClaimData,
163+
);
164+
165+
$trustMarkIssuersClaimValues = [];
166+
167+
foreach ($trustMarkIssuersClaimData as $trustMarkType => $trustMarkIssuersClaim) {
168+
$trustMarkIssuersClaim = $this->helpers->type()->ensureArrayWithValuesAsNonEmptyStrings(
169+
$trustMarkIssuersClaim,
170+
);
171+
172+
$trustMarkIssuersClaimValues[] = $this->buildTrustMarkIssuersClaimValue(
173+
$trustMarkType,
174+
$trustMarkIssuersClaim,
175+
);
176+
}
177+
178+
return $this->buildTrustMarkIssuerClaimBag(...$trustMarkIssuersClaimValues);
179+
}
180+
181+
182+
public function buildTrustMarkIssuersClaimValue(
183+
mixed $trustMarkType,
184+
mixed $trustMarkIssuers,
185+
): TrustMarkIssuersClaimValue {
186+
$trustMarkType = $this->helpers->type()->ensureNonEmptyString($trustMarkType);
187+
$trustMarkIssuers = $this->helpers->type()->ensureArrayWithValuesAsNonEmptyStrings($trustMarkIssuers);
188+
189+
return new TrustMarkIssuersClaimValue(
190+
$trustMarkType,
191+
$trustMarkIssuers,
192+
);
193+
}
194+
195+
196+
public function buildTrustMarkIssuerClaimBag(
197+
TrustMarkIssuersClaimValue ...$trustMarkIssuersClaimValues,
198+
): TrustMarkIssuersClaimBag {
199+
return new TrustMarkIssuersClaimBag(...$trustMarkIssuersClaimValues);
200+
}
155201
}

src/Federation/Factories/TrustChainFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function fromStatements(EntityStatement ...$statements): TrustChain
7474
public function fromTokens(string ...$tokens): TrustChain
7575
{
7676
$statements = array_map(
77-
fn(string $token): EntityStatement => $this->entityStatementFactory->fromToken($token),
77+
$this->entityStatementFactory->fromToken(...),
7878
$tokens,
7979
);
8080

src/Federation/TrustMarkValidator.php

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,9 @@ public function doForTrustMark(
421421
);
422422

423423
$this->validateSubjectClaim($trustMark, $leafEntityConfiguration);
424+
$this->validateTrustMarkIssuers($trustMark, $trustAnchorEntityConfiguration);
424425

425-
// If Trust Mark Issuer is the Trust Anchor itself, we don't have to resolve chain, as Trust Anchor is trusted
426+
// If Trust Mark Issuer is the Trust Anchor itself, we don't have to resolve a chain, as Trust Anchor is trusted
426427
// out-of-band. Otherwise, we have to resolve trust for Trust Mark Issuer.
427428
$trustMarkIssuerEntityConfiguration =
428429
$trustMark->getIssuer() === $trustAnchorEntityConfiguration->getIssuer() ?
@@ -516,6 +517,91 @@ public function validateSubjectClaim(
516517
}
517518

518519

520+
/**
521+
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
522+
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
523+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
524+
*/
525+
public function validateTrustMarkIssuers(
526+
TrustMark $trustMark,
527+
EntityStatement $trustAnchorEntityConfiguration,
528+
): void {
529+
$this->logger?->debug('Validating Trust Mark Issuers.');
530+
531+
if (is_null($trustMarkIssuersClaimBag = $trustAnchorEntityConfiguration->getTrustMarkIssuers())) {
532+
$this->logger?->debug(
533+
sprintf(
534+
'Trust Anchor %s does not define Trust Mark Issuers. Skipping validation.',
535+
$trustAnchorEntityConfiguration->getIssuer(),
536+
),
537+
);
538+
return;
539+
}
540+
541+
$this->logger?->debug(
542+
sprintf(
543+
'Trust Anchor %s defines Trust Mark Issuers.',
544+
$trustAnchorEntityConfiguration->getIssuer(),
545+
),
546+
['trustMarkIssuers' => $trustMarkIssuersClaimBag->jsonSerialize()],
547+
);
548+
549+
$trustMarkIssuersClaimValue = $trustMarkIssuersClaimBag->get($trustMark->getTrustMarkType());
550+
551+
if (is_null($trustMarkIssuersClaimValue)) {
552+
$this->logger?->debug(
553+
sprintf(
554+
'Trust Anchor %s does not define issuers of Trust Mark %s. Skipping validation.',
555+
$trustAnchorEntityConfiguration->getIssuer(),
556+
$trustMark->getTrustMarkType(),
557+
),
558+
);
559+
return;
560+
}
561+
562+
if ($trustMarkIssuersClaimValue->getTrustMarkIssuers() === []) {
563+
$this->logger?->debug(
564+
sprintf(
565+
'Trust Anchor %s defines any issuers of Trust Mark %s. Skipping validation.',
566+
$trustAnchorEntityConfiguration->getIssuer(),
567+
$trustMark->getTrustMarkType(),
568+
),
569+
);
570+
return;
571+
}
572+
573+
$this->logger?->debug(
574+
sprintf(
575+
'Trust Anchor %s defines issuers %s of Trust Mark %s.',
576+
$trustAnchorEntityConfiguration->getIssuer(),
577+
implode(', ', $trustMarkIssuersClaimValue->getTrustMarkIssuers()),
578+
$trustMark->getTrustMarkType(),
579+
),
580+
);
581+
582+
if (!in_array($trustMark->getIssuer(), $trustMarkIssuersClaimValue->getTrustMarkIssuers(), true)) {
583+
$error = sprintf(
584+
'Trust Mark %s is not issued by any of the Trust Mark Issuers %s defined by Trust Anchor %s.',
585+
$trustMark->getTrustMarkType(),
586+
implode(', ', $trustMarkIssuersClaimValue->getTrustMarkIssuers()),
587+
$trustAnchorEntityConfiguration->getIssuer(),
588+
);
589+
590+
$this->logger?->error($error);
591+
throw new TrustMarkException($error);
592+
}
593+
594+
$this->logger?->debug(
595+
sprintf(
596+
'Trust Mark %s is issued by one of the Trust Mark Issuers %s defined by Trust Anchor %s.',
597+
$trustMark->getTrustMarkType(),
598+
implode(', ', $trustMarkIssuersClaimValue->getTrustMarkIssuers()),
599+
$trustAnchorEntityConfiguration->getIssuer(),
600+
),
601+
);
602+
}
603+
604+
519605
/**
520606
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
521607
* @throws \SimpleSAML\OpenID\Exceptions\JwsException

src/Jws/ParsedJws.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public function getPayload(): array
125125
}
126126

127127
$payloadString = $this->jwsDecorator->jws()->getPayload();
128-
if ($payloadString === null || $payloadString === '' || $payloadString === '0') {
128+
if (in_array($payloadString, [null, '', '0'], true)) {
129129
return $this->payload = [];
130130
}
131131

0 commit comments

Comments
 (0)