Passed
Branch master (ff35cf)
by Tim
02:03
created

SignedElementTestTrait   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 10
eloc 78
c 1
b 0
f 0
dl 0
loc 142
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\TestUtils;
6
7
use DOMDocument;
8
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
9
use SimpleSAML\XMLSecurity\Constants as C;
10
use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException;
11
use SimpleSAML\XMLSecurity\Exception\NoSignatureFoundException;
12
use SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException;
13
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
14
use SimpleSAML\XMLSecurity\XML\ds\KeyInfo;
15
use SimpleSAML\XMLSecurity\XML\ds\X509Certificate;
16
use SimpleSAML\XMLSecurity\XML\ds\X509Data;
17
18
use function array_keys;
19
use function boolval;
20
use function class_exists;
21
use function hexdec;
22
use function sprintf;
23
24
/**
25
 * A trait providing basic tests for signed elements.
26
 *
27
 * Only to be used by classes extending \PHPUnit\Framework\TestCase. Make sure to assign the class name of the class
28
 * you are testing to the $testedClass property.
29
 *
30
 * @package simplesamlphp/xml-security
31
 */
32
trait SignedElementTestTrait
33
{
34
    /**
35
     * A base document that we can reuse in our tests.
36
     *
37
     * @var \DOMDocument
38
     */
39
    protected static DOMDocument $xmlRepresentation;
40
41
    /**
42
     * The name of the class we are testing.
43
     *
44
     * @var class-string
45
     */
46
    protected static string $testedClass;
47
48
49
    /**
50
     * Test signing / verifying
51
     */
52
    public function testSignatures(): void
53
    {
54
        if (!class_exists(self::$testedClass)) {
55
            $this->markTestSkipped(
56
                'Unable to run ' . self::class . '::testSignatures(). Please set ' . self::class
57
                . ':$testedClass to a class-string representing the XML-class being tested',
58
            );
59
        } elseif (empty(self::$xmlRepresentation)) {
60
            $this->markTestSkipped(
61
                'Unable to run ' . self::class . '::testSignatures(). Please set ' . self::class
62
                . ':$xmlRepresentation to a DOMDocument representing the XML-class being tested',
63
            );
64
        } else {
65
            $algorithms = array_keys(C::$RSA_DIGESTS);
66
            foreach ($algorithms as $algorithm) {
67
                if (
68
                    boolval(OPENSSL_VERSION_NUMBER >= hexdec('0x30000000')) === true
69
                    && ($algorithm === C::SIG_RSA_SHA1 || $algorithm === C::SIG_RSA_RIPEMD160)
70
                ) {
71
                    // OpenSSL 3.0 disabled SHA1 and RIPEMD160 support
72
                    continue;
73
                }
74
75
                //
76
                // sign with two certificates
77
                //
78
                $signer = (new SignatureAlgorithmFactory([]))->getAlgorithm(
79
                    $algorithm,
80
                    PEMCertificatesMock::getPrivateKey(PEMCertificatesMock::PRIVATE_KEY),
81
                );
82
83
                $keyInfo = new KeyInfo([
84
                    new X509Data([new X509Certificate(
85
                        PEMCertificatesMock::getPlainPublicKeyContents(PEMCertificatesMock::PUBLIC_KEY),
86
                    )]),
87
                    new X509Data([new X509Certificate(
88
                        PEMCertificatesMock::getPlainPublicKeyContents(PEMCertificatesMock::OTHER_PUBLIC_KEY),
89
                    )]),
90
                ]);
91
92
                $unsigned = self::$testedClass::fromXML(self::$xmlRepresentation->documentElement);
93
                $unsigned->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo);
94
                $signed = self::$testedClass::fromXML($unsigned->toXML());
95
                $this->assertEquals(
96
                    $algorithm,
97
                    $signed->getSignature()->getSignedInfo()->getSignatureMethod()->getAlgorithm(),
98
                );
99
100
                // verify signature
101
                $verifier = (new SignatureAlgorithmFactory([]))->getAlgorithm(
102
                    $signed->getSignature()->getSignedInfo()->getSignatureMethod()->getAlgorithm(),
103
                    PEMCertificatesMock::getPublicKey(PEMCertificatesMock::PUBLIC_KEY),
104
                );
105
106
                try {
107
                    $verified = $signed->verify($verifier);
108
                } catch (
109
                    NoSignatureFoundException |
110
                    InvalidArgumentException |
111
                    SignatureVerificationFailedException $s
112
                ) {
113
                    $this->fail(sprintf('%s:  %s', $algorithm, $e->getMessage()));
114
                }
115
                $this->assertInstanceOf(self::$testedClass, $verified);
116
117
                $this->assertEquals(
118
                    PEMCertificatesMock::getPublicKey(PEMCertificatesMock::PUBLIC_KEY),
119
                    $verified->getVerifyingKey(),
120
                    sprintf('No validating certificate for algorithm: %s', $algorithm),
121
                );
122
123
                //
124
                // sign without certificates
125
                //
126
                $unsigned->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, null);
127
                $signed = self::$testedClass::fromXML($unsigned->toXML());
128
129
                // verify signature
130
                try {
131
                    $verified = $signed->verify($verifier);
132
                } catch (
133
                    NoSignatureFoundException |
134
                    InvalidArgumentException |
135
                    SignatureVerificationFailedException $e
136
                ) {
137
                    $this->fail(sprintf('%s:  %s', $algorithm, $e->getMessage()));
138
                }
139
                $this->assertInstanceOf(self::$testedClass, $verified);
140
141
                $this->assertEquals(
142
                    PEMCertificatesMock::getPublicKey(PEMCertificatesMock::PUBLIC_KEY),
143
                    $verified->getVerifyingKey(),
144
                    sprintf('No validating certificate for algorithm: %s', $algorithm),
145
                );
146
147
                //
148
                // verify with wrong key
149
                //
150
                $signer = (new SignatureAlgorithmFactory([]))->getAlgorithm(
151
                    $algorithm,
152
                    PEMCertificatesMock::getPrivateKey(PEMCertificatesMock::OTHER_PRIVATE_KEY),
153
                );
154
                $unsigned->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, null);
155
                $signed = self::$testedClass::fromXML($unsigned->toXML());
156
157
                // verify signature
158
                try {
159
                    $verified = $signed->verify($verifier);
160
                    $this->fail('Signature validated correctly with wrong certificate.');
161
                } catch (
162
                    NoSignatureFoundException |
163
                    InvalidArgumentException |
164
                    SignatureVerificationFailedException $e
165
                ) {
166
                    $this->assertEquals('Failed to verify signature.', $e->getMessage());
167
                }
168
                $this->assertInstanceOf(self::$testedClass, $verified);
169
170
                $this->assertEquals(
171
                    PEMCertificatesMock::getPublicKey(PEMCertificatesMock::PUBLIC_KEY),
172
                    $verified->getVerifyingKey(),
173
                    sprintf('No validating certificate for algorithm: %s', $algorithm),
174
                );
175
            }
176
        }
177
    }
178
}
179