ACValidator::validate()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 9.9666
c 0
b 0
f 0
nc 1
cc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace X509\AttributeCertificate\Validation;
6
7
use Sop\CryptoBridge\Crypto;
8
use X509\AttributeCertificate\AttributeCertificate;
9
use X509\AttributeCertificate\Validation\Exception\ACValidationException;
10
use X509\Certificate\Certificate;
11
use X509\Certificate\Extension\Extension;
12
use X509\Certificate\Extension\TargetInformationExtension;
13
use X509\Certificate\Extension\Target\Targets;
14
use X509\CertificationPath\Exception\PathValidationException;
15
use X509\CertificationPath\PathValidation\PathValidationConfig;
16
17
/**
18
 * Implements attribute certificate validation conforming to RFC 5755.
19
 *
20
 * @link https://tools.ietf.org/html/rfc5755#section-5
21
 */
22
class ACValidator
23
{
24
    /**
25
     * Attribute certificate.
26
     *
27
     * @var AttributeCertificate
28
     */
29
    protected $_ac;
30
    
31
    /**
32
     * Validation configuration.
33
     *
34
     * @var ACValidationConfig
35
     */
36
    protected $_config;
37
    
38
    /**
39
     * Crypto engine.
40
     *
41
     * @var Crypto
42
     */
43
    protected $_crypto;
44
    
45
    /**
46
     * Constructor.
47
     *
48
     * @param AttributeCertificate $ac Attribute certificate to validate
49
     * @param ACValidationConfig $config Validation configuration
50
     * @param Crypto|null $crypto Crypto engine, use default if not set
51
     */
52 12
    public function __construct(AttributeCertificate $ac,
53
        ACValidationConfig $config, Crypto $crypto = null)
54
    {
55 12
        $this->_ac = $ac;
56 12
        $this->_config = $config;
57 12
        $this->_crypto = $crypto ?: Crypto::getDefault();
58 12
    }
59
    
60
    /**
61
     * Validate attribute certificate.
62
     *
63
     * @throws ACValidationException If validation fails
64
     * @return AttributeCertificate Validated AC
65
     */
66 12
    public function validate(): AttributeCertificate
67
    {
68 12
        $this->_validateHolder();
69 10
        $issuer = $this->_verifyIssuer();
70 7
        $this->_validateIssuerProfile($issuer);
71 5
        $this->_validateTime();
72 3
        $this->_validateTargeting();
73 2
        return $this->_ac;
74
    }
75
    
76
    /**
77
     * Validate AC holder's certification.
78
     *
79
     * @throws ACValidationException
80
     * @return Certificate Certificate of the AC's holder
81
     */
82 12
    private function _validateHolder(): Certificate
83
    {
84 12
        $path = $this->_config->holderPath();
85 12
        $config = PathValidationConfig::defaultConfig()->withMaxLength(
86 12
            count($path))->withDateTime($this->_config->evaluationTime());
87
        try {
88 12
            $holder = $path->validate($config, $this->_crypto)->certificate();
89 1
        } catch (PathValidationException $e) {
90 1
            throw new ACValidationException(
91 1
                "Failed to validate holder PKC's certification path.", 0, $e);
92
        }
93 11
        if (!$this->_ac->isHeldBy($holder)) {
94 1
            throw new ACValidationException("Name mismatch of AC's holder PKC.");
95
        }
96 10
        return $holder;
97
    }
98
    
99
    /**
100
     * Verify AC's signature and issuer's certification.
101
     *
102
     * @throws ACValidationException
103
     * @return Certificate Certificate of the AC's issuer
104
     */
105 10
    private function _verifyIssuer(): Certificate
106
    {
107 10
        $path = $this->_config->issuerPath();
108 10
        $config = PathValidationConfig::defaultConfig()->withMaxLength(
109 10
            count($path))->withDateTime($this->_config->evaluationTime());
110
        try {
111 10
            $issuer = $path->validate($config, $this->_crypto)->certificate();
112 1
        } catch (PathValidationException $e) {
113 1
            throw new ACValidationException(
114 1
                "Failed to validate issuer PKC's certification path.", 0, $e);
115
        }
116 9
        if (!$this->_ac->isIssuedBy($issuer)) {
117 1
            throw new ACValidationException("Name mismatch of AC's issuer PKC.");
118
        }
119 8
        $pubkey_info = $issuer->tbsCertificate()->subjectPublicKeyInfo();
120 8
        if (!$this->_ac->verify($pubkey_info, $this->_crypto)) {
121 1
            throw new ACValidationException("Failed to verify signature.");
122
        }
123 7
        return $issuer;
124
    }
125
    
126
    /**
127
     * Validate AC issuer's profile.
128
     *
129
     * @link https://tools.ietf.org/html/rfc5755#section-4.5
130
     * @param Certificate $cert
131
     * @throws ACValidationException
132
     */
133 7
    private function _validateIssuerProfile(Certificate $cert)
134
    {
135 7
        $exts = $cert->tbsCertificate()->extensions();
136 7
        if ($exts->hasKeyUsage() && !$exts->keyUsage()->isDigitalSignature()) {
137 1
            throw new ACValidationException(
138
                "Issuer PKC's Key Usage extension doesn't permit" .
139 1
                     " verification of digital signatures.");
140
        }
141 6
        if ($exts->hasBasicConstraints() && $exts->basicConstraints()->isCA()) {
142 1
            throw new ACValidationException("Issuer PKC must not be a CA.");
143
        }
144 5
    }
145
    
146
    /**
147
     * Validate AC's validity period.
148
     *
149
     * @throws ACValidationException
150
     */
151 5
    private function _validateTime()
152
    {
153 5
        $t = $this->_config->evaluationTime();
154 5
        $validity = $this->_ac->acinfo()->validityPeriod();
155 5
        if ($validity->notBeforeTime()->diff($t)->invert) {
156 1
            throw new ACValidationException("Validity period has not started.");
157
        }
158 4
        if ($t->diff($validity->notAfterTime())->invert) {
159 1
            throw new ACValidationException("Attribute certificate has expired.");
160
        }
161 3
    }
162
    
163
    /**
164
     * Validate AC's target information.
165
     *
166
     * @throws ACValidationException
167
     */
168 3
    private function _validateTargeting()
169
    {
170 3
        $exts = $this->_ac->acinfo()->extensions();
171
        // if target information extension is not present
172 3
        if (!$exts->has(Extension::OID_TARGET_INFORMATION)) {
173 1
            return;
174
        }
175 2
        $ext = $exts->get(Extension::OID_TARGET_INFORMATION);
176 2
        if ($ext instanceof TargetInformationExtension &&
177 2
             !$this->_hasMatchingTarget($ext->targets())) {
178 1
            throw new ACValidationException(
179 1
                "Attribute certificate doesn't have a matching target.");
180
        }
181 1
    }
182
    
183
    /**
184
     * Check whether validation configuration has matching targets.
185
     *
186
     * @param Targets $targets Set of eligible targets
187
     * @return boolean
188
     */
189 2
    private function _hasMatchingTarget(Targets $targets): bool
190
    {
191 2
        foreach ($this->_config->targets() as $target) {
192 2
            if ($targets->hasTarget($target)) {
193 2
                return true;
194
            }
195
        }
196 1
        return false;
197
    }
198
}
199