Completed
Push — master ( 68cb6f...34a869 )
by rugk
03:08
created

CryptToolTests::testCompare()   C

Complexity

Conditions 10
Paths 1

Size

Total Lines 76
Code Lines 47

Duplication

Lines 8
Ratio 10.53 %

Importance

Changes 7
Bugs 2 Features 0
Metric Value
dl 8
loc 76
rs 5.7198
c 7
b 2
f 0
cc 10
eloc 47
nc 1
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Threema GmbH
4
 * @copyright Copyright (c) 2015-2016 Threema GmbH
5
 */
6
7
8
9
namespace Threema\MsgApi\Tests;
10
11
use Threema\Console\Common;
12
use Threema\MsgApi\Messages\TextMessage;
13
use Threema\MsgApi\Tools\CryptTool;
14
15
class CryptToolTests extends \PHPUnit_Framework_TestCase {
16
17
	/**
18
	 * test generating key pair
19
	 */
20
	public function testCreateKeyPair() {
21
		$this->doTest(function(CryptTool $cryptTool, $prefix) {
22
			$this->assertNotNull($cryptTool, $prefix.' could not instance crypto tool');
23
			$keyPair = $cryptTool->generateKeyPair();
24
			$this->assertNotNull($keyPair, $prefix.': invalid key pair');
25
			$this->assertNotNull($keyPair->privateKey, $prefix.': private key is null');
26
			$this->assertNotNull($keyPair->publicKey, $prefix.': public key is null');
27
		});
28
	}
29
30
	/**
31
	 * test generating random nonce
32
	 */
33
	public function testRandomNonce() {
34
		$this->doTest(function(CryptTool $cryptTool, $prefix) {
35
			$randomNonce = $cryptTool->randomNonce();
36
			$this->assertEquals(24, strlen($randomNonce), $prefix.': random nonce size not 24');
37
		});
38
	}
39
40
	public function testDecrypt() {
41
		/** @noinspection PhpUnusedParameterInspection */
42
		$this->doTest(function(CryptTool $cryptTool, $prefix) {
0 ignored issues
show
Unused Code introduced by
The parameter $prefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
43
			$nonce = '0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0';
44
			$box = '45181c7aed95a1c100b1b559116c61b43ce15d04014a805288b7d14bf3a993393264fe554794ce7d6007233e8ef5a0f1ccdd704f34e7c7b77c72c239182caf1d061d6fff6ffbbfe8d3b8f3475c2fe352e563aa60290c666b2e627761e32155e62f048b52ef2f39c13ac229f393c67811749467396ecd09f42d32a4eb419117d0451056ac18fac957c52b0cca67568e2d97e5a3fd829a77f914a1ad403c5909fd510a313033422ea5db71eaf43d483238612a54cb1ecfe55259b1de5579e67c6505df7d674d34a737edf721ea69d15b567bc2195ec67e172f3cb8d6842ca88c29138cc33e9351dbc1e4973a82e1cf428c1c763bb8f3eb57770f914a';
45
46
			$privateKey = Common::getPrivateKey(Constants::otherPrivateKey);
47
			$this->assertNotNull($privateKey);
48
49
			$publicKey = Common::getPublicKey(Constants::myPublicKey);
50
			$this->assertNotNull($publicKey);
51
52
			$message = $cryptTool->decryptMessage($cryptTool->hex2bin($box),
53
				$cryptTool->hex2bin($privateKey),
54
				$cryptTool->hex2bin($publicKey),
55
				$cryptTool->hex2bin($nonce));
56
57
			$this->assertNotNull($message);
58
			$this->assertTrue($message instanceof TextMessage);
59
			if($message instanceof TextMessage) {
60
				$this->assertEquals($message->getText(), 'Dies ist eine Testnachricht. äöü');
61
			}
62
		});
63
	}
64
65
	public function testEncrypt() {
66
		/** @noinspection PhpUnusedParameterInspection */
67
		$this->doTest(function(CryptTool $cryptTool, $prefix) {
0 ignored issues
show
Unused Code introduced by
The parameter $prefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
68
			$text = 'Dies ist eine Testnachricht. äöü';
69
			$nonce = '0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0';
70
71
			$privateKey = Common::getPrivateKey(Constants::myPrivateKey);
72
			$this->assertNotNull($privateKey);
73
74
			$publicKey = Common::getPublicKey(Constants::otherPublicKey);
75
			$this->assertNotNull($publicKey);
76
77
			$message = $cryptTool->encryptMessageText($text,
78
				$cryptTool->hex2bin($privateKey),
79
				$cryptTool->hex2bin($publicKey),
80
				$cryptTool->hex2bin($nonce));
81
82
			$this->assertNotNull($message);
83
84
			$box = $cryptTool->decryptMessage($message,
85
				$cryptTool->hex2bin(Common::getPrivateKey(Constants::otherPrivateKey)),
86
				$cryptTool->hex2bin(Common::getPublicKey(Constants::myPublicKey)),
87
				$cryptTool->hex2bin($nonce));
88
89
			$this->assertNotNull($box);
90
		});
91
	}
92
93
94
	public function testDerivePublicKey() {
95
		$this->doTest(function(CryptTool $cryptTool, $prefix){
96
			$publicKey = $cryptTool->derivePublicKey($cryptTool->hex2bin(Common::getPrivateKey(Constants::myPrivateKey)));
97
			$myPublicKey = $cryptTool->hex2bin(Common::getPublicKey(Constants::myPublicKey));
98
99
			$this->assertEquals($publicKey, $myPublicKey, $prefix.' derive public key failed');
100
		});
101
	}
102
103
	public function testEncryptImage() {
104
		$threemaIconContent = file_get_contents(dirname(__FILE__).'/threema.jpg');
105
106
		/** @noinspection PhpUnusedParameterInspection */
107
		$this->doTest(function(CryptTool $cryptTool, $prefix) use($threemaIconContent) {
0 ignored issues
show
Unused Code introduced by
The parameter $prefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
108
			$privateKey = $cryptTool->hex2bin(Common::getPrivateKey(Constants::myPrivateKey));
109
			$publicKey = $cryptTool->hex2bin(Common::getPublicKey(Constants::myPublicKey));
110
111
			$otherPrivateKey = $cryptTool->hex2bin(Common::getPrivateKey(Constants::otherPrivateKey));
112
			$otherPublicKey = $cryptTool->hex2bin(Common::getPublicKey(Constants::otherPublicKey));
113
114
			$result = $cryptTool->encryptImage($threemaIconContent, $privateKey, $otherPublicKey);
115
116
			$decryptedImage = $cryptTool->decryptImage($result->getData(), $publicKey, $otherPrivateKey, $result->getNonce());
117
118
			$this->assertEquals($decryptedImage, $threemaIconContent, 'decryption of image failed');
119
120
		});
121
	}
122
123
	/**
124
	 * test hex2bin and bin2hex
125
	 */
126
	public function testHexBin() {
127
		$this->doTest(function(CryptTool $cryptTool, $prefix) {
128
			$testStr = Constants::myPrivateKeyExtract;
129
130
			// convert hex to bin
131
			$testStrBin = $cryptTool->hex2bin($testStr);
132
			$this->assertNotNull($testStrBin);
133
			$testStrBinPhp = hex2bin($testStr);
134
135
			// compare usual PHP conversion with crypt tool version
136
			$this->assertEquals($testStrBin, $testStrBinPhp, $prefix.': hex2bin returns different result than PHP-only implementation');
137
138
			// convert back to hex
139
			$testStrHex = $cryptTool->bin2hex($testStrBin);
140
			$this->assertNotNull($testStrHex);
141
			$testStrHexPhp = bin2hex($testStrBin);
142
143
			// compare usual PHP conversion with crypt tool version
144
			$this->assertEquals($testStrHexPhp, $testStrHex, $prefix.': bin2hex returns different result than PHP-only implementation');
145
			// compare with initial value
146
			$this->assertEquals($testStrHex, $testStr, $prefix.': binary string is different than initial string after conversions');
147
		});
148
	}
149
150
	/**
151
	 * test compare functions to make sure they are resistant to timing attacks
152
	 */
153
	public function testCompare() {
154
		$this->doTest(function(CryptTool $cryptTool, $prefix) {
155
				// make strings large enough
156
				$string1 = str_repeat(Constants::myPrivateKey, 100000);
157
				$string2 = str_repeat(Constants::otherPrivateKey, 100000);
158
				echo PHP_EOL;
159
160
				$humanDescr = [
161
					'length' => 'different length',
162
					'diff' => 'same length, different content',
163
					'same' => 'same length, same content'
164
				];
165
166
				// test different strings when comparing and get time needed
167
				$timeElapsed = [];
168
				$comparisonResult = [];
169
				$timeElapsedAvg = [];
170
				foreach(array(
171
					'length' => [$string1, $string1 . 'a'],
172
					'diff' => [$string1, $string2],
173
					'same' => [$string1, $string1]
174
				) as $testName => $strings) {
175
					$timeMin = [null, 0];
176
					$timeMax = [null, 0];
177
					for ($i=0; $i < 3; $i++) {
178
						// test run with delay
179
						$timeElapsed[$testName][$i] = -microtime(true);
180
						$comparisonResult[$testName][$i] = $cryptTool->stringCompare($strings[0], $strings[1]);
181
						$timeElapsed[$testName][$i] += microtime(true);
182
						usleep(1000);
183
184
						// debug output
185
						echo $prefix.': '.$humanDescr[$testName].' #'.$i.': '.$timeElapsed[$testName][$i].'; result: '.$comparisonResult[$testName][$i].PHP_EOL;
186
187
						// check result
188
						if ($testName == 'length' || $testName == 'diff') {
189
							$this->assertEquals(false, $comparisonResult[$testName][$i], $prefix.': comparison of "'.$humanDescr[$testName].' #'.$i.'" is wrong: expected: false, got '.$comparisonResult[$testName][$i]);
190
						} else {
191
							$this->assertEquals(true, $comparisonResult[$testName][$i], $prefix.': comparison of "'.$humanDescr[$testName].' #'.$i.'" is wrong: expected: true, got '.$comparisonResult[$testName][$i]);
192
						}
193
194
						// get min/max values for later normalisation
195 View Code Duplication
						if (is_null($timeMin[0]) || $timeElapsed[$testName][$i] < $timeMin[0]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
196
							$timeMin[0] = $timeElapsed[$testName][$i]; //minimum value
197
							$timeMin[1] = $i; //index
198
						}
199 View Code Duplication
						if (is_null($timeMax[0]) || $timeElapsed[$testName][$i] > $timeMax[0]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
							$timeMax[0] = $timeElapsed[$testName][$i]; //maxiumum value
201
							$timeMax[1] = $i; //index
202
						}
203
					}
204
205
					// remove min/max value from array to normalize value
206
					array_splice($timeElapsed[$testName], $timeMin[1], 1);
207
					array_splice($timeElapsed[$testName], $timeMax[1] >= $timeMin[1] ? ($timeMax[1]-1) : $timeMax[1], 1);
208
209
					// calculate average (currently useless as there is only one value left, but it may be useful later)
210
					$timeElapsedAvg[$testName] = array_sum($timeElapsed[$testName]) / count($timeElapsed[$testName]);
211
				}
212
213
				// check timings
214
				echo 'Timing test results with '.$prefix.':'.PHP_EOL;
215
				$timingRatio = $timeElapsedAvg['diff'] / $timeElapsedAvg['same'];
216
				$absoluteDifference = abs($timeElapsedAvg['diff'] - $timeElapsedAvg['same']);
217
				echo 'timing ratio: '.$timingRatio.PHP_EOL;
218
				echo 'absolute difference: '.$absoluteDifference.PHP_EOL;
219
220
				// only allow 25% relative difference of two values
221
				$allowedDifference = 0.25;
222
				$this->assertLessThan(1+$allowedDifference, $timingRatio, $prefix.': difference of comparison ration of "'.$humanDescr['diff'].'" compared to "'.$humanDescr['same'].'" is too high. Ratio: '.$timingRatio);
223
				$this->assertGreaterThan(1-$allowedDifference, $timingRatio, $prefix.': difference of comparison ration of "'.$humanDescr['diff'].'" compared to "'.$humanDescr['same'].'" is too low. Ratio: '.$timingRatio);
224
225
				// make sure the absolute difference is smaller than 1 second!
226
				$this->assertLessThan(1, $absoluteDifference, $prefix.': difference of comparison ration of "'.$humanDescr['diff'].'" compared to "'.$humanDescr['same'].'" is too high. Value is: '.$absoluteDifference.' micro seconds');
227
			});
228
	}
229
230
	/**
231
	 * test variable deletion
232
	 */
233
	public function testRemoveVar() {
234
		$this->doTest(function(CryptTool $cryptTool, $prefix) {
235
			foreach(array(
236
						'hex' => Constants::myPrivateKeyExtract,
237
						'bin' => $cryptTool->hex2bin(Constants::myPrivateKeyExtract)
238
					) as $key => $testVar) {
239
				// let it remove
240
				$cryptTool->removeVar($testVar);
241
242
				$this->assertEmpty($testVar, $prefix.': variable is not empty (test: '.$key.')');
243
				$this->assertNull($testVar, $prefix.': variable is not null (test: '.$key.')');
244
			}
245
		});
246
	}
247
248
	private function doTest(\Closure $c) {
249
		foreach(array(
250
					'Salt' => CryptTool::createInstance(CryptTool::TYPE_SALT),
251
					'Sodium' => CryptTool::createInstance(CryptTool::TYPE_SODIUM)
252
				) as $key => $instance) {
253
254
			if($instance === null) {
255
				echo $key.": could not instance crypt tool\n";
256
				break;
257
			}
258
			/** @noinspection PhpUndefinedMethodInspection */
259
			$this->assertTrue($instance->isSupported(), $key.' not supported');
260
			$c->__invoke($instance, $key);
261
		}
262
	}
263
}
264