Passed
Branch master (c86cc6)
by Tim
03:54
created

testVerifyingTamperedSignatureWithoutKeyFails()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 13
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\Test\XML;
6
7
use DOMElement;
8
use PHPUnit\Framework\TestCase;
9
use SimpleSAML\XML\DOMDocumentFactory;
10
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
11
use SimpleSAML\XMLSecurity\Constants as C;
12
use SimpleSAML\XMLSecurity\CryptoEncoding\PEM;
13
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
14
use SimpleSAML\XMLSecurity\Key\PublicKey;
15
use SimpleSAML\XMLSecurity\Key\X509Certificate;
16
use SimpleSAML\XMLSecurity\XML\ds\Signature;
17
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
18
19
use function dirname;
20
use function file_get_contents;
21
use function strval;
22
23
/**
24
 * Class \SimpleSAML\XMLSecurity\Test\XML\SignedElementTest
25
 *
26
 * @covers \SimpleSAML\XMLSecurity\XML\SignedElementTrait
27
 * @covers \SimpleSAML\XMLSecurity\Test\XML\CustomSignable
28
 *
29
 * @package simplesamlphp/xml-security
30
 */
31
final class SignedElementTest extends TestCase
32
{
33
    /** @var \SimpleSAML\XMLSecurity\CryptoEncoding\PEM */
34
    private PEM $certificate;
35
36
    /** @var \DOMElement */
37
    private DOMElement $signedDocumentWithComments;
38
39
    /** @var \DOMElement */
40
    private DOMElement $signedDocument;
41
42
    /** @var \DOMElement  */
43
    private DOMElement $tamperedDocument;
44
45
46
    /**
47
     */
48
    public function setUp(): void
49
    {
50
        $this->signedDocumentWithComments = DOMDocumentFactory::fromFile(
51
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSignedWithComments.xml',
52
        )->documentElement;
53
54
        $this->signedDocument = DOMDocumentFactory::fromFile(
55
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSigned.xml',
56
        )->documentElement;
57
58
        $this->tamperedDocument = DOMDocumentFactory::fromFile(
59
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSignedTampered.xml',
60
        )->documentElement;
61
62
        $this->certificate = PEM::fromString(
63
            PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE)
64
        );
65
    }
66
67
68
    /**
69
     * Test creating a signed object from its XML representation.
70
     */
71
    public function testUnmarshalling(): void
72
    {
73
        $customSigned = CustomSignable::fromXML($this->signedDocument);
74
75
        $this->assertEquals(
76
            $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

76
            $this->signedDocument->ownerDocument->/** @scrutinizer ignore-call */ 
77
                                                  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...
77
            strval($customSigned),
78
        );
79
    }
80
81
82
    /**
83
     * Test the verification of a signature with a given key.
84
     */
85
    public function testSuccessfulVerifyingWithGivenKey(): void
86
    {
87
        $customSigned = CustomSignable::fromXML($this->signedDocument);
88
89
        $this->assertTrue($customSigned->isSigned());
90
        $signature = $customSigned->getSignature();
91
        $this->assertInstanceOf(Signature::class, $signature);
92
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
93
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
94
        $factory = new SignatureAlgorithmFactory();
95
        $certificate = new X509Certificate($this->certificate);
96
        $verifier = $factory->getAlgorithm($sigAlg, $certificate->getPublicKey());
97
98
        $verified = $customSigned->verify($verifier);
99
        $this->assertInstanceOf(CustomSignable::class, $verified);
100
        $this->assertFalse($verified->isSigned());
101
        $this->assertEquals(
102
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace"><ssp:Chunk>Some' .
103
            '</ssp:Chunk></ssp:CustomSignable>',
104
            strval($verified),
105
        );
106
        $this->assertEquals($certificate->getPublicKey(), $verified->getVerifyingKey());
107
    }
108
109
110
    /**
111
     * Test the verification of a signature without passing a key, just what's in KeyInfo
112
     */
113
    public function testSuccessfulVerifyingWithoutKey(): void
114
    {
115
        $customSigned = CustomSignable::fromXML($this->signedDocument);
116
117
        $this->assertTrue($customSigned->isSigned());
118
        $signature = $customSigned->getSignature();
119
        $this->assertInstanceOf(Signature::class, $signature);
120
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
121
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
122
        $certificate = new X509Certificate($this->certificate);
123
124
        $verified = $customSigned->verify();
125
        $this->assertInstanceOf(CustomSignable::class, $verified);
126
        $this->assertFalse($verified->isSigned());
127
        $this->assertEquals(
128
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace"><ssp:Chunk>Some' .
129
            '</ssp:Chunk></ssp:CustomSignable>',
130
            strval($verified),
131
        );
132
        $validatingKey = $verified->getVerifyingKey();
133
        $this->assertInstanceOf(PublicKey::class, $validatingKey);
134
        $this->assertEquals($certificate->getPublicKey(), $validatingKey);
135
    }
136
137
138
    /**
139
     * Test that verifying a tampered signature, without giving a key for verification, fails as expected.
140
     */
141
    public function testVerifyingTamperedSignatureWithoutKeyFails(): void
142
    {
143
        $customSigned = CustomSignable::fromXML($this->tamperedDocument);
144
145
        $this->assertTrue($customSigned->isSigned());
146
        $signature = $customSigned->getSignature();
147
        $this->assertInstanceOf(Signature::class, $signature);
148
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
149
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
150
151
        $this->expectException(RuntimeException::class);
152
        $this->expectExceptionMessage('Failed to verify signature.');
153
        $customSigned->verify();
154
    }
155
156
157
    /**
158
     * Test that verifying a tampered signature with a given key fails as expected.
159
     */
160
    public function testVerifyingTamperedSignatureWithKeyFails(): void
161
    {
162
        $customSigned = CustomSignable::fromXML($this->tamperedDocument);
163
164
        $this->assertTrue($customSigned->isSigned());
165
        $signature = $customSigned->getSignature();
166
        $this->assertInstanceOf(Signature::class, $signature);
167
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
168
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
169
        $factory = new SignatureAlgorithmFactory();
170
        $certificate = new X509Certificate($this->certificate);
171
        $verifier = $factory->getAlgorithm($sigAlg, $certificate->getPublicKey());
172
173
        $this->expectException(RuntimeException::class);
174
        $this->expectExceptionMessage('Failed to verify signature.');
175
        $customSigned->verify($verifier);
176
    }
177
178
179
180
    /**
181
     * Test the verification of a signature with a given key, for an element that has comments in it.
182
     *
183
     * In this case, canonicalization must remove the comments, and the object resulting the verification must NOT
184
     * have them.
185
     */
186
    public function testSuccessfulVerifyingDocumentWithComments(): void
187
    {
188
        $customSigned = CustomSignable::fromXML($this->signedDocumentWithComments);
189
190
        $this->assertTrue($customSigned->isSigned());
191
        $signature = $customSigned->getSignature();
192
        $this->assertInstanceOf(Signature::class, $signature);
193
        $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm();
194
        $this->assertEquals(C::SIG_RSA_SHA256, $sigAlg);
195
        $factory = new SignatureAlgorithmFactory();
196
        $certificate = new X509Certificate($this->certificate);
197
        $verifier = $factory->getAlgorithm($sigAlg, $certificate->getPublicKey());
198
199
        // verify first that our dumb object normally retains comments
200
        $this->assertEquals(
201
            $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

201
            $this->signedDocumentWithComments->ownerDocument->/** @scrutinizer ignore-call */ 
202
                                                              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...
202
            strval($customSigned),
203
        );
204
205
        $verified = $customSigned->verify($verifier);
206
        $this->assertInstanceOf(CustomSignable::class, $verified);
207
        $this->assertFalse($verified->isSigned());
208
        $this->assertEquals(
209
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace"><ssp:Chunk><!--comment-->Some' .
210
            '<!--comment--></ssp:Chunk></ssp:CustomSignable>',
211
            strval($verified),
212
        );
213
        $this->assertEquals($certificate->getPublicKey(), $verified->getVerifyingKey());
214
    }
215
}
216