ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
ProofValidator.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21namespace ILIAS\WOPI\Handler;
22
25
36final class ProofValidator
37{
41 private const TICKS_AT_UNIX_EPOCH = 621_355_968_000_000_000;
42 private const TICKS_PER_SECOND = 10_000_000;
43 private const MAX_AGE = 20 * 60;
44
48 private \Closure $clock;
49
53 public function __construct(
54 private ?ProofKeys $keys,
55 ?\Closure $clock = null
56 ) {
57 $this->clock = $clock ?? static fn(): int => time();
58 }
59
60 public function withKeys(ProofKeys $keys): self
61 {
62 $clone = clone $this;
63 $clone->keys = $keys;
64
65 return $clone;
66 }
67
71 public function isValid(
72 string $access_token,
73 string $url,
74 ?string $timestamp,
75 ?string $proof,
76 ?string $old_proof
77 ): bool {
78 if ($this->keys === null) {
79 return true;
80 }
81 if ($timestamp === null || $proof === null || !ctype_digit($timestamp)) {
82 return false;
83 }
84 $age = ($this->clock)() - intdiv((int) $timestamp - self::TICKS_AT_UNIX_EPOCH, self::TICKS_PER_SECOND);
85 if (abs($age) > self::MAX_AGE) {
86 return false;
87 }
88
89 $expected = self::expected($access_token, $url, (int) $timestamp);
90
91 // the current key signed the proof, or it signed the old proof (the host has the new key,
92 // the client still signs with the old one as well), or the old key signed the proof
93 // (the client has rotated its keys and the host has not seen the new discovery yet)
94 return $this->verify($expected, $proof, $this->keys->current)
95 || ($old_proof !== null && $this->verify($expected, $old_proof, $this->keys->current))
96 || ($this->keys->old !== null && $this->verify($expected, $proof, $this->keys->old));
97 }
98
99 public static function expected(string $access_token, string $url, int $timestamp): string
100 {
101 $url = strtoupper($url);
102
103 return pack('N', strlen($access_token)) . $access_token
104 . pack('N', strlen($url)) . $url
105 . pack('N', 8) . pack('J', $timestamp);
106 }
107
108 private function verify(string $expected, string $signature, ProofKey $key): bool
109 {
110 $decoded = base64_decode($signature, true);
111 if ($decoded === false || $decoded === '') {
112 return false;
113 }
114 try {
115 $public_key = openssl_pkey_get_public($key->toPem());
116 } catch (\InvalidArgumentException) {
117 return false;
118 }
119 if ($public_key === false) {
120 return false;
121 }
122
123 return openssl_verify($expected, $decoded, $public_key, OPENSSL_ALGO_SHA256) === 1;
124 }
125}
foreach($mandatory_scripts as $file) $timestamp
Definition: buildRTE.php:70
Class Exception Thrown if some procedure did not get the arguments it expected.
A public RSA key of a WOPI client, as published in its discovery with base64 encoded modulus and expo...
Definition: ProofKey.php:30
The current and the previous proof key of a WOPI client.
Definition: ProofKeys.php:30
Verifies that a request was sent by the WOPI client, see https://learn.microsoft.com/en-us/microsoft-...
verify(string $expected, string $signature, ProofKey $key)
isValid(string $access_token, string $url, ?string $timestamp, ?string $proof, ?string $old_proof)
__construct(private ?ProofKeys $keys, ?\Closure $clock=null)
static expected(string $access_token, string $url, int $timestamp)
const TICKS_AT_UNIX_EPOCH
X-WOPI-TimeStamp counts the 100 nanosecond intervals since 0001-01-01 (.NET ticks).
if(!file_exists('../ilias.ini.php'))