CertificationPathBuilder::_findIssuers()   A
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 14
cts 14
cp 1
rs 9.2408
c 0
b 0
f 0
nc 3
cc 5
nop 2
crap 5
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace X509\CertificationPath\PathBuilding;
6
7
use X509\Certificate\Certificate;
8
use X509\Certificate\CertificateBundle;
9
use X509\CertificationPath\CertificationPath;
10
use X509\CertificationPath\Exception\PathBuildingException;
11
12
/**
13
 * Class for resolving certification paths.
14
 *
15
 * @link https://tools.ietf.org/html/rfc4158
16
 */
17
class CertificationPathBuilder
18
{
19
    /**
20
     * Trust anchors.
21
     *
22
     * @var CertificateBundle
23
     */
24
    protected $_trustList;
25
    
26
    /**
27
     * Constructor.
28
     *
29
     * @param CertificateBundle $trust_list List of trust anchors
30
     */
31 9
    public function __construct(CertificateBundle $trust_list)
32
    {
33 9
        $this->_trustList = $trust_list;
34 9
    }
35
    
36
    /**
37
     * Get all certification paths to given target certificate from
38
     * any trust anchor.
39
     *
40
     * @param Certificate $target Target certificate
41
     * @param CertificateBundle|null $intermediate Optional intermediate
42
     *        certificates
43
     * @return CertificationPath[]
44
     */
45 9
    public function allPathsToTarget(Certificate $target,
46
        CertificateBundle $intermediate = null): array
47
    {
48 9
        $paths = $this->_resolvePathsToTarget($target, $intermediate);
49
        // map paths to CertificationPath objects
50 9
        return array_map(
51 9
            function ($certs) {
52 8
                return new CertificationPath(...$certs);
53 9
            }, $paths);
54
    }
55
    
56
    /**
57
     * Resolve all possible certification paths from any trust anchor to
58
     * the target certificate, using optional intermediate certificates.
59
     *
60
     * Helper method for allPathsToTarget to be called recursively.
61
     *
62
     * @todo Implement loop detection
63
     * @param Certificate $target
64
     * @param CertificateBundle $intermediate
65
     * @return array[] Array of arrays containing path certificates
66
     */
67 9
    private function _resolvePathsToTarget(Certificate $target,
68
        CertificateBundle $intermediate = null): array
69
    {
70
        // array of possible paths
71 9
        $paths = array();
72
        // signed by certificate in the trust list
73 9
        foreach ($this->_findIssuers($target, $this->_trustList) as $issuer) {
74
            // if target is self-signed, path consists of only
75
            // the target certificate
76 8
            if ($target->equals($issuer)) {
77 1
                $paths[] = array($target);
78
            } else {
79 8
                $paths[] = array($issuer, $target);
80
            }
81
        }
82 9
        if (isset($intermediate)) {
83
            // signed by intermediate certificate
84 6
            foreach ($this->_findIssuers($target, $intermediate) as $issuer) {
85
                // intermediate certificate must not be self-signed
86 5
                if ($issuer->isSelfIssued()) {
87 2
                    continue;
88
                }
89
                // resolve paths to issuer
90 5
                $subpaths = $this->_resolvePathsToTarget($issuer, $intermediate);
91 5
                foreach ($subpaths as $path) {
92 5
                    $paths[] = array_merge($path, array($target));
93
                }
94
            }
95
        }
96 9
        return $paths;
97
    }
98
    
99
    /**
100
     * Get shortest path to given target certificate from any trust anchor.
101
     *
102
     * @param Certificate $target Target certificate
103
     * @param CertificateBundle|null $intermediate Optional intermediate
104
     *        certificates
105
     * @throws PathBuildingException
106
     * @return CertificationPath
107
     */
108 8
    public function shortestPathToTarget(Certificate $target,
109
        CertificateBundle $intermediate = null): CertificationPath
110
    {
111 8
        $paths = $this->allPathsToTarget($target, $intermediate);
112 8
        if (!count($paths)) {
113 1
            throw new PathBuildingException("No certification paths.");
114
        }
115 7
        usort($paths,
116 7
            function ($a, $b) {
117 1
                return count($a) < count($b) ? -1 : 1;
118 7
            });
119 7
        return reset($paths);
120
    }
121
    
122
    /**
123
     * Find all issuers of the target certificate from a given bundle.
124
     *
125
     * @param Certificate $target Target certificate
126
     * @param CertificateBundle $bundle Certificates to search
127
     * @return Certificate[]
128
     */
129 9
    protected function _findIssuers(Certificate $target,
130
        CertificateBundle $bundle): array
131
    {
132 9
        $issuers = array();
133 9
        $issuer_name = $target->tbsCertificate()->issuer();
134 9
        $extensions = $target->tbsCertificate()->extensions();
135
        // find by authority key identifier
136 9
        if ($extensions->hasAuthorityKeyIdentifier()) {
137 9
            $ext = $extensions->authorityKeyIdentifier();
138 9
            if ($ext->hasKeyIdentifier()) {
139 9
                foreach ($bundle->allBySubjectKeyIdentifier(
140 9
                    $ext->keyIdentifier()) as $issuer) {
141
                    // check that issuer name matches
142 8
                    if ($issuer->tbsCertificate()
143 8
                        ->subject()
144 8
                        ->equals($issuer_name)) {
145 8
                        $issuers[] = $issuer;
146
                    }
147
                }
148
            }
149
        }
150 9
        return $issuers;
151
    }
152
}
153