Passed
Branch master (c86cc6)
by Tim
01:57
created

SignableElementTest::testUnmarshalling()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\Test\XML;
6
7
use DOMDocument;
8
use PHPUnit\Framework\TestCase;
9
use SimpleSAML\XML\DOMDocumentFactory;
10
use SimpleSAML\XML\TestUtils\SerializableElementTestTrait;
11
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
12
use SimpleSAML\XMLSecurity\Constants as C;
13
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
14
use SimpleSAML\XMLSecurity\Key\PrivateKey;
15
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
16
use SimpleSAML\XMLSecurity\XML\ds\KeyInfo;
17
use SimpleSAML\XMLSecurity\XML\ds\X509Certificate;
18
use SimpleSAML\XMLSecurity\XML\ds\X509Data;
19
20
use function array_pop;
21
use function array_shift;
22
use function dirname;
23
use function explode;
24
use function file_get_contents;
25
use function join;
26
use function strval;
27
use function trim;
28
29
/**
30
 * Class \SimpleSAML\XMLSecurity\Test\XML\SignableElementTest
31
 *
32
 * @covers \SimpleSAML\XMLSecurity\Test\XML\CustomSignable
33
 *
34
 * @package simplesamlphp/xml-security
35
 */
36
final class SignableElementTest extends TestCase
37
{
38
    use SerializableElementTestTrait;
0 ignored issues
show
Bug introduced by
The trait SimpleSAML\XML\TestUtils...lizableElementTestTrait requires the property $documentElement which is not provided by SimpleSAML\XMLSecurity\T...XML\SignableElementTest.
Loading history...
39
40
    /** @var string */
41
    private static string $certificate;
42
43
    /** @var \SimpleSAML\XMLSecurity\Key\PrivateKey */
44
    private static PrivateKey $key;
45
46
    /** @var \DOMDocument */
47
    private static DOMDocument $signed;
48
49
50
    /**
51
     */
52
    public function setUp(): void
53
    {
54
        self::$testedClass = CustomSignable::class;
55
56
        self::$xmlRepresentation = DOMDocumentFactory::fromFile(
57
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignable.xml',
58
        );
59
60
        self::$signed = DOMDocumentFactory::fromFile(
61
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSigned.xml',
62
        );
63
64
        $certificate = PEMCertificatesMock::loadPlainCertificateFile(PEMCertificatesMock::SELFSIGNED_CERTIFICATE);
65
        $certificateLines = explode("\n", trim($certificate));
66
        array_pop($certificateLines);
67
        array_shift($certificateLines);
68
        self::$certificate = join("\n", $certificateLines);
69
70
        self::$key = PEMCertificatesMock::getPrivateKey(PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY);
71
    }
72
73
74
    /**
75
     * Test that signing produces the expected output.
76
     *
77
     * In this test we try to sign an entire document, since the element is the root of it, and doesn't have an ID.
78
     */
79
    public function testMarshalling(): void
80
    {
81
        $customSignable = CustomSignable::fromXML(self::$xmlRepresentation->documentElement);
82
        $this->assertFalse($customSignable->isEmptyElement());
83
84
        $factory = new SignatureAlgorithmFactory();
85
        $signer = $factory->getAlgorithm(C::SIG_RSA_SHA256, self::$key);
86
87
        $keyInfo = new KeyInfo([
88
            new X509Data([
89
                new X509Certificate(self::$certificate),
90
            ]),
91
        ]);
92
93
        $customSignable->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo);
94
95
        $this->assertEquals(
96
            self::$signed->saveXML(self::$signed->documentElement),
97
            strval($customSignable),
98
        );
99
    }
100
101
102
    /**
103
     * Test that signing an element works.
104
     *
105
     * This test implies signing an element. Since the element itself has an ID, we use that to create our reference.
106
     */
107
    public function testSigningElement(): void
108
    {
109
        $xml = DOMDocumentFactory::fromString(
110
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace" id="_1234"><ssp:Chunk><!--comment-->Some' .
111
            '<!--comment--></ssp:Chunk></ssp:CustomSignable>',
112
        );
113
        $customSignable = CustomSignable::fromXML($xml->documentElement);
114
        $this->assertFalse($customSignable->isEmptyElement());
115
116
        $factory = new SignatureAlgorithmFactory();
117
        $signer = $factory->getAlgorithm(C::SIG_RSA_SHA256, self::$key);
118
119
        $keyInfo = new KeyInfo([
120
            new X509Data([
121
                new X509Certificate(self::$certificate),
122
            ]),
123
        ]);
124
125
        $customSignable->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo);
126
        $signed = DOMDocumentFactory::fromFile(
127
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSignedWithId.xml',
128
        );
129
130
        $this->assertEquals(
131
            $signed->saveXML($signed->documentElement),
132
            strval($customSignable),
133
        );
134
    }
135
136
137
    /**
138
     * Test that signing a document with comments works.
139
     *
140
     * This tests attempts to sign a document with comments, and verifies that the resulting reference is an xpointer
141
     * pointing to the root of the document.
142
     */
143
    public function testSigningDocumentWithComments(): void
144
    {
145
        $xml = DOMDocumentFactory::fromString(
146
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace"><ssp:Chunk><!--comment-->Some' .
147
            '<!--comment--></ssp:Chunk></ssp:CustomSignable>',
148
        );
149
        $customSignable = CustomSignable::fromXML($xml->documentElement);
150
        $this->assertFalse($customSignable->isEmptyElement());
151
152
        $factory = new SignatureAlgorithmFactory();
153
        $signer = $factory->getAlgorithm(C::SIG_RSA_SHA256, self::$key);
154
155
        $keyInfo = new KeyInfo([
156
            new X509Data([
157
                new X509Certificate(self::$certificate),
158
            ]),
159
        ]);
160
161
        $customSignable->sign($signer, C::C14N_EXCLUSIVE_WITH_COMMENTS, $keyInfo);
162
        $signed = DOMDocumentFactory::fromFile(
163
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSignedWithComments.xml',
164
        );
165
166
        $this->assertEquals(
167
            $signed->saveXML($signed->documentElement),
168
            strval($customSignable),
169
        );
170
    }
171
172
173
    /**
174
     * Test that signing an element with an ID including comments works.
175
     *
176
     * This test attempts to sign an element with an ID, using exclusive canonicalization with comments. The resulting
177
     * reference should be an xpointer specifying the ID of the element.
178
     */
179
    public function testSigningElementWithIdAndComments(): void
180
    {
181
        $xml = DOMDocumentFactory::fromString(
182
            '<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace" id="_1234"><ssp:Chunk><!--comment-->Some' .
183
            '<!--comment--></ssp:Chunk></ssp:CustomSignable>',
184
        );
185
        $customSignable = CustomSignable::fromXML($xml->documentElement);
186
        $this->assertFalse($customSignable->isEmptyElement());
187
188
        $factory = new SignatureAlgorithmFactory();
189
        $signer = $factory->getAlgorithm(C::SIG_RSA_SHA256, self::$key);
190
191
        $keyInfo = new KeyInfo([
192
            new X509Data([
193
                new X509Certificate(self::$certificate),
194
            ]),
195
        ]);
196
197
        $customSignable->sign($signer, C::C14N_EXCLUSIVE_WITH_COMMENTS, $keyInfo);
198
        $signed = DOMDocumentFactory::fromFile(
199
            dirname(__FILE__, 2) . '/resources/xml/custom_CustomSignableSignedWithCommentsAndId.xml'
200
        );
201
202
        $this->assertEquals(
203
            $signed->saveXML($signed->documentElement),
204
            strval($customSignable),
205
        );
206
    }
207
208
209
    /**
210
     * Test that signing an object with a document reference fails if there's no document root.
211
     *
212
     * This test attempts to sign a document with an element without an ID that's not marked as its root. This should
213
     * fail since we cannot use a self-document reference (because the element is not the root), and we don't have an
214
     * ID for the element, so we have no way to refer to it.
215
     */
216
    public function testSigningDocumentWithoutRoot(): void
217
    {
218
        $doc = new DOMDocument('1.0', 'UTF-8');
219
        $node = $doc->importNode(self::$xmlRepresentation->documentElement, true);
220
        $customSignable = CustomSignable::fromXML($node);
221
        $factory = new SignatureAlgorithmFactory();
222
        $signer = $factory->getAlgorithm(C::SIG_RSA_SHA256, self::$key);
223
        $customSignable->sign($signer);
224
225
        $this->expectException(RuntimeException::class);
226
        $this->expectExceptionMessage('Cannot create a document reference without a root element in the document.');
227
        $customSignable->toXML();
228
    }
229
230
231
    /**
232
     * Test that signing an object with a document reference fails if the object is not the document's root.
233
     *
234
     * This test attempts to sign an element without an ID, forcing us to use a self-document reference. However, the
235
     * document contains another element before the one we try to sign, and that other element is marked as the root
236
     * of the document. We cannot therefore create the self-document reference because the element we try to sign is
237
     * not the root, and we should fail accordingly.
238
     */
239
    public function testSigningWithDifferentRoot(): void
240
    {
241
        $doc = DOMDocumentFactory::fromString('<ns:Root><ns:foo>bar</ns:foo></ns:Root>');
242
        $node = $doc->importNode(self::$xmlRepresentation->documentElement, true);
243
        $doc->appendChild($node);
244
        $customSignable = CustomSignable::fromXML($node);
245
        $factory = new SignatureAlgorithmFactory();
246
        $signer = $factory->getAlgorithm(C::SIG_RSA_SHA256, self::$key);
247
        $customSignable->sign($signer);
248
249
        $this->expectException(RuntimeException::class);
250
        $this->expectExceptionMessage(
251
            'Cannot create a document reference when signing an object that is not the root of the document. Please ' .
252
            'give your object an identifier.',
253
        );
254
        $customSignable->toXML($doc->documentElement);
255
    }
256
257
258
    /**
259
     */
260
    public function testUnmarshalling(): void
261
    {
262
        $customSignable = CustomSignable::fromXML(self::$xmlRepresentation->documentElement);
263
264
        $this->assertEquals(
265
            self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
266
            strval($customSignable),
267
        );
268
    }
269
}
270