Passed
Branch master (c86cc6)
by Tim
11:28
created

OpenSSLTest   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 285
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 119
dl 0
loc 285
rs 10
c 0
b 0
f 0
wmc 16

16 Methods

Rating   Name   Duplication   Size   Complexity  
A testUnpad() 0 6 1
A testVerify() 0 9 1
A testSetUnknownDigest() 0 5 1
A testEncryptOAEPDecryptRSA15() 0 8 1
A testSymmetricGCMEncryption() 0 13 1
A testPad() 0 6 1
A testMismatchingSymmetricEncryptionAlgorithm() 0 7 1
A testEquivalentOAEP() 0 8 1
A testSetUnknownCipher() 0 5 1
A setUpBeforeClass() 0 12 1
A testDecrypt() 0 41 1
A testSign() 0 3 1
A testSymmetricCBCEncryption() 0 17 1
A testSignFailure() 0 5 1
A testEncryptRSA15DecryptOAEP() 0 8 1
A testEncrypt() 0 12 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\Test\Backend;
6
7
use PHPUnit\Framework\TestCase;
8
use SimpleSAML\XMLSecurity\Backend\OpenSSL;
9
use SimpleSAML\XMLSecurity\Constants as C;
10
use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException;
11
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
12
use SimpleSAML\XMLSecurity\Key\PrivateKey;
13
use SimpleSAML\XMLSecurity\Key\PublicKey;
14
use SimpleSAML\XMLSecurity\Key\SymmetricKey;
15
16
use function dirname;
17
use function bin2hex;
18
use function hex2bin;
19
20
/**
21
 * Tests for SimpleSAML\XMLSecurity\Backend\OpenSSL.
22
 *
23
 * @package SimpleSAML\XMLSecurity\Backend
24
 */
25
final class OpenSSLTest extends TestCase
26
{
27
    /** @var string */
28
    protected const VALIDSIG =
29
        'cdd80e925e509f954807448217157367c00f7ff53c5eec74ea51ef5fee48a048283b37639c7f43400631fa2b9063a1ed057' .
30
        '104721887a10ad62f128c26e01f363538a84ad261f40b80df86de9cc920d1dce2c27058da81d9c7aa0e68e459ab94995e27' .
31
        'e57d183ff08188b338f7975681ad67b1b6f8d174b57b666f787b801df9511d7a90e90e9af2386f4051669a4763ce5e9720f' .
32
        'c8ae2bc90e7c33d92a4bcecefddb06599b1f3adf48cde42d442d76c4d938d1570379bf1ab45feae95f94f48a460a8894f90' .
33
        'e0208ba93d86b505f32942f53bdab8e506ba227cc813cd26a0ba9a93c46f27dd0c2b7452fd8c79c7aa72b885d95ef6d1dc8' .
34
        '10829b0832abe290d';
35
36
    /** @var \SimpleSAML\XMLSecurity\Key\PrivateKey */
37
    protected static PrivateKey $privKey;
38
39
    /** @var \SimpleSAML\XMLSecurity\Key\PublicKey */
40
    protected static PublicKey $pubKey;
41
42
    /** @var \SimpleSAML\XMLSecurity\Backend\OpenSSL */
43
    protected static OpenSSL $backend;
44
45
    /** @var \SimpleSAML\XMLSecurity\Key\SymmetricKey */
46
    protected static SymmetricKey $sharedKey;
47
48
49
    public static function setUpBeforeClass(): void
50
    {
51
        self::$privKey = PrivateKey::fromFile(
52
            'file://' . dirname(__FILE__, 3) . '/resources/keys/privkey.pem'
53
        );
54
        self::$pubKey = PublicKey::fromFile(
55
            'file://' . dirname(__FILE__, 3) . '/resources/keys/pubkey.pem'
56
        );
57
        self::$sharedKey = new SymmetricKey(hex2bin('54c98b0ea7d98186c27a6c0c6f35ee1a'));
58
        self::$backend = new OpenSSL();
59
        self::$backend->setDigestAlg(C::DIGEST_SHA256);
60
        self::$backend->setCipher(C::BLOCK_ENC_AES256_GCM);
61
    }
62
63
64
    /**
65
     * Test that signing works.
66
     */
67
    public function testSign(): void
68
    {
69
        $this->assertEquals(self::VALIDSIG, bin2hex(self::$backend->sign(self::$privKey, 'Signed text')));
70
    }
71
72
73
    /**
74
     * Test signing with something that's not a private key.
75
     */
76
    public function testSignFailure(): void
77
    {
78
        $k = SymmetricKey::generate(10);
79
        $this->expectException(RuntimeException::class);
80
        @self::$backend->sign($k, 'Signed text');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for sign(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

80
        /** @scrutinizer ignore-unhandled */ @self::$backend->sign($k, 'Signed text');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
81
    }
82
83
84
    /**
85
     * Test the verification of signatures.
86
     */
87
    public function testVerify(): void
88
    {
89
        // test successful verification
90
        $this->assertTrue(self::$backend->verify(self::$pubKey, 'Signed text', hex2bin(self::VALIDSIG)));
91
92
        // test forged signature
93
        $wrongSig = self::VALIDSIG;
94
        $wrongSig[10] = '6';
95
        $this->assertFalse(self::$backend->verify(self::$pubKey, 'Signed text', hex2bin($wrongSig)));
96
    }
97
98
99
    /**
100
     * Test encryption.
101
     */
102
    public function testEncrypt(): void
103
    {
104
        // test symmetric encryption
105
        self::$backend->setCipher(C::BLOCK_ENC_AES128);
106
        $this->assertNotEmpty(self::$backend->encrypt(self::$sharedKey, 'Plaintext'));
107
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
108
109
        // test encryption with public key
110
        $this->assertNotEmpty(self::$backend->encrypt(self::$pubKey, 'Plaintext'));
111
112
        // test encryption with private key
113
        $this->assertNotEmpty(self::$backend->encrypt(self::$privKey, 'Plaintext'));
114
    }
115
116
117
    /**
118
     * Test decryption.
119
     */
120
    public function testDecrypt(): void
121
    {
122
        // test decryption with symmetric key
123
        self::$backend->setCipher(C::BLOCK_ENC_AES128);
124
        $this->assertEquals(
125
            'Plaintext',
126
            self::$backend->decrypt(
127
                self::$sharedKey,
128
                hex2bin('9faa2195bd89d2b8b3721f4fea39e904250096ad2bcd66cf77f8423af83d18ba'),
129
            ),
130
        );
131
132
        // test decryption with private key
133
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
134
        $this->assertEquals(
135
            'Plaintext',
136
            self::$backend->decrypt(
137
                self::$privKey,
138
                hex2bin(
139
                    'c2aa74a85de59daef76c4f4736680ff55503d1ce991a6b947ad5d269b93ef97acf761c1c1ccfedc1382d2c16ea52b7f' .
140
                    '6b298d8a0f6dbf5e46c41df70804888758e2b95502d9b0849c8d670e4bb9f13bb9afa1d51a76a32625513599c4a2d84' .
141
                    '1cb79beec171b9c0cf11466e90187e91377a7f7582f3eec3df6703a1abda89339d0f490bca61ceac743be401d861d50' .
142
                    'eb6aaa2db63264cd2013e4008d82c4e7b3f8f13447cf136e52c9b9f06c062a3fe66d3b9f7fa78281d149e7756a97edb' .
143
                    '0b2a500f110587f2d81790922def9061c4d8d500cd67ade406b61a20a8fe3b7db1ccc69095a20f556e5ed1f91ccaff1' .
144
                    'cb3f13065ebee9e20064b0a75edb2b603af6c'
145
                ),
146
            ),
147
        );
148
149
        // test decryption with public key
150
        $this->assertEquals(
151
            'Plaintext',
152
            self::$backend->decrypt(
153
                self::$pubKey,
154
                hex2bin(
155
                    'd012f638b7814f63cce16d1938d34e1f82abcbe925cf579a4dd6e5b0d8f0c524b77a94423625c1cec7cc45e26f37188' .
156
                    'ff18870cd4f8cd3e0de6084413c71c1f4f14f04858a655162e9332f4b26fe4523cebf7de51267290f8ae290c869fb32' .
157
                    '4570d9065b9604587111b116e8d15d8ef820f2ea2c1ae129ce27a20c4a7e4df815fb47a047cd11b06ada9f4ad881545' .
158
                    '2380a09fb6bff787ff167a20662740e1ac034e66612e2194d8b60a22341032d758fd94221314125dbb2d1432b4a3633' .
159
                    'b0857d8d4938aabe1b53ab5f970fb4ad0ed0a554771cfa819cffba8ec5935a6d2f706dfcada355da34b994691c76a60' .
160
                    'd10c746a5b683b2a0080d847ff208cf240a1c'
161
                ),
162
            ),
163
        );
164
    }
165
166
167
    /**
168
     * Test that RSA-OAEP and RSA-OAEP-MGF1P are equivalent by default.
169
     */
170
    public function testEquivalentOAEP(): void
171
    {
172
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP_MGF1P);
173
        $ciphertext = self::$backend->encrypt(self::$pubKey, 'Plaintext');
174
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP);
175
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$privKey, $ciphertext));
176
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP_MGF1P);
177
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$privKey, $ciphertext));
178
    }
179
180
181
    /**
182
     * Test that encrypting with RSA 1.5 and decrypting with RSA-OAEP* fails.
183
     */
184
    public function testEncryptRSA15DecryptOAEP(): void
185
    {
186
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
187
        $ciphertext = self::$backend->encrypt(self::$pubKey, 'Plaintext');
188
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP);
189
        $this->expectException(RuntimeException::class);
190
        $this->expectExceptionMessageMatches('/^Cannot decrypt data:/');
191
        self::$backend->decrypt(self::$privKey, $ciphertext);
192
    }
193
194
195
    /**
196
     * Test that encrypting with RSA-OAEP* and decrypting with RSA 1.5 fails.
197
     */
198
    public function testEncryptOAEPDecryptRSA15(): void
199
    {
200
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP);
201
        $ciphertext = self::$backend->encrypt(self::$pubKey, 'Plaintext');
202
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
203
        $this->expectException(RuntimeException::class);
204
        $this->expectExceptionMessageMatches('/^Cannot decrypt data:/');
205
        self::$backend->decrypt(self::$privKey, $ciphertext);
206
    }
207
208
209
    /**
210
     * Test that CBC and GCM modes are incompatible.
211
     */
212
    public function testMismatchingSymmetricEncryptionAlgorithm(): void
213
    {
214
        self::$backend->setCipher(C::BLOCK_ENC_AES128);
215
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
216
        self::$backend->setCipher(C::BLOCK_ENC_AES128_GCM);
217
        $this->expectException(RuntimeException::class);
218
        $plaintext = self::$backend->decrypt(self::$sharedKey, $ciphertext);
0 ignored issues
show
Unused Code introduced by
The assignment to $plaintext is dead and can be removed.
Loading history...
219
    }
220
221
222
    /**
223
     * Test that all symmetric encryption CBC modes work.
224
     */
225
    public function testSymmetricCBCEncryption(): void
226
    {
227
        self::$backend->setCipher(C::BLOCK_ENC_3DES);
228
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
229
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
230
231
        self::$backend->setCipher(C::BLOCK_ENC_AES128);
232
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
233
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
234
235
        self::$backend->setCipher(C::BLOCK_ENC_AES192);
236
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
237
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
238
239
        self::$backend->setCipher(C::BLOCK_ENC_AES256);
240
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
241
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
242
    }
243
244
245
    /**
246
     * Test that all symmetric encryption GCM modes work.
247
     */
248
    public function testSymmetricGCMEncryption(): void
249
    {
250
        self::$backend->setCipher(C::BLOCK_ENC_AES128_GCM);
251
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
252
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
253
254
        self::$backend->setCipher(C::BLOCK_ENC_AES192_GCM);
255
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
256
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
257
258
        self::$backend->setCipher(C::BLOCK_ENC_AES256_GCM);
259
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
260
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
261
    }
262
263
264
    /**
265
     * Test for wrong digests.
266
     */
267
    public function testSetUnknownDigest(): void
268
    {
269
        $backend = new OpenSSL();
270
        $this->expectException(InvalidArgumentException::class);
271
        $backend->setDigestAlg('foo');
272
    }
273
274
275
276
    /**
277
     * Test ISO 10126 padding.
278
     */
279
    public function testPad(): void
280
    {
281
        $this->assertEquals('666f6f0d0d0d0d0d0d0d0d0d0d0d0d0d', bin2hex(self::$backend->pad('foo')));
282
        $this->assertEquals(
283
            '666f6f626172666f6f626172666f6f6261720e0e0e0e0e0e0e0e0e0e0e0e0e0e',
284
            bin2hex(self::$backend->pad('foobarfoobarfoobar')),
285
        );
286
    }
287
288
289
    /**
290
     * Test ISO 10126 unpadding.
291
     */
292
    public function testUnpad(): void
293
    {
294
        $this->assertEquals('foo', self::$backend->unpad(hex2bin('666f6f0d0d0d0d0d0d0d0d0d0d0d0d0d')));
295
        $this->assertEquals(
296
            'foobarfoobarfoobar',
297
            self::$backend->unpad(hex2bin('666f6f626172666f6f626172666f6f6261720e0e0e0e0e0e0e0e0e0e0e0e0e0e')),
298
        );
299
    }
300
301
302
    /**
303
     * Test for wrong ciphers.
304
     */
305
    public function testSetUnknownCipher(): void
306
    {
307
        $backend = new OpenSSL();
308
        $this->expectException(InvalidArgumentException::class);
309
        $backend->setCipher('foo');
310
    }
311
}
312