Skip to content

Commit

Permalink
Add expiration to challenge interface (#73)
Browse files Browse the repository at this point in the history
For storage mechanisms where `serialize` would be inappropriate, this
provides an alternate path to determining if and when a timestamp
expires.

As of now, this is only a partial integration of the expiration logic -
as noted in the interface, this is only for storage, not enforcement.
The W3C spec is at best vague about challenge expiration, and really
only covers the request timeout flags.
  • Loading branch information
Firehed committed Mar 2, 2024
1 parent 158af06 commit dcef544
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 4 deletions.
7 changes: 7 additions & 0 deletions src/Challenge.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Firehed\WebAuthn;

use DateTimeImmutable;

/**
* The Challenge object has limited public-facing API:
* - Create a challenge through the `::random()` method
Expand Down Expand Up @@ -55,6 +57,11 @@ public function getBase64Url(): string
return $this->wrapped->toBase64Url();
}

public function getExpiration(): ?DateTimeImmutable
{
return null;
}

/**
* @return SerializationFormat
*/
Expand Down
15 changes: 15 additions & 0 deletions src/ChallengeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Firehed\WebAuthn;

use DateTimeImmutable;

interface ChallengeInterface
{
/**
Expand Down Expand Up @@ -44,4 +46,17 @@ public function getBase64Url(): string;
* @internal
*/
public function getBinary(): BinaryString;

/**
* If non-null, indicates when the challenge should be considered expired.
* This should be used in conjunction with request generation and align
* with the `timeout` used by `pkOptions`. Be aware that browsers may
* override the specified value; the current W3C recommendation (lv3) is
* between 5 and 10 minutes (300-600 seconds).
*
* At present, this is intended as a convenience for storage mechanisms and
* expected to be enforced by _ChallengeInterface_ implemetions, not the RP
* server and associated internals.
*/
public function getExpiration(): ?DateTimeImmutable;
}
12 changes: 8 additions & 4 deletions src/ExpiringChallenge.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Firehed\WebAuthn;

use DateTimeInterface;
use DateInterval;
use DateTimeImmutable;
use InvalidArgumentException;
Expand All @@ -18,13 +17,13 @@
*
* @phpstan-type SerializationFormat array{
* c: string,
* e: int,
* e: numeric-string,
* }
*/
class ExpiringChallenge implements ChallengeInterface
{
private ChallengeInterface $wrapped;
private DateTimeInterface $expiration;
private DateTimeImmutable $expiration;

/**
* @internal
Expand Down Expand Up @@ -74,6 +73,11 @@ public function getBinary(): BinaryString
return $this->wrapped->getBinary();
}

public function getExpiration(): DateTimeImmutable
{
return $this->expiration;
}

private function isExpired(): bool
{
$diff = $this->expiration->diff(new DateTimeImmutable());
Expand All @@ -88,7 +92,7 @@ public function __serialize(): array
{
return [
'c' => $this->wrapped->getBase64(),
'e' => $this->expiration->getTimestamp(),
'e' => $this->expiration->format('U.u'),
];
}

Expand Down
5 changes: 5 additions & 0 deletions tests/ChallengeInterfaceTestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public function testSerializationRoundTrip(): void
$unserialized->getBase64(),
'Base64 changed',
);

self::assertEquals(
$challenge->getExpiration(),
$unserialized->getExpiration(),
);
}

public function testBinaryMatchesBase64(): void
Expand Down
6 changes: 6 additions & 0 deletions tests/TestUtilities/TestVectorFixedChallenge.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Firehed\WebAuthn\TestUtilities;

use DateTimeImmutable;
use Exception;
use Firehed\WebAuthn\{
BinaryString,
Expand All @@ -21,6 +22,11 @@ public function __construct(private string $b64u)
{
}

public function getExpiration(): ?DateTimeImmutable
{
return null;
}

public function getBinary(): BinaryString
{
return BinaryString::fromBase64Url($this->b64u);
Expand Down

0 comments on commit dcef544

Please sign in to comment.