primitives.derivation.spec.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. /*
  2. * Copyright 2018 Scytl Secure Electronic Voting SA
  3. *
  4. * All rights reserved
  5. *
  6. * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
  7. */
  8. describe('derivation module: ', function() {
  9. 'use strict';
  10. it('should exist', function() {
  11. cryptolib('primitives.derivation', function(box) {
  12. expect(box.primitives.derivation).toBeDefined();
  13. });
  14. });
  15. it('should contain a PBKDF generator factory', function() {
  16. cryptolib('primitives.derivation', function(box) {
  17. var pbkdfGeneratorFactory =
  18. new box.primitives.derivation.factory.SecretKeyGeneratorFactory();
  19. expect(pbkdfGeneratorFactory).toBeDefined();
  20. });
  21. });
  22. describe('PBKDF generator factory', function() {
  23. it('should generate a secretKey.', function() {
  24. cryptolib(
  25. 'primitives.derivation', 'commons.utils', 'primitives.securerandom',
  26. function(box) {
  27. var converters = new box.commons.utils.Converters();
  28. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  29. .SecretKeyGeneratorFactory();
  30. var secureRandomFactory =
  31. new box.primitives.securerandom.factory.SecureRandomFactory();
  32. var cryptoRandomBytes = secureRandomFactory.getCryptoRandomBytes();
  33. var cryptoPbkdfSecretKeyGenerator =
  34. pbkdfGeneratorFactory.createPBKDF();
  35. var encodedPassword = converters.base64Encode('scytl');
  36. var salt = cryptoRandomBytes.nextRandom(20);
  37. var saltB64 = converters.base64Encode(salt);
  38. var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret(
  39. encodedPassword, saltB64);
  40. expect(secretKeyB64).toBeDefined();
  41. expect(converters.base64Decode(secretKeyB64).length)
  42. .toBe(box.policies.primitives.derivation.pbkdf.keyLengthBytes);
  43. });
  44. });
  45. it('should generate a secretKey with hash SHA256 and salt length 32 bytes.',
  46. function() {
  47. cryptolib(
  48. 'primitives.derivation', 'commons.utils',
  49. 'primitives.securerandom', function() {
  50. var f = function(box) {
  51. var converters = new box.commons.utils.Converters();
  52. var pbkdfGeneratorFactory =
  53. new box.primitives.derivation.factory
  54. .SecretKeyGeneratorFactory();
  55. var secureRandomFactory = new box.primitives.securerandom
  56. .factory.SecureRandomFactory();
  57. var cryptoRandomBytes =
  58. secureRandomFactory.getCryptoRandomBytes();
  59. var cryptoPbkdfSecretKeyGenerator =
  60. pbkdfGeneratorFactory.createPBKDF();
  61. var encodedPassword = converters.base64Encode('scytl');
  62. var salt = cryptoRandomBytes.nextRandom(32);
  63. var saltB64 = converters.base64Encode(salt);
  64. var secretKeyB64 =
  65. cryptoPbkdfSecretKeyGenerator.generateSecret(
  66. encodedPassword, saltB64);
  67. expect(secretKeyB64).toBeDefined();
  68. expect(converters.base64Decode(secretKeyB64).length)
  69. .toBe(box.policies.primitives.derivation.pbkdf
  70. .keyLengthBytes);
  71. };
  72. f.policies = {
  73. primitives: {
  74. secureRandom: {
  75. provider: Config.primitives.secureRandom.provider.SCYTL
  76. },
  77. derivation: {
  78. pbkdf: {
  79. provider: 'Forge',
  80. hash: 'SHA256',
  81. saltLengthBytes: 32,
  82. keyLengthBytes: 32,
  83. iterations: 1
  84. }
  85. }
  86. }
  87. };
  88. cryptolib(
  89. 'primitives.derivation', 'commons.utils',
  90. 'primitives.securerandom', f);
  91. });
  92. });
  93. it('should generate a secretKey that matches one created with another engine, if the policies and parameters are the same.',
  94. function() {
  95. var f = function(box) {
  96. var converters = new box.commons.utils.Converters();
  97. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  98. .SecretKeyGeneratorFactory();
  99. var calculatedKey = 'vD80dK3PvPrQkALIY87oi7agHGoOCGGhvkZFuNASgL8=';
  100. var cryptoPbkdfSecretKeyGenerator =
  101. pbkdfGeneratorFactory.createPBKDF();
  102. var encodedPassword = converters.base64Encode('scytl');
  103. var saltB64 = 'CpHq+38iqF3Ls7j97qBQ2A9Diy8=';
  104. var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret(
  105. encodedPassword, saltB64);
  106. expect(secretKeyB64).toEqual(calculatedKey);
  107. };
  108. f.policies = {
  109. primitives: {
  110. derivation: {
  111. pbkdf: {
  112. provider: 'Forge',
  113. hash: 'SHA256',
  114. saltLengthBytes: 20,
  115. keyLengthBytes: 32,
  116. iterations: 1
  117. }
  118. }
  119. }
  120. };
  121. cryptolib('primitives.derivation', 'commons.utils', f);
  122. });
  123. it('should generate a secretKey that does not match one created with another engine, if the key lengths are different.',
  124. function() {
  125. var f = function(box) {
  126. var converters = new box.commons.utils.Converters();
  127. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  128. .SecretKeyGeneratorFactory();
  129. var calculatedKey = 'vD80dK3PvPrQkALIY87oi7agHGoOCGGhvkZFuNASgL8=';
  130. var cryptoPbkdfSecretKeyGenerator =
  131. pbkdfGeneratorFactory.createPBKDF();
  132. var encodedPassword = converters.base64Encode('scytl');
  133. var saltB64 = 'CpHq+38iqF3Ls7j97qBQ2A9Diy8=';
  134. var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret(
  135. encodedPassword, saltB64);
  136. expect(secretKeyB64).not.toEqual(calculatedKey);
  137. };
  138. f.policies = {
  139. primitives: {
  140. derivation: {
  141. pbkdf: {
  142. provider: 'Forge',
  143. hash: 'SHA256',
  144. saltLengthBytes: 20,
  145. keyLengthBytes: 16,
  146. iterations: 1
  147. }
  148. }
  149. }
  150. };
  151. cryptolib('primitives.derivation', 'commons.utils', f);
  152. });
  153. it('should generate a secretKey that does not match one created with another engine, if the number of iterations are different.',
  154. function() {
  155. var f = function(box) {
  156. var converters = new box.commons.utils.Converters();
  157. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  158. .SecretKeyGeneratorFactory();
  159. var calculatedKey = 'T5dDOwtsmPEnIYuCqUAldZZo4tO86FgcO7GAgDhaxYc=';
  160. var cryptoPbkdfSecretKeyGenerator =
  161. pbkdfGeneratorFactory.createPBKDF();
  162. var encodedPassword = converters.base64Encode('scytl');
  163. var saltB64 = 'CpHq+38iqF3Ls7j97qBQ2A9Diy8=';
  164. var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret(
  165. encodedPassword, saltB64);
  166. expect(secretKeyB64).not.toEqual(calculatedKey);
  167. };
  168. f.policies = {
  169. primitives: {
  170. derivation: {
  171. pbkdf: {
  172. provider: 'Forge',
  173. hash: 'SHA256',
  174. saltLengthBytes: 20,
  175. keyLengthBytes: 32,
  176. iterations: 8000
  177. }
  178. }
  179. }
  180. };
  181. cryptolib('primitives.derivation', 'commons.utils', f);
  182. });
  183. it('should generate a secretKey that does not match one created with another engine, if the password is different.',
  184. function() {
  185. var f = function(box) {
  186. var converters = new box.commons.utils.Converters();
  187. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  188. .SecretKeyGeneratorFactory();
  189. var calculatedKey = 'T5dDOwtsmPEnIYuCqUAldZZo4tO86FgcO7GAgDhaxYc=';
  190. var cryptoPbkdfSecretKeyGenerator =
  191. pbkdfGeneratorFactory.createPBKDF();
  192. var encodedPassword = converters.base64Encode('Scytl');
  193. var saltB64 = 'CpHq+38iqF3Ls7j97qBQ2A9Diy8=';
  194. var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret(
  195. encodedPassword, saltB64);
  196. expect(secretKeyB64).not.toEqual(calculatedKey);
  197. };
  198. f.policies = {
  199. primitives: {
  200. derivation: {
  201. pbkdf: {
  202. provider: 'Forge',
  203. hash: 'SHA256',
  204. saltLengthBytes: 20,
  205. keyLengthBytes: 32,
  206. iterations: 1
  207. }
  208. }
  209. }
  210. };
  211. cryptolib('primitives.derivation', 'commons.utils', f);
  212. });
  213. it('should generate a secretKey that does not match one created with another engine, if the salt is different.',
  214. function() {
  215. var f = function(box) {
  216. var converters = new box.commons.utils.Converters();
  217. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  218. .SecretKeyGeneratorFactory();
  219. var calculatedKey = 'T5dDOwtsmPEnIYuCqUAldZZo4tO86FgcO7GAgDhaxYc=';
  220. var cryptoPbkdfSecretKeyGenerator =
  221. pbkdfGeneratorFactory.createPBKDF();
  222. var encodedPassword = converters.base64Encode('scytl');
  223. var saltB64 = 'cpHq+38iqF3Ls7j97qBQ2A9Diy8=';
  224. var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret(
  225. encodedPassword, saltB64);
  226. expect(secretKeyB64).not.toEqual(calculatedKey);
  227. };
  228. f.policies = {
  229. primitives: {
  230. derivation: {
  231. pbkdf: {
  232. provider: 'Forge',
  233. hash: 'SHA256',
  234. saltLengthBytes: 20,
  235. keyLengthBytes: 32,
  236. iterations: 1
  237. }
  238. }
  239. }
  240. };
  241. cryptolib('primitives.derivation', 'commons.utils', f);
  242. });
  243. it('should generate a secretKey that does not match one created with another engine, if the hash algorithm is different.',
  244. function() {
  245. var f = function(box) {
  246. var converters = new box.commons.utils.Converters();
  247. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  248. .SecretKeyGeneratorFactory();
  249. var cryptoPbkdfSecretKeyGenerator =
  250. pbkdfGeneratorFactory.createPBKDF();
  251. var saltB64 = 'T4DtI/lcymMpBjJ7Ib9xpyS6xpuB5/+G68X3dT66eJs=';
  252. var generatedKeySHA1 =
  253. 'Ol8LDensA6B3CNhguSJrcuAnHAaB1NnGL0VfgbzDi34=';
  254. var encodedPassword = converters.base64Encode('scytl');
  255. var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret(
  256. encodedPassword, saltB64);
  257. expect(secretKeyB64).not.toEqual(generatedKeySHA1);
  258. };
  259. f.policies = {
  260. primitives: {
  261. derivation: {
  262. pbkdf: {
  263. provider: 'Forge',
  264. hash: 'SHA256',
  265. saltLengthBytes: 32,
  266. keyLengthBytes: 32,
  267. iterations: 1
  268. }
  269. }
  270. }
  271. };
  272. cryptolib('primitives.derivation', 'commons.utils', f);
  273. });
  274. it('should throw an error when an unsupported provider is specified in the policy.',
  275. function() {
  276. var f = function(box) {
  277. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  278. .SecretKeyGeneratorFactory();
  279. expect(function() {
  280. pbkdfGeneratorFactory.createPBKDF();
  281. }).toThrow();
  282. };
  283. f.policies = {
  284. primitives: {
  285. derivation: {
  286. pbkdf: {
  287. provider: 'WRONG',
  288. hash: 'SHA256',
  289. saltLengthBytes: 20,
  290. keyLengthBytes: 32,
  291. iterations: 1
  292. }
  293. }
  294. }
  295. };
  296. cryptolib('primitives.derivation', 'commons.utils', f);
  297. });
  298. it('should throw an error when an unsupported hash algorithm is specified in the policy.',
  299. function() {
  300. var f = function(box) {
  301. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  302. .SecretKeyGeneratorFactory();
  303. expect(function() {
  304. pbkdfGeneratorFactory.createPBKDF();
  305. }).toThrow();
  306. };
  307. f.policies = {
  308. primitives: {
  309. derivation: {
  310. pbkdf: {
  311. provider: 'Forge',
  312. hash: 'WRONG',
  313. saltLengthBytes: 20,
  314. keyLengthBytes: 32,
  315. iterations: 1
  316. }
  317. }
  318. }
  319. };
  320. cryptolib('primitives.derivation', 'commons.utils', f);
  321. });
  322. it('should throw an error when an unsupported minimum salt length is specified in the policy.',
  323. function() {
  324. var f = function(box) {
  325. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  326. .SecretKeyGeneratorFactory();
  327. expect(function() {
  328. pbkdfGeneratorFactory.createPBKDF();
  329. }).toThrow();
  330. };
  331. f.policies = {
  332. primitives: {
  333. derivation: {
  334. pbkdf: {
  335. provider: 'Forge',
  336. hash: 'SHA256',
  337. saltLengthBytes: 21,
  338. keyLengthBytes: 32,
  339. iterations: 1
  340. }
  341. }
  342. }
  343. };
  344. cryptolib('primitives.derivation', 'commons.utils', f);
  345. });
  346. it('should throw an error when an unsupported key length is specified in the policy.',
  347. function() {
  348. var f = function(box) {
  349. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  350. .SecretKeyGeneratorFactory();
  351. expect(function() {
  352. pbkdfGeneratorFactory.createPBKDF();
  353. }).toThrow();
  354. };
  355. f.policies = {
  356. primitives: {
  357. derivation: {
  358. pbkdf: {
  359. provider: 'Forge',
  360. hash: 'SHA256',
  361. saltLengthBytes: 20,
  362. keyLengthBytes: 33,
  363. iterations: 1
  364. }
  365. }
  366. }
  367. };
  368. cryptolib('primitives.derivation', 'commons.utils', f);
  369. });
  370. it('should throw an error when an unsupported number of iterations is specified in the policy.',
  371. function() {
  372. var f = function(box) {
  373. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  374. .SecretKeyGeneratorFactory();
  375. expect(function() {
  376. pbkdfGeneratorFactory.createPBKDF();
  377. }).toThrow();
  378. };
  379. f.policies = {
  380. primitives: {
  381. derivation: {
  382. pbkdf: {
  383. provider: 'Forge',
  384. hash: 'SHA256',
  385. saltLengthBytes: 20,
  386. keyLengthBytes: 32,
  387. iterations: 2
  388. }
  389. }
  390. }
  391. };
  392. cryptolib('primitives.derivation', 'commons.utils', f);
  393. });
  394. it('should throw an error when a salt with length less than the minimum specified in the policy is used.',
  395. function() {
  396. var f = function(box) {
  397. var converters = new box.commons.utils.Converters();
  398. var secureRandomFactory =
  399. new box.primitives.securerandom.factory.SecureRandomFactory();
  400. var randomByteGenerator = secureRandomFactory.getCryptoRandomBytes();
  401. var salt = randomByteGenerator.nextRandom(
  402. box.policies.primitives.derivation.pbkdf.saltLengthBytes - 1);
  403. var saltB64 = converters.base64Encode(salt);
  404. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  405. .SecretKeyGeneratorFactory();
  406. var cryptoPbkdfSecretKeyGenerator =
  407. pbkdfGeneratorFactory.createPBKDF();
  408. var encodedPassword = converters.base64Encode('scytl');
  409. expect(function() {
  410. cryptoPbkdfSecretKeyGenerator.generateSecret(
  411. encodedPassword, saltB64);
  412. }).toThrow();
  413. };
  414. cryptolib(
  415. 'primitives.securerandom', 'primitives.derivation',
  416. 'commons.utils', f);
  417. });
  418. it('should produce the same results when using SJCL or Forge as the cryptographic service provider.',
  419. function() {
  420. cryptolib(
  421. 'primitives.derivation', 'commons.utils',
  422. 'primitives.securerandom', function(box) {
  423. var converters = new box.commons.utils.Converters();
  424. var pbkdfGeneratorFactory = new box.primitives.derivation.factory
  425. .SecretKeyGeneratorFactory();
  426. var secureRandomFactory = new box.primitives.securerandom.factory
  427. .SecureRandomFactory();
  428. var cryptoRandomBytes =
  429. secureRandomFactory.getCryptoRandomBytes();
  430. var cryptoPbkdfSecretKeyGenerator =
  431. pbkdfGeneratorFactory.createPBKDF();
  432. var password =
  433. generateRandomString(Math.floor(Math.random() * 128) + 1);
  434. var passwordB64 = converters.base64Encode(password);
  435. var salt = cryptoRandomBytes.nextRandom(20);
  436. var saltB64 = converters.base64Encode(salt);
  437. var secretKeyFromSjclB64 =
  438. cryptoPbkdfSecretKeyGenerator.generateSecret(
  439. passwordB64, saltB64);
  440. var iterations =
  441. cryptolibPolicies.primitives.derivation.pbkdf.iterations;
  442. var keyLengthBytes =
  443. cryptolibPolicies.primitives.derivation.pbkdf.keyLengthBytes;
  444. var secretKeyFromForge = forge.pkcs5.pbkdf2(
  445. password, salt, iterations, keyLengthBytes,
  446. getHashGenerator(
  447. cryptolibPolicies.primitives.derivation.pbkdf.hash,
  448. Config.primitives.derivation.pbkdf.hash));
  449. var secretKeyFromForgeB64 =
  450. converters.base64Encode(secretKeyFromForge);
  451. expect(secretKeyFromForgeB64).toBe(secretKeyFromSjclB64);
  452. });
  453. });
  454. });
  455. });
  456. function generateRandomString(length) {
  457. 'use strict';
  458. var allowedChars =
  459. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,-./:;<=>?@[\]^_`{|}~';
  460. var str = '';
  461. for (var i = 0; i < length; i++) {
  462. str += allowedChars.charAt(Math.floor(Math.random() * allowedChars.length));
  463. }
  464. return str;
  465. }
  466. function getHashGenerator(algorithm, config) {
  467. 'use strict';
  468. if (algorithm === config.SHA256) {
  469. return forge.sha256.create();
  470. } else {
  471. throw 'Hash algorithm \'' + algorithm + '\' is unsupported';
  472. }
  473. }