Passed
Push — master ( 06d808...f4e8b7 )
by Pol
01:55
created

WopiProofValidator::isValid()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 4.0012

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 4
eloc 22
c 4
b 0
f 0
nc 4
nop 1
dl 0
loc 34
ccs 22
cts 23
cp 0.9565
crap 4.0012
rs 9.568
1
<?php
2
3
/**
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 */
7
8
declare(strict_types=1);
9
10
namespace ChampsLibres\WopiLib\Service;
11
12
use ChampsLibres\WopiLib\Discovery\WopiDiscoveryInterface;
13
use ChampsLibres\WopiLib\Service\Contract\ClockInterface;
14
use ChampsLibres\WopiLib\Service\Contract\WopiProofValidatorInterface;
15
use DateTimeImmutable;
16
use phpseclib3\Crypt\PublicKeyLoader;
17
use Psr\Http\Message\RequestInterface;
18
use Throwable;
19
20
use function strlen;
21
22
use const OPENSSL_ALGO_SHA256;
23
24
final class WopiProofValidator implements WopiProofValidatorInterface
25
{
26
    private ClockInterface $clock;
27
28
    private WopiDiscoveryInterface $wopiDiscovery;
29
30 2
    public function __construct(WopiDiscoveryInterface $wopiDiscovery, ClockInterface $clock)
31
    {
32 2
        $this->wopiDiscovery = $wopiDiscovery;
33 2
        $this->clock = $clock;
34 2
    }
35
36 1
    public function isValid(RequestInterface $request): bool
37
    {
38 1
        $timestamp = $request->getHeaderLine('X-WOPI-Timestamp');
39
40
        // Ensure that the X-WOPI-TimeStamp header is no more than 20 minutes old.
41 1
        $date = (new DateTimeImmutable())->setTimestamp((int) (((float) $timestamp - 621355968000000000) / 10000000));
42
43 1
        if (20 * 60 < ($this->clock->now()->getTimestamp() - $date->getTimestamp())) {
44
            return false;
45
        }
46
47 1
        $xWopiProof = $request->getHeaderLine('X-WOPI-Proof');
48 1
        $xWopiProofOld = $request->getHeaderLine('X-WOPI-ProofOld');
49
50 1
        $key = $this->wopiDiscovery->getPublicKey();
51 1
        $keyOld = $this->wopiDiscovery->getPublicKeyOld();
52
53 1
        $url = (string) $request->getUri();
54 1
        $params = [];
55 1
        parse_str($request->getUri()->getQuery(), $params);
56
57 1
        $expected = sprintf(
58 1
            '%s%s%s%s%s%s',
59 1
            pack('N', strlen($params['access_token'])),
60 1
            $params['access_token'],
61 1
            pack('N', strlen($url)),
62 1
            strtoupper($url),
63 1
            pack('N', 8),
64 1
            pack('J', $timestamp)
65
        );
66
67 1
        return $this->verify($expected, $xWopiProof, $key)
68 1
            || $this->verify($expected, $xWopiProofOld, $key)
69 1
            || $this->verify($expected, $xWopiProof, $keyOld);
70
    }
71
72
    /**
73
     * @param string $key The key in MSBLOB format
74
     */
75 1
    private function verify(string $expected, string $proof, string $key): bool
76
    {
77
        try {
78 1
            $key = PublicKeyLoader::loadPublicKey($key);
79
        } catch (Throwable $e) {
80
            return false;
81
        }
82
83 1
        return 1 === openssl_verify(
84
            $expected,
85 1
            (string) base64_decode($proof, true),
86 1
            $key->toString('pkcs8'),
87 1
            OPENSSL_ALGO_SHA256
88
        );
89
    }
90
}
91