Passed
Push — master ( 01128b...efda70 )
by Tim
02:10
created

testSuccessfulVerifyingWithWrongKeyFirstRightOneSecond()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 32
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 23
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 32
rs 9.552
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\Test\XML;
6
7
use DOMElement;
8
use PHPUnit\Framework\Attributes\CoversClass;
1 ignored issue
show
Bug introduced by
The type PHPUnit\Framework\Attributes\CoversClass was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use PHPUnit\Framework\TestCase;
10
use SimpleSAML\XML\DOMDocumentFactory;
11
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
12
use SimpleSAML\XMLSecurity\Constants as C;
13
use SimpleSAML\XMLSecurity\CryptoEncoding\PEM;
14
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
15
use SimpleSAML\XMLSecurity\Key\PublicKey;
16
use SimpleSAML\XMLSecurity\Key\X509Certificate;
17
use SimpleSAML\XMLSecurity\Test\XML\CustomSignable;
18
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
19
use SimpleSAML\XMLSecurity\XML\ds\Signature;
20
use SimpleSAML\XMLSecurity\XML\SignedElementTrait;
21
22
use function dirname;
23
use function strval;
24
25
/**
26
 * Class \SimpleSAML\XMLSecurity\Test\XML\SignedElementTest
27
 *
28
 * @package simplesamlphp/xml-security
29
 */
30
#[CoversClass(SignedElementTrait::class)]
31
#[CoversClass(CustomSignable::class)]
32
final class SignedElementTest extends TestCase
33
{
34
    /** @var \SimpleSAML\XMLSecurity\CryptoEncoding\PEM */
35
    private PEM $certificate;
36
37
    /** @var \SimpleSAML\XMLSecurity\CryptoEncoding\PEM */
38
    private PEM $wrong_certificate;
39
40
    /** @var \DOMElement */
41
    private DOMElement $signedDocumentWithComments;
42
43
    /** @var \DOMElement */
44
    private DOMElement $signedDocument;
45
46
    /** @var \DOMElement  */
47
    private DOMElement $tamperedDocument;
48
49
50
    /**
51
     */
52
    public function setUp(): void
53
    {
54
        $this->signedDocumentWithComments = DOMDocumentFactory::fromFile(
55
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSignedWithComments.xml',
56
        )->documentElement;
57
58
        $this->signedDocument = DOMDocumentFactory::fromFile(
59
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSigned.xml',
60
        )->documentElement;
61
62
        $this->tamperedDocument = DOMDocumentFactory::fromFile(
63
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSignedTampered.xml',
64
        )->documentElement;
65
66
        $this->certificate = PEM::fromString(
67
            PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE),
68
        );
69
70
        $this->wrong_certificate = PEM::fromString(
71
            PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::OTHER_CERTIFICATE),
72
        );
73
    }
74
75
76
    /**
77
     * Test creating a signed object from its XML representation.
78
     */
79
    public function testUnmarshalling(): void
80
    {
81
        $customSigned = CustomSignable::fromXML($this->signedDocument);
82
83
        $this->assertEquals(
84
            $this->signedDocument->ownerDocument->saveXML($this->signedDocument),
0 ignored issues
show
Bug introduced by
The method saveXML() does not exist on null. ( Ignorable by Annotation )

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

84
            $this->signedDocument->ownerDocument->/** @scrutinizer ignore-call */ 
85
                                                  saveXML($this->signedDocument),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
85
            strval($customSigned),
86
        );
87
    }
88
89
90
    /**
91
     * Test the verification of a signature with a given key.
92
     */
93
    public function testSuccessfulVerifyingWithGivenKey(): void
94
    {
95
        $customSigned = CustomSignable::fromXML($this->signedDocument);
96
97
        $this->assertTrue($customSigned->isSigned());
98
        $signature = $customSigned->getSignature();
99
        $this->assertInstanceOf(Signature::class, $signature);
100
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
101
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
102
        $factory = new SignatureAlgorithmFactory();
103
        $certificate = new X509Certificate($this->certificate);
104
        $verifier = $factory->getAlgorithm($sigAlg, $certificate->getPublicKey());
105
106
        $verified = $customSigned->verify($verifier);
107
        $this->assertInstanceOf(CustomSignable::class, $verified);
108
        $this->assertFalse($verified->isSigned());
109
        $this->assertEquals(
110
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace"><ssp:Chunk>Some' .
111
            '</ssp:Chunk></ssp:CustomSignable>',
112
            strval($verified),
113
        );
114
        $this->assertEquals($certificate->getPublicKey(), $verified->getVerifyingKey());
115
    }
116
117
118
    /**
119
     * Test the verification of a signature with the wrong key first, and the right one second.
120
     */
121
    public function testSuccessfulVerifyingWithWrongKeyFirstRightOneSecond(): void
122
    {
123
        $customSigned = CustomSignable::fromXML($this->signedDocument);
124
125
        $this->assertTrue($customSigned->isSigned());
126
        $signature = $customSigned->getSignature();
127
        $this->assertInstanceOf(Signature::class, $signature);
128
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
129
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
130
131
        $verified = null;
132
        foreach ([$this->wrong_certificate, $this->certificate] as $i => $key) {
133
            $factory = new SignatureAlgorithmFactory();
134
            $certificate = new X509Certificate($key);
135
            $verifier = $factory->getAlgorithm($sigAlg, $certificate->getPublicKey());
136
137
            try {
138
                $verified = $customSigned->verify($verifier);
139
                break 1;
140
            } catch (\SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException $e) {
141
                continue;
142
            }
143
        }
144
145
        $this->assertInstanceOf(CustomSignable::class, $verified);
146
        $this->assertFalse($verified->isSigned());
147
        $this->assertEquals(
148
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace"><ssp:Chunk>Some' .
149
            '</ssp:Chunk></ssp:CustomSignable>',
150
            strval($verified),
151
        );
152
        $this->assertEquals($certificate->getPublicKey(), $verified->getVerifyingKey());
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable $certificate seems to be defined by a foreach iteration on line 132. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
153
    }
154
155
156
    /**
157
     * Test the verification of a signature without passing a key, just what's in KeyInfo
158
     */
159
    public function testSuccessfulVerifyingWithoutKey(): void
160
    {
161
        $customSigned = CustomSignable::fromXML($this->signedDocument);
162
163
        $this->assertTrue($customSigned->isSigned());
164
        $signature = $customSigned->getSignature();
165
        $this->assertInstanceOf(Signature::class, $signature);
166
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
167
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
168
        $certificate = new X509Certificate($this->certificate);
169
170
        $verified = $customSigned->verify();
171
        $this->assertInstanceOf(CustomSignable::class, $verified);
172
        $this->assertFalse($verified->isSigned());
173
        $this->assertEquals(
174
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace"><ssp:Chunk>Some' .
175
            '</ssp:Chunk></ssp:CustomSignable>',
176
            strval($verified),
177
        );
178
        $validatingKey = $verified->getVerifyingKey();
179
        $this->assertInstanceOf(PublicKey::class, $validatingKey);
180
        $this->assertEquals($certificate->getPublicKey(), $validatingKey);
181
    }
182
183
184
    /**
185
     * Test that verifying a tampered signature, without giving a key for verification, fails as expected.
186
     */
187
    public function testVerifyingTamperedSignatureWithoutKeyFails(): void
188
    {
189
        $customSigned = CustomSignable::fromXML($this->tamperedDocument);
190
191
        $this->assertTrue($customSigned->isSigned());
192
        $signature = $customSigned->getSignature();
193
        $this->assertInstanceOf(Signature::class, $signature);
194
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
195
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
196
197
        $this->expectException(RuntimeException::class);
198
        $this->expectExceptionMessage('Failed to verify signature.');
199
        $customSigned->verify();
200
    }
201
202
203
    /**
204
     * Test that verifying a tampered signature with a given key fails as expected.
205
     */
206
    public function testVerifyingTamperedSignatureWithKeyFails(): void
207
    {
208
        $customSigned = CustomSignable::fromXML($this->tamperedDocument);
209
210
        $this->assertTrue($customSigned->isSigned());
211
        $signature = $customSigned->getSignature();
212
        $this->assertInstanceOf(Signature::class, $signature);
213
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
214
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
215
        $factory = new SignatureAlgorithmFactory();
216
        $certificate = new X509Certificate($this->certificate);
217
        $verifier = $factory->getAlgorithm($sigAlg, $certificate->getPublicKey());
218
219
        $this->expectException(RuntimeException::class);
220
        $this->expectExceptionMessage('Failed to verify signature.');
221
        $customSigned->verify($verifier);
222
    }
223
224
225
226
    /**
227
     * Test the verification of a signature with a given key, for an element that has comments in it.
228
     *
229
     * In this case, canonicalization must remove the comments, and the object resulting the verification must NOT
230
     * have them.
231
     */
232
    public function testSuccessfulVerifyingDocumentWithComments(): void
233
    {
234
        $customSigned = CustomSignable::fromXML($this->signedDocumentWithComments);
235
236
        $this->assertTrue($customSigned->isSigned());
237
        $signature = $customSigned->getSignature();
238
        $this->assertInstanceOf(Signature::class, $signature);
239
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
240
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
241
        $factory = new SignatureAlgorithmFactory();
242
        $certificate = new X509Certificate($this->certificate);
243
        $verifier = $factory->getAlgorithm($sigAlg, $certificate->getPublicKey());
244
245
        // verify first that our dumb object normally retains comments
246
        $this->assertEquals(
247
            $this->signedDocumentWithComments->ownerDocument->saveXML($this->signedDocumentWithComments),
0 ignored issues
show
Bug introduced by
The method saveXML() does not exist on null. ( Ignorable by Annotation )

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

247
            $this->signedDocumentWithComments->ownerDocument->/** @scrutinizer ignore-call */ 
248
                                                              saveXML($this->signedDocumentWithComments),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
248
            strval($customSigned),
249
        );
250
251
        $verified = $customSigned->verify($verifier);
252
        $this->assertInstanceOf(CustomSignable::class, $verified);
253
        $this->assertFalse($verified->isSigned());
254
        $this->assertEquals(
255
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace"><ssp:Chunk><!--comment-->Some' .
256
            '<!--comment--></ssp:Chunk></ssp:CustomSignable>',
257
            strval($verified),
258
        );
259
        $this->assertEquals($certificate->getPublicKey(), $verified->getVerifyingKey());
260
    }
261
}
262