Passed
Push — master ( 6db269...70aff8 )
by Jaime Pérez
02:49
created

Signature::fromXML()   B

Complexity

Conditions 8
Paths 18

Size

Total Lines 54
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 29
nc 18
nop 1
dl 0
loc 54
rs 8.2114
c 1
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SAML2\XML\ds;
6
7
use DOMElement;
8
use Exception;
9
use RobRichards\XMLSecLibs\XMLSecurityDSig;
10
use RobRichards\XMLSecLibs\XMLSecurityKey;
11
use SAML2\Utilities\Certificate;
12
use SAML2\Utils;
13
use Webmozart\Assert\Assert;
14
15
/**
16
 * Wrapper class for XML signatures
17
 *
18
 * @package simplesamlphp/saml2
19
 */
20
final class Signature extends AbstractDsElement
21
{
22
    /** @var string */
23
    protected $algorithm;
24
25
    /** @var string[] */
26
    protected $certificates = [];
27
28
    /** @var \RobRichards\XMLSecLibs\XMLSecurityKey */
29
    protected $key;
30
31
    /** @var \RobRichards\XMLSecLibs\XMLSecurityDSig */
32
    protected $signer;
33
34
35
    /**
36
     * Signature constructor.
37
     *
38
     * @param string $algorithm
39
     * @param string[] $certificates
40
     * @param XMLSecurityKey|null $key
41
     *
42
     * @throws \Exception
43
     */
44
    public function __construct(
45
        string $algorithm,
46
        array $certificates = [],
47
        ?XMLSecurityKey $key = null
48
    ) {
49
        $this->setAlgorithm($algorithm);
50
        $this->setCertificates($certificates);
51
        $this->setKey($key);
52
53
        $this->signer = new XMLSecurityDSig();
54
        $this->signer->idKeys[] = 'ID';
55
    }
56
57
58
    /**
59
     * Get the algorithm used by this signature.
60
     *
61
     * @return string
62
     */
63
    public function getAlgorithm(): string
64
    {
65
        return $this->algorithm;
66
    }
67
68
69
    /**
70
     * Set the algorithm used by this signature.
71
     *
72
     * @param string $algorithm
73
     */
74
    protected function setAlgorithm(string $algorithm): void
75
    {
76
        Assert::notEmpty($algorithm, 'Signature algorithm cannot be empty');
77
        $this->algorithm = $algorithm;
78
    }
79
80
81
    /**
82
     * Get the array of certificates attached to this signature.
83
     *
84
     * @return array
85
     */
86
    public function getCertificates(): array
87
    {
88
        return $this->certificates;
89
    }
90
91
92
    /**
93
     * Set the array of certificates (in PEM format) attached to this signature.
94
     *
95
     * @param string[] $certificates
96
     */
97
    protected function setCertificates(array $certificates): void
98
    {
99
        Assert::allStringNotEmpty($certificates, 'Cannot add empty certificates.');
100
        Assert::allTrue(
101
            array_map([Certificate::class, 'hasValidStructure'], $certificates),
102
            'One or more certificates have an invalid format.'
103
        );
104
        $this->certificates = $certificates;
105
    }
106
107
108
    /**
109
     * @param XMLSecurityKey $key
110
     */
111
    protected function setKey(?XMLSecurityKey $key): void
112
    {
113
        if ($key !== null) {
114
            Assert::eq($this->algorithm, $key->getAlgorithm(), 'Key type does not match signature algorithm.');
115
        }
116
        $this->key = $key;
117
    }
118
119
120
    /**
121
     * @return XMLSecurityDSig
122
     */
123
    public function getSigner(): XMLSecurityDSig
124
    {
125
        return $this->signer;
126
    }
127
128
129
    /**
130
     * @param DOMElement $xml
131
     *
132
     * @return object
133
     * @throws \Exception
134
     */
135
    public static function fromXML(DOMElement $xml): object
136
    {
137
        Assert::same($xml->localName, 'Signature');
138
        Assert::same($xml->namespaceURI, Signature::NS);
139
140
        $parent = $xml->parentNode;
141
142
        $sigMethod = Utils::xpQuery($xml, './ds:SignedInfo/ds:SignatureMethod');
143
        Assert::notEmpty($sigMethod, 'Missing ds:SignatureMethod element.');
144
        /** @var \DOMElement $sigMethod */
145
        $sigMethod = $sigMethod[0];
146
        Assert::true(
147
            $sigMethod->hasAttribute('Algorithm'),
148
            'Missing "Algorithm" attribute on ds:SignatureMethod element.'
149
        );
150
151
        // now we extract all available X509 certificates in the signature element
152
        $certificates = [];
153
        foreach (Utils::xpQuery($xml, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) {
154
            $certificates[] = Certificate::convertToCertificate(
155
                str_replace(["\r", "\n", "\t", ' '], '', trim($certNode->textContent))
156
            );
157
        }
158
159
        $signature = new self($sigMethod->getAttribute('Algorithm'), $certificates);
160
161
        $signature->signer->sigNode = $xml;
162
163
        // canonicalize the XMLDSig SignedInfo element in the message
164
        $signature->signer->canonicalizeSignedInfo();
165
166
        // validate referenced xml nodes
167
        if (!$signature->signer->validateReference()) {
168
            throw new Exception('Digest validation failed.');
169
        }
170
171
        // check that $root is one of the signed nodes
172
        $rootSigned = false;
173
        /** @var \DOMNode $signedNode */
174
        foreach ($signature->signer->getValidatedNodes() as $signedNode) {
175
            if ($signedNode->isSameNode($parent)) {
176
                $rootSigned = true;
177
                break;
178
            } elseif ($parent->parentNode instanceof \DOMDocument && $signedNode->isSameNode($parent->ownerDocument)) {
179
                // $parent is the root element of a signed document
180
                $rootSigned = true;
181
                break;
182
            }
183
        }
184
        if (!$rootSigned) {
185
            throw new Exception('The parent element is not signed.');
186
        }
187
188
        return $signature;
189
    }
190
191
192
    /**
193
     * @param DOMElement|null $parent
194
     *
195
     * @return DOMElement
196
     */
197
    public function toXML(DOMElement $parent = null): DOMElement
198
    {
199
        Assert::notNull($parent, 'Cannot create a Signature without anything to sign.');
200
        Assert::notNull($this->key, 'Cannot sign without a signing key.');
201
202
        // find first child element
203
        $childElements = Utils::xpQuery($parent, './*');
0 ignored issues
show
Bug introduced by
It seems like $parent can also be of type null; however, parameter $node of SAML2\Utils::xpQuery() does only seem to accept DOMNode, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

203
        $childElements = Utils::xpQuery(/** @scrutinizer ignore-type */ $parent, './*');
Loading history...
204
        $firstChildElement = null;
205
        if (count($childElements) > 0) {
206
            $firstChildElement = $childElements[0];
207
        }
208
209
        Utils::insertSignature($this->key, $this->certificates, $parent, $firstChildElement);
0 ignored issues
show
Bug introduced by
It seems like $parent can also be of type null; however, parameter $root of SAML2\Utils::insertSignature() does only seem to accept DOMElement, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

209
        Utils::insertSignature($this->key, $this->certificates, /** @scrutinizer ignore-type */ $parent, $firstChildElement);
Loading history...
210
        return Utils::xpQuery($parent, './ds:Signature')[0];
211
    }
212
}
213