From e4f6bf241a17a99da40e2b708346f9f0c1465501 Mon Sep 17 00:00:00 2001 From: Pascal Perrenoud Date: Tue, 14 May 2024 15:08:02 +0200 Subject: [PATCH] Implement asym box --- src/boxes/asymmetric.ts | 57 +++++++++++++++++++++++++++-------- test/boxes/asymmetric.test.ts | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 test/boxes/asymmetric.test.ts diff --git a/src/boxes/asymmetric.ts b/src/boxes/asymmetric.ts index 98936a1..d0a03fa 100644 --- a/src/boxes/asymmetric.ts +++ b/src/boxes/asymmetric.ts @@ -1,24 +1,57 @@ import {Result} from 'result' +import * as misc from 'misc' -export type PubKey = void -export type PrivKey = void +export type PubKey = CryptoKey +export type PrivKey = CryptoKey -export class PrivateBox { - public static gen_keypair() : [PrivKey, PubKey] { - throw "todo" +const algorithm: RsaOaepParams = {name: "RSA-OAEP"} + +export class PrivateBox { + readonly cipher: Uint8Array; + readonly _phantom!: T; + + private constructor(cipher: Uint8Array) { + this.cipher = cipher } - public static encrypt(key: PubKey, data: Uint8Array) : PrivateBox { - throw "todo" + public static async gen_keypair(extractable: boolean = false): Promise<[PrivKey, PubKey]> { + const keys = await window.crypto.subtle.generateKey( + { + name: "RSA-OAEP", + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: {name: "SHA-512"} + }, + extractable, + ["encrypt", "decrypt"] + ) + + return [keys.privateKey, keys.publicKey] } - public decrypt(key: PrivKey) : Result { - throw "todo" + + public static async encrypt(key: PubKey, data: T): Promise>> { + try { + const cipher = await window.crypto.subtle.encrypt(algorithm, key, data) + return Result.ok(new PrivateBox(new Uint8Array(cipher))) + } catch(_) {} + + return Result.error([]) + } + public async decrypt(key: PrivKey): Promise> { + try { + const plain = await window.crypto.subtle.decrypt(algorithm, key, this.cipher) + return Result.ok(new Uint8Array(plain)) + } catch(_) {} + + return Result.error([]) } public toString() : string { - throw "todo" + return misc.a2b64(new Uint8Array(this.cipher)) } - public static fromString(data: string) : Result> { - throw "todo" + public static fromString(data: string) : Result> { + const res = misc.b642a(data) + if (res.error()) return Result.error([]) + return Result.ok(new PrivateBox(res.unwrap())) } } diff --git a/test/boxes/asymmetric.test.ts b/test/boxes/asymmetric.test.ts new file mode 100644 index 0000000..71b6bb0 --- /dev/null +++ b/test/boxes/asymmetric.test.ts @@ -0,0 +1,56 @@ +import {beforeAll, expect, test} from 'bun:test' + +import * as asymmetric from 'boxes/asymmetric' + +let k1!: [asymmetric.PrivKey, asymmetric.PubKey]; +let k2!: [asymmetric.PrivKey, asymmetric.PubKey]; + +beforeAll(async () => { + k1 = await asymmetric.PrivateBox.gen_keypair(false) + k2 = await asymmetric.PrivateBox.gen_keypair(true) + + expect(k1[0].extractable).toBe(false) + expect(k1[1].extractable).toBe(true) + expect(k2[0].extractable).toBe(true) + expect(k2[1].extractable).toBe(true) +}) + +test('base case', async () => { + const [priv, pub] = k1 + const data = new Uint8Array([1, 2, 3, 4, 5]) + + const box = (await asymmetric.PrivateBox.encrypt(pub, data)).expect("Should encrypt the data") + const result = (await box.decrypt(priv)).expect("Should decrypt the data") + + expect(result).toEqual(data) +}) + +test('toString and fromString are inverses', async () => { + const [priv, pub] = k1 + const data = new Uint8Array([1, 2, 3, 4, 5]) + + const box = (await asymmetric.PrivateBox.encrypt(pub, data)).expect("Should encrypt the data") + const str = box.toString() + const box2 = asymmetric.PrivateBox.fromString(str).expect("Should parse the string") + const plain = (await box2.decrypt(priv)).expect("Should decrypt the data") + + expect(plain).toEqual(data) +}) + +test('Tampered cipher fails', async () => { + const [priv, pub] = k1 + const data = new Uint8Array([1, 2, 3, 4, 5]) + + const box = (await asymmetric.PrivateBox.encrypt(pub, data)).expect("Should encrypt the data") + box.cipher[0] += 1 + ;(await box.decrypt(priv)).expect_err("Should fail to decrypt the data") +}) + +test('Wrong pubkey should fail', async () => { + const [_priv, pub] = k1 + const [priv, _pub] = k2 + const data = new Uint8Array([1, 2, 3, 4, 5]) + + const box = (await asymmetric.PrivateBox.encrypt(pub, data)).expect("Should encrypt the data") + ;(await box.decrypt(priv)).expect_err("Should fail to decrypt the data") +})