Passed
Pull Request — master (#61)
by
unknown
12:50
created

OpenSSLTest::testDecrypt()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 29
nc 1
nop 0
dl 0
loc 41
rs 9.456
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\Test\Backend;
6
7
use PHPUnit\Framework\Attributes\RequiresOperatingSystem;
8
use PHPUnit\Framework\TestCase;
9
use SimpleSAML\XMLSecurity\Backend\OpenSSL;
10
use SimpleSAML\XMLSecurity\Constants as C;
11
use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException;
12
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
13
use SimpleSAML\XMLSecurity\Key\PrivateKey;
14
use SimpleSAML\XMLSecurity\Key\PublicKey;
15
use SimpleSAML\XMLSecurity\Key\SymmetricKey;
16
17
use function bin2hex;
18
use function dirname;
19
use function hex2bin;
20
21
/**
22
 * Tests for SimpleSAML\XMLSecurity\Backend\OpenSSL.
23
 *
24
 * @package SimpleSAML\XMLSecurity\Backend
25
 */
26
final class OpenSSLTest extends TestCase
27
{
28
    /** @var string */
29
    protected const VALIDSIG =
30
        'cdd80e925e509f954807448217157367c00f7ff53c5eec74ea51ef5fee48a048283b37639c7f43400631fa2b9063a1ed057' .
31
        '104721887a10ad62f128c26e01f363538a84ad261f40b80df86de9cc920d1dce2c27058da81d9c7aa0e68e459ab94995e27' .
32
        'e57d183ff08188b338f7975681ad67b1b6f8d174b57b666f787b801df9511d7a90e90e9af2386f4051669a4763ce5e9720f' .
33
        'c8ae2bc90e7c33d92a4bcecefddb06599b1f3adf48cde42d442d76c4d938d1570379bf1ab45feae95f94f48a460a8894f90' .
34
        'e0208ba93d86b505f32942f53bdab8e506ba227cc813cd26a0ba9a93c46f27dd0c2b7452fd8c79c7aa72b885d95ef6d1dc8' .
35
        '10829b0832abe290d';
36
37
    /** @var \SimpleSAML\XMLSecurity\Key\PrivateKey */
38
    protected static PrivateKey $privKey;
39
40
    /** @var \SimpleSAML\XMLSecurity\Key\PublicKey */
41
    protected static PublicKey $pubKey;
42
43
    /** @var \SimpleSAML\XMLSecurity\Backend\OpenSSL */
44
    protected static OpenSSL $backend;
45
46
    /** @var \SimpleSAML\XMLSecurity\Key\SymmetricKey */
47
    protected static SymmetricKey $sharedKey;
48
49
50
    public static function setUpBeforeClass(): void
51
    {
52
        self::$privKey = PrivateKey::fromFile(
53
            'file://' . dirname(__FILE__, 3) . '/resources/keys/privkey.pem',
54
        );
55
        self::$pubKey = PublicKey::fromFile(
56
            'file://' . dirname(__FILE__, 3) . '/resources/keys/pubkey.pem',
57
        );
58
        self::$sharedKey = new SymmetricKey(hex2bin('54c98b0ea7d98186c27a6c0c6f35ee1a'));
59
        self::$backend = new OpenSSL();
60
        self::$backend->setDigestAlg(C::DIGEST_SHA256);
61
        self::$backend->setCipher(C::BLOCK_ENC_AES256_GCM);
62
    }
63
64
65
    /**
66
     * Test that signing works.
67
     */
68
    public function testSign(): void
69
    {
70
        $this->assertEquals(self::VALIDSIG, bin2hex(self::$backend->sign(self::$privKey, 'Signed text')));
71
    }
72
73
74
    /**
75
     * Test signing with something that's not a private key.
76
     */
77
    public function testSignFailure(): void
78
    {
79
        $k = SymmetricKey::generate(10);
80
        $this->expectException(RuntimeException::class);
81
        @self::$backend->sign($k, 'Signed text');
82
    }
83
84
85
    /**
86
     * Test the verification of signatures.
87
     */
88
    public function testVerify(): void
89
    {
90
        // test successful verification
91
        $this->assertTrue(self::$backend->verify(self::$pubKey, 'Signed text', hex2bin(self::VALIDSIG)));
92
93
        // test forged signature
94
        $wrongSig = self::VALIDSIG;
95
        $wrongSig[10] = '6';
96
        $this->assertFalse(self::$backend->verify(self::$pubKey, 'Signed text', hex2bin($wrongSig)));
97
    }
98
99
100
    /**
101
     * Test encryption.
102
     */
103
    public function testEncrypt(): void
104
    {
105
        // test symmetric encryption
106
        self::$backend->setCipher(C::BLOCK_ENC_AES128);
107
        $this->assertNotEmpty(self::$backend->encrypt(self::$sharedKey, 'Plaintext'));
108
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
109
110
        // test encryption with public key
111
        $this->assertNotEmpty(self::$backend->encrypt(self::$pubKey, 'Plaintext'));
112
113
        // test encryption with private key
114
        $this->assertNotEmpty(self::$backend->encrypt(self::$privKey, 'Plaintext'));
115
    }
116
117
118
    /**
119
     * Test decryption.
120
     */
121
    public function testDecrypt(): void
122
    {
123
        // test decryption with symmetric key
124
        self::$backend->setCipher(C::BLOCK_ENC_AES128);
125
        $this->assertEquals(
126
            'Plaintext',
127
            self::$backend->decrypt(
128
                self::$sharedKey,
129
                hex2bin('9faa2195bd89d2b8b3721f4fea39e904250096ad2bcd66cf77f8423af83d18ba'),
130
            ),
131
        );
132
133
        // test decryption with private key
134
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
135
        $this->assertEquals(
136
            'Plaintext',
137
            self::$backend->decrypt(
138
                self::$privKey,
139
                hex2bin(
140
                    'c2aa74a85de59daef76c4f4736680ff55503d1ce991a6b947ad5d269b93ef97acf761c1c1ccfedc1382d2c16ea52b7f' .
141
                    '6b298d8a0f6dbf5e46c41df70804888758e2b95502d9b0849c8d670e4bb9f13bb9afa1d51a76a32625513599c4a2d84' .
142
                    '1cb79beec171b9c0cf11466e90187e91377a7f7582f3eec3df6703a1abda89339d0f490bca61ceac743be401d861d50' .
143
                    'eb6aaa2db63264cd2013e4008d82c4e7b3f8f13447cf136e52c9b9f06c062a3fe66d3b9f7fa78281d149e7756a97edb' .
144
                    '0b2a500f110587f2d81790922def9061c4d8d500cd67ade406b61a20a8fe3b7db1ccc69095a20f556e5ed1f91ccaff1' .
145
                    'cb3f13065ebee9e20064b0a75edb2b603af6c',
146
                ),
147
            ),
148
        );
149
150
        // test decryption with public key
151
        $this->assertEquals(
152
            'Plaintext',
153
            self::$backend->decrypt(
154
                self::$pubKey,
155
                hex2bin(
156
                    'd012f638b7814f63cce16d1938d34e1f82abcbe925cf579a4dd6e5b0d8f0c524b77a94423625c1cec7cc45e26f37188' .
157
                    'ff18870cd4f8cd3e0de6084413c71c1f4f14f04858a655162e9332f4b26fe4523cebf7de51267290f8ae290c869fb32' .
158
                    '4570d9065b9604587111b116e8d15d8ef820f2ea2c1ae129ce27a20c4a7e4df815fb47a047cd11b06ada9f4ad881545' .
159
                    '2380a09fb6bff787ff167a20662740e1ac034e66612e2194d8b60a22341032d758fd94221314125dbb2d1432b4a3633' .
160
                    'b0857d8d4938aabe1b53ab5f970fb4ad0ed0a554771cfa819cffba8ec5935a6d2f706dfcada355da34b994691c76a60' .
161
                    'd10c746a5b683b2a0080d847ff208cf240a1c',
162
                ),
163
            ),
164
        );
165
    }
166
167
168
    /**
169
     * Test that RSA-OAEP and RSA-OAEP-MGF1P are equivalent by default.
170
     */
171
    public function testEquivalentOAEP(): void
172
    {
173
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP_MGF1P);
174
        $ciphertext = self::$backend->encrypt(self::$pubKey, 'Plaintext');
175
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP);
176
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$privKey, $ciphertext));
177
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP_MGF1P);
178
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$privKey, $ciphertext));
179
    }
180
181
182
    /**
183
     * Test that encrypting with RSA 1.5 and decrypting with RSA-OAEP* fails.
184
     */
185
    public function testEncryptRSA15DecryptOAEP(): void
186
    {
187
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
188
        $ciphertext = self::$backend->encrypt(self::$pubKey, 'Plaintext');
189
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP);
190
        $this->expectException(RuntimeException::class);
191
        $this->expectExceptionMessageMatches('/^Cannot decrypt data;/');
192
        self::$backend->decrypt(self::$privKey, $ciphertext);
193
    }
194
195
196
    /**
197
     * Test that encrypting with RSA-OAEP* and decrypting with RSA 1.5 fails.
198
     */
199
    #[RequiresOperatingSystem('Linux')]
200
    public function testEncryptOAEPDecryptRSA15Unix(): void
201
    {
202
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP);
203
        $ciphertext = self::$backend->encrypt(self::$pubKey, 'Plaintext');
204
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
205
        $plaintext = self::$backend->decrypt(self::$privKey, $ciphertext);
206
        $this->assertNotEquals('Plaintext', $plaintext);
207
    }
208
209
210
    /**
211
     * Test that encrypting with RSA-OAEP* and decrypting with RSA 1.5 fails.
212
     */
213
    #[RequiresOperatingSystem('Windows')]
214
    public function testEncryptOAEPDecryptRSA15Windows(): void
215
    {
216
        self::$backend->setCipher(C::KEY_TRANSPORT_OAEP);
217
        $ciphertext = self::$backend->encrypt(self::$pubKey, 'Plaintext');
218
        self::$backend->setCipher(C::KEY_TRANSPORT_RSA_1_5);
219
        $this->expectException(RuntimeException::class);
220
        $this->expectExceptionMessageMatches('/^Cannot decrypt data:/');
221
        self::$backend->decrypt(self::$privKey, $ciphertext);
222
    }
223
224
225
    /**
226
     * Test that CBC and GCM modes are incompatible.
227
     */
228
    public function testMismatchingSymmetricEncryptionAlgorithm(): void
229
    {
230
        self::$backend->setCipher(C::BLOCK_ENC_AES128);
231
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
232
        self::$backend->setCipher(C::BLOCK_ENC_AES128_GCM);
233
        $this->expectException(RuntimeException::class);
234
        $plaintext = self::$backend->decrypt(self::$sharedKey, $ciphertext);
235
    }
236
237
238
    /**
239
     * Test that all symmetric encryption CBC modes work.
240
     */
241
    public function testSymmetricCBCEncryption(): void
242
    {
243
        self::$backend->setCipher(C::BLOCK_ENC_3DES);
244
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
245
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
246
247
        self::$backend->setCipher(C::BLOCK_ENC_AES128);
248
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
249
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
250
251
        self::$backend->setCipher(C::BLOCK_ENC_AES192);
252
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
253
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
254
255
        self::$backend->setCipher(C::BLOCK_ENC_AES256);
256
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
257
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
258
    }
259
260
261
    /**
262
     * Test that all symmetric encryption GCM modes work.
263
     */
264
    public function testSymmetricGCMEncryption(): void
265
    {
266
        self::$backend->setCipher(C::BLOCK_ENC_AES128_GCM);
267
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
268
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
269
270
        self::$backend->setCipher(C::BLOCK_ENC_AES192_GCM);
271
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
272
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
273
274
        self::$backend->setCipher(C::BLOCK_ENC_AES256_GCM);
275
        $ciphertext = self::$backend->encrypt(self::$sharedKey, 'Plaintext');
276
        $this->assertEquals('Plaintext', self::$backend->decrypt(self::$sharedKey, $ciphertext));
277
    }
278
279
280
    /**
281
     * Test for wrong digests.
282
     */
283
    public function testSetUnknownDigest(): void
284
    {
285
        $backend = new OpenSSL();
286
        $this->expectException(InvalidArgumentException::class);
287
        $backend->setDigestAlg('foo');
288
    }
289
290
291
292
    /**
293
     * Test ISO 10126 padding.
294
     */
295
    public function testPad(): void
296
    {
297
        $this->assertEquals('666f6f0d0d0d0d0d0d0d0d0d0d0d0d0d', bin2hex(self::$backend->pad('foo')));
298
        $this->assertEquals(
299
            '666f6f626172666f6f626172666f6f6261720e0e0e0e0e0e0e0e0e0e0e0e0e0e',
300
            bin2hex(self::$backend->pad('foobarfoobarfoobar')),
301
        );
302
    }
303
304
305
    /**
306
     * Test ISO 10126 unpadding.
307
     */
308
    public function testUnpad(): void
309
    {
310
        $this->assertEquals('foo', self::$backend->unpad(hex2bin('666f6f0d0d0d0d0d0d0d0d0d0d0d0d0d')));
311
        $this->assertEquals(
312
            'foobarfoobarfoobar',
313
            self::$backend->unpad(hex2bin('666f6f626172666f6f626172666f6f6261720e0e0e0e0e0e0e0e0e0e0e0e0e0e')),
314
        );
315
    }
316
317
318
    /**
319
     * Test for wrong ciphers.
320
     */
321
    public function testSetUnknownCipher(): void
322
    {
323
        $backend = new OpenSSL();
324
        $this->expectException(InvalidArgumentException::class);
325
        $backend->setCipher('foo');
326
    }
327
}
328