Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 145
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
CryptoTest
0.00% covered (danger)
0.00%
0 / 144
0.00% covered (danger)
0.00%
0 / 8
342
0.00% covered (danger)
0.00%
0 / 1
 testGenerateRandomKey
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 testGetDefaultParams
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
42
 testNonAESEncryptionSupport
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 testMessageEncryptionAgainstFixture
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 testMessageFromEncodedArray
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 testPresenceMessageEncryptionAgainstFixture
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
6
 testPresenceMessageFromEncodedArray
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 filenameProvider
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace tests;
3use Ably\Models\CipherParams;
4use Ably\Models\Message;
5use Ably\Models\PresenceMessage;
6use Ably\Utils\Crypto;
7
8require_once __DIR__ . '/factories/TestApp.php';
9
10class CryptoTest extends \PHPUnit_Framework_TestCase {
11
12    public function testGenerateRandomKey() {
13        $keyDefault = Crypto::generateRandomKey();
14        $this->assertEquals( 256, strlen( $keyDefault ) * 8, 'Expected the default key length to be 256 bits' );
15        $this->assertInternalType( 'string', $keyDefault, 'Expected to return a binary string' );
16
17        $key128 = Crypto::generateRandomKey( 128 );
18        $this->assertEquals( 128, strlen( $key128 ) * 8, 'Expected to return a random key of specified length (128 bits)' );
19    }
20
21    public function testGetDefaultParams() {
22        $key = Crypto::generateRandomKey( 128 );
23
24        $cipherParams = Crypto::getDefaultParams([ 'key' => $key ]);
25        $this->assertInstanceOf( 'Ably\Models\CipherParams', $cipherParams );
26        $this->assertEquals( $key, $cipherParams->key, 'Expected the key to match the provided key' );
27        $this->assertEquals( 'aes', $cipherParams->algorithm, 'Expected \'aes\' algorithm' );
28        $this->assertEquals( 128, $cipherParams->keyLength, 'Expected keyLength of 128' );
29        $this->assertEquals( 'cbc', $cipherParams->mode, 'Expected \'cbc\' mode' );
30        $this->assertEquals( strlen( $key ), strlen( $cipherParams->iv), 'Expected key length and IV length to match' );
31
32        $defaults = [
33            'key' => $key,
34            'keyLength' => 128,
35            'iv' => Crypto::generateRandomKey( 128 ),
36            'algorithm' => 'aes',
37            'mode' => 'cbc',
38        ];
39
40        $cipherParamsDef = Crypto::getDefaultParams( $defaults );
41        $this->assertEquals( get_object_vars( $cipherParamsDef ), $defaults, 'Expected created CipherParams to match provided values');
42
43        $defaultsB64 = $defaults; // copied by value in PHP
44        $defaultsB64['key'] = base64_encode( $defaultsB64['key'] );
45        $defaultsB64['base64Key'] = true;
46        $defaultsB64['iv'] = strtr( base64_encode( $defaultsB64['iv'] ), '/+', '_-' ); // RFC4648 section 5 ("url-safe")
47        $defaultsB64['base64Iv'] = true;
48
49        $cipherParamsDefB64 = Crypto::getDefaultParams( $defaultsB64 );
50        $this->assertEquals( get_object_vars( $cipherParamsDefB64 ), $defaults,
51            'Expected created CipherParams to match provided values and decode base64 fields'
52        );
53
54        try {
55            Crypto::getDefaultParams( [] );
56        } catch (\Exception $ex) {
57            $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, 'Expected to check for key being provided' );
58        }
59
60        try {
61            Crypto::getDefaultParams([ 'key' => 'abcd', 'keyLength' => 128 ]); // 32-bit key
62        } catch (\Exception $ex) {
63            $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, 'Expected to check for key and keyLength mismatch' );
64        }
65
66        try {
67            Crypto::getDefaultParams([ 'key' => 'abcd', 'keyLength' => 32 ]); // 32-bit key
68        } catch (\Exception $ex) {
69            $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, 'Expected to check for an unacceptable key length' );
70        }
71
72        try {
73            Crypto::getDefaultParams([ 'key' => Crypto::generateRandomKey(), 'algorithm' => 'fake' ]);
74        } catch (\Exception $ex) {
75            $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, 'Expected to raise an exception on unknown encryption algorithm' );
76        }
77
78        try {
79            Crypto::getDefaultParams([ 'key' => Crypto::generateRandomKey(), 'mode' => 'fake' ]);
80        } catch (\Exception $ex) {
81            $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, 'Expected to raise an exception on unknown encryption mode' );
82        }
83    }
84
85    public function testNonAESEncryptionSupport() {
86        $blowfishParams = Crypto::getDefaultParams( [
87            'key' => Crypto::generateRandomKey(128),
88            'algorithm' => 'bf',
89            'mode' => 'ecb',
90        ]);
91
92        $encrypted = Crypto::encrypt( 'test', $blowfishParams );
93        $decrypted = Crypto::decrypt( $encrypted, $blowfishParams );
94        $this->assertNotEquals( $encrypted, $decrypted );
95        $this->assertEquals( 'test', $decrypted );
96
97        try {
98            Crypto::getDefaultParams([ 'key' => Crypto::generateRandomKey(), 'algorithm' => 'fake' ]);
99        } catch (\Exception $ex) {
100            $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, 'Expected to raise an exception on unknown encryption mode' );
101        }
102    }
103
104    /**
105     * Tests if example messages match actual messages after encryption/decryption and vice versa:
106     * decrypt(encrypted_example) == unencrypted_example
107     * encrypt(unencrypted_example) == encrypted_example
108     *
109     * @dataProvider filenameProvider
110     */
111    public function testMessageEncryptionAgainstFixture( $filename ) {
112        $fixture = json_decode( file_get_contents( $filename ) );
113
114        foreach ($fixture->items as $example) {
115            $cipherParams = Crypto::getDefaultParams([ // instantiate every time, as same IV is required
116                'key'       => $fixture->key,
117                'algorithm' => $fixture->algorithm,
118                'keyLength' => $fixture->keylength,
119                'mode'      => $fixture->mode,
120                'iv'        => $fixture->iv,
121                'base64Key' => true,
122                'base64Iv' => true,
123            ]);
124
125            $decodedExample = new Message();
126            $decodedExample->fromJSON( $example->encoded );
127            $decryptedExample = new Message();
128            $decryptedExample->setCipherParams( $cipherParams );
129            $decryptedExample->fromJSON( $example->encrypted );
130
131            $this->assertEquals( $decodedExample->data, $decryptedExample->data,
132                                 'Expected unencrypted and decrypted message\'s contents to match' );
133
134            $decodedExample->setCipherParams( $cipherParams );
135            $encryptedJSON = json_decode( $decodedExample->toJSON() );
136            $this->assertEquals( $example->encrypted->data, $encryptedJSON->data,
137                                 'Expected encrypted and example encrypted message\'s contents to match' );
138        }
139    }
140
141    /**
142     * Tests Message:fromEncodedArray
143     *
144     * @dataProvider filenameProvider
145     */
146    public function testMessageFromEncodedArray( $filename ) {
147        $fixture = json_decode( file_get_contents( $filename ) );
148
149        $cipherParams = Crypto::getDefaultParams([
150            'key'       => $fixture->key,
151            'algorithm' => $fixture->algorithm,
152            'keyLength' => $fixture->keylength,
153            'mode'      => $fixture->mode,
154            'iv'        => $fixture->iv,
155            'base64Key' => true,
156            'base64Iv' => true,
157        ]);
158
159        $encrypted = array_map( function ( $x ) { return $x->encrypted ; }, $fixture->items );
160        $encoded = array_map( function ( $x ) { return $x->encoded ; }, $fixture->items );
161
162        $encrypted = Message::fromEncodedArray( $encrypted, $cipherParams );
163        $encoded = Message::fromEncodedArray( $encoded );
164
165        foreach ($encrypted as $i => $decryptedExample) {
166            $decodedExample = $encoded[$i];
167            $this->assertEquals( $decodedExample->data, $decryptedExample->data,
168                                 'Expected unencrypted and decrypted message\'s contents to match' );
169        }
170    }
171
172    /**
173     * Tests if example presence messages match actual presence messages after encryption/decryption and vice versa:
174     * decrypt(encrypted_example) == unencrypted_example
175     * encrypt(unencrypted_example) == encrypted_example
176     *
177     * @dataProvider filenameProvider
178     */
179    public function testPresenceMessageEncryptionAgainstFixture( $filename ) {
180        $fixture = json_decode( file_get_contents( $filename ) );
181
182        foreach ($fixture->items as $example) {
183            $cipherParams = Crypto::getDefaultParams([ // instantiate every time, as same IV is required
184                'key'       => $fixture->key,
185                'algorithm' => $fixture->algorithm,
186                'keyLength' => $fixture->keylength,
187                'mode'      => $fixture->mode,
188                'iv'        => $fixture->iv,
189                'base64Key' => true,
190                'base64Iv' => true,
191            ]);
192
193            unset ($example->encoded->name); // we're reusing fixtures for standard messages, but presence messages do not have a name
194            unset ($example->encrypted->name);
195
196            $decodedExample = new PresenceMessage();
197            $decodedExample->fromJSON( $example->encoded );
198            $decryptedExample = new PresenceMessage();
199            $decryptedExample->setCipherParams( $cipherParams );
200            $decryptedExample->fromJSON( $example->encrypted );
201
202            $this->assertEquals( $decodedExample->data, $decryptedExample->data,
203                                 'Expected unencrypted and decrypted message\'s contents to match' );
204
205            $decodedExample->setCipherParams( $cipherParams );
206            $encryptedJSON = json_decode( $decodedExample->toJSON() );
207            $this->assertEquals( $example->encrypted->data, $encryptedJSON->data,
208                                 'Expected encrypted and example encrypted message\'s contents to match' );
209        }
210    }
211
212    /**
213     * Tests PresenceMessage:fromEncodedArray
214     *
215     * @dataProvider filenameProvider
216     */
217    public function testPresenceMessageFromEncodedArray( $filename ) {
218        $fixture = json_decode( file_get_contents( $filename ) );
219
220        $cipherParams = Crypto::getDefaultParams([
221            'key'       => $fixture->key,
222            'algorithm' => $fixture->algorithm,
223            'keyLength' => $fixture->keylength,
224            'mode'      => $fixture->mode,
225            'iv'        => $fixture->iv,
226            'base64Key' => true,
227            'base64Iv' => true,
228        ]);
229
230        $encrypted = array_map( function ( $x ) { return $x->encrypted ; }, $fixture->items );
231        $encoded = array_map( function ( $x ) { return $x->encoded ; }, $fixture->items );
232
233        $encrypted = PresenceMessage::fromEncodedArray( $encrypted, $cipherParams );
234        $encoded = PresenceMessage::fromEncodedArray( $encoded );
235
236        foreach ($encrypted as $i => $decryptedExample) {
237            $decodedExample = $encoded[$i];
238            $this->assertEquals( $decodedExample->data, $decryptedExample->data,
239                                 'Expected unencrypted and decrypted message\'s contents to match' );
240        }
241    }
242
243    public function filenameProvider() {
244        return [
245            [ __DIR__ . '/../ably-common/test-resources/crypto-data-128.json'],
246            [ __DIR__ . '/../ably-common/test-resources/crypto-data-256.json'],
247        ];
248    }
249}