summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/internal/crypto/cipher.ts
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2023-09-05 22:31:50 -0700
committerGitHub <noreply@github.com>2023-09-06 11:01:50 +0530
commit9befa566ec3ef4594fd7ffb2cbdf5b34d9705e16 (patch)
treeb70936cb5bb1e1f73a84ccf3dc9f5edfe085f7a3 /ext/node/polyfills/internal/crypto/cipher.ts
parenta0af53fea134f712408fa2d2d20078dd8ca7d0e6 (diff)
fix(ext/node): implement AES GCM cipher (#20368)
Adds support for AES-GCM 128/256 bit keys in `node:crypto` and `setAAD()`, `setAuthTag()` and `getAuthTag()` Uses https://github.com/littledivy/aead-gcm-stream Fixes https://github.com/denoland/deno/issues/19836 https://github.com/denoland/deno/issues/20353
Diffstat (limited to 'ext/node/polyfills/internal/crypto/cipher.ts')
-rw-r--r--ext/node/polyfills/internal/crypto/cipher.ts87
1 files changed, 70 insertions, 17 deletions
diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts
index 5622576cd..cf1641326 100644
--- a/ext/node/polyfills/internal/crypto/cipher.ts
+++ b/ext/node/polyfills/internal/crypto/cipher.ts
@@ -36,6 +36,8 @@ function isStringOrBuffer(val) {
const { ops, encode } = globalThis.__bootstrap.core;
+const NO_TAG = new Uint8Array();
+
export type CipherCCMTypes =
| "aes-128-ccm"
| "aes-192-ccm"
@@ -143,6 +145,10 @@ export class Cipheriv extends Transform implements Cipher {
/** plaintext data cache */
#cache: BlockModeCache;
+ #needsBlockCache: boolean;
+
+ #authTag?: Buffer;
+
constructor(
cipher: string,
key: CipherKey,
@@ -162,6 +168,8 @@ export class Cipheriv extends Transform implements Cipher {
});
this.#cache = new BlockModeCache(false);
this.#context = ops.op_node_create_cipheriv(cipher, toU8(key), toU8(iv));
+ this.#needsBlockCache =
+ !(cipher == "aes-128-gcm" || cipher == "aes-256-gcm");
if (this.#context == 0) {
throw new TypeError("Unknown cipher");
}
@@ -169,21 +177,29 @@ export class Cipheriv extends Transform implements Cipher {
final(encoding: string = getDefaultEncoding()): Buffer | string {
const buf = new Buffer(16);
- ops.op_node_cipheriv_final(this.#context, this.#cache.cache, buf);
+ const maybeTag = ops.op_node_cipheriv_final(
+ this.#context,
+ this.#cache.cache,
+ buf,
+ );
+ if (maybeTag) {
+ this.#authTag = Buffer.from(maybeTag);
+ return encoding === "buffer" ? Buffer.from([]) : "";
+ }
return encoding === "buffer" ? buf : buf.toString(encoding);
}
getAuthTag(): Buffer {
- notImplemented("crypto.Cipheriv.prototype.getAuthTag");
+ return this.#authTag!;
}
setAAD(
- _buffer: ArrayBufferView,
+ buffer: ArrayBufferView,
_options?: {
plaintextLength: number;
},
): this {
- notImplemented("crypto.Cipheriv.prototype.setAAD");
+ ops.op_node_cipheriv_set_aad(this.#context, buffer);
return this;
}
@@ -198,13 +214,23 @@ export class Cipheriv extends Transform implements Cipher {
outputEncoding: Encoding = getDefaultEncoding(),
): Buffer | string {
// TODO(kt3k): throw ERR_INVALID_ARG_TYPE if data is not string, Buffer, or ArrayBufferView
+ let buf = data;
if (typeof data === "string" && typeof inputEncoding === "string") {
- this.#cache.add(Buffer.from(data, inputEncoding));
- } else {
- this.#cache.add(data);
+ buf = Buffer.from(data, inputEncoding);
}
- const input = this.#cache.get();
+
let output;
+ if (!this.#needsBlockCache) {
+ output = Buffer.allocUnsafe(buf.length);
+ ops.op_node_cipheriv_encrypt(this.#context, buf, output);
+ return outputEncoding === "buffer"
+ ? output
+ : output.toString(outputEncoding);
+ }
+
+ this.#cache.add(buf);
+ const input = this.#cache.get();
+
if (input === null) {
output = Buffer.alloc(0);
} else {
@@ -262,6 +288,10 @@ export class Decipheriv extends Transform implements Cipher {
/** ciphertext data cache */
#cache: BlockModeCache;
+ #needsBlockCache: boolean;
+
+ #authTag?: BinaryLike;
+
constructor(
cipher: string,
key: CipherKey,
@@ -281,6 +311,8 @@ export class Decipheriv extends Transform implements Cipher {
});
this.#cache = new BlockModeCache(true);
this.#context = ops.op_node_create_decipheriv(cipher, toU8(key), toU8(iv));
+ this.#needsBlockCache =
+ !(cipher == "aes-128-gcm" || cipher == "aes-256-gcm");
if (this.#context == 0) {
throw new TypeError("Unknown cipher");
}
@@ -288,22 +320,34 @@ export class Decipheriv extends Transform implements Cipher {
final(encoding: string = getDefaultEncoding()): Buffer | string {
let buf = new Buffer(16);
- ops.op_node_decipheriv_final(this.#context, this.#cache.cache, buf);
+ ops.op_node_decipheriv_final(
+ this.#context,
+ this.#cache.cache,
+ buf,
+ this.#authTag || NO_TAG,
+ );
+
+ if (!this.#needsBlockCache) {
+ return encoding === "buffer" ? Buffer.from([]) : "";
+ }
+
buf = buf.subarray(0, 16 - buf.at(-1)); // Padded in Pkcs7 mode
return encoding === "buffer" ? buf : buf.toString(encoding);
}
setAAD(
- _buffer: ArrayBufferView,
+ buffer: ArrayBufferView,
_options?: {
plaintextLength: number;
},
): this {
- notImplemented("crypto.Decipheriv.prototype.setAAD");
+ ops.op_node_decipheriv_set_aad(this.#context, buffer);
+ return this;
}
- setAuthTag(_buffer: BinaryLike, _encoding?: string): this {
- notImplemented("crypto.Decipheriv.prototype.setAuthTag");
+ setAuthTag(buffer: BinaryLike, _encoding?: string): this {
+ this.#authTag = buffer;
+ return this;
}
setAutoPadding(_autoPadding?: boolean): this {
@@ -316,13 +360,22 @@ export class Decipheriv extends Transform implements Cipher {
outputEncoding: Encoding = getDefaultEncoding(),
): Buffer | string {
// TODO(kt3k): throw ERR_INVALID_ARG_TYPE if data is not string, Buffer, or ArrayBufferView
+ let buf = data;
if (typeof data === "string" && typeof inputEncoding === "string") {
- this.#cache.add(Buffer.from(data, inputEncoding));
- } else {
- this.#cache.add(data);
+ buf = Buffer.from(data, inputEncoding);
}
- const input = this.#cache.get();
+
let output;
+ if (!this.#needsBlockCache) {
+ output = Buffer.allocUnsafe(buf.length);
+ ops.op_node_decipheriv_decrypt(this.#context, buf, output);
+ return outputEncoding === "buffer"
+ ? output
+ : output.toString(outputEncoding);
+ }
+
+ this.#cache.add(buf);
+ const input = this.#cache.get();
if (input === null) {
output = Buffer.alloc(0);
} else {