Skip to content

Commit

Permalink
Implement initial crypto.subtle.decrypt() for "AES-CBC"
Browse files Browse the repository at this point in the history
  • Loading branch information
TooTallNate committed Dec 20, 2024
1 parent 30aa733 commit c2244dd
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 6 deletions.
75 changes: 75 additions & 0 deletions apps/tests/src/crypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ function toHex(arr: ArrayBuffer) {
.join('');
}

function fromHex(hex: string): ArrayBuffer {
const arr = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
arr[i / 2] = parseInt(hex.substr(i, 2), 16);
}
return arr.buffer;
}

test('`crypto.getRandomValues()`', () => {
const arr = new Uint8Array(5);
assert.equal(toHex(arr.buffer), '0000000000');
Expand Down Expand Up @@ -49,4 +57,71 @@ test("`crypto.subtle.digest('sha-512')`", async () => {
);
});

test("`crypto.subtle.importKey()` with 'raw' format and 'AES-CBC' algorithm`", async () => {
const keyData = new Uint8Array([
188, 136, 184, 200, 227, 200, 149, 203, 33, 186, 60, 145, 54, 19, 92, 88,
]);

const key = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'AES-CBC' },
false,
['encrypt', 'decrypt'],
);

assert.instance(key, CryptoKey);
assert.equal(key.algorithm.name, 'AES-CBC');
// @ts-expect-error `length` is not defined on `KeyAlgorithm`
assert.equal(key.algorithm.length, 128);
});

test("`crypto.subtle.encrypt()` with 'AES-CBC' algorithm`", async () => {
const keyData = new Uint8Array([
188, 136, 184, 200, 227, 200, 149, 203, 33, 186, 60, 145, 54, 19, 92, 88,
]);
const iv = new Uint8Array([
38, 89, 172, 231, 98, 165, 172, 212, 137, 184, 41, 162, 105, 26, 119, 158,
]);

const key = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'AES-CBC' },
false,
['encrypt'],
);

const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-CBC', iv: iv.buffer },
key,
new TextEncoder().encode('hello'),
);

assert.instance(ciphertext, ArrayBuffer);
assert.equal(toHex(ciphertext), '4b4fddd4b88f2e6a36500f89aa177d0d');
});

test("`crypto.subtle.decrypt()` with 'AES-CBC' algorithm`", async () => {
const keyData = new Uint8Array([
188, 136, 184, 200, 227, 200, 149, 203, 33, 186, 60, 145, 54, 19, 92, 88,
]);
const iv = new Uint8Array([
38, 89, 172, 231, 98, 165, 172, 212, 137, 184, 41, 162, 105, 26, 119, 158,
]);

const key = await crypto.subtle.importKey('raw', keyData, 'AES-CBC', false, [
'decrypt',
]);

const plaintext = await crypto.subtle.decrypt(
{ name: 'AES-CBC', iv },
key,
fromHex('4b4fddd4b88f2e6a36500f89aa177d0d'),
);

assert.instance(plaintext, ArrayBuffer);
assert.equal(new TextDecoder().decode(new Uint8Array(plaintext)), 'hello');
});

test.run();
3 changes: 2 additions & 1 deletion packages/runtime/src/$.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import type { URL, URLSearchParams } from './polyfills/url';
import type { DOMPoint, DOMPointInit } from './dompoint';
import type { DOMMatrix, DOMMatrixReadOnly, DOMMatrixInit } from './dommatrix';
import type { Gamepad, GamepadButton } from './navigator/gamepad';
import type { CryptoKey } from './crypto';
import type { CryptoKey, SubtleCrypto } from './crypto';
import type { Algorithm } from './types';
import type { PromiseState } from '@nx.js/inspect';

Expand Down Expand Up @@ -127,6 +127,7 @@ export interface Init {
keyUsages: KeyUsage[],
): CryptoKey;
cryptoKeyInit(c: ClassOf<CryptoKey>): void;
cryptoSubtleInit(c: ClassOf<SubtleCrypto>): void;
cryptoEncrypt(
algorithm: Algorithm,
key: CryptoKey,
Expand Down
18 changes: 17 additions & 1 deletion packages/runtime/src/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
bufferSourceToArrayBuffer,
createInternal,
def,
stub,
} from './utils';
import { CryptoKey } from './crypto/crypto-key';
import type {
Expand Down Expand Up @@ -140,6 +141,14 @@ export class SubtleCrypto implements globalThis.SubtleCrypto {
assertInternalConstructor(arguments);
}

/**
* Decrypts some encrypted data.
*
* It takes as arguments a key to decrypt with, some optional extra parameters, and the data to decrypt (also known as "ciphertext").
*
* @returns A Promise which will be fulfilled with the decrypted data (also known as "plaintext") as an `ArrayBuffer`.
* @see https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt
*/
decrypt(
algorithm:
| AlgorithmIdentifier
Expand All @@ -150,7 +159,7 @@ export class SubtleCrypto implements globalThis.SubtleCrypto {
key: CryptoKey,
data: BufferSource,
): Promise<ArrayBuffer> {
throw new Error('Method not implemented.');
stub();
}

deriveBits(
Expand Down Expand Up @@ -278,6 +287,12 @@ export class SubtleCrypto implements globalThis.SubtleCrypto {
throw new Error('Method not implemented.');
}

/**
* Takes as input a key in an external, portable format and gives you a
* {@link CryptoKey} object that you can use in the Web Crypto API.
*
* @see https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey
*/
importKey(
format: 'jwk',
keyData: JsonWebKey,
Expand Down Expand Up @@ -381,6 +396,7 @@ export class SubtleCrypto implements globalThis.SubtleCrypto {
throw new Error('Method not implemented.');
}
}
$.cryptoSubtleInit(SubtleCrypto);
def(SubtleCrypto);

function normalizeAlgorithm(algorithm: AlgorithmIdentifier): Algorithm {
Expand Down
Loading

0 comments on commit c2244dd

Please sign in to comment.