crypto_spec.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. import {
  16. AES128Cipher,
  17. AES256Cipher,
  18. ARCFourCipher,
  19. calculateMD5,
  20. calculateSHA256,
  21. calculateSHA384,
  22. calculateSHA512,
  23. CipherTransformFactory,
  24. PDF17,
  25. PDF20,
  26. } from "../../src/core/crypto.js";
  27. import { Dict, Name } from "../../src/core/primitives.js";
  28. import {
  29. PasswordException,
  30. PasswordResponses,
  31. stringToBytes,
  32. } from "../../src/shared/util.js";
  33. describe("crypto", function () {
  34. function hex2binary(s) {
  35. const digits = "0123456789ABCDEF";
  36. s = s.toUpperCase();
  37. const n = s.length >> 1;
  38. const result = new Uint8Array(n);
  39. for (let i = 0, j = 0; i < n; ++i) {
  40. const d1 = s.charAt(j++);
  41. const d2 = s.charAt(j++);
  42. const value = (digits.indexOf(d1) << 4) | digits.indexOf(d2);
  43. result[i] = value;
  44. }
  45. return result;
  46. }
  47. // RFC 1321, A.5 Test suite
  48. describe("calculateMD5", function () {
  49. it("should pass RFC 1321 test #1", function () {
  50. const input = stringToBytes("");
  51. const result = calculateMD5(input, 0, input.length);
  52. const expected = hex2binary("d41d8cd98f00b204e9800998ecf8427e");
  53. expect(result).toEqual(expected);
  54. });
  55. it("should pass RFC 1321 test #2", function () {
  56. const input = stringToBytes("a");
  57. const result = calculateMD5(input, 0, input.length);
  58. const expected = hex2binary("0cc175b9c0f1b6a831c399e269772661");
  59. expect(result).toEqual(expected);
  60. });
  61. it("should pass RFC 1321 test #3", function () {
  62. const input = stringToBytes("abc");
  63. const result = calculateMD5(input, 0, input.length);
  64. const expected = hex2binary("900150983cd24fb0d6963f7d28e17f72");
  65. expect(result).toEqual(expected);
  66. });
  67. it("should pass RFC 1321 test #4", function () {
  68. const input = stringToBytes("message digest");
  69. const result = calculateMD5(input, 0, input.length);
  70. const expected = hex2binary("f96b697d7cb7938d525a2f31aaf161d0");
  71. expect(result).toEqual(expected);
  72. });
  73. it("should pass RFC 1321 test #5", function () {
  74. const input = stringToBytes("abcdefghijklmnopqrstuvwxyz");
  75. const result = calculateMD5(input, 0, input.length);
  76. const expected = hex2binary("c3fcd3d76192e4007dfb496cca67e13b");
  77. expect(result).toEqual(expected);
  78. });
  79. it("should pass RFC 1321 test #6", function () {
  80. const input = stringToBytes(
  81. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  82. );
  83. const result = calculateMD5(input, 0, input.length);
  84. const expected = hex2binary("d174ab98d277d9f5a5611c2c9f419d9f");
  85. expect(result).toEqual(expected);
  86. });
  87. it("should pass RFC 1321 test #7", function () {
  88. const input = stringToBytes(
  89. "123456789012345678901234567890123456789012345678" +
  90. "90123456789012345678901234567890"
  91. );
  92. const result = calculateMD5(input, 0, input.length);
  93. const expected = hex2binary("57edf4a22be3c955ac49da2e2107b67a");
  94. expect(result).toEqual(expected);
  95. });
  96. });
  97. // http://www.freemedialibrary.com/index.php/RC4_test_vectors are used
  98. describe("ARCFourCipher", function () {
  99. it("should pass test #1", function () {
  100. const key = hex2binary("0123456789abcdef");
  101. const input = hex2binary("0123456789abcdef");
  102. const cipher = new ARCFourCipher(key);
  103. const result = cipher.encryptBlock(input);
  104. const expected = hex2binary("75b7878099e0c596");
  105. expect(result).toEqual(expected);
  106. });
  107. it("should pass test #2", function () {
  108. const key = hex2binary("0123456789abcdef");
  109. const input = hex2binary("0000000000000000");
  110. const cipher = new ARCFourCipher(key);
  111. const result = cipher.encryptBlock(input);
  112. const expected = hex2binary("7494c2e7104b0879");
  113. expect(result).toEqual(expected);
  114. });
  115. it("should pass test #3", function () {
  116. const key = hex2binary("0000000000000000");
  117. const input = hex2binary("0000000000000000");
  118. const cipher = new ARCFourCipher(key);
  119. const result = cipher.encryptBlock(input);
  120. const expected = hex2binary("de188941a3375d3a");
  121. expect(result).toEqual(expected);
  122. });
  123. it("should pass test #4", function () {
  124. const key = hex2binary("ef012345");
  125. const input = hex2binary("00000000000000000000");
  126. const cipher = new ARCFourCipher(key);
  127. const result = cipher.encryptBlock(input);
  128. const expected = hex2binary("d6a141a7ec3c38dfbd61");
  129. expect(result).toEqual(expected);
  130. });
  131. it("should pass test #5", function () {
  132. const key = hex2binary("0123456789abcdef");
  133. const input = hex2binary(
  134. "010101010101010101010101010101010101010101010101010" +
  135. "10101010101010101010101010101010101010101010101010101010101010101010" +
  136. "10101010101010101010101010101010101010101010101010101010101010101010" +
  137. "10101010101010101010101010101010101010101010101010101010101010101010" +
  138. "10101010101010101010101010101010101010101010101010101010101010101010" +
  139. "10101010101010101010101010101010101010101010101010101010101010101010" +
  140. "10101010101010101010101010101010101010101010101010101010101010101010" +
  141. "10101010101010101010101010101010101010101010101010101010101010101010" +
  142. "10101010101010101010101010101010101010101010101010101010101010101010" +
  143. "10101010101010101010101010101010101010101010101010101010101010101010" +
  144. "10101010101010101010101010101010101010101010101010101010101010101010" +
  145. "10101010101010101010101010101010101010101010101010101010101010101010" +
  146. "10101010101010101010101010101010101010101010101010101010101010101010" +
  147. "10101010101010101010101010101010101010101010101010101010101010101010" +
  148. "10101010101010101010101010101010101010101010101010101010101010101010" +
  149. "101010101010101010101"
  150. );
  151. const cipher = new ARCFourCipher(key);
  152. const result = cipher.encryptBlock(input);
  153. const expected = hex2binary(
  154. "7595c3e6114a09780c4ad452338e1ffd9a1be9498f813d76" +
  155. "533449b6778dcad8c78a8d2ba9ac66085d0e53d59c26c2d1c490c1ebbe0ce66d1b6b" +
  156. "1b13b6b919b847c25a91447a95e75e4ef16779cde8bf0a95850e32af9689444fd377" +
  157. "108f98fdcbd4e726567500990bcc7e0ca3c4aaa304a387d20f3b8fbbcd42a1bd311d" +
  158. "7a4303dda5ab078896ae80c18b0af66dff319616eb784e495ad2ce90d7f772a81747" +
  159. "b65f62093b1e0db9e5ba532fafec47508323e671327df9444432cb7367cec82f5d44" +
  160. "c0d00b67d650a075cd4b70dedd77eb9b10231b6b5b741347396d62897421d43df9b4" +
  161. "2e446e358e9c11a9b2184ecbef0cd8e7a877ef968f1390ec9b3d35a5585cb009290e" +
  162. "2fcde7b5ec66d9084be44055a619d9dd7fc3166f9487f7cb272912426445998514c1" +
  163. "5d53a18c864ce3a2b7555793988126520eacf2e3066e230c91bee4dd5304f5fd0405" +
  164. "b35bd99c73135d3d9bc335ee049ef69b3867bf2d7bd1eaa595d8bfc0066ff8d31509" +
  165. "eb0c6caa006c807a623ef84c3d33c195d23ee320c40de0558157c822d4b8c569d849" +
  166. "aed59d4e0fd7f379586b4b7ff684ed6a189f7486d49b9c4bad9ba24b96abf924372c" +
  167. "8a8fffb10d55354900a77a3db5f205e1b99fcd8660863a159ad4abe40fa48934163d" +
  168. "dde542a6585540fd683cbfd8c00f12129a284deacc4cdefe58be7137541c047126c8" +
  169. "d49e2755ab181ab7e940b0c0"
  170. );
  171. expect(result).toEqual(expected);
  172. });
  173. it("should pass test #6", function () {
  174. const key = hex2binary("fb029e3031323334");
  175. const input = hex2binary(
  176. "aaaa0300000008004500004e661a00008011be640a0001220af" +
  177. "fffff00890089003a000080a601100001000000000000204543454a4548454346434" +
  178. "550464545494546464343414341434143414341414100002000011bd0b604"
  179. );
  180. const cipher = new ARCFourCipher(key);
  181. const result = cipher.encryptBlock(input);
  182. const expected = hex2binary(
  183. "f69c5806bd6ce84626bcbefb9474650aad1f7909b0f64d5f" +
  184. "58a503a258b7ed22eb0ea64930d3a056a55742fcce141d485f8aa836dea18df42c53" +
  185. "80805ad0c61a5d6f58f41040b24b7d1a693856ed0d4398e7aee3bf0e2a2ca8f7"
  186. );
  187. expect(result).toEqual(expected);
  188. });
  189. it("should pass test #7", function () {
  190. const key = hex2binary("0123456789abcdef");
  191. const input = hex2binary(
  192. "123456789abcdef0123456789abcdef0123456789abcdef012345678"
  193. );
  194. const cipher = new ARCFourCipher(key);
  195. const result = cipher.encryptBlock(input);
  196. const expected = hex2binary(
  197. "66a0949f8af7d6891f7f832ba833c00c892ebe30143ce28740011ecf"
  198. );
  199. expect(result).toEqual(expected);
  200. });
  201. });
  202. describe("calculateSHA256", function () {
  203. it("should properly hash abc", function () {
  204. const input = stringToBytes("abc");
  205. const result = calculateSHA256(input, 0, input.length);
  206. const expected = hex2binary(
  207. "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD"
  208. );
  209. expect(result).toEqual(expected);
  210. });
  211. it("should properly hash a multiblock input", function () {
  212. const input = stringToBytes(
  213. "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
  214. );
  215. const result = calculateSHA256(input, 0, input.length);
  216. const expected = hex2binary(
  217. "248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1"
  218. );
  219. expect(result).toEqual(expected);
  220. });
  221. });
  222. describe("calculateSHA384", function () {
  223. it("should properly hash abc", function () {
  224. const input = stringToBytes("abc");
  225. const result = calculateSHA384(input, 0, input.length);
  226. const expected = hex2binary(
  227. "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED163" +
  228. "1A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7"
  229. );
  230. expect(result).toEqual(expected);
  231. });
  232. it("should properly hash a multiblock input", function () {
  233. const input = stringToBytes(
  234. "abcdefghbcdefghicdefghijdefghijkefghijklfghijklm" +
  235. "ghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrs" +
  236. "mnopqrstnopqrstu"
  237. );
  238. const result = calculateSHA384(input, 0, input.length);
  239. const expected = hex2binary(
  240. "09330C33F71147E83D192FC782CD1B4753111B173B3B05D2" +
  241. "2FA08086E3B0F712FCC7C71A557E2DB966C3E9FA91746039"
  242. );
  243. expect(result).toEqual(expected);
  244. });
  245. });
  246. describe("calculateSHA512", function () {
  247. it("should properly hash abc", function () {
  248. const input = stringToBytes("abc");
  249. const result = calculateSHA512(input, 0, input.length);
  250. const expected = hex2binary(
  251. "DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA2" +
  252. "0A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD" +
  253. "454D4423643CE80E2A9AC94FA54CA49F"
  254. );
  255. expect(result).toEqual(expected);
  256. });
  257. it("should properly hash a multiblock input", function () {
  258. const input = stringToBytes(
  259. "abcdefghbcdefghicdefghijdefghijkefghijklfghijklm" +
  260. "ghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrs" +
  261. "mnopqrstnopqrstu"
  262. );
  263. const result = calculateSHA512(input, 0, input.length);
  264. const expected = hex2binary(
  265. "8E959B75DAE313DA8CF4F72814FC143F8F7779C6EB9F7FA1" +
  266. "7299AEADB6889018501D289E4900F7E4331B99DEC4B5433A" +
  267. "C7D329EEB6DD26545E96E55B874BE909"
  268. );
  269. expect(result).toEqual(expected);
  270. });
  271. });
  272. describe("AES128", function () {
  273. describe("Encryption", function () {
  274. it("should be able to encrypt a block", function () {
  275. const input = hex2binary("00112233445566778899aabbccddeeff");
  276. const key = hex2binary("000102030405060708090a0b0c0d0e0f");
  277. const iv = hex2binary("00000000000000000000000000000000");
  278. const cipher = new AES128Cipher(key);
  279. const result = cipher.encrypt(input, iv);
  280. const expected = hex2binary("69c4e0d86a7b0430d8cdb78070b4c55a");
  281. expect(result).toEqual(expected);
  282. });
  283. });
  284. describe("Decryption", function () {
  285. it("should be able to decrypt a block with IV in stream", function () {
  286. const input = hex2binary(
  287. "0000000000000000000000000000000069c4e0d86a7b0430d" +
  288. "8cdb78070b4c55a"
  289. );
  290. const key = hex2binary("000102030405060708090a0b0c0d0e0f");
  291. const cipher = new AES128Cipher(key);
  292. const result = cipher.decryptBlock(input);
  293. const expected = hex2binary("00112233445566778899aabbccddeeff");
  294. expect(result).toEqual(expected);
  295. });
  296. });
  297. });
  298. describe("AES256", function () {
  299. describe("Encryption", function () {
  300. it("should be able to encrypt a block", function () {
  301. const input = hex2binary("00112233445566778899aabbccddeeff");
  302. const key = hex2binary(
  303. "000102030405060708090a0b0c0d0e0f101112131415161718" +
  304. "191a1b1c1d1e1f"
  305. );
  306. const iv = hex2binary("00000000000000000000000000000000");
  307. const cipher = new AES256Cipher(key);
  308. const result = cipher.encrypt(input, iv);
  309. const expected = hex2binary("8ea2b7ca516745bfeafc49904b496089");
  310. expect(result).toEqual(expected);
  311. });
  312. });
  313. describe("Decryption", function () {
  314. it("should be able to decrypt a block with specified iv", function () {
  315. const input = hex2binary("8ea2b7ca516745bfeafc49904b496089");
  316. const key = hex2binary(
  317. "000102030405060708090a0b0c0d0e0f101112131415161718" +
  318. "191a1b1c1d1e1f"
  319. );
  320. const iv = hex2binary("00000000000000000000000000000000");
  321. const cipher = new AES256Cipher(key);
  322. const result = cipher.decryptBlock(input, false, iv);
  323. const expected = hex2binary("00112233445566778899aabbccddeeff");
  324. expect(result).toEqual(expected);
  325. });
  326. it("should be able to decrypt a block with IV in stream", function () {
  327. const input = hex2binary(
  328. "000000000000000000000000000000008ea2b7ca516745bf" +
  329. "eafc49904b496089"
  330. );
  331. const key = hex2binary(
  332. "000102030405060708090a0b0c0d0e0f101112131415161718" +
  333. "191a1b1c1d1e1f"
  334. );
  335. const cipher = new AES256Cipher(key);
  336. const result = cipher.decryptBlock(input, false);
  337. const expected = hex2binary("00112233445566778899aabbccddeeff");
  338. expect(result).toEqual(expected);
  339. });
  340. });
  341. });
  342. describe("PDF17Algorithm", function () {
  343. it("should correctly check a user key", function () {
  344. const alg = new PDF17();
  345. const password = new Uint8Array([117, 115, 101, 114]);
  346. const userValidation = new Uint8Array([
  347. 117, 169, 4, 32, 159, 101, 22, 220,
  348. ]);
  349. const userPassword = new Uint8Array([
  350. 131, 242, 143, 160, 87, 2, 138, 134, 79, 253, 189, 173, 224, 73, 144,
  351. 241, 190, 81, 197, 15, 249, 105, 145, 151, 15, 194, 65, 3, 1, 126, 187,
  352. 221,
  353. ]);
  354. const result = alg.checkUserPassword(
  355. password,
  356. userValidation,
  357. userPassword
  358. );
  359. expect(result).toEqual(true);
  360. });
  361. it("should correctly check an owner key", function () {
  362. const alg = new PDF17();
  363. const password = new Uint8Array([111, 119, 110, 101, 114]);
  364. const ownerValidation = new Uint8Array([
  365. 243, 118, 71, 153, 128, 17, 101, 62,
  366. ]);
  367. const ownerPassword = new Uint8Array([
  368. 60, 98, 137, 35, 51, 101, 200, 152, 210, 178, 226, 228, 134, 205, 163,
  369. 24, 204, 126, 177, 36, 106, 50, 36, 125, 210, 172, 171, 120, 222, 108,
  370. 139, 115,
  371. ]);
  372. const uBytes = new Uint8Array([
  373. 131, 242, 143, 160, 87, 2, 138, 134, 79, 253, 189, 173, 224, 73, 144,
  374. 241, 190, 81, 197, 15, 249, 105, 145, 151, 15, 194, 65, 3, 1, 126, 187,
  375. 221, 117, 169, 4, 32, 159, 101, 22, 220, 168, 94, 215, 192, 100, 38,
  376. 188, 40,
  377. ]);
  378. const result = alg.checkOwnerPassword(
  379. password,
  380. ownerValidation,
  381. uBytes,
  382. ownerPassword
  383. );
  384. expect(result).toEqual(true);
  385. });
  386. it("should generate a file encryption key from the user key", function () {
  387. const alg = new PDF17();
  388. const password = new Uint8Array([117, 115, 101, 114]);
  389. const userKeySalt = new Uint8Array([168, 94, 215, 192, 100, 38, 188, 40]);
  390. const userEncryption = new Uint8Array([
  391. 35, 150, 195, 169, 245, 51, 51, 255, 158, 158, 33, 242, 231, 75, 125,
  392. 190, 25, 126, 172, 114, 195, 244, 137, 245, 234, 165, 42, 74, 60, 38,
  393. 17, 17,
  394. ]);
  395. const result = alg.getUserKey(password, userKeySalt, userEncryption);
  396. const expected = new Uint8Array([
  397. 63, 114, 136, 209, 87, 61, 12, 30, 249, 1, 186, 144, 254, 248, 163, 153,
  398. 151, 51, 133, 10, 80, 152, 206, 15, 72, 187, 231, 33, 224, 239, 13, 213,
  399. ]);
  400. expect(result).toEqual(expected);
  401. });
  402. it("should generate a file encryption key from the owner key", function () {
  403. const alg = new PDF17();
  404. const password = new Uint8Array([111, 119, 110, 101, 114]);
  405. const ownerKeySalt = new Uint8Array([
  406. 200, 245, 242, 12, 218, 123, 24, 120,
  407. ]);
  408. const ownerEncryption = new Uint8Array([
  409. 213, 202, 14, 189, 110, 76, 70, 191, 6, 195, 10, 190, 157, 100, 144, 85,
  410. 8, 62, 123, 178, 156, 229, 50, 40, 229, 216, 54, 222, 34, 38, 106, 223,
  411. ]);
  412. const uBytes = new Uint8Array([
  413. 131, 242, 143, 160, 87, 2, 138, 134, 79, 253, 189, 173, 224, 73, 144,
  414. 241, 190, 81, 197, 15, 249, 105, 145, 151, 15, 194, 65, 3, 1, 126, 187,
  415. 221, 117, 169, 4, 32, 159, 101, 22, 220, 168, 94, 215, 192, 100, 38,
  416. 188, 40,
  417. ]);
  418. const result = alg.getOwnerKey(
  419. password,
  420. ownerKeySalt,
  421. uBytes,
  422. ownerEncryption
  423. );
  424. const expected = new Uint8Array([
  425. 63, 114, 136, 209, 87, 61, 12, 30, 249, 1, 186, 144, 254, 248, 163, 153,
  426. 151, 51, 133, 10, 80, 152, 206, 15, 72, 187, 231, 33, 224, 239, 13, 213,
  427. ]);
  428. expect(result).toEqual(expected);
  429. });
  430. });
  431. describe("PDF20Algorithm", function () {
  432. it("should correctly check a user key", function () {
  433. const alg = new PDF20();
  434. const password = new Uint8Array([117, 115, 101, 114]);
  435. const userValidation = new Uint8Array([
  436. 83, 245, 146, 101, 198, 247, 34, 198,
  437. ]);
  438. const userPassword = new Uint8Array([
  439. 94, 230, 205, 75, 166, 99, 250, 76, 219, 128, 17, 85, 57, 17, 33, 164,
  440. 150, 46, 103, 176, 160, 156, 187, 233, 166, 223, 163, 253, 147, 235, 95,
  441. 184,
  442. ]);
  443. const result = alg.checkUserPassword(
  444. password,
  445. userValidation,
  446. userPassword
  447. );
  448. expect(result).toEqual(true);
  449. });
  450. it("should correctly check an owner key", function () {
  451. const alg = new PDF20();
  452. const password = new Uint8Array([111, 119, 110, 101, 114]);
  453. const ownerValidation = new Uint8Array([
  454. 142, 232, 169, 208, 202, 214, 5, 185,
  455. ]);
  456. const ownerPassword = new Uint8Array([
  457. 88, 232, 62, 54, 245, 26, 245, 209, 137, 123, 221, 72, 199, 49, 37, 217,
  458. 31, 74, 115, 167, 127, 158, 176, 77, 45, 163, 87, 47, 39, 90, 217, 141,
  459. ]);
  460. const uBytes = new Uint8Array([
  461. 94, 230, 205, 75, 166, 99, 250, 76, 219, 128, 17, 85, 57, 17, 33, 164,
  462. 150, 46, 103, 176, 160, 156, 187, 233, 166, 223, 163, 253, 147, 235, 95,
  463. 184, 83, 245, 146, 101, 198, 247, 34, 198, 191, 11, 16, 94, 237, 216,
  464. 20, 175,
  465. ]);
  466. const result = alg.checkOwnerPassword(
  467. password,
  468. ownerValidation,
  469. uBytes,
  470. ownerPassword
  471. );
  472. expect(result).toEqual(true);
  473. });
  474. it("should generate a file encryption key from the user key", function () {
  475. const alg = new PDF20();
  476. const password = new Uint8Array([117, 115, 101, 114]);
  477. const userKeySalt = new Uint8Array([191, 11, 16, 94, 237, 216, 20, 175]);
  478. const userEncryption = new Uint8Array([
  479. 121, 208, 2, 181, 230, 89, 156, 60, 253, 143, 212, 28, 84, 180, 196,
  480. 177, 173, 128, 221, 107, 46, 20, 94, 186, 135, 51, 95, 24, 20, 223, 254,
  481. 36,
  482. ]);
  483. const result = alg.getUserKey(password, userKeySalt, userEncryption);
  484. const expected = new Uint8Array([
  485. 42, 218, 213, 39, 73, 91, 72, 79, 67, 38, 248, 133, 18, 189, 61, 34,
  486. 107, 79, 29, 56, 59, 181, 213, 118, 113, 34, 65, 210, 87, 174, 22, 239,
  487. ]);
  488. expect(result).toEqual(expected);
  489. });
  490. it("should generate a file encryption key from the owner key", function () {
  491. const alg = new PDF20();
  492. const password = new Uint8Array([111, 119, 110, 101, 114]);
  493. const ownerKeySalt = new Uint8Array([29, 208, 185, 46, 11, 76, 135, 149]);
  494. const ownerEncryption = new Uint8Array([
  495. 209, 73, 224, 77, 103, 155, 201, 181, 190, 68, 223, 20, 62, 90, 56, 210,
  496. 5, 240, 178, 128, 238, 124, 68, 254, 253, 244, 62, 108, 208, 135, 10,
  497. 251,
  498. ]);
  499. const uBytes = new Uint8Array([
  500. 94, 230, 205, 75, 166, 99, 250, 76, 219, 128, 17, 85, 57, 17, 33, 164,
  501. 150, 46, 103, 176, 160, 156, 187, 233, 166, 223, 163, 253, 147, 235, 95,
  502. 184, 83, 245, 146, 101, 198, 247, 34, 198, 191, 11, 16, 94, 237, 216,
  503. 20, 175,
  504. ]);
  505. const result = alg.getOwnerKey(
  506. password,
  507. ownerKeySalt,
  508. uBytes,
  509. ownerEncryption
  510. );
  511. const expected = new Uint8Array([
  512. 42, 218, 213, 39, 73, 91, 72, 79, 67, 38, 248, 133, 18, 189, 61, 34,
  513. 107, 79, 29, 56, 59, 181, 213, 118, 113, 34, 65, 210, 87, 174, 22, 239,
  514. ]);
  515. expect(result).toEqual(expected);
  516. });
  517. });
  518. });
  519. describe("CipherTransformFactory", function () {
  520. function buildDict(map) {
  521. const dict = new Dict();
  522. for (const key in map) {
  523. dict.set(key, map[key]);
  524. }
  525. return dict;
  526. }
  527. function ensurePasswordCorrect(dict, fileId, password) {
  528. try {
  529. const factory = new CipherTransformFactory(dict, fileId, password);
  530. expect("createCipherTransform" in factory).toEqual(true);
  531. } catch (ex) {
  532. // Shouldn't get here.
  533. expect(false).toEqual(true);
  534. }
  535. }
  536. function ensurePasswordNeeded(dict, fileId, password) {
  537. try {
  538. // eslint-disable-next-line no-new
  539. new CipherTransformFactory(dict, fileId, password);
  540. // Shouldn't get here.
  541. expect(false).toEqual(true);
  542. } catch (ex) {
  543. expect(ex instanceof PasswordException).toEqual(true);
  544. expect(ex.code).toEqual(PasswordResponses.NEED_PASSWORD);
  545. }
  546. }
  547. function ensurePasswordIncorrect(dict, fileId, password) {
  548. try {
  549. // eslint-disable-next-line no-new
  550. new CipherTransformFactory(dict, fileId, password);
  551. // Shouldn't get here.
  552. expect(false).toEqual(true);
  553. } catch (ex) {
  554. expect(ex instanceof PasswordException).toEqual(true);
  555. expect(ex.code).toEqual(PasswordResponses.INCORRECT_PASSWORD);
  556. }
  557. }
  558. function ensureAESEncryptedStringHasCorrectLength(
  559. dict,
  560. fileId,
  561. password,
  562. string
  563. ) {
  564. const factory = new CipherTransformFactory(dict, fileId, password);
  565. const cipher = factory.createCipherTransform(123, 0);
  566. const encrypted = cipher.encryptString(string);
  567. // The final length is a multiple of 16.
  568. // If the initial string has a length which is a multiple of 16
  569. // then 16 chars of padding are added.
  570. // So we've the mapping:
  571. // - length: [0-15] => new length: 16
  572. // - length: [16-31] => new length: 32
  573. // - length: [32-47] => new length: 48
  574. // ...
  575. expect(encrypted.length).toEqual(
  576. 16 /* initialization vector length */ +
  577. 16 * Math.ceil((string.length + 1) / 16)
  578. );
  579. }
  580. function ensureEncryptDecryptIsIdentity(dict, fileId, password, string) {
  581. const factory = new CipherTransformFactory(dict, fileId, password);
  582. const cipher = factory.createCipherTransform(123, 0);
  583. const encrypted = cipher.encryptString(string);
  584. const decrypted = cipher.decryptString(encrypted);
  585. expect(string).toEqual(decrypted);
  586. }
  587. let fileId1, fileId2, dict1, dict2, dict3;
  588. let aes256Dict, aes256IsoDict, aes256BlankDict, aes256IsoBlankDict;
  589. beforeAll(function () {
  590. fileId1 = unescape("%F6%C6%AF%17%F3rR%8DRM%9A%80%D1%EF%DF%18");
  591. fileId2 = unescape("%3CL_%3AD%96%AF@%9A%9D%B3%3Cx%1Cv%AC");
  592. dict1 = buildDict({
  593. Filter: Name.get("Standard"),
  594. V: 2,
  595. Length: 128,
  596. O: unescape(
  597. "%80%C3%04%96%91o%20sl%3A%E6%1B%13T%91%F2%0DV%12%E3%FF%5E%B" +
  598. "B%E9VO%D8k%9A%CA%7C%5D"
  599. ),
  600. U: unescape(
  601. "j%0C%8D%3EY%19%00%BCjd%7D%91%BD%AA%00%18%00%00%00%00%00%00" +
  602. "%00%00%00%00%00%00%00%00%00%00"
  603. ),
  604. P: -1028,
  605. R: 3,
  606. });
  607. dict2 = buildDict({
  608. Filter: Name.get("Standard"),
  609. V: 4,
  610. Length: 128,
  611. O: unescape(
  612. "sF%14v.y5%27%DB%97%0A5%22%B3%E1%D4%AD%BD%9B%3C%B4%A5%89u%1" +
  613. "5%B2Y%F1h%D9%E9%F4"
  614. ),
  615. U: unescape(
  616. "%93%04%89%A9%BF%8AE%A6%88%A2%DB%C2%A0%A8gn%00%00%00%00%00%" +
  617. "00%00%00%00%00%00%00%00%00%00%00"
  618. ),
  619. P: -1084,
  620. R: 4,
  621. });
  622. dict3 = {
  623. Filter: Name.get("Standard"),
  624. V: 5,
  625. Length: 256,
  626. O: unescape(
  627. "%3Cb%89%233e%C8%98%D2%B2%E2%E4%86%CD%A3%18%CC%7E%B1%24j2%2" +
  628. "4%7D%D2%AC%ABx%DEl%8Bs%F3vG%99%80%11e%3E%C8%F5%F2%0C%DA%7B" +
  629. "%18x"
  630. ),
  631. U: unescape(
  632. "%83%F2%8F%A0W%02%8A%86O%FD%BD%AD%E0I%90%F1%BEQ%C5%0F%F9i%9" +
  633. "1%97%0F%C2A%03%01%7E%BB%DDu%A9%04%20%9Fe%16%DC%A8%5E%D7%C0" +
  634. "d%26%BC%28"
  635. ),
  636. OE: unescape(
  637. "%D5%CA%0E%BDnLF%BF%06%C3%0A%BE%9Dd%90U%08%3E%7B%B2%9C%E52" +
  638. "%28%E5%D86%DE%22%26j%DF"
  639. ),
  640. UE: unescape(
  641. "%23%96%C3%A9%F533%FF%9E%9E%21%F2%E7K%7D%BE%19%7E%ACr%C3%F" +
  642. "4%89%F5%EA%A5*J%3C%26%11%11"
  643. ),
  644. Perms: unescape("%D8%FC%844%E5e%0DB%5D%7Ff%FD%3COMM"),
  645. P: -1084,
  646. R: 5,
  647. };
  648. aes256Dict = buildDict(dict3);
  649. aes256IsoDict = buildDict({
  650. Filter: Name.get("Standard"),
  651. V: 5,
  652. Length: 256,
  653. O: unescape(
  654. "X%E8%3E6%F5%1A%F5%D1%89%7B%DDH%C71%25%D9%1FJs%A7%7F%9E%B0M" +
  655. "-%A3W/%27Z%D9%8D%8E%E8%A9%D0%CA%D6%05%B9%1D%D0%B9.%0BL%87%" +
  656. "95"
  657. ),
  658. U: unescape(
  659. "%5E%E6%CDK%A6c%FAL%DB%80%11U9%11%21%A4%96.g%B0%A0%9C%BB%E9" +
  660. "%A6%DF%A3%FD%93%EB_%B8S%F5%92e%C6%F7%22%C6%BF%0B%10%5E%ED%" +
  661. "D8%14%AF"
  662. ),
  663. OE: unescape(
  664. "%D1I%E0Mg%9B%C9%B5%BED%DF%14%3EZ8%D2%05%F0%B2%80%EE%7CD%F" +
  665. "E%FD%F4%3El%D0%87%0A%FB"
  666. ),
  667. UE: unescape(
  668. "y%D0%02%B5%E6Y%9C%3C%FD%8F%D4%1CT%B4%C4%B1%AD%80%DDk.%14%" +
  669. "5E%BA%873_%18%14%DF%FE%24"
  670. ),
  671. Perms: unescape("l%AD%0F%A0%EBM%86WM%3E%CB%B5%E0X%C97"),
  672. P: -1084,
  673. R: 6,
  674. });
  675. aes256BlankDict = buildDict({
  676. Filter: Name.get("Standard"),
  677. V: 5,
  678. Length: 256,
  679. O: unescape(
  680. "%B8p%04%C3g%26%FCW%CCN%D4%16%A1%E8%950YZ%C9%9E%B1-%97%F3%F" +
  681. "E%03%13%19ffZn%8F%F5%EB%EC%CC5sV%10e%CEl%B5%E9G%C1"
  682. ),
  683. U: unescape(
  684. "%83%D4zi%F1O0%961%12%CC%82%CB%CA%BF5y%FD%21%EB%E4%D1%B5%1D" +
  685. "%D6%FA%14%F3%BE%8Fqs%EF%88%DE%E2%E8%DC%F55%E4%B8%16%C8%14%" +
  686. "8De%1E"
  687. ),
  688. OE: unescape(
  689. "%8F%19%E8%D4%27%D5%07%CA%C6%A1%11%A6a%5Bt%F4%DF%0F%84%29%" +
  690. "0F%E4%EFF7%5B%5B%11%A0%8F%17e"
  691. ),
  692. UE: unescape(
  693. "%81%F5%5D%B0%28%81%E4%7F_%7C%8F%85b%A0%7E%10%D0%88lx%7B%7" +
  694. "EJ%5E%912%B6d%12%27%05%F6"
  695. ),
  696. Perms: unescape("%86%1562%0D%AE%A2%FB%5D%3B%22%3Dq%12%B2H"),
  697. P: -1084,
  698. R: 5,
  699. });
  700. aes256IsoBlankDict = buildDict({
  701. Filter: Name.get("Standard"),
  702. V: 5,
  703. Length: 256,
  704. O: unescape(
  705. "%F7%DB%99U%A6M%ACk%AF%CF%D7AFw%E9%C1%91%CBDgI%23R%CF%0C%15" +
  706. "r%D74%0D%CE%E9%91@%E4%98QF%BF%88%7Ej%DE%AD%8F%F4@%C1"
  707. ),
  708. U: unescape(
  709. "%1A%A9%DC%918%83%93k%29%5B%117%B16%DB%E8%8E%FE%28%E5%89%D4" +
  710. "%0E%AD%12%3B%7DN_6fez%8BG%18%05YOh%7DZH%A3Z%87%17*"
  711. ),
  712. OE: unescape(
  713. "%A4a%88%20h%1B%7F%CD%D5%CAc%D8R%83%E5%D6%1C%D2%98%07%984%" +
  714. "BA%AF%1B%B4%7FQ%F8%1EU%7D"
  715. ),
  716. UE: unescape(
  717. "%A0%0AZU%27%1D%27%2C%0B%FE%0E%A2L%F9b%5E%A1%B9%D6v7b%B26%" +
  718. "A9N%99%F1%A4Deq"
  719. ),
  720. Perms: unescape("%03%F2i%07%0D%C3%F9%F2%28%80%B7%F5%DD%D1c%EB"),
  721. P: -1084,
  722. R: 6,
  723. });
  724. });
  725. afterAll(function () {
  726. fileId1 = fileId2 = dict1 = dict2 = dict3 = null;
  727. aes256Dict = aes256IsoDict = aes256BlankDict = aes256IsoBlankDict = null;
  728. });
  729. describe("#ctor", function () {
  730. describe("AES256 Revision 5", function () {
  731. it("should accept user password", function () {
  732. ensurePasswordCorrect(aes256Dict, fileId1, "user");
  733. });
  734. it("should accept owner password", function () {
  735. ensurePasswordCorrect(aes256Dict, fileId1, "owner");
  736. });
  737. it("should not accept blank password", function () {
  738. ensurePasswordNeeded(aes256Dict, fileId1);
  739. });
  740. it("should not accept wrong password", function () {
  741. ensurePasswordIncorrect(aes256Dict, fileId1, "wrong");
  742. });
  743. it("should accept blank password", function () {
  744. ensurePasswordCorrect(aes256BlankDict, fileId1);
  745. });
  746. });
  747. describe("AES256 Revision 6", function () {
  748. it("should accept user password", function () {
  749. ensurePasswordCorrect(aes256IsoDict, fileId1, "user");
  750. });
  751. it("should accept owner password", function () {
  752. ensurePasswordCorrect(aes256IsoDict, fileId1, "owner");
  753. });
  754. it("should not accept blank password", function () {
  755. ensurePasswordNeeded(aes256IsoDict, fileId1);
  756. });
  757. it("should not accept wrong password", function () {
  758. ensurePasswordIncorrect(aes256IsoDict, fileId1, "wrong");
  759. });
  760. it("should accept blank password", function () {
  761. ensurePasswordCorrect(aes256IsoBlankDict, fileId1);
  762. });
  763. });
  764. it("should accept user password", function () {
  765. ensurePasswordCorrect(dict1, fileId1, "123456");
  766. });
  767. it("should accept owner password", function () {
  768. ensurePasswordCorrect(dict1, fileId1, "654321");
  769. });
  770. it("should not accept blank password", function () {
  771. ensurePasswordNeeded(dict1, fileId1);
  772. });
  773. it("should not accept wrong password", function () {
  774. ensurePasswordIncorrect(dict1, fileId1, "wrong");
  775. });
  776. it("should accept blank password", function () {
  777. ensurePasswordCorrect(dict2, fileId2);
  778. });
  779. });
  780. describe("Encrypt and decrypt", function () {
  781. it("should encrypt and decrypt using ARCFour", function () {
  782. dict3.CF = buildDict({
  783. Identity: buildDict({
  784. CFM: Name.get("V2"),
  785. }),
  786. });
  787. const dict = buildDict(dict3);
  788. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "hello world");
  789. });
  790. it("should encrypt and decrypt using AES128", function () {
  791. dict3.CF = buildDict({
  792. Identity: buildDict({
  793. CFM: Name.get("AESV2"),
  794. }),
  795. });
  796. const dict = buildDict(dict3);
  797. // 0 char
  798. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "");
  799. // 1 char
  800. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "a");
  801. // 2 chars
  802. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aa");
  803. // 16 chars
  804. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaaaaaaaaaaaaaa");
  805. // 19 chars
  806. ensureEncryptDecryptIsIdentity(
  807. dict,
  808. fileId1,
  809. "user",
  810. "aaaaaaaaaaaaaaaaaaa"
  811. );
  812. });
  813. it("should encrypt and decrypt using AES256", function () {
  814. dict3.CF = buildDict({
  815. Identity: buildDict({
  816. CFM: Name.get("AESV3"),
  817. }),
  818. });
  819. const dict = buildDict(dict3);
  820. // 0 chars
  821. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "");
  822. // 4 chars
  823. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaa");
  824. // 5 chars
  825. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaaa");
  826. // 16 chars
  827. ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaaaaaaaaaaaaaa");
  828. // 22 chars
  829. ensureEncryptDecryptIsIdentity(
  830. dict,
  831. fileId1,
  832. "user",
  833. "aaaaaaaaaaaaaaaaaaaaaa"
  834. );
  835. });
  836. it("should encrypt and have the correct length using AES128", function () {
  837. dict3.CF = buildDict({
  838. Identity: buildDict({
  839. CFM: Name.get("AESV2"),
  840. }),
  841. });
  842. const dict = buildDict(dict3);
  843. // 0 char
  844. ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "");
  845. // 1 char
  846. ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "a");
  847. // 2 chars
  848. ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aa");
  849. // 16 chars
  850. ensureAESEncryptedStringHasCorrectLength(
  851. dict,
  852. fileId1,
  853. "user",
  854. "aaaaaaaaaaaaaaaa"
  855. );
  856. // 19 chars
  857. ensureAESEncryptedStringHasCorrectLength(
  858. dict,
  859. fileId1,
  860. "user",
  861. "aaaaaaaaaaaaaaaaaaa"
  862. );
  863. });
  864. it("should encrypt and have the correct length using AES256", function () {
  865. dict3.CF = buildDict({
  866. Identity: buildDict({
  867. CFM: Name.get("AESV3"),
  868. }),
  869. });
  870. const dict = buildDict(dict3);
  871. // 0 char
  872. ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "");
  873. // 4 chars
  874. ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaa");
  875. // 5 chars
  876. ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaaa");
  877. // 16 chars
  878. ensureAESEncryptedStringHasCorrectLength(
  879. dict,
  880. fileId1,
  881. "user",
  882. "aaaaaaaaaaaaaaaa"
  883. );
  884. // 22 chars
  885. ensureAESEncryptedStringHasCorrectLength(
  886. dict,
  887. fileId1,
  888. "user",
  889. "aaaaaaaaaaaaaaaaaaaaaa"
  890. );
  891. });
  892. });
  893. });