Skip to content

Commit 7bdf75d

Browse files
Spomkyclaude
andauthored
fix: derive compound attestation type from nested attestation types (#819)
Instead of hardcoding TYPE_BASIC, the compound attestation type is now derived from the nested attestation types by selecting the weakest (least trusted) type. This prevents misrepresenting the trust level when sub-attestations have lower trust than basic. Trust order (strongest to weakest): attca > anonca > basic > self > none Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f3b22ba commit 7bdf75d

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

src/webauthn/src/AttestationStatement/CompoundAttestationStatementSupport.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ final class CompoundAttestationStatementSupport implements AttestationStatementS
3434
{
3535
use AttestationStatementSupportManagerAwareTrait;
3636

37+
private const ATTESTATION_TYPE_TRUST_ORDER = [
38+
AttestationStatement::TYPE_ATTCA,
39+
AttestationStatement::TYPE_ANONCA,
40+
AttestationStatement::TYPE_BASIC,
41+
AttestationStatement::TYPE_SELF,
42+
AttestationStatement::TYPE_NONE,
43+
];
44+
3745
private EventDispatcherInterface $dispatcher;
3846

3947
private ?float $ratio = 1.0;
@@ -118,11 +126,12 @@ public function load(array $attestation): AttestationStatement
118126

119127
/** @var string $compoundFmt */
120128
$compoundFmt = $attestation['fmt'];
129+
$attestationType = $this->deriveAttestationType($loadedAttestations);
121130
// Create a compound attestation statement with compound trust path
122131
$attestationStatement = AttestationStatement::create(
123132
$compoundFmt,
124133
$loadedAttestations,
125-
AttestationStatement::TYPE_BASIC,
134+
$attestationType,
126135
EmptyTrustPath::create()
127136
);
128137

@@ -171,6 +180,28 @@ public function isValid(
171180
return $countValid / $total >= $this->ratio;
172181
}
173182

183+
/**
184+
* Derives the compound attestation type from the nested attestation types
185+
* by selecting the weakest (least trusted) type among them.
186+
*
187+
* @param array<AttestationStatement> $loadedAttestations
188+
*/
189+
private function deriveAttestationType(array $loadedAttestations): string
190+
{
191+
$weakest = 0;
192+
foreach ($loadedAttestations as $stmt) {
193+
$index = array_search($stmt->type, self::ATTESTATION_TYPE_TRUST_ORDER, true);
194+
if ($index === false) {
195+
return AttestationStatement::TYPE_NONE;
196+
}
197+
if ($index > $weakest) {
198+
$weakest = $index;
199+
}
200+
}
201+
202+
return self::ATTESTATION_TYPE_TRUST_ORDER[$weakest];
203+
}
204+
174205
private function checkRules(?float $ratio, ?int $minimum): void
175206
{
176207
if ($ratio !== null && ($ratio <= 0 || $ratio > 1)) {

tests/library/Unit/AttestationStatement/CompoundAttestationStatementSupportTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PHPUnit\Framework\Attributes\Test;
88
use PHPUnit\Framework\TestCase;
9+
use Webauthn\AttestationStatement\AttestationStatement;
910
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
1011
use Webauthn\AttestationStatement\CompoundAttestationStatementSupport;
1112
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
@@ -196,6 +197,35 @@ public function loadingCompoundAttestationWithUnsupportedNestedFormatThrowsExcep
196197
$support->load($attestation);
197198
}
198199

200+
#[Test]
201+
public function loadingCompoundAttestationDerivesTypeFromNestedAttestations(): void
202+
{
203+
// Given
204+
$manager = new AttestationStatementSupportManager([]);
205+
$support = new CompoundAttestationStatementSupport();
206+
$support->setAttestationStatementSupportManager($manager);
207+
208+
$attestation = [
209+
'fmt' => 'compound',
210+
'attStmt' => [
211+
[
212+
'fmt' => 'none',
213+
'attStmt' => [],
214+
],
215+
[
216+
'fmt' => 'none',
217+
'attStmt' => [],
218+
],
219+
],
220+
];
221+
222+
// When
223+
$result = $support->load($attestation);
224+
225+
// Then — all nested are TYPE_NONE, so compound should derive TYPE_NONE
226+
static::assertSame(AttestationStatement::TYPE_NONE, $result->type);
227+
}
228+
199229
#[Test]
200230
public function validatingCompoundAttestationWithValidNestedAttestationsSucceeds(): void
201231
{

0 commit comments

Comments
 (0)