Completed
Push — master ( f64a2b...48aedc )
by thomas
05:58
created

RequestValidation::validateX509Signature()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 26
ccs 16
cts 16
cp 1
rs 8.8571
c 1
b 0
f 0
cc 3
eloc 16
nc 4
nop 2
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Bip70\X509;
6
7
use Bip70\Exception\X509Exception;
8
use Bip70\X509\Exception\InvalidCertificateChainException;
9
use Bip70\X509\Exception\InvalidX509Signature;
10
use Bip70\Protobuf\Codec\NonDiscardingBinaryCodec;
11
use Bip70\Protobuf\Proto\PaymentRequest;
12
use Bip70\Protobuf\Proto\X509Certificates;
13
use Sop\CryptoBridge\Crypto;
14
use Sop\CryptoTypes\AlgorithmIdentifier\Feature\AsymmetricCryptoAlgorithmIdentifier;
15
use Sop\CryptoTypes\Signature\Signature;
16
use X509\Certificate\Certificate;
17
use X509\Certificate\CertificateBundle;
18
use X509\CertificationPath\CertificationPath;
19
use X509\CertificationPath\PathValidation\PathValidationConfig;
20
21
class RequestValidation
22
{
23
    /**
24
     * @var PathValidationConfig
25
     */
26
    private $validationConfig;
27
28
    /**
29
     * @var CertificateBundle
30
     */
31
    private $trustStore;
32
33
    /**
34
     * RequestValidation constructor.
35
     * @param PathValidationConfig|null $validationConfig
36
     * @param CertificateBundle|null $trustStore
37
     */
38 14
    public function __construct(PathValidationConfig $validationConfig = null, CertificateBundle $trustStore = null)
39
    {
40 14
        if (null === $validationConfig) {
41 6
            $validationConfig = PathValidationConfig::defaultConfig();
42
        }
43
44 14
        if (null === $trustStore) {
45 5
            $trustStore = new CertificateBundle();
46
        }
47
48 14
        $this->validationConfig = $validationConfig;
49 14
        $this->trustStore = $trustStore;
50 14
    }
51
52
    /**
53
     * @param X509Certificates $x509
54
     * @return \Bip70\X509\QualifiedCertificate
55
     * @throws InvalidCertificateChainException
56
     */
57 8
    public function validateCertificateChain(X509Certificates $x509)
58
    {
59 8
        if (!$x509->hasCertificate()) {
60 1
            throw new \RuntimeException("No certificates in bundle");
61
        }
62
63
        /** @var Certificate[] $certificates */
64 7
        $certificates = [];
65 7
        foreach ($x509->getCertificateList() as $certDer) {
66 7
            $certificates[] = Certificate::fromDER($certDer);
67
        }
68
69 7
        $endEntity = $certificates[0];
70 7
        $intermediate = new CertificateBundle(...array_slice($certificates, 1));
71
72 7
        return $this->validateCertificates($endEntity, $intermediate);
73
    }
74
75
    /**
76
     * @param Certificate $certificate
77
     * @param CertificateBundle $intermediate
78
     * @return QualifiedCertificate
79
     */
80 7
    public function validateCertificates(Certificate $certificate, CertificateBundle $intermediate): QualifiedCertificate
81
    {
82 7
        $path = CertificationPath::toTarget($certificate, $this->trustStore, $intermediate);
83 6
        return new QualifiedCertificate($path, $path->validate($this->validationConfig));
84
    }
85
86
    /**
87
     * @param Certificate $endEntity
88
     * @param PaymentRequest $paymentRequest
89
     * @throws InvalidX509Signature
90
     * @throws X509Exception
91
     */
92 5
    public function validateX509Signature(Certificate $endEntity, PaymentRequest $paymentRequest)
93
    {
94 5
        $subjectKey = $endEntity->tbsCertificate()->subjectPublicKeyInfo();
95
96
        /** @var AsymmetricCryptoAlgorithmIdentifier $algOid */
97 5
        $algOid = $subjectKey->algorithmIdentifier();
98 5
        $signAlgorithm = SignatureAlgorithmFactory::getSignatureAlgorithm($paymentRequest->getPkiType(), $algOid);
99
100 4
        $clone = new PaymentRequest();
101 4
        if ($paymentRequest->hasPaymentDetailsVersion()) {
102 4
            $clone->setPaymentDetailsVersion($paymentRequest->getPaymentDetailsVersion());
103
        }
104
105 4
        $clone->setPkiType($paymentRequest->getPkiType());
106 4
        $clone->setPkiData($paymentRequest->getPkiData());
107 4
        $clone->setSerializedPaymentDetails($paymentRequest->getSerializedPaymentDetails());
108 4
        $clone->setSignature('');
109
110 4
        $signData = $clone->serialize(new NonDiscardingBinaryCodec());
111 4
        $signature = Signature::fromSignatureData($paymentRequest->getSignature(), $signAlgorithm);
112 4
        if (!Crypto::getDefault()->verify($signData, $signature, $subjectKey, $signAlgorithm)) {
113 1
            throw new InvalidX509Signature("Invalid signature on request");
114
        }
115
116 3
        return;
0 ignored issues
show
Coding Style introduced by
Empty return statement not required here
Loading history...
117
    }
118
119
    /**
120
     * @param PaymentRequest $paymentRequest
121
     * @return \X509\CertificationPath\PathValidation\PathValidationResult
122
     */
123 4
    public function verifyX509Details(PaymentRequest $paymentRequest)
124
    {
125 4
        if (PKIType::NONE === $paymentRequest->getPkiType()) {
126 1
            throw new X509Exception("Cannot verify a request without a signature. You should check before calling verify.");
127
        }
128
129 3
        $x509 = new X509Certificates();
130 3
        $x509->parse($paymentRequest->getPkiData());
131
132 3
        $qualifiedCert = $this->validateCertificateChain($x509);
133 3
        $validationResult = $qualifiedCert->getValidationResult();
134 3
        $this->validateX509Signature($validationResult->certificate(), $paymentRequest);
135
136 3
        return $validationResult;
137
    }
138
}
139