Completed
Push — master ( eef77b...d5fe2b )
by thomas
05:20
created

RequestValidation   B

Complexity

Total Complexity 13

Size/Duplication

Total Lines 119
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 13
c 1
b 0
f 0
lcom 1
cbo 17
dl 0
loc 119
ccs 46
cts 46
cp 1
rs 7.8571

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A validateCertificateChain() 0 17 3
A validateCertificates() 0 5 1
B validateX509Signature() 0 27 4
A verifyX509Details() 0 15 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Bip70\X509;
6
7
use Bip70\X509\Exception\InvalidCertificateChainException;
8
use Bip70\X509\Exception\InvalidX509Signature;
9
use Bip70\Protobuf\Codec\NonDiscardingBinaryCodec;
10
use Bip70\Protobuf\Proto\PaymentRequest;
11
use Bip70\Protobuf\Proto\X509Certificates;
12
use Sop\CryptoBridge\Crypto;
13
use Sop\CryptoTypes\AlgorithmIdentifier\Hash\SHA1AlgorithmIdentifier;
14
use Sop\CryptoTypes\AlgorithmIdentifier\Hash\SHA256AlgorithmIdentifier;
15
use Sop\CryptoTypes\AlgorithmIdentifier\Signature\SignatureAlgorithmIdentifierFactory;
16
use Sop\CryptoTypes\Signature\Signature;
17
use X509\Certificate\Certificate;
18
use X509\Certificate\CertificateBundle;
19
use X509\CertificationPath\CertificationPath;
20
use X509\CertificationPath\PathValidation\PathValidationConfig;
21
22
class RequestValidation
23
{
24
    /**
25
     * @var null|PathValidationConfig
26
     */
27
    private $validationConfig;
28
29
    /**
30
     * @var CertificateBundle
31
     */
32
    private $trustStore;
33
34
    /**
35
     * RequestValidation constructor.
36
     * @param PathValidationConfig|null $validationConfig
37
     * @param CertificateBundle|null $trustStore
38
     */
39 10
    public function __construct(PathValidationConfig $validationConfig = null, CertificateBundle $trustStore = null)
40
    {
41 10
        if (null === $validationConfig) {
42 10
            $validationConfig = PathValidationConfig::defaultConfig();
43
        }
44
45 10
        if (null === $trustStore) {
46 5
            $trustStore = new CertificateBundle();
47
        }
48
49 10
        $this->validationConfig = $validationConfig;
50 10
        $this->trustStore = $trustStore;
51 10
    }
52
53
    /**
54
     * @param X509Certificates $x509
55
     * @return \Bip70\X509\QualifiedCertificate
56
     * @throws InvalidCertificateChainException
57
     */
58 7
    public function validateCertificateChain(X509Certificates $x509)
59
    {
60 7
        if (!$x509->hasCertificate()) {
61 1
            throw new \RuntimeException("No certificates in bundle");
62
        }
63
64
        /** @var Certificate[] $certificates */
65 6
        $certificates = [];
66 6
        foreach ($x509->getCertificateList() as $certDer) {
67 6
            $certificates[] = Certificate::fromDER($certDer);
68
        }
69
70 6
        $endEntity = $certificates[0];
71 6
        $intermediate = new CertificateBundle(...array_slice($certificates, 1));
72
73 6
        return $this->validateCertificates($endEntity, $intermediate);
74
    }
75
76
    /**
77
     * @param Certificate $certificate
78
     * @param CertificateBundle $intermediate
79
     * @return QualifiedCertificate
80
     */
81 6
    public function validateCertificates(Certificate $certificate, CertificateBundle $intermediate): QualifiedCertificate
82
    {
83 6
        $path = CertificationPath::toTarget($certificate, $this->trustStore, $intermediate);
84 5
        return new QualifiedCertificate($path, $path->validate($this->validationConfig));
0 ignored issues
show
Bug introduced by
It seems like $this->validationConfig can be null; however, validate() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
85
    }
86
87
    /**
88
     * @param Certificate $endEntity
89
     * @param PaymentRequest $paymentRequest
90
     * @return void
91
     * @throws InvalidX509Signature
92
     */
93 4
    public function validateX509Signature(Certificate $endEntity, PaymentRequest $paymentRequest)
94
    {
95 4
        if ($paymentRequest->getPkiType() === PKIType::X509_SHA1) {
96 3
            $hashAlgId = new SHA1AlgorithmIdentifier();
97 3
        } else if ($paymentRequest->getPkiType() === PKIType::X509_SHA256) {
98 2
            $hashAlgId = new SHA256AlgorithmIdentifier();
99
        } else {
100 1
            throw new \RuntimeException("Unknown signature scheme");
101
        }
102
103 3
        $subjectKey = $endEntity->tbsCertificate()->subjectPublicKeyInfo();
104 3
        $signAlgorithm = SignatureAlgorithmIdentifierFactory::algoForAsymmetricCrypto(
105 3
            $subjectKey->algorithmIdentifier(),
106 3
            $hashAlgId
107
        );
108
109 3
        $clone = clone $paymentRequest;
110 3
        $clone->setSignature('');
111
112 3
        $signData = $clone->serialize(new NonDiscardingBinaryCodec());
113 3
        $signature = Signature::fromSignatureData($paymentRequest->getSignature(), $signAlgorithm);
114 3
        if (!Crypto::getDefault()->verify($signData, $signature, $subjectKey, $signAlgorithm)) {
115 1
            throw new InvalidX509Signature("Invalid signature on request");
116
        }
117
118 2
        return;
0 ignored issues
show
Coding Style introduced by
Empty return statement not required here
Loading history...
119
    }
120
121
    /**
122
     * @param PaymentRequest $paymentRequest
123
     * @return \X509\CertificationPath\PathValidation\PathValidationResult
124
     */
125 3
    public function verifyX509Details(PaymentRequest $paymentRequest)
126
    {
127 3
        if (PKIType::NONE === $paymentRequest->getPkiType()) {
128 1
            throw new \RuntimeException("Cannot verify a request without a signature. You should check before calling verify.");
129
        }
130
131 2
        $x509 = new X509Certificates();
132 2
        $x509->parse($paymentRequest->getPkiData());
133
134 2
        $qualifiedCert = $this->validateCertificateChain($x509);
135 2
        $validationResult = $qualifiedCert->getValidationResult();
136 2
        $this->validateX509Signature($validationResult->certificate(), $paymentRequest);
137
138 2
        return $validationResult;
139
    }
140
}
141