Passed
Pull Request — master (#15)
by Jaime Pérez
02:17
created

Signature::getAlgorithm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\XML\ds;
6
7
use DOMElement;
8
use Exception;
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\XML\Exception\InvalidDOMElementException;
11
use SimpleSAML\XML\Utils as XMLUtils;
12
use SimpleSAML\XMLSecurity\Utils\Certificate;
13
use SimpleSAML\XMLSecurity\Utils\Security;
14
use SimpleSAML\XMLSecurity\XMLSecurityDSig;
15
use SimpleSAML\XMLSecurity\XMLSecurityKey;
16
17
/**
18
 * Wrapper class for XML signatures
19
 *
20
 * @package simplesamlphp/xml-security
21
 */
22
final class Signature extends AbstractDsElement
23
{
24
    /** @var string */
25
    protected string $algorithm;
26
27
    /** @var string[] */
28
    protected array $certificates = [];
29
30
    /** @var SignatureValue */
31
    protected SignatureValue $value;
32
33
    /** @var \SimpleSAML\XMLSecurity\XMLSecurityKey|null */
34
    protected ?XMLSecurityKey $key;
35
36
    /** @var \SimpleSAML\XMLSecurity\XMLSecurityDSig */
37
    protected XMLSecurityDSig $signer;
38
39
40
    /**
41
     * Signature constructor.
42
     *
43
     * @param string $algorithm
44
     * @param string[] $certificates
45
     * @param \SimpleSAML\XMLSecurity\XMLSecurityKey|null $key
46
     *
47
     * @throws \Exception
48
     */
49
    public function __construct(
50
        string $algorithm,
51
        SignatureValue $value,
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

51
        /** @scrutinizer ignore-unused */ SignatureValue $value,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
52
        array $certificates = [],
53
        ?XMLSecurityKey $key = null
54
    ) {
55
        $this->setAlgorithm($algorithm);
56
        $this->setCertificates($certificates);
57
        $this->setKey($key);
58
59
        $this->signer = new XMLSecurityDSig();
60
        $this->signer->idKeys[] = 'ID';
61
    }
62
63
64
    /**
65
     * Get the algorithm used by this signature.
66
     *
67
     * @return string
68
     */
69
    public function getAlgorithm(): string
70
    {
71
        return $this->algorithm;
72
    }
73
74
75
    /**
76
     * Set the algorithm used by this signature.
77
     *
78
     * @param string $algorithm
79
     */
80
    protected function setAlgorithm(string $algorithm): void
81
    {
82
        Assert::notEmpty($algorithm, 'Signature algorithm cannot be empty');
83
        $this->algorithm = $algorithm;
84
    }
85
86
87
    /**
88
     * Get the array of certificates attached to this signature.
89
     *
90
     * @return string[]
91
     */
92
    public function getCertificates(): array
93
    {
94
        return $this->certificates;
95
    }
96
97
98
    /**
99
     * Set the array of certificates (in PEM format) attached to this signature.
100
     *
101
     * @param string[] $certificates
102
     */
103
    protected function setCertificates(array $certificates): void
104
    {
105
        Assert::allStringNotEmpty($certificates, 'Cannot add empty certificates.');
106
        Assert::allTrue(
107
            array_map([Certificate::class, 'hasValidStructure'], $certificates),
108
            'One or more certificates have an invalid format.'
109
        );
110
        $this->certificates = $certificates;
111
    }
112
113
114
    /**
115
     * @param \SimpleSAML\XMLSecurity\XMLSecurityKey|null $key
116
     */
117
    protected function setKey(?XMLSecurityKey $key): void
118
    {
119
        if ($key !== null) {
120
            Assert::eq($this->algorithm, $key->getAlgorithm(), 'Key type does not match signature algorithm.');
121
        }
122
        $this->key = $key;
123
    }
124
125
126
    /**
127
     * Get the SignatureValue corresponding to this signature.
128
     *
129
     * @return SignatureValue
130
     */
131
    public function getSignatureValue(): SignatureValue
132
    {
133
        return $this->value;
134
    }
135
136
137
    /**
138
     * Set the SignatureValue.
139
     *
140
     * @param SignatureValue $value
141
     */
142
    protected function setSignatureValue(SignatureValue $value): void
143
    {
144
        $this->value = $value;
145
    }
146
147
148
    /**
149
     * @return XMLSecurityDSig
150
     */
151
    public function getSigner(): XMLSecurityDSig
152
    {
153
        return $this->signer;
154
    }
155
156
157
    /**
158
     * @param DOMElement $xml
159
     *
160
     * @return \SimpleSAML\XML\AbstractXMLElement
161
     * @throws \Exception
162
     *
163
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
164
     *   If the qualified name of the supplied element is wrong
165
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException
166
     *   If the supplied signature is missing an Algorithm attribute
167
     */
168
    public static function fromXML(DOMElement $xml): object
169
    {
170
        Assert::same($xml->localName, 'Signature', InvalidDOMElementException::class);
171
        Assert::same($xml->namespaceURI, Signature::NS, InvalidDOMElementException::class);
172
173
        $parent = $xml->parentNode;
174
175
        $sigMethod = XMLUtils::xpQuery($xml, './ds:SignedInfo/ds:SignatureMethod');
176
        Assert::notEmpty($sigMethod, 'Missing ds:SignatureMethod element.');
177
        /** @var \DOMElement $sigMethod */
178
        $sigMethod = $sigMethod[0];
179
        Assert::true(
180
            $sigMethod->hasAttribute('Algorithm'),
181
            'Missing "Algorithm" attribute on ds:SignatureMethod element.'
182
        );
183
184
        // now we extract all available X509 certificates in the signature element
185
        $certificates = [];
186
        foreach (XMLUtils::xpQuery($xml, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) {
187
            $certificates[] = Certificate::convertToCertificate(
188
                str_replace(["\r", "\n", "\t", ' '], '', trim($certNode->textContent))
189
            );
190
        }
191
192
        $value = SignatureValue::getChildrenOfClass($xml);
193
        Assert::count($value, 1, 'ds:Signature needs exactly one ds:SignatureValue');
194
195
        $signature = new self(self::getAttribute($sigMethod, 'Algorithm'), $value[0], $certificates);
196
197
        $signature->signer->sigNode = $xml;
198
199
        // canonicalize the XMLDSig SignedInfo element in the message
200
        $signature->signer->canonicalizeSignedInfo();
201
202
        // validate referenced xml nodes
203
        if (!$signature->signer->validateReference()) {
204
            throw new Exception('Digest validation failed.');
205
        }
206
207
        // check that $root is one of the signed nodes
208
        $rootSigned = false;
209
        /** @var \DOMNode $signedNode */
210
        foreach ($signature->signer->getValidatedNodes() as $signedNode) {
211
            if ($signedNode->isSameNode($parent)) {
212
                $rootSigned = true;
213
                break;
214
            } elseif ($parent->parentNode instanceof \DOMDocument && $signedNode->isSameNode($parent->ownerDocument)) {
215
                // $parent is the root element of a signed document
216
                $rootSigned = true;
217
                break;
218
            }
219
        }
220
        if (!$rootSigned) {
221
            throw new Exception('The parent element is not signed.');
222
        }
223
224
        return $signature;
225
    }
226
227
228
    /**
229
     * @param \DOMElement|null $parent
230
     *
231
     * @return \DOMElement
232
     *
233
     * @psalm-suppress MoreSpecificReturnType
234
     */
235
    public function toXML(DOMElement $parent = null): DOMElement
236
    {
237
        Assert::notNull($parent, 'Cannot create a Signature without anything to sign.');
238
        Assert::notNull($this->key, 'Cannot sign without a signing key.');
239
240
        // find first child element
241
        $childElements = XMLUtils::xpQuery($parent, './*');
0 ignored issues
show
Bug introduced by
It seems like $parent can also be of type null; however, parameter $node of SimpleSAML\XML\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

241
        $childElements = XMLUtils::xpQuery(/** @scrutinizer ignore-type */ $parent, './*');
Loading history...
242
        $firstChildElement = null;
243
        if (count($childElements) > 0) {
244
            $firstChildElement = $childElements[0];
245
        }
246
247
        Security::insertSignature($this->key, $this->certificates, $parent, $firstChildElement);
0 ignored issues
show
Bug introduced by
It seems like $this->key can also be of type null; however, parameter $key of SimpleSAML\XMLSecurity\U...rity::insertSignature() does only seem to accept SimpleSAML\XMLSecurity\XMLSecurityKey, 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

247
        Security::insertSignature(/** @scrutinizer ignore-type */ $this->key, $this->certificates, $parent, $firstChildElement);
Loading history...
Bug introduced by
It seems like $parent can also be of type null; however, parameter $root of SimpleSAML\XMLSecurity\U...rity::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

247
        Security::insertSignature($this->key, $this->certificates, /** @scrutinizer ignore-type */ $parent, $firstChildElement);
Loading history...
248
249
        /** @psalm-suppress LessSpecificReturnStatement */
250
        return XMLUtils::xpQuery($parent, './ds:Signature')[0];
251
    }
252
}
253