diff --git a/packages/core/mod.ts b/packages/core/mod.ts index 5a4363b1c..96e9c4ab6 100644 --- a/packages/core/mod.ts +++ b/packages/core/mod.ts @@ -48,3 +48,4 @@ export { } from "./src/native.ts"; export { DhkemX25519HkdfSha256 } from "./src/kems/dhkemX25519.ts"; +export { DhkemX448HkdfSha512 } from "./src/kems/dhkemX448.ts"; diff --git a/packages/core/src/kems/dhkemPrimitives/x25519.ts b/packages/core/src/kems/dhkemPrimitives/x25519.ts index f7368eb46..03aad1df1 100644 --- a/packages/core/src/kems/dhkemPrimitives/x25519.ts +++ b/packages/core/src/kems/dhkemPrimitives/x25519.ts @@ -1,6 +1,7 @@ import type { DhkemPrimitives, KdfInterface } from "@hpke/common"; import { + base64UrlToBytes, DeriveKeyPairError, DeserializeError, EMPTY, @@ -20,16 +21,6 @@ const PKCS8_ALG_ID_X25519 = new Uint8Array([ 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20, ]); -function base64UrlToBytes(v: string): Uint8Array { - const base64 = v.replace(/-/g, "+").replace(/_/g, "/"); - const byteString = atob(base64); - const ret = new Uint8Array(byteString.length); - for (let i = 0; i < byteString.length; i++) { - ret[i] = byteString.charCodeAt(i); - } - return ret; -} - export class X25519 extends NativeAlgorithm implements DhkemPrimitives { private _hkdf: KdfInterface; private _alg: KeyAlgorithm; diff --git a/packages/core/src/kems/dhkemPrimitives/x448.ts b/packages/core/src/kems/dhkemPrimitives/x448.ts new file mode 100644 index 000000000..5b3032ddb --- /dev/null +++ b/packages/core/src/kems/dhkemPrimitives/x448.ts @@ -0,0 +1,246 @@ +import type { DhkemPrimitives, KdfInterface } from "@hpke/common"; + +import { + base64UrlToBytes, + DeriveKeyPairError, + DeserializeError, + EMPTY, + KEM_USAGES, + LABEL_DKP_PRK, + LABEL_SK, + NativeAlgorithm, + NotSupportedError, + SerializeError, +} from "@hpke/common"; + +const ALG_NAME = "X448"; + +// deno-fmt-ignore +const PKCS8_ALG_ID_X448 = new Uint8Array([ + 0x30, 0x46, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, + 0x03, 0x2b, 0x65, 0x6f, 0x04, 0x3a, 0x04, 0x38, +]); + +export class X448 extends NativeAlgorithm implements DhkemPrimitives { + private _hkdf: KdfInterface; + private _alg: KeyAlgorithm; + private _nPk: number; + private _nSk: number; + private _nDh: number; + private _pkcs8AlgId: Uint8Array; + + constructor(hkdf: KdfInterface) { + super(); + this._alg = { name: ALG_NAME }; + this._hkdf = hkdf; + this._nPk = 56; + this._nSk = 56; + this._nDh = 56; + this._pkcs8AlgId = PKCS8_ALG_ID_X448; + } + + public async serializePublicKey(key: CryptoKey): Promise { + await this._setup(); + try { + return await (this._api as SubtleCrypto).exportKey("raw", key); + } catch (e: unknown) { + throw new SerializeError(e); + } + } + + public async deserializePublicKey(key: ArrayBuffer): Promise { + await this._setup(); + try { + return await this._importRawKey(key, true); + } catch (e: unknown) { + throw new DeserializeError(e); + } + } + + public async serializePrivateKey(key: CryptoKey): Promise { + await this._setup(); + try { + const jwk = await (this._api as SubtleCrypto).exportKey("jwk", key); + if (!("d" in jwk)) { + throw new Error("Not private key"); + } + return base64UrlToBytes(jwk["d"] as string); + } catch (e: unknown) { + throw new SerializeError(e); + } + } + + public async deserializePrivateKey(key: ArrayBuffer): Promise { + await this._setup(); + try { + return await this._importRawKey(key, false); + } catch (e: unknown) { + throw new DeserializeError(e); + } + } + + public async importKey( + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, + isPublic: boolean, + ): Promise { + await this._setup(); + try { + if (format === "raw") { + return await this._importRawKey(key as ArrayBuffer, isPublic); + } + // jwk + if (key instanceof ArrayBuffer) { + throw new Error("Invalid jwk key format"); + } + return await this._importJWK(key as JsonWebKey, isPublic); + } catch (e: unknown) { + throw new DeserializeError(e); + } + } + + public async generateKeyPair(): Promise { + await this._setup(); + try { + return await (this._api as SubtleCrypto).generateKey( + ALG_NAME, + true, + KEM_USAGES, + ) as CryptoKeyPair; + } catch (e: unknown) { + throw new NotSupportedError(e); + } + } + + public async deriveKeyPair(ikm: ArrayBuffer): Promise { + await this._setup(); + try { + const dkpPrk = await this._hkdf.labeledExtract( + EMPTY, + LABEL_DKP_PRK, + new Uint8Array(ikm), + ); + const rawSk = await this._hkdf.labeledExpand( + dkpPrk, + LABEL_SK, + EMPTY, + this._nSk, + ); + const rawSkBytes = new Uint8Array(rawSk); + const sk = await this._deserializePkcs8Key(rawSkBytes); + rawSkBytes.fill(0); + return { + privateKey: sk, + publicKey: await this.derivePublicKey(sk), + }; + } catch (e: unknown) { + throw new DeriveKeyPairError(e); + } + } + + public async derivePublicKey(key: CryptoKey): Promise { + await this._setup(); + try { + const jwk = await (this._api as SubtleCrypto).exportKey("jwk", key); + delete jwk["d"]; + delete jwk["key_ops"]; + return await (this._api as SubtleCrypto).importKey( + "jwk", + jwk, + this._alg, + true, + [], + ); + } catch (e: unknown) { + throw new DeserializeError(e); + } + } + + public async dh(sk: CryptoKey, pk: CryptoKey): Promise { + await this._setup(); + try { + const bits = await (this._api as SubtleCrypto).deriveBits( + { + name: ALG_NAME, + public: pk, + }, + sk, + this._nDh * 8, + ); + return bits; + } catch (e: unknown) { + throw new SerializeError(e); + } + } + + private async _importRawKey( + key: ArrayBuffer, + isPublic: boolean, + ): Promise { + if (isPublic && key.byteLength !== this._nPk) { + throw new Error("Invalid public key for the ciphersuite"); + } + if (!isPublic && key.byteLength !== this._nSk) { + throw new Error("Invalid private key for the ciphersuite"); + } + if (isPublic) { + return await (this._api as SubtleCrypto).importKey( + "raw", + key, + this._alg, + true, + [], + ); + } + return await this._deserializePkcs8Key(new Uint8Array(key)); + } + + private async _importJWK( + key: JsonWebKey, + isPublic: boolean, + ): Promise { + if (typeof key.kty === "undefined" || key.kty !== "OKP") { + throw new Error(`Invalid kty: ${key.crv}`); + } + if (typeof key.crv === "undefined" || key.crv !== ALG_NAME) { + throw new Error(`Invalid crv: ${key.crv}`); + } + if (isPublic) { + if (typeof key.d !== "undefined") { + throw new Error("Invalid key: `d` should not be set"); + } + return await (this._api as SubtleCrypto).importKey( + "jwk", + key, + this._alg, + true, + [], + ); + } + if (typeof key.d === "undefined") { + throw new Error("Invalid key: `d` not found"); + } + return await (this._api as SubtleCrypto).importKey( + "jwk", + key, + this._alg, + true, + KEM_USAGES, + ); + } + + private async _deserializePkcs8Key(k: Uint8Array): Promise { + const pkcs8Key = new Uint8Array( + this._pkcs8AlgId.length + k.length, + ); + pkcs8Key.set(this._pkcs8AlgId, 0); + pkcs8Key.set(k, this._pkcs8AlgId.length); + return await (this._api as SubtleCrypto).importKey( + "pkcs8", + pkcs8Key, + this._alg, + true, + KEM_USAGES, + ); + } +} diff --git a/packages/core/src/kems/dhkemX448.ts b/packages/core/src/kems/dhkemX448.ts new file mode 100644 index 000000000..9969d7517 --- /dev/null +++ b/packages/core/src/kems/dhkemX448.ts @@ -0,0 +1,44 @@ +import { Dhkem, HkdfSha512Native, KemId } from "@hpke/common"; + +import { X448 } from "./dhkemPrimitives/x448.ts"; + +/** + * The DHKEM(X448, HKDF-SHA512) for HPKE KEM implementing {@link KemInterface}. + * + * The instance of this class can be specified to the + * {@link https://jsr.io/@hpke/core/doc/~/CipherSuiteParams | CipherSuiteParams} as follows: + * + * @example + * + * ```ts + * import { + * Aes256Gcm, + * CipherSuite, + * HkdfSha512, + * DhkemX448HkdfSha512, + * } from "@hpke/core"; + * + * const suite = new CipherSuite({ + * kem: new DhkemX448HkdfSha512(), + * kdf: new HkdfSha512(), + * aead: new Aes256Gcm(), + * }); + * ``` + */ +export class DhkemX448HkdfSha512 extends Dhkem { + /** KemId.DhkemX448HkdfSha512 (0x0021) */ + override id: KemId = KemId.DhkemX448HkdfSha512; + /** 64 */ + override secretSize: number = 64; + /** 56 */ + override encSize: number = 56; + /** 56 */ + override publicKeySize: number = 56; + /** 56 */ + override privateKeySize: number = 56; + + constructor() { + const kdf = new HkdfSha512Native(); + super(KemId.DhkemX448HkdfSha512, new X448(kdf), kdf); + } +} diff --git a/packages/core/test/conformance.test.ts b/packages/core/test/conformance.test.ts index 65619483f..44835edf0 100644 --- a/packages/core/test/conformance.test.ts +++ b/packages/core/test/conformance.test.ts @@ -124,15 +124,18 @@ describe("RFC9180 conformance", () => { }); }); - // describe("Base/DhkemX448/HkdfSha*/Aes*Gcm in test-vectors.json", () => { - // it("should match demonstrated values", async () => { - // for (const v of testVectors) { - // if (v.mode === 0 && v.kem_id === 0x0021 && v.aead_id <= 0x0002) { - // await tester.test(v); - // } - // } - // }); - // }); + describe("Base/DhkemX448/HkdfSha*/Aes*Gcm in test-vectors.json", () => { + it("should match demonstrated values", async () => { + for (const v of testVectors) { + if (v.mode === 0 && v.kem_id === 0x0021 && v.aead_id <= 0x0002) { + if (isDeno()) { + continue; + } + await tester.test(v); + } + } + }); + }); // describe("Base/DhkemX448/HkdfSha*/ChaCha20Poly1305 in test-vectors.json", () => { // it("should match demonstrated values", async () => { @@ -144,15 +147,18 @@ describe("RFC9180 conformance", () => { // }); // }); - // describe("Base/DhkemX448/HkdfSha*/ExportOnly in test-vectors.json", () => { - // it("should match demonstrated values", async () => { - // for (const v of testVectors) { - // if (v.mode === 0 && v.kem_id === 0x0021 && v.aead_id === 0xFFFF) { - // await tester.test(v); - // } - // } - // }); - // }); + describe("Base/DhkemX448/HkdfSha*/ExportOnly in test-vectors.json", () => { + it("should match demonstrated values", async () => { + for (const v of testVectors) { + if (v.mode === 0 && v.kem_id === 0x0021 && v.aead_id === 0xFFFF) { + if (isDeno()) { + continue; + } + await tester.test(v); + } + } + }); + }); describe("PSK/DhkemP*/HkdfSha*/Aes*Gcm in test-vectors.json", () => { it("should match demonstrated values", async () => { @@ -229,15 +235,18 @@ describe("RFC9180 conformance", () => { }); }); - // describe("PSK/DhkemX448/HkdfSha*/Aes*Gcm in test-vectors.json", () => { - // it("should match demonstrated values", async () => { - // for (const v of testVectors) { - // if (v.mode === 1 && v.kem_id === 0x0021 && v.aead_id <= 0x0002) { - // await tester.test(v); - // } - // } - // }); - // }); + describe("PSK/DhkemX448/HkdfSha*/Aes*Gcm in test-vectors.json", () => { + it("should match demonstrated values", async () => { + for (const v of testVectors) { + if (v.mode === 1 && v.kem_id === 0x0021 && v.aead_id <= 0x0002) { + if (isDeno()) { + continue; + } + await tester.test(v); + } + } + }); + }); // describe("PSK/DhkemX448/HkdfSha*/ChaCha20Poly1305 in test-vectors.json", () => { // it("should match demonstrated values", async () => { @@ -249,15 +258,18 @@ describe("RFC9180 conformance", () => { // }); // }); - // describe("PSK/DhkemX448/HkdfSha*/ExportOnly in test-vectors.json", () => { - // it("should match demonstrated values", async () => { - // for (const v of testVectors) { - // if (v.mode === 1 && v.kem_id === 0x0021 && v.aead_id === 0xFFFF) { - // await tester.test(v); - // } - // } - // }); - // }); + describe("PSK/DhkemX448/HkdfSha*/ExportOnly in test-vectors.json", () => { + it("should match demonstrated values", async () => { + for (const v of testVectors) { + if (v.mode === 1 && v.kem_id === 0x0021 && v.aead_id === 0xFFFF) { + if (isDeno()) { + continue; + } + await tester.test(v); + } + } + }); + }); describe("Auth/DhkemP*/HkdfSha*/Aes*Gcm in test-vectors.json", () => { it("should match demonstrated values", async () => { @@ -334,15 +346,18 @@ describe("RFC9180 conformance", () => { }); }); - // describe("Auth/DhkemX448/HkdfSha*/Aes*Gcm in test-vectors.json", () => { - // it("should match demonstrated values", async () => { - // for (const v of testVectors) { - // if (v.mode === 2 && v.kem_id === 0x0021 && v.aead_id <= 0x0002) { - // await tester.test(v); - // } - // } - // }); - // }); + describe("Auth/DhkemX448/HkdfSha*/Aes*Gcm in test-vectors.json", () => { + it("should match demonstrated values", async () => { + for (const v of testVectors) { + if (v.mode === 2 && v.kem_id === 0x0021 && v.aead_id <= 0x0002) { + if (isDeno()) { + continue; + } + await tester.test(v); + } + } + }); + }); // describe("Auth/DhkemX448/HkdfSha*/ChaCha20Poly1305 in test-vectors.json", () => { // it("should match demonstrated values", async () => { @@ -354,15 +369,18 @@ describe("RFC9180 conformance", () => { // }); // }); - // describe("Auth/DhkemX448/HkdfSha*/ExportOnly in test-vectors.json", () => { - // it("should match demonstrated values", async () => { - // for (const v of testVectors) { - // if (v.mode === 2 && v.kem_id === 0x0021 && v.aead_id === 0xFFFF) { - // await tester.test(v); - // } - // } - // }); - // }); + describe("Auth/DhkemX448/HkdfSha*/ExportOnly in test-vectors.json", () => { + it("should match demonstrated values", async () => { + for (const v of testVectors) { + if (v.mode === 2 && v.kem_id === 0x0021 && v.aead_id === 0xFFFF) { + if (isDeno()) { + continue; + } + await tester.test(v); + } + } + }); + }); describe("AuthPSK/DhkemP*/HkdfSha*/Aes*Gcm in test-vectors.json", () => { it("should match demonstrated values", async () => { @@ -439,15 +457,18 @@ describe("RFC9180 conformance", () => { }); }); - // describe("AuthPSK/DhkemX448/HkdfSha*/Aes*Gcm in test-vectors.json", () => { - // it("should match demonstrated values", async () => { - // for (const v of testVectors) { - // if (v.mode === 3 && v.kem_id === 0x0021 && v.aead_id <= 0x0002) { - // await tester.test(v); - // } - // } - // }); - // }); + describe("AuthPSK/DhkemX448/HkdfSha*/Aes*Gcm in test-vectors.json", () => { + it("should match demonstrated values", async () => { + for (const v of testVectors) { + if (v.mode === 3 && v.kem_id === 0x0021 && v.aead_id <= 0x0002) { + if (isDeno()) { + continue; + } + await tester.test(v); + } + } + }); + }); // describe("AuthPSK/DhkemX448/HkdfSha*/ChaCha20Poly1305 in test-vectors.json", () => { // it("should match demonstrated values", async () => { @@ -459,13 +480,16 @@ describe("RFC9180 conformance", () => { // }); // }); - // describe("AuthPSK/DhkemX448/HkdfSha*/ExportOnly in test-vectors.json", () => { - // it("should match demonstrated values", async () => { - // for (const v of testVectors) { - // if (v.mode === 3 && v.kem_id === 0x0021 && v.aead_id === 0xFFFF) { - // await tester.test(v); - // } - // } - // }); - // }); + describe("AuthPSK/DhkemX448/HkdfSha*/ExportOnly in test-vectors.json", () => { + it("should match demonstrated values", async () => { + for (const v of testVectors) { + if (v.mode === 3 && v.kem_id === 0x0021 && v.aead_id === 0xFFFF) { + if (isDeno()) { + continue; + } + await tester.test(v); + } + } + }); + }); }); diff --git a/packages/core/test/conformanceTester.ts b/packages/core/test/conformanceTester.ts index ddde0bcf4..409a8e052 100644 --- a/packages/core/test/conformanceTester.ts +++ b/packages/core/test/conformanceTester.ts @@ -18,6 +18,7 @@ import { DhkemP384HkdfSha384, DhkemP521HkdfSha512, DhkemX25519HkdfSha256, + DhkemX448HkdfSha512, ExportOnly, HkdfSha256, HkdfSha384, @@ -39,6 +40,8 @@ function createKem(id: KemId): KemInterface { return new DhkemP521HkdfSha512(); case KemId.DhkemX25519HkdfSha256: return new DhkemX25519HkdfSha256(); + case KemId.DhkemX448HkdfSha512: + return new DhkemX448HkdfSha512(); default: break; } diff --git a/packages/core/test/kemContext.test.ts b/packages/core/test/kemContext.test.ts index 8d91d87a3..8635b1872 100644 --- a/packages/core/test/kemContext.test.ts +++ b/packages/core/test/kemContext.test.ts @@ -9,25 +9,11 @@ import { DhkemP384HkdfSha384, DhkemP521HkdfSha512, DhkemX25519HkdfSha256, + DhkemX448HkdfSha512, KemId, // SerializeError, } from "../mod.ts"; -// async function loadSubtleCrypto(): Promise { -// if (globalThis !== undefined && globalThis.crypto !== undefined) { -// // Browsers, Node.js >= v19, Cloudflare Workers, Bun, etc. -// return globalThis.crypto.subtle; -// } -// // Node.js <= v18 -// try { -// // @ts-ignore: to ignore "crypto" -// const { webcrypto } = await import("crypto"); // node:crypto -// return (webcrypto as unknown as Crypto).subtle; -// } catch (e: unknown) { -// throw e as Error; -// } -// } - describe("constructor", () => { describe("with valid parameters", () => { it("should return a proper instance", () => { @@ -64,13 +50,13 @@ describe("constructor", () => { assertEquals(dhkemX25519.publicKeySize, 32); assertEquals(dhkemX25519.privateKeySize, 32); - // const dhkemX448 = new DhkemX448HkdfSha512(); - // assertEquals(typeof dhkemX448, "object"); - // assertEquals(dhkemX448.id, KemId.DhkemX448HkdfSha512); - // assertEquals(dhkemX448.secretSize, 64); - // assertEquals(dhkemX448.encSize, 56); - // assertEquals(dhkemX448.publicKeySize, 56); - // assertEquals(dhkemX448.privateKeySize, 56); + const dhkemX448 = new DhkemX448HkdfSha512(); + assertEquals(typeof dhkemX448, "object"); + assertEquals(dhkemX448.id, KemId.DhkemX448HkdfSha512); + assertEquals(dhkemX448.secretSize, 64); + assertEquals(dhkemX448.encSize, 56); + assertEquals(dhkemX448.publicKeySize, 56); + assertEquals(dhkemX448.privateKeySize, 56); }); }); }); @@ -144,21 +130,23 @@ describe("generateKeyPair", () => { assertEquals(kp.privateKey.usages[0], "deriveBits"); }); - // it("should return a proper instance with DhkemX448HkdfSha512", async () => { - // // assert - // const kemContext = new DhkemX448HkdfSha512(); - // const kp = await kemContext.generateKeyPair(); - // assertEquals(kp.publicKey.type, "public"); - // assertEquals(kp.publicKey.extractable, true); - // assertEquals(kp.publicKey.algorithm.name, "X448"); - // assertEquals(kp.publicKey.usages.length, 0); - // // assertEquals(kp.publicKey.usages[0], "deriveBits"); - // assertEquals(kp.privateKey.type, "private"); - // assertEquals(kp.privateKey.extractable, true); - // assertEquals(kp.privateKey.algorithm.name, "X448"); - // assertEquals(kp.privateKey.usages.length, 1); - // assertEquals(kp.privateKey.usages[0], "deriveBits"); - // }); + it("should return a proper instance with DhkemX448HkdfSha512", async () => { + if (isDenoV1()) { + return; + } + // assert + const kemContext = new DhkemX448HkdfSha512(); + const kp = await kemContext.generateKeyPair(); + assertEquals(kp.publicKey.type, "public"); + assertEquals(kp.publicKey.extractable, true); + assertEquals(kp.publicKey.algorithm.name, "X448"); + assertEquals(kp.publicKey.usages.length, 0); + assertEquals(kp.privateKey.type, "private"); + assertEquals(kp.privateKey.extractable, true); + assertEquals(kp.privateKey.algorithm.name, "X448"); + assertEquals(kp.privateKey.usages.length, 1); + assertEquals(kp.privateKey.usages[0], "deriveBits"); + }); }); }); @@ -258,24 +246,27 @@ describe("deriveKeyPair", () => { assertEquals(kp.privateKey.usages[0], "deriveBits"); }); - // it("should return a proper instance with DhkemX448HkdfSha512", async () => { - // const cryptoApi = await loadCrypto(); + it("should return a proper instance with DhkemX448HkdfSha512", async () => { + if (isDeno()) { + return; + } + const cryptoApi = await loadCrypto(); - // // assert - // const kemContext = new DhkemX448HkdfSha512(); - // const ikm = new Uint8Array(32); - // cryptoApi.getRandomValues(ikm); - // const kp = await kemContext.deriveKeyPair(ikm.buffer); - // assertEquals(kp.publicKey.type, "public"); - // assertEquals(kp.publicKey.extractable, true); - // assertEquals(kp.publicKey.algorithm.name, "X448"); - // assertEquals(kp.publicKey.usages.length, 0); - // assertEquals(kp.privateKey.type, "private"); - // assertEquals(kp.privateKey.extractable, true); - // assertEquals(kp.privateKey.algorithm.name, "X448"); - // assertEquals(kp.privateKey.usages.length, 1); - // assertEquals(kp.privateKey.usages[0], "deriveBits"); - // }); + // assert + const kemContext = new DhkemX448HkdfSha512(); + const ikm = new Uint8Array(32); + cryptoApi.getRandomValues(ikm); + const kp = await kemContext.deriveKeyPair(ikm.buffer); + assertEquals(kp.publicKey.type, "public"); + assertEquals(kp.publicKey.extractable, true); + assertEquals(kp.publicKey.algorithm.name, "X448"); + assertEquals(kp.publicKey.usages.length, 0); + assertEquals(kp.privateKey.type, "private"); + assertEquals(kp.privateKey.extractable, true); + assertEquals(kp.privateKey.algorithm.name, "X448"); + assertEquals(kp.privateKey.usages.length, 1); + assertEquals(kp.privateKey.usages[0], "deriveBits"); + }); it("should return a proper instance with DhkemX25519HkdfSha256 and zero-length ikm", async () => { if (isDeno()) { @@ -374,18 +365,21 @@ describe("serialize/deserializePublicKey", () => { // assertEquals(kp.privateKey.usages[0], "deriveBits"); }); - // it("should return a proper instance with DhkemX448HkdfSha512", async () => { - // // assert - // const kemContext = new DhkemX448HkdfSha512(); - // const kp = await kemContext.generateKeyPair(); - // const bPubKey = await kemContext.serializePublicKey(kp.publicKey); - // const pubKey = await kemContext.deserializePublicKey(bPubKey); - // assertEquals(pubKey.type, "public"); - // assertEquals(pubKey.extractable, true); - // assertEquals(pubKey.algorithm.name, "X448"); - // assertEquals(pubKey.usages.length, 0); - // // assertEquals(kp.privateKey.usages[0], "deriveBits"); - // }); + it("should return a proper instance with DhkemX448HkdfSha512", async () => { + if (isDenoV1()) { + return; + } + // assert + const kemContext = new DhkemX448HkdfSha512(); + const kp = await kemContext.generateKeyPair(); + const bPubKey = await kemContext.serializePublicKey(kp.publicKey); + const pubKey = await kemContext.deserializePublicKey(bPubKey); + assertEquals(pubKey.type, "public"); + assertEquals(pubKey.extractable, true); + assertEquals(pubKey.algorithm.name, "X448"); + assertEquals(pubKey.usages.length, 0); + // assertEquals(kp.privateKey.usages[0], "deriveBits"); + }); }); describe("with invalid parameters", () => { @@ -492,17 +486,17 @@ describe("serialize/deserializePublicKey", () => { ); }); - // it("should throw DeserializeError on DhkemX448HkdfSha512.deserializePublicKey with invalid length key", async () => { - // // assert - // const kemContext = new DhkemX25519HkdfSha256(); - // const cryptoApi = await loadCrypto(); - // const rawKey = new Uint8Array(56 - 1); - // cryptoApi.getRandomValues(rawKey); - // await assertRejects( - // () => kemContext.deserializePublicKey(rawKey.buffer), - // DeserializeError, - // ); - // }); + it("should throw DeserializeError on DhkemX448HkdfSha512.deserializePublicKey with invalid length key", async () => { + // assert + const kemContext = new DhkemX25519HkdfSha256(); + const cryptoApi = await loadCrypto(); + const rawKey = new Uint8Array(56 - 1); + cryptoApi.getRandomValues(rawKey); + await assertRejects( + () => kemContext.deserializePublicKey(rawKey.buffer), + DeserializeError, + ); + }); }); }); @@ -577,18 +571,21 @@ describe("serialize/deserializePrivateKey", () => { assertEquals(privKey.usages[0], "deriveBits"); }); - // it("should return a proper instance with DhkemX448HkdfSha512", async () => { - // // assert - // const kemContext = new DhkemX448HkdfSha512(); - // const kp = await kemContext.generateKeyPair(); - // const bPrivKey = await kemContext.serializePrivateKey(kp.privateKey); - // const privKey = await kemContext.deserializePrivateKey(bPrivKey); - // assertEquals(privKey.type, "private"); - // assertEquals(privKey.extractable, true); - // assertEquals(privKey.algorithm.name, "X448"); - // assertEquals(privKey.usages.length, 1); - // assertEquals(privKey.usages[0], "deriveBits"); - // }); + it("should return a proper instance with DhkemX448HkdfSha512", async () => { + if (isDeno()) { + return; + } + // assert + const kemContext = new DhkemX448HkdfSha512(); + const kp = await kemContext.generateKeyPair(); + const bPrivKey = await kemContext.serializePrivateKey(kp.privateKey); + const privKey = await kemContext.deserializePrivateKey(bPrivKey); + assertEquals(privKey.type, "private"); + assertEquals(privKey.extractable, true); + assertEquals(privKey.algorithm.name, "X448"); + assertEquals(privKey.usages.length, 1); + assertEquals(privKey.usages[0], "deriveBits"); + }); }); describe("with invalid parameters", () => { @@ -695,17 +692,17 @@ describe("serialize/deserializePrivateKey", () => { ); }); - // it("should throw DeserializeError on DhkemX448HkdfSha512.deserializePrivateKey with invalid length key", async () => { - // // assert - // const kemContext = new DhkemX448HkdfSha512(); - // const cryptoApi = await loadCrypto(); - // const rawKey = new Uint8Array(55); - // cryptoApi.getRandomValues(rawKey); - // await assertRejects( - // () => kemContext.deserializePrivateKey(rawKey), - // DeserializeError, - // ); - // }); + it("should throw DeserializeError on DhkemX448HkdfSha512.deserializePrivateKey with invalid length key", async () => { + // assert + const kemContext = new DhkemX448HkdfSha512(); + const cryptoApi = await loadCrypto(); + const rawKey = new Uint8Array(55); + cryptoApi.getRandomValues(rawKey); + await assertRejects( + () => kemContext.deserializePrivateKey(rawKey), + DeserializeError, + ); + }); }); }); @@ -856,39 +853,45 @@ describe("importKey", () => { assertEquals(privKey.usages.length, 0); }); - // it("should return a valid private key for DhkemX448HkdfSha512 from JWK", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should return a valid private key for DhkemX448HkdfSha512 from JWK", async () => { + if (isDenoV1()) { + return; + } + const kemContext = new DhkemX448HkdfSha512(); - // const jwk = { - // kty: "OKP", - // crv: "X448", - // kid: "X448-01", - // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", - // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", - // key_ops: ["deriveBits"], - // }; - // const privKey = await kemContext.importKey("jwk", jwk, false); + const jwk = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: ["deriveBits"], + }; + const privKey = await kemContext.importKey("jwk", jwk, false); - // // assert - // assertEquals(privKey.usages.length, 1); - // assertEquals(privKey.usages[0], "deriveBits"); - // }); + // assert + assertEquals(privKey.usages.length, 1); + assertEquals(privKey.usages[0], "deriveBits"); + }); - // it("should return a valid public key for DhkemX448HkdfSha512 from JWK", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should return a valid public key for DhkemX448HkdfSha512 from JWK", async () => { + if (isDenoV1()) { + return; + } + const kemContext = new DhkemX448HkdfSha512(); - // const jwk = { - // kty: "OKP", - // crv: "X448", - // kid: "X448-01", - // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", - // key_ops: [], - // }; - // const privKey = await kemContext.importKey("jwk", jwk, true); + const jwk = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + key_ops: [], + }; + const privKey = await kemContext.importKey("jwk", jwk, true); - // // assert - // assertEquals(privKey.usages.length, 0); - // }); + // assert + assertEquals(privKey.usages.length, 0); + }); }); describe("with invalid parameters", () => { @@ -1157,132 +1160,135 @@ describe("importKey", () => { ); }); - // it("should throw DeserializeError with private raw key(OKP/448) with 'jwk'", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should throw DeserializeError with private raw key(OKP/448) with 'jwk'", async () => { + const kemContext = new DhkemX448HkdfSha512(); - // const cryptoApi = await loadCrypto(); - // const rawKey = new Uint8Array(56); - // cryptoApi.getRandomValues(rawKey); + const cryptoApi = await loadCrypto(); + const rawKey = new Uint8Array(56); + cryptoApi.getRandomValues(rawKey); - // // assert - // await assertRejects( - // () => kemContext.importKey("jwk", rawKey.buffer, false), - // DeserializeError, - // ); - // }); + // assert + await assertRejects( + () => kemContext.importKey("jwk", rawKey.buffer, false), + DeserializeError, + ); + }); - // it("should throw DeserializeError with private JWK(OKP/448) with 'raw'", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should throw DeserializeError with private JWK(OKP/448) with 'raw'", async () => { + const kemContext = new DhkemX448HkdfSha512(); - // const jwk = { - // kty: "OKP", - // crv: "X448", - // kid: "X448-01", - // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", - // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", - // key_ops: ["deriveBits"], - // }; + const jwk = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: ["deriveBits"], + }; - // // assert - // await assertRejects( - // () => kemContext.importKey("raw", jwk, false), - // DeserializeError, - // ); - // }); + // assert + await assertRejects( + () => kemContext.importKey("raw", jwk, false), + DeserializeError, + ); + }); - // it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'kty'", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'kty'", async () => { + const kemContext = new DhkemX448HkdfSha512(); - // const jwk = { - // // kty: "OKP", - // crv: "X448", - // kid: "X448-01", - // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", - // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", - // key_ops: ["deriveBits"], - // }; + const jwk = { + // kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: ["deriveBits"], + }; - // // assert - // await assertRejects( - // () => kemContext.importKey("jwk", jwk, false), - // DeserializeError, - // ); - // }); + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + DeserializeError, + ); + }); - // it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'crv'", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'crv'", async () => { + const kemContext = new DhkemX448HkdfSha512(); - // const jwk = { - // kty: "OKP", - // // crv: "X448", - // kid: "X448-01", - // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", - // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", - // key_ops: ["deriveBits"], - // }; + const jwk = { + kty: "OKP", + // crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: ["deriveBits"], + }; - // // assert - // await assertRejects( - // () => kemContext.importKey("jwk", jwk, false), - // DeserializeError, - // ); - // }); + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + DeserializeError, + ); + }); - // it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'd'", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'd'", async () => { + const kemContext = new DhkemX448HkdfSha512(); - // const jwk = { - // kty: "OKP", - // crv: "X448", - // kid: "X448-01", - // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", - // // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", - // key_ops: ["deriveBits"], - // }; + const jwk = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: ["deriveBits"], + }; - // // assert - // await assertRejects( - // () => kemContext.importKey("jwk", jwk, false), - // DeserializeError, - // ); - // }); + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + DeserializeError, + ); + }); - // it("should throw DeserializeError with invalid public JWK(OKP/X448) without 'x'", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should throw DeserializeError with invalid public JWK(OKP/X448) without 'x'", async () => { + if (isDeno()) { + return; + } + const kemContext = new DhkemX448HkdfSha512(); - // const jwk = { - // kty: "OKP", - // crv: "X448", - // kid: "X448-01", - // // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", - // // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", - // key_ops: [], - // }; + const jwk = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: [], + }; - // // assert - // await assertRejects( - // () => kemContext.importKey("jwk", jwk, true), - // DeserializeError, - // ); - // }); + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, true), + DeserializeError, + ); + }); - // it("should throw DeserializeError with invalid public JWK(X448) with 'd'", async () => { - // const kemContext = new DhkemX448HkdfSha512(); + it("should throw DeserializeError with invalid public JWK(X448) with 'd'", async () => { + const kemContext = new DhkemX448HkdfSha512(); - // const jwk = { - // kty: "OKP", - // crv: "X448", - // kid: "X448-01", - // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", - // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", - // key_ops: [], - // }; + const jwk = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: [], + }; - // // assert - // await assertRejects( - // () => kemContext.importKey("jwk", jwk, true), - // DeserializeError, - // ); - // }); + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, true), + DeserializeError, + ); + }); }); }); diff --git a/packages/core/test/sample.test.ts b/packages/core/test/sample.test.ts new file mode 100644 index 000000000..950b57839 --- /dev/null +++ b/packages/core/test/sample.test.ts @@ -0,0 +1,1171 @@ +import { assertEquals, assertRejects } from "@std/assert"; +import { describe, it } from "@std/testing/bdd"; + +import { isDeno, isDenoV1 } from "@hpke/common"; +import { + Aes128Gcm, + Aes256Gcm, + CipherSuite, + DhkemP256HkdfSha256, + DhkemP384HkdfSha384, + DhkemP521HkdfSha512, + DhkemX25519HkdfSha256, + DhkemX448HkdfSha512, + ExportOnly, + HkdfSha256, + HkdfSha384, + HkdfSha512, + NotSupportedError, +} from "../mod.ts"; + +describe("README examples", () => { + describe("Base mode with DhkemP256HkdfSha256/HkdfSha256/Aes128Gcm", () => { + it("should work normally with instances", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + + it("should work normally with importKey('jwk')", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const jwkPkR = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + key_ops: [], + }; + const pkR = await suite.kem.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: ["deriveBits"], + }; + const skR = await suite.kem.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: skR, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("Base mode with DhkemP384HkdfSha384/HkdfSha384/Aes128Gcm.", () => { + it("should work normally with instances", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP384HkdfSha384(), + kdf: new HkdfSha384(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with importKey('jwk')", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP384HkdfSha384(), + kdf: new HkdfSha384(), + aead: new Aes128Gcm(), + }); + + const jwkPkR = { + kty: "EC", + crv: "P-384", + kid: "P-384-01", + x: "_XyN9woHaS0mPimSW-etwJMEDSzxIMjp4PjezavU8SHJoClz1bQrcmPb1ZJxHxhI", + y: "GCNfc32p9sRotx7u2oDGJ3Eqz6q5zPHLdizNn83oRsUTN31eCWfGLHWRury3xF50", + key_ops: [], + }; + const pkR = await suite.kem.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "EC", + crv: "P-384", + kid: "P-384-01", + x: "_XyN9woHaS0mPimSW-etwJMEDSzxIMjp4PjezavU8SHJoClz1bQrcmPb1ZJxHxhI", + y: "GCNfc32p9sRotx7u2oDGJ3Eqz6q5zPHLdizNn83oRsUTN31eCWfGLHWRury3xF50", + d: "1pImEKbrr771-RKi8Tb7tou_WjiR7kwui_nMu16449rk3lzAqf9buUhTkJ-pogkb", + key_ops: ["deriveBits"], + }; + const skR = await suite.kem.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: skR, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("Base mode with DhkemP521HkdfSha512/HkdfSha512/Aes128Gcm", () => { + it("should work normally", async () => { + if (isDeno()) { + return; + } + + // setup + const suite = new CipherSuite({ + kem: new DhkemP521HkdfSha512(), + kdf: new HkdfSha512(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with importKey('jwk')", async () => { + if (isDeno()) { + return; + } + + // setup + const suite = new CipherSuite({ + kem: new DhkemP521HkdfSha512(), + kdf: new HkdfSha512(), + aead: new Aes128Gcm(), + }); + + const jwkPkR = { + kty: "EC", + crv: "P-521", + kid: "P-521-01", + x: "APkZitSJMJUMB-iPCt47sWu_CrnUHg6IAR4qjmHON-2u41Rjg6DNOS0LZYJJt-AVH5NgGVi8ElIfjo71b9HXCTOc", + y: "ASx-Cb--149HJ-e1KlSaY-1BOhwOdcTkxSt8BGbW7_hnGfzHsoXM3ywwNcp1Yad-FHUKwmCyMelMQEn2Rh4V2l3I", + key_ops: [], + }; + const pkR = await suite.kem.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "EC", + crv: "P-521", + kid: "P-521-01", + x: "APkZitSJMJUMB-iPCt47sWu_CrnUHg6IAR4qjmHON-2u41Rjg6DNOS0LZYJJt-AVH5NgGVi8ElIfjo71b9HXCTOc", + y: "ASx-Cb--149HJ-e1KlSaY-1BOhwOdcTkxSt8BGbW7_hnGfzHsoXM3ywwNcp1Yad-FHUKwmCyMelMQEn2Rh4V2l3I", + d: "ADYyo73ZKicOjwGDYQ_ybZKnVzdAcxGm9OVAxQjzgVM4jaS-Iwtkz90oLdDz3shgKlDgtRK2Aa9lMhqR94hBo4IE", + key_ops: ["deriveBits"], + }; + const skR = await suite.kem.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: skR, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("Base mode with DhkemX25519HkdfSha256/HkdfSha256/Aes128Gcm", () => { + it("should work normally", async () => { + if (isDenoV1()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX25519HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with importKey('jwk')", async () => { + if (isDeno()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX25519HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const jwkPkR = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + key_ops: [], + }; + const pkR = await suite.kem.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: ["deriveBits"], + }; + const skR = await suite.kem.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: skR, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + + it("should work normally with importKey('jwk') and using CryptoKeyPair", async () => { + if (isDenoV1()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX25519HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const jwkPkR = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + key_ops: [], + }; + const pkR = await suite.kem.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: ["deriveBits"], + }; + const skR = await suite.kem.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: { privateKey: skR, publicKey: pkR }, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + describe("Base mode with DhkemX448HkdfSha256/HkdfSha512/Aes256Gcm)", () => { + it("should work normally", async () => { + if (isDenoV1()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX448HkdfSha512(), + kdf: new HkdfSha512(), + aead: new Aes256Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with importKey('jwk')", async () => { + if (isDeno()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX448HkdfSha512(), + kdf: new HkdfSha512(), + aead: new Aes256Gcm(), + }); + + const jwkPkR = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + key_ops: [], + }; + const pkR = await suite.kem.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: ["deriveBits"], + }; + const skR = await suite.kem.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: skR, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + + it("should work normally with importKey('jwk') and using CryptoKeyPair", async () => { + if (isDenoV1()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX448HkdfSha512(), + kdf: new HkdfSha512(), + aead: new Aes256Gcm(), + }); + + const jwkPkR = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + key_ops: [], + }; + const pkR = await suite.kem.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + key_ops: ["deriveBits"], + }; + const skR = await suite.kem.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: { privateKey: skR, publicKey: pkR }, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("Base mode with DhkemP256HkdfSha256/HkdfSha256/ExportOnly", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new ExportOnly(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + const te = new TextEncoder(); + + // export + const pskS = sender.export(te.encode("jugemujugemu"), 32); + const pskR = recipient.export(te.encode("jugemujugemu"), 32); + assertEquals(pskR, pskS); + + // other functions are disabled. + await assertRejects( + () => sender.seal(te.encode("my-secret-message")), + NotSupportedError, + ); + await assertRejects( + () => sender.open(te.encode("xxxxxxxxxxxxxxxxx")), + NotSupportedError, + ); + }); + }); + + describe("Base mode with DhkemX25519HkdfSha256/HkdfSha256/ExportOnly", () => { + it("should work normally", async () => { + if (isDenoV1()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX25519HkdfSha256(), + kdf: new HkdfSha256(), + aead: new ExportOnly(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + const te = new TextEncoder(); + + // export + const pskS = sender.export(te.encode("jugemujugemu"), 32); + const pskR = recipient.export(te.encode("jugemujugemu"), 32); + assertEquals(pskR, pskS); + + // other functions are disabled. + await assertRejects( + () => sender.seal(te.encode("my-secret-message")), + NotSupportedError, + ); + await assertRejects( + () => sender.open(te.encode("xxxxxxxxxxxxxxxxx")), + NotSupportedError, + ); + }); + }); + + describe("PSK mode", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("Auth mode", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + const skp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + senderKey: skp, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + senderPublicKey: skp.publicKey, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("AuthPSK mode with DhkemP256HkdfSha256", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + const skp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + senderKey: skp, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + senderPublicKey: skp.publicKey, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("AuthPSK mode with DhkemX25519HkdfSha256", () => { + it("should work normally", async () => { + if (isDenoV1()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX25519HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + const skp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + senderKey: skp, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + senderPublicKey: skp.publicKey, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("AuthPSK mode with DhkemX448HkdfSha512", () => { + it("should work normally", async () => { + if (isDenoV1()) { + return; + } + // setup + const suite = new CipherSuite({ + kem: new DhkemX448HkdfSha512(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.kem.generateKeyPair(); + const skp = await suite.kem.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + senderKey: skp, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + senderPublicKey: skp.publicKey, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("Bidirectional Encryption with DhkemP256HkdfSha256/HkdfSha256/Aes128Gcm", () => { + it("should work normally", async () => { + const te = new TextEncoder(); + + // A recipient generates a keypair. + const recipient = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + const rkp = await recipient.kem.generateKeyPair(); + + // A sender generates an encapusulated key (enc) with the recipient's public key. + const sender = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + const ctxS = await sender.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + // The recipient decapsulates the enc with the recipient's private key. + const ctxR = await recipient.createRecipientContext({ + recipientKey: rkp.privateKey, + enc: ctxS.enc, + }); + + // The recipient encrypts a plaintext. + const keyR = await ctxR.export( + te.encode("response key"), + recipient.aead.keySize, + ); + const nonceR = await ctxR.export( + te.encode("response nonce"), + recipient.aead.nonceSize, + ); + const aeadCtxR = await recipient.aead.createEncryptionContext(keyR); + const ct = await aeadCtxR.seal( + nonceR, + te.encode("Hello world!"), + te.encode("jugemu-jugemu"), + ); + + // The sender decrypts the ciphertext. + const keyS = await ctxS.export( + te.encode("response key"), + sender.aead.keySize, + ); + const nonceS = await ctxS.export( + te.encode("response nonce"), + sender.aead.nonceSize, + ); + const aeadCtxS = await sender.aead.createEncryptionContext(keyS); + const pt = await aeadCtxS.open(nonceS, ct, te.encode("jugemu-jugemu")); + + // pt === "Hello world!" + assertEquals(te.encode("Hello world!"), new Uint8Array(pt)); + }); + }); + + // describe("Oblivious HTTP with DhkemP256HkdfSha256/HkdfSha256/Aes128Gcm", () => { + // it("should work normally", async () => { + // const te = new TextEncoder(); + // const cryptoApi = await loadCrypto(); + + // const suite = new CipherSuite({ + // kem: new DhkemP256HkdfSha256(), + // kdf: new HkdfSha256(), + // aead: new Aes128Gcm(), + // }); + // const rkp = await suite.kem.generateKeyPair(); + + // // The sender (OHTTP client) side: + // const response = te.encode("This is the response."); + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // }); + + // const secretS = await sender.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const responseNonce = new Uint8Array(suite.aead.keySize); + // cryptoApi.getRandomValues(responseNonce); + // const saltS = concat(new Uint8Array(sender.enc), responseNonce); + + // const prkS = await suite.kdf.extract(saltS, new Uint8Array(secretS)); + // const keyS = await suite.kdf.expand( + // prkS, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceS = await suite.kdf.expand( + // prkS, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + + // const aeadKeyS = await suite.aead.createEncryptionContext(keyS); + // const ct = await aeadKeyS.seal(nonceS, response, te.encode("")); + // const encResponse = concat(responseNonce, new Uint8Array(ct)); + + // // The recipient (OHTTP server) side: + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp.privateKey, + // enc: sender.enc, + // }); + + // const secretR = await recipient.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const saltR = concat( + // new Uint8Array(sender.enc), + // encResponse.slice(0, suite.aead.keySize), + // ); + // const prkR = await suite.kdf.extract( + // saltR, + // new Uint8Array(secretR), + // ); + // const keyR = await suite.kdf.expand( + // prkR, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceR = await suite.kdf.expand( + // prkR, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + // const aeadKeyR = await suite.aead.createEncryptionContext(keyR); + // const pt = await aeadKeyR.open( + // nonceR, + // encResponse.slice(suite.aead.keySize), + // te.encode(""), + // ); + + // // pt === "This is the response." + // assertEquals(response, new Uint8Array(pt)); + // }); + // }); + + // describe("Oblivious HTTP with DhkemP384HkdfSha384/HkdfSha384/Aes256Gcm", () => { + // it("should work normally", async () => { + // const te = new TextEncoder(); + // const cryptoApi = await loadCrypto(); + + // const suite = new CipherSuite({ + // kem: new DhkemP384HkdfSha384(), + // kdf: new HkdfSha384(), + // aead: new Aes256Gcm(), + // }); + // const rkp = await suite.kem.generateKeyPair(); + + // // The sender (OHTTP client) side: + // const response = te.encode("This is the response."); + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // }); + + // const secretS = await sender.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const responseNonce = new Uint8Array(suite.aead.keySize); + // cryptoApi.getRandomValues(responseNonce); + // const saltS = concat(new Uint8Array(sender.enc), responseNonce); + + // const prkS = await suite.kdf.extract(saltS, new Uint8Array(secretS)); + // const keyS = await suite.kdf.expand( + // prkS, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceS = await suite.kdf.expand( + // prkS, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + + // const aeadKeyS = await suite.aead.createEncryptionContext(keyS); + // const ct = await aeadKeyS.seal(nonceS, response, te.encode("")); + // const encResponse = concat(responseNonce, new Uint8Array(ct)); + + // // The recipient (OHTTP server) side: + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp.privateKey, + // enc: sender.enc, + // }); + + // const secretR = await recipient.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const saltR = concat( + // new Uint8Array(sender.enc), + // encResponse.slice(0, suite.aead.keySize), + // ); + // const prkR = await suite.kdf.extract( + // saltR, + // new Uint8Array(secretR), + // ); + // const keyR = await suite.kdf.expand( + // prkR, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceR = await suite.kdf.expand( + // prkR, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + // const aeadKeyR = await suite.aead.createEncryptionContext(keyR); + // const pt = await aeadKeyR.open( + // nonceR, + // encResponse.slice(suite.aead.keySize), + // te.encode(""), + // ); + + // // pt === "This is the response." + // assertEquals(response, new Uint8Array(pt)); + // }); + // }); + + // describe("Oblivious HTTP with DhkemP521HkdfSha512/HkdfSha512/Aes256Gcm", () => { + // it("should work normally", async () => { + // const te = new TextEncoder(); + // const cryptoApi = await loadCrypto(); + + // const suite = new CipherSuite({ + // kem: new DhkemP521HkdfSha512(), + // kdf: new HkdfSha512(), + // aead: new Aes256Gcm(), + // }); + // const rkp = await suite.kem.generateKeyPair(); + + // // The sender (OHTTP client) side: + // const response = te.encode("This is the response."); + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // }); + + // const secretS = await sender.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const responseNonce = new Uint8Array(suite.aead.keySize); + // cryptoApi.getRandomValues(responseNonce); + // const saltS = concat(new Uint8Array(sender.enc), responseNonce); + + // const prkS = await suite.kdf.extract(saltS, new Uint8Array(secretS)); + // const keyS = await suite.kdf.expand( + // prkS, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceS = await suite.kdf.expand( + // prkS, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + + // const aeadKeyS = await suite.aead.createEncryptionContext(keyS); + // const ct = await aeadKeyS.seal(nonceS, response, te.encode("")); + // const encResponse = concat(responseNonce, new Uint8Array(ct)); + + // // The recipient (OHTTP server) side: + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp.privateKey, + // enc: sender.enc, + // }); + + // const secretR = await recipient.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const saltR = concat( + // new Uint8Array(sender.enc), + // encResponse.slice(0, suite.aead.keySize), + // ); + // const prkR = await suite.kdf.extract( + // saltR, + // new Uint8Array(secretR), + // ); + // const keyR = await suite.kdf.expand( + // prkR, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceR = await suite.kdf.expand( + // prkR, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + // const aeadKeyR = await suite.aead.createEncryptionContext(keyR); + // const pt = await aeadKeyR.open( + // nonceR, + // encResponse.slice(suite.aead.keySize), + // te.encode(""), + // ); + + // // pt === "This is the response." + // assertEquals(response, new Uint8Array(pt)); + // }); + // }); +});