Passed
Push — master ( 99f234...d026ff )
by Pol
01:57
created

WopiProofValidator   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 63
Duplicated Lines 0 %

Test Coverage

Coverage 91.18%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 34
c 5
b 0
f 0
dl 0
loc 63
ccs 31
cts 34
cp 0.9118
rs 10
wmc 7

3 Methods

Rating   Name   Duplication   Size   Complexity  
A isValid() 0 33 4
A __construct() 0 4 1
A verify() 0 13 2
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 phpseclib3\Crypt\RSA;
18
use Psr\Http\Message\RequestInterface;
19
use Throwable;
20
21
use function strlen;
22
23
final class WopiProofValidator implements WopiProofValidatorInterface
24
{
25
    private ClockInterface $clock;
26
27
    private WopiDiscoveryInterface $wopiDiscovery;
28
29 2
    public function __construct(WopiDiscoveryInterface $wopiDiscovery, ClockInterface $clock)
30
    {
31 2
        $this->wopiDiscovery = $wopiDiscovery;
32 2
        $this->clock = $clock;
33 2
    }
34
35 1
    public function isValid(RequestInterface $request): bool
36
    {
37 1
        $timestamp = $request->getHeaderLine('X-WOPI-Timestamp');
38
39
        // Ensure that the X-WOPI-TimeStamp header is no more than 20 minutes old.
40 1
        $date = (new DateTimeImmutable())->setTimestamp((int) (((float) $timestamp - 621355968000000000) / 10000000));
41
42 1
        if (20 * 60 < ($this->clock->now()->getTimestamp() - $date->getTimestamp())) {
43
            return false;
44
        }
45
46 1
        $params = [];
47 1
        parse_str($request->getUri()->getQuery(), $params);
48 1
        $url = (string) $request->getUri();
49
50 1
        $expected = sprintf(
51 1
            '%s%s%s%s%s%s',
52 1
            pack('N', strlen($params['access_token'])),
53 1
            $params['access_token'],
54 1
            pack('N', strlen($url)),
55 1
            strtoupper($url),
56 1
            pack('N', 8),
57 1
            pack('J', $timestamp)
58
        );
59
60 1
        $key = $this->wopiDiscovery->getPublicKey();
61 1
        $keyOld = $this->wopiDiscovery->getPublicKeyOld();
62 1
        $xWopiProof = $request->getHeaderLine('X-WOPI-Proof');
63 1
        $xWopiProofOld = $request->getHeaderLine('X-WOPI-ProofOld');
64
65 1
        return $this->verify($expected, $xWopiProof, $key)
66 1
            || $this->verify($expected, $xWopiProofOld, $key)
67 1
            || $this->verify($expected, $xWopiProof, $keyOld);
68
    }
69
70
    /**
71
     * @param string $key The key in MSBLOB format
72
     */
73 1
    private function verify(string $expected, string $proof, string $key): bool
74
    {
75
        try {
76
            /** @var RSA $key */
77 1
            $key = PublicKeyLoader::loadPublicKey($key);
78
        } catch (Throwable $e) {
79
            return false;
80
        }
81
82
        return $key
83 1
            ->withHash('sha256')
84 1
            ->withPadding(RSA::SIGNATURE_RELAXED_PKCS1)
85 1
            ->verify($expected, (string) base64_decode($proof, true));
0 ignored issues
show
Bug introduced by
The method verify() does not exist on phpseclib3\Crypt\RSA. It seems like you code against a sub-type of phpseclib3\Crypt\RSA such as phpseclib3\Crypt\RSA\PublicKey. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

85
            ->/** @scrutinizer ignore-call */ verify($expected, (string) base64_decode($proof, true));
Loading history...
86
    }
87
}
88