content_encryption.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import { concat, uint64be } from './buffer_utils.js';
  2. import { checkEncCryptoKey } from './crypto_key.js';
  3. import { invalidKeyInput } from './invalid_key_input.js';
  4. import { JOSENotSupported, JWEDecryptionFailed, JWEInvalid } from '../util/errors.js';
  5. import { isCryptoKey } from './is_key_like.js';
  6. export function cekLength(alg) {
  7. switch (alg) {
  8. case 'A128GCM':
  9. return 128;
  10. case 'A192GCM':
  11. return 192;
  12. case 'A256GCM':
  13. case 'A128CBC-HS256':
  14. return 256;
  15. case 'A192CBC-HS384':
  16. return 384;
  17. case 'A256CBC-HS512':
  18. return 512;
  19. default:
  20. throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`);
  21. }
  22. }
  23. export const generateCek = (alg) => crypto.getRandomValues(new Uint8Array(cekLength(alg) >> 3));
  24. function checkCekLength(cek, expected) {
  25. const actual = cek.byteLength << 3;
  26. if (actual !== expected) {
  27. throw new JWEInvalid(`Invalid Content Encryption Key length. Expected ${expected} bits, got ${actual} bits`);
  28. }
  29. }
  30. function ivBitLength(alg) {
  31. switch (alg) {
  32. case 'A128GCM':
  33. case 'A128GCMKW':
  34. case 'A192GCM':
  35. case 'A192GCMKW':
  36. case 'A256GCM':
  37. case 'A256GCMKW':
  38. return 96;
  39. case 'A128CBC-HS256':
  40. case 'A192CBC-HS384':
  41. case 'A256CBC-HS512':
  42. return 128;
  43. default:
  44. throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`);
  45. }
  46. }
  47. export const generateIv = (alg) => crypto.getRandomValues(new Uint8Array(ivBitLength(alg) >> 3));
  48. export function checkIvLength(enc, iv) {
  49. if (iv.length << 3 !== ivBitLength(enc)) {
  50. throw new JWEInvalid('Invalid Initialization Vector length');
  51. }
  52. }
  53. async function cbcKeySetup(enc, cek, usage) {
  54. if (!(cek instanceof Uint8Array)) {
  55. throw new TypeError(invalidKeyInput(cek, 'Uint8Array'));
  56. }
  57. const keySize = parseInt(enc.slice(1, 4), 10);
  58. const encKey = await crypto.subtle.importKey('raw', cek.subarray(keySize >> 3), 'AES-CBC', false, [usage]);
  59. const macKey = await crypto.subtle.importKey('raw', cek.subarray(0, keySize >> 3), {
  60. hash: `SHA-${keySize << 1}`,
  61. name: 'HMAC',
  62. }, false, ['sign']);
  63. return { encKey, macKey, keySize };
  64. }
  65. async function cbcHmacTag(macKey, macData, keySize) {
  66. return new Uint8Array((await crypto.subtle.sign('HMAC', macKey, macData)).slice(0, keySize >> 3));
  67. }
  68. async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
  69. const { encKey, macKey, keySize } = await cbcKeySetup(enc, cek, 'encrypt');
  70. const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
  71. iv: iv,
  72. name: 'AES-CBC',
  73. }, encKey, plaintext));
  74. const macData = concat(aad, iv, ciphertext, uint64be(aad.length << 3));
  75. const tag = await cbcHmacTag(macKey, macData, keySize);
  76. return { ciphertext, tag, iv };
  77. }
  78. async function timingSafeEqual(a, b) {
  79. if (!(a instanceof Uint8Array)) {
  80. throw new TypeError('First argument must be a buffer');
  81. }
  82. if (!(b instanceof Uint8Array)) {
  83. throw new TypeError('Second argument must be a buffer');
  84. }
  85. const algorithm = { name: 'HMAC', hash: 'SHA-256' };
  86. const key = (await crypto.subtle.generateKey(algorithm, false, ['sign']));
  87. const aHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, a));
  88. const bHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, b));
  89. let out = 0;
  90. let i = -1;
  91. while (++i < 32) {
  92. out |= aHmac[i] ^ bHmac[i];
  93. }
  94. return out === 0;
  95. }
  96. async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
  97. const { encKey, macKey, keySize } = await cbcKeySetup(enc, cek, 'decrypt');
  98. const macData = concat(aad, iv, ciphertext, uint64be(aad.length << 3));
  99. const expectedTag = await cbcHmacTag(macKey, macData, keySize);
  100. let macCheckPassed;
  101. try {
  102. macCheckPassed = await timingSafeEqual(tag, expectedTag);
  103. }
  104. catch {
  105. }
  106. if (!macCheckPassed) {
  107. throw new JWEDecryptionFailed();
  108. }
  109. let plaintext;
  110. try {
  111. plaintext = new Uint8Array(await crypto.subtle.decrypt({ iv: iv, name: 'AES-CBC' }, encKey, ciphertext));
  112. }
  113. catch {
  114. }
  115. if (!plaintext) {
  116. throw new JWEDecryptionFailed();
  117. }
  118. return plaintext;
  119. }
  120. async function gcmEncrypt(enc, plaintext, cek, iv, aad) {
  121. let encKey;
  122. if (cek instanceof Uint8Array) {
  123. encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['encrypt']);
  124. }
  125. else {
  126. checkEncCryptoKey(cek, enc, 'encrypt');
  127. encKey = cek;
  128. }
  129. const encrypted = new Uint8Array(await crypto.subtle.encrypt({
  130. additionalData: aad,
  131. iv: iv,
  132. name: 'AES-GCM',
  133. tagLength: 128,
  134. }, encKey, plaintext));
  135. const tag = encrypted.slice(-16);
  136. const ciphertext = encrypted.slice(0, -16);
  137. return { ciphertext, tag, iv };
  138. }
  139. async function gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) {
  140. let encKey;
  141. if (cek instanceof Uint8Array) {
  142. encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['decrypt']);
  143. }
  144. else {
  145. checkEncCryptoKey(cek, enc, 'decrypt');
  146. encKey = cek;
  147. }
  148. try {
  149. return new Uint8Array(await crypto.subtle.decrypt({
  150. additionalData: aad,
  151. iv: iv,
  152. name: 'AES-GCM',
  153. tagLength: 128,
  154. }, encKey, concat(ciphertext, tag)));
  155. }
  156. catch {
  157. throw new JWEDecryptionFailed();
  158. }
  159. }
  160. const unsupportedEnc = 'Unsupported JWE Content Encryption Algorithm';
  161. export async function encrypt(enc, plaintext, cek, iv, aad) {
  162. if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) {
  163. throw new TypeError(invalidKeyInput(cek, 'CryptoKey', 'KeyObject', 'Uint8Array', 'JSON Web Key'));
  164. }
  165. if (iv) {
  166. checkIvLength(enc, iv);
  167. }
  168. else {
  169. iv = generateIv(enc);
  170. }
  171. switch (enc) {
  172. case 'A128CBC-HS256':
  173. case 'A192CBC-HS384':
  174. case 'A256CBC-HS512':
  175. if (cek instanceof Uint8Array) {
  176. checkCekLength(cek, parseInt(enc.slice(-3), 10));
  177. }
  178. return cbcEncrypt(enc, plaintext, cek, iv, aad);
  179. case 'A128GCM':
  180. case 'A192GCM':
  181. case 'A256GCM':
  182. if (cek instanceof Uint8Array) {
  183. checkCekLength(cek, parseInt(enc.slice(1, 4), 10));
  184. }
  185. return gcmEncrypt(enc, plaintext, cek, iv, aad);
  186. default:
  187. throw new JOSENotSupported(unsupportedEnc);
  188. }
  189. }
  190. export async function decrypt(enc, cek, ciphertext, iv, tag, aad) {
  191. if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) {
  192. throw new TypeError(invalidKeyInput(cek, 'CryptoKey', 'KeyObject', 'Uint8Array', 'JSON Web Key'));
  193. }
  194. if (!iv) {
  195. throw new JWEInvalid('JWE Initialization Vector missing');
  196. }
  197. if (!tag) {
  198. throw new JWEInvalid('JWE Authentication Tag missing');
  199. }
  200. checkIvLength(enc, iv);
  201. switch (enc) {
  202. case 'A128CBC-HS256':
  203. case 'A192CBC-HS384':
  204. case 'A256CBC-HS512':
  205. if (cek instanceof Uint8Array)
  206. checkCekLength(cek, parseInt(enc.slice(-3), 10));
  207. return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad);
  208. case 'A128GCM':
  209. case 'A192GCM':
  210. case 'A256GCM':
  211. if (cek instanceof Uint8Array)
  212. checkCekLength(cek, parseInt(enc.slice(1, 4), 10));
  213. return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad);
  214. default:
  215. throw new JOSENotSupported(unsupportedEnc);
  216. }
  217. }