Passed
Push — master ( 2df2dd...fa75b2 )
by Pol
02:00
created

ProofValidator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
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\Contract\Service\Clock\ClockInterface;
13
use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
14
use ChampsLibres\WopiLib\Contract\Service\ProofValidatorInterface;
15
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
16
use DateTimeImmutable;
17
use phpseclib3\Crypt\PublicKeyLoader;
18
use phpseclib3\Crypt\RSA;
19
use Psr\Http\Message\RequestInterface;
20
use Throwable;
21
22
use function strlen;
23
24
final class ProofValidator implements ProofValidatorInterface
25
{
26
    private ClockInterface $clock;
27
28
    private DiscoveryInterface $discovery;
29
30 2
    public function __construct(DiscoveryInterface $discovery, ClockInterface $clock)
31
    {
32 2
        $this->discovery = $discovery;
33 2
        $this->clock = $clock;
34 2
    }
35
36 1
    public function isValid(RequestInterface $request): bool
37
    {
38 1
        $timestamp = $request->getHeaderLine(WopiInterface::HEADER_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
        $params = [];
48 1
        parse_str($request->getUri()->getQuery(), $params);
49 1
        $url = (string) $request->getUri();
50
51 1
        $expected = sprintf(
52 1
            '%s%s%s%s%s%s',
53 1
            pack('N', strlen($params['access_token'])),
54 1
            $params['access_token'],
55 1
            pack('N', strlen($url)),
56 1
            strtoupper($url),
57 1
            pack('N', 8),
58 1
            pack('J', $timestamp)
59
        );
60
61 1
        $key = $this->discovery->getPublicKey();
62 1
        $keyOld = $this->discovery->getPublicKeyOld();
63 1
        $xWopiProof = $request->getHeaderLine(WopiInterface::HEADER_PROOF);
64 1
        $xWopiProofOld = $request->getHeaderLine(WopiInterface::HEADER_PROOF_OLD);
65
66 1
        return $this->verify($expected, $xWopiProof, $key)
67 1
            || $this->verify($expected, $xWopiProofOld, $key)
68 1
            || $this->verify($expected, $xWopiProof, $keyOld);
69
    }
70
71
    /**
72
     * @param string $key The key in MSBLOB format
73
     */
74 1
    private function verify(string $expected, string $proof, string $key): bool
75
    {
76
        try {
77
            /** @var RSA $key */
78 1
            $key = PublicKeyLoader::loadPublicKey($key);
79
        } catch (Throwable $e) {
80
            return false;
81
        }
82
83
        return $key
84 1
            ->withHash('sha256')
85 1
            ->withPadding(RSA::SIGNATURE_RELAXED_PKCS1)
86 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

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