diff --git a/src/cipherSuite.ts b/src/cipherSuite.ts index a13650243..3a1665973 100644 --- a/src/cipherSuite.ts +++ b/src/cipherSuite.ts @@ -242,8 +242,8 @@ export class CipherSuite { * @throws {@link DeserializeError} */ public async importKey( - format: "raw", - key: ArrayBuffer, + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, isPublic = true, ): Promise { await this.setup(); diff --git a/src/interfaces/kemInterface.ts b/src/interfaces/kemInterface.ts index ff07dc809..722692e5c 100644 --- a/src/interfaces/kemInterface.ts +++ b/src/interfaces/kemInterface.ts @@ -42,8 +42,8 @@ export interface KemInterface { * Imports a key for the KEM. */ importKey( - format: "raw", - key: ArrayBuffer, + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, isPublic: boolean, ): Promise; diff --git a/src/interfaces/kemPrimitives.ts b/src/interfaces/kemPrimitives.ts index 11589194a..bec11e940 100644 --- a/src/interfaces/kemPrimitives.ts +++ b/src/interfaces/kemPrimitives.ts @@ -4,8 +4,8 @@ export interface KemPrimitives { deserializePublicKey(key: ArrayBuffer): Promise; importKey( - format: "raw", - key: ArrayBuffer, + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, isPublic: boolean, ): Promise; diff --git a/src/kems/dhkem.ts b/src/kems/dhkem.ts index ebdd5f8f8..0502af1cf 100644 --- a/src/kems/dhkem.ts +++ b/src/kems/dhkem.ts @@ -64,8 +64,8 @@ export class Dhkem extends WebCrypto implements KemInterface { } public async importKey( - format: "raw", - key: ArrayBuffer, + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, isPublic: boolean, ): Promise { try { diff --git a/src/kems/dhkemPrimitives/ec.ts b/src/kems/dhkemPrimitives/ec.ts index 4a8235b41..3659e781c 100644 --- a/src/kems/dhkemPrimitives/ec.ts +++ b/src/kems/dhkemPrimitives/ec.ts @@ -185,13 +185,24 @@ export class Ec implements KemPrimitives { } public async importKey( - format: "raw", - key: ArrayBuffer, + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, isPublic: boolean, ): Promise { - if (format !== "raw") { - throw new Error("Unsupported format"); + 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); + } + + private async _importRawKey( + key: ArrayBuffer, + isPublic: boolean, + ): Promise { if (isPublic && key.byteLength !== this._nPk) { throw new Error("Invalid public key for the ciphersuite"); } @@ -201,7 +212,7 @@ export class Ec implements KemPrimitives { try { if (isPublic) { // return await this._api.importKey(format, key, this._alg, true, consts.KEM_USAGES); - return await this._api.importKey(format, key, this._alg, true, []); + return await this._api.importKey("raw", key, this._alg, true, []); } const k = new Uint8Array(key); const pkcs8Key = new Uint8Array(this._pkcs8AlgId.length + k.length); @@ -219,6 +230,31 @@ export class Ec implements KemPrimitives { } } + private async _importJWK( + key: JsonWebKey, + isPublic: boolean, + ): Promise { + if (typeof key.crv === "undefined" || key.crv !== this._alg.namedCurve) { + 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.importKey("jwk", key, this._alg, true, []); + } + if (typeof key.d === "undefined") { + throw new Error("Invalid key: `d` not found"); + } + return await this._api.importKey( + "jwk", + key, + this._alg, + true, + consts.KEM_USAGES, + ); + } + public async derivePublicKey(key: CryptoKey): Promise { const jwk = await this._api.exportKey("jwk", key); delete jwk["d"]; diff --git a/src/kems/dhkemPrimitives/x25519.ts b/src/kems/dhkemPrimitives/x25519.ts index 4176f1c78..c6662a97f 100644 --- a/src/kems/dhkemPrimitives/x25519.ts +++ b/src/kems/dhkemPrimitives/x25519.ts @@ -6,6 +6,7 @@ import type { KdfInterface } from "../../interfaces/kdfInterface.ts"; import { XCryptoKey } from "../../xCryptoKey.ts"; import * as consts from "../../consts.ts"; +import { base64UrlToBytes } from "../../utils/misc.ts"; const ALG_NAME = "X25519"; @@ -29,14 +30,18 @@ export class X25519 implements KemPrimitives { } public async importKey( - format: "raw", - key: ArrayBuffer, + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, isPublic: boolean, ): Promise { - if (format !== "raw") { - throw new Error("Unsupported format"); + 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._importKey(key, isPublic); + return await this._importJWK(key as JsonWebKey, isPublic); } public async derivePublicKey(key: CryptoKey): Promise { @@ -89,7 +94,10 @@ export class X25519 implements KemPrimitives { }); } - private _importKey(key: ArrayBuffer, isPublic: boolean): Promise { + private _importRawKey( + key: ArrayBuffer, + isPublic: boolean, + ): Promise { return new Promise((resolve, reject) => { if (isPublic && key.byteLength !== this._nPk) { reject(new Error("Invalid public key for the ciphersuite")); @@ -107,6 +115,43 @@ export class X25519 implements KemPrimitives { }); } + private _importJWK(key: JsonWebKey, isPublic: boolean): Promise { + return new Promise((resolve, reject) => { + if (typeof key.kty === "undefined" || key.kty !== "OKP") { + reject(new Error(`Invalid kty: ${key.kty}`)); + } + if (typeof key.crv === "undefined" || key.crv !== "X25519") { + reject(new Error(`Invalid crv: ${key.crv}`)); + } + if (isPublic) { + if (typeof key.d !== "undefined") { + reject(new Error("Invalid key: `d` should not be set")); + } + if (typeof key.x === "undefined") { + reject(new Error("Invalid key: `x` not found")); + } + resolve( + new XCryptoKey( + ALG_NAME, + base64UrlToBytes(key.x as string), + "public", + ), + ); + } else { + if (typeof key.d !== "string") { + reject(new Error("Invalid key: `d` not found")); + } + resolve( + new XCryptoKey( + ALG_NAME, + base64UrlToBytes(key.d as string), + "private", + ), + ); + } + }); + } + private _derivePublicKey(k: XCryptoKey): Promise { return new Promise((resolve) => { const pk = x25519.getPublicKey(k.key); diff --git a/src/kems/dhkemPrimitives/x448.ts b/src/kems/dhkemPrimitives/x448.ts index 940b24965..b6113009c 100644 --- a/src/kems/dhkemPrimitives/x448.ts +++ b/src/kems/dhkemPrimitives/x448.ts @@ -6,6 +6,7 @@ import type { KdfInterface } from "../../interfaces/kdfInterface.ts"; import { XCryptoKey } from "../../xCryptoKey.ts"; import * as consts from "../../consts.ts"; +import { base64UrlToBytes } from "../../utils/misc.ts"; const ALG_NAME = "X448"; @@ -29,14 +30,18 @@ export class X448 implements KemPrimitives { } public async importKey( - format: "raw", - key: ArrayBuffer, + format: "raw" | "jwk", + key: ArrayBuffer | JsonWebKey, isPublic: boolean, ): Promise { - if (format !== "raw") { - throw new Error("Unsupported format"); + 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._importKey(key, isPublic); + return await this._importJWK(key as JsonWebKey, isPublic); } public async derivePublicKey(key: CryptoKey): Promise { @@ -89,7 +94,10 @@ export class X448 implements KemPrimitives { }); } - private _importKey(key: ArrayBuffer, isPublic: boolean): Promise { + private _importRawKey( + key: ArrayBuffer, + isPublic: boolean, + ): Promise { return new Promise((resolve, reject) => { if (isPublic && key.byteLength !== this._nPk) { reject(new Error("Invalid public key for the ciphersuite")); @@ -107,6 +115,43 @@ export class X448 implements KemPrimitives { }); } + private _importJWK(key: JsonWebKey, isPublic: boolean): Promise { + return new Promise((resolve, reject) => { + if (key.kty !== "OKP") { + reject(new Error(`Invalid kty: ${key.kty}`)); + } + if (key.crv !== "X448") { + reject(new Error(`Invalid crv: ${key.crv}`)); + } + if (isPublic) { + if (typeof key.d !== "undefined") { + reject(new Error("Invalid key: `d` should not be set")); + } + if (typeof key.x !== "string") { + reject(new Error("Invalid key: `x` not found")); + } + resolve( + new XCryptoKey( + ALG_NAME, + base64UrlToBytes(key.x as string), + "public", + ), + ); + } else { + if (typeof key.d !== "string") { + reject(new Error("Invalid key: `d` not found")); + } + resolve( + new XCryptoKey( + ALG_NAME, + base64UrlToBytes(key.d as string), + "private", + ), + ); + } + }); + } + private _derivePublicKey(k: XCryptoKey): Promise { return new Promise((resolve) => { const pk = x448.getPublicKey(k.key); diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 4eaece538..b6d3fc51e 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -101,6 +101,19 @@ export function hexToBytes(v: string): Uint8Array { /** * Converts bytes to hex string. */ -export function bytesToHex(v: Uint8Array) { +export function bytesToHex(v: Uint8Array): string { return [...v].map((x) => x.toString(16).padStart(2, "0")).join(""); } + +/** + * Decodes Base64Url-encoded data. + */ +export 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; +} diff --git a/src/xCryptoKey.ts b/src/xCryptoKey.ts index d0dda9530..527eb74b5 100644 --- a/src/xCryptoKey.ts +++ b/src/xCryptoKey.ts @@ -11,5 +11,8 @@ export class XCryptoKey implements CryptoKey { this.key = key; this.type = type; this.algorithm = { name: name }; + if (type === "public") { + this.usages = []; + } } } diff --git a/test/cipherSuite.test.ts b/test/cipherSuite.test.ts index a9676e98f..98d3b48d3 100644 --- a/test/cipherSuite.test.ts +++ b/test/cipherSuite.test.ts @@ -182,7 +182,7 @@ describe("CipherSuite", () => { }); describe("A README example of Base mode", () => { - it("should work normally", async () => { + it("should work normally with generateKeyPair", async () => { // setup const suite = new CipherSuite({ kem: Kem.DhkemP256HkdfSha256, @@ -214,10 +214,61 @@ describe("CipherSuite", () => { await assertRejects(() => recipient.seal(pt), errors.SealError); await assertRejects(() => sender.open(ct), errors.OpenError); }); + + it("should work normally with importKey('jwk')", async () => { + // setup + const suite = new CipherSuite({ + kem: Kem.DhkemP256HkdfSha256, + kdf: Kdf.HkdfSha256, + aead: Aead.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.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.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), errors.SealError); + await assertRejects(() => sender.open(ct), errors.OpenError); + }); }); describe("A README example of Base mode (Kem.DhkemP384HkdfSha384/Kdf.HkdfSha384)", () => { - it("should work normally", async () => { + it("should work normally with generateKeyPair", async () => { // setup const suite = new CipherSuite({ kem: Kem.DhkemP384HkdfSha384, @@ -247,10 +298,61 @@ describe("CipherSuite", () => { // assert assertEquals(new TextDecoder().decode(pt), "my-secret-message"); }); + + it("should work normally with importKey('jwk')", async () => { + // setup + const suite = new CipherSuite({ + kem: Kem.DhkemP384HkdfSha384, + kdf: Kdf.HkdfSha384, + aead: Aead.Aes128Gcm, + }); + + const jwkPkR = { + kty: "EC", + crv: "P-384", + kid: "P-384-01", + x: "_XyN9woHaS0mPimSW-etwJMEDSzxIMjp4PjezavU8SHJoClz1bQrcmPb1ZJxHxhI", + y: "GCNfc32p9sRotx7u2oDGJ3Eqz6q5zPHLdizNn83oRsUTN31eCWfGLHWRury3xF50", + key_ops: [], + }; + const pkR = await suite.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.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), errors.SealError); + await assertRejects(() => sender.open(ct), errors.OpenError); + }); }); - describe("A README example of Base mode (Kem.DhkemP521HkdfSha512/Kdf.HkdfSha384)", () => { - it("should work normally", async () => { + describe("A README example of Base mode (Kem.DhkemP521HkdfSha512/Kdf.HkdfSha512)", () => { + it("should work normally with generateKeyPair", async () => { if (isDeno()) { return; } @@ -258,7 +360,7 @@ describe("CipherSuite", () => { // setup const suite = new CipherSuite({ kem: Kem.DhkemP521HkdfSha512, - kdf: Kdf.HkdfSha384, + kdf: Kdf.HkdfSha512, aead: Aead.Aes128Gcm, }); @@ -284,14 +386,69 @@ describe("CipherSuite", () => { // 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: Kem.DhkemP521HkdfSha512, + kdf: Kdf.HkdfSha512, + aead: Aead.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.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.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), errors.SealError); + await assertRejects(() => sender.open(ct), errors.OpenError); + }); }); - describe("A README example of Base mode (Kem.DhkemX25519HkdfSha256/Kdf.HkdfSha384)", () => { + describe("A README example of Base mode (Kem.DhkemX25519HkdfSha256/Kdf.HkdfSha256)", () => { it("should work normally", async () => { // setup const suite = new CipherSuite({ kem: Kem.DhkemX25519HkdfSha256, - kdf: Kdf.HkdfSha384, + kdf: Kdf.HkdfSha256, aead: Aead.Aes128Gcm, }); @@ -317,6 +474,55 @@ describe("CipherSuite", () => { // assert assertEquals(new TextDecoder().decode(pt), "my-secret-message"); }); + + it("should work normally with importKey('jwk')", async () => { + // setup + const suite = new CipherSuite({ + kem: Kem.DhkemX25519HkdfSha256, + kdf: Kdf.HkdfSha256, + aead: Aead.Aes128Gcm, + }); + + const jwkPkR = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + key_ops: [], + }; + const pkR = await suite.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.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), errors.SealError); + await assertRejects(() => sender.open(ct), errors.OpenError); + }); }); describe("A README example of Base mode (Kem.DhkemSecp256K1HkdfSha256/Kdf.HkdfSha256)", () => { @@ -352,13 +558,13 @@ describe("CipherSuite", () => { }); }); - describe("A README example of Base mode (Kem.DhkemX448HkdfSha256/Kdf.HkdfSha384)", () => { + describe("A README example of Base mode (Kem.DhkemX448HkdfSha256/Kdf.HkdfSha512)", () => { it("should work normally", async () => { // setup const suite = new CipherSuite({ kem: Kem.DhkemX448HkdfSha512, - kdf: Kdf.HkdfSha384, - aead: Aead.Aes128Gcm, + kdf: Kdf.HkdfSha512, + aead: Aead.Aes256Gcm, }); const rkp = await suite.generateKeyPair(); @@ -383,6 +589,55 @@ describe("CipherSuite", () => { // assert assertEquals(new TextDecoder().decode(pt), "my-secret-message"); }); + + it("should work normally with importKey('jwk')", async () => { + // setup + const suite = new CipherSuite({ + kem: Kem.DhkemX448HkdfSha512, + kdf: Kdf.HkdfSha512, + aead: Aead.Aes256Gcm, + }); + + const jwkPkR = { + kty: "OKP", + crv: "X448", + kid: "X448-01", + x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + key_ops: [], + }; + const pkR = await suite.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.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), errors.SealError); + await assertRejects(() => sender.open(ct), errors.OpenError); + }); }); describe("A README example of Base mode (ExportOnly)", () => { diff --git a/test/kemContext.test.ts b/test/kemContext.test.ts index 8688f8a96..07b298e6c 100644 --- a/test/kemContext.test.ts +++ b/test/kemContext.test.ts @@ -147,8 +147,8 @@ describe("generateKeyPair", () => { assertEquals(kp.publicKey.extractable, true); assertEquals(kp.publicKey.algorithm.name, "ECDH"); // assertEquals(kp.publicKey.algorithm.namedCurve, "secp256k1"); - assertEquals(kp.publicKey.usages.length, 1); - assertEquals(kp.publicKey.usages[0], "deriveBits"); + 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, "ECDH"); @@ -166,8 +166,8 @@ describe("generateKeyPair", () => { assertEquals(kp.publicKey.type, "public"); assertEquals(kp.publicKey.extractable, true); assertEquals(kp.publicKey.algorithm.name, "X25519"); - assertEquals(kp.publicKey.usages.length, 1); - assertEquals(kp.publicKey.usages[0], "deriveBits"); + 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, "X25519"); @@ -184,8 +184,8 @@ describe("generateKeyPair", () => { assertEquals(kp.publicKey.type, "public"); assertEquals(kp.publicKey.extractable, true); assertEquals(kp.publicKey.algorithm.name, "X448"); - assertEquals(kp.publicKey.usages.length, 1); - assertEquals(kp.publicKey.usages[0], "deriveBits"); + 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"); @@ -301,8 +301,8 @@ describe("deriveKeyPair", () => { assertEquals(kp.publicKey.extractable, true); assertEquals(kp.publicKey.algorithm.name, "ECDH"); // assertEquals(kp.publicKey.algorithm.namedCurve, "secp256k1"); - assertEquals(kp.publicKey.usages.length, 1); - assertEquals(kp.publicKey.usages[0], "deriveBits"); + 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, "ECDH"); @@ -322,8 +322,8 @@ describe("deriveKeyPair", () => { assertEquals(kp.publicKey.type, "public"); assertEquals(kp.publicKey.extractable, true); assertEquals(kp.publicKey.algorithm.name, "X25519"); - assertEquals(kp.publicKey.usages.length, 1); - assertEquals(kp.publicKey.usages[0], "deriveBits"); + 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, "X25519"); @@ -343,8 +343,8 @@ describe("deriveKeyPair", () => { assertEquals(kp.publicKey.type, "public"); assertEquals(kp.publicKey.extractable, true); assertEquals(kp.publicKey.algorithm.name, "X448"); - assertEquals(kp.publicKey.usages.length, 1); - assertEquals(kp.publicKey.usages[0], "deriveBits"); + 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"); @@ -435,8 +435,8 @@ describe("serialize/deserializePublicKey", () => { assertEquals(pubKey.extractable, true); assertEquals(pubKey.algorithm.name, "ECDH"); // assertEquals(pubKey.algorithm.namedCurve, "secp256k1"); - assertEquals(pubKey.usages.length, 1); - assertEquals(pubKey.usages[0], "deriveBits"); + assertEquals(pubKey.usages.length, 0); + // assertEquals(pubKey.usages[0], "deriveBits"); }); it("should return a proper instance with DhkemX25519HkdfSha256", async () => { @@ -450,8 +450,8 @@ describe("serialize/deserializePublicKey", () => { assertEquals(pubKey.type, "public"); assertEquals(pubKey.extractable, true); assertEquals(pubKey.algorithm.name, "X25519"); - assertEquals(pubKey.usages.length, 1); - assertEquals(kp.privateKey.usages[0], "deriveBits"); + assertEquals(pubKey.usages.length, 0); + // assertEquals(kp.privateKey.usages[0], "deriveBits"); }); it("should return a proper instance with DhkemX448HkdfSha512", async () => { @@ -465,8 +465,8 @@ describe("serialize/deserializePublicKey", () => { assertEquals(pubKey.type, "public"); assertEquals(pubKey.extractable, true); assertEquals(pubKey.algorithm.name, "X448"); - assertEquals(pubKey.usages.length, 1); - assertEquals(kp.privateKey.usages[0], "deriveBits"); + assertEquals(pubKey.usages.length, 0); + // assertEquals(kp.privateKey.usages[0], "deriveBits"); }); }); @@ -508,7 +508,199 @@ describe("serialize/deserializePublicKey", () => { describe("importKey", () => { describe("with valid parameters", () => { - it("should return a valid private key with DhkemSecp256K1HkdfSha256 private key", async () => { + it("should return a valid private key for DhkemP256HkdfSha256 from JWK", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const jwk = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: ["deriveBits"], + }; + const privKey = await kemContext.importKey("jwk", jwk, false); + + // assert + assertEquals(privKey.usages.length, 1); + assertEquals(privKey.usages[0], "deriveBits"); + }); + + it("should return a valid public key for DhkemP256HkdfSha256 from JWK", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const jwk = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + key_ops: [], + }; + const privKey = await kemContext.importKey("jwk", jwk, true); + + // assert + assertEquals(privKey.usages.length, 0); + }); + + it("should return a valid private key for DhkemP384HkdfSha384 from JWK", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP384HkdfSha384(api); + + const jwk = { + kty: "EC", + crv: "P-384", + kid: "P-384-01", + x: "_XyN9woHaS0mPimSW-etwJMEDSzxIMjp4PjezavU8SHJoClz1bQrcmPb1ZJxHxhI", + y: "GCNfc32p9sRotx7u2oDGJ3Eqz6q5zPHLdizNn83oRsUTN31eCWfGLHWRury3xF50", + d: "1pImEKbrr771-RKi8Tb7tou_WjiR7kwui_nMu16449rk3lzAqf9buUhTkJ-pogkb", + key_ops: ["deriveBits"], + }; + const privKey = await kemContext.importKey("jwk", jwk, false); + + // assert + assertEquals(privKey.usages.length, 1); + assertEquals(privKey.usages[0], "deriveBits"); + }); + + it("should return a valid public key for DhkemP384HkdfSha384 from JWK", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP384HkdfSha384(api); + + const jwk = { + kty: "EC", + crv: "P-384", + kid: "P-384-01", + x: "_XyN9woHaS0mPimSW-etwJMEDSzxIMjp4PjezavU8SHJoClz1bQrcmPb1ZJxHxhI", + y: "GCNfc32p9sRotx7u2oDGJ3Eqz6q5zPHLdizNn83oRsUTN31eCWfGLHWRury3xF50", + key_ops: [], + }; + const privKey = await kemContext.importKey("jwk", jwk, true); + + // assert + assertEquals(privKey.usages.length, 0); + }); + + it("should return a valid private key for DhkemP521HkdfSha512 from JWK", async () => { + if (isDeno()) { + return; + } + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP521HkdfSha512(api); + + const jwk = { + 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 privKey = await kemContext.importKey("jwk", jwk, false); + + // assert + assertEquals(privKey.usages.length, 1); + assertEquals(privKey.usages[0], "deriveBits"); + }); + + it("should return a valid public key for DhkemP521HkdfSha512 from JWK", async () => { + if (isDeno()) { + return; + } + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP521HkdfSha512(api); + + const jwk = { + 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 privKey = await kemContext.importKey("jwk", jwk, true); + + // assert + assertEquals(privKey.usages.length, 0); + }); + + it("should return a valid private key for DhkemX25519HkdfSha256 from JWK", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const jwk = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: ["deriveBits"], + }; + const privKey = await kemContext.importKey("jwk", jwk, false); + + // assert + assertEquals(privKey.usages.length, 1); + assertEquals(privKey.usages[0], "deriveBits"); + }); + + it("should return a valid public key for DhkemX25519HkdfSha256 from JWK", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const jwk = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + key_ops: [], + }; + const privKey = await kemContext.importKey("jwk", jwk, true); + + // assert + assertEquals(privKey.usages.length, 0); + }); + + it("should return a valid private key for DhkemX448HkdfSha512 from JWK", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + 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"); + }); + + it("should return a valid public key for DhkemX448HkdfSha512 from JWK", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + 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); + }); + + it("should return a valid private key for DhkemSecp256K1HkdfSha256 from raw key", async () => { const api = await loadSubtleCrypto(); const kemContext = new DhkemSecp256K1HkdfSha256(api); @@ -522,7 +714,7 @@ describe("importKey", () => { assertEquals(privKey.usages[0], "deriveBits"); }); - it("should return a valid public key with DhkemSecp256K1HkdfSha256 public key", async () => { + it("should return a valid public key for DhkemSecp256K1HkdfSha256 from raw key", async () => { const api = await loadSubtleCrypto(); const kemContext = new DhkemSecp256K1HkdfSha256(api); @@ -533,12 +725,423 @@ describe("importKey", () => { const privKey = await kemContext.importKey("raw", rawKey, true); // assert - assertEquals(privKey.usages.length, 1); - assertEquals(privKey.usages[0], "deriveBits"); + assertEquals(privKey.usages.length, 0); + // assertEquals(privKey.usages[0], "deriveBits"); }); }); describe("with invalid parameters", () => { + it("should throw DeserializeError with private raw key(EC/P-256) with 'jwk'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const cryptoApi = await loadCrypto(); + const rawKey = new Uint8Array(32); + cryptoApi.getRandomValues(rawKey); + + // assert + await assertRejects( + () => kemContext.importKey("jwk", rawKey.buffer, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with private JWK(EC/P-256) with 'raw'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const jwk = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: ["deriveBits"], + }; + + // assert + await assertRejects( + () => kemContext.importKey("raw", jwk, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(EC/P-256) without 'kty'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const jwk = { + // kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: ["deriveBits"], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(EC/P-256) without 'crv'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const jwk = { + kty: "EC", + // crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: ["deriveBits"], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(EC/P-256) without 'd'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const jwk = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + // d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: ["deriveBits"], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid public JWK(EC/P-256) without 'x'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const jwk = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + // x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + // d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: [], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, true), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid public JWK(EC/P-256) with 'd'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemP256HkdfSha256(api); + + const jwk = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: [], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, true), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with private raw key(OKP/X25519) with 'jwk'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const cryptoApi = await loadCrypto(); + const rawKey = new Uint8Array(32); + cryptoApi.getRandomValues(rawKey); + + // assert + await assertRejects( + () => kemContext.importKey("jwk", rawKey.buffer, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with private JWK(OKP/X25519) with 'raw'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const jwk = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: ["deriveBits"], + }; + + // assert + await assertRejects( + () => kemContext.importKey("raw", jwk, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(OKP/X25519) without 'kty'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const jwk = { + // kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: ["deriveBits"], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(OKP/X25519) without 'crv'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const jwk = { + kty: "OKP", + // crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: ["deriveBits"], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(OKP/X25519) without 'd'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const jwk = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + // d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: ["deriveBits"], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid public JWK(OKP/X25519) without 'x'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const jwk = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + // x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + // d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: [], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, true), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid public JWK(OKP/X25519) with 'd'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX25519HkdfSha256(api); + + const jwk = { + kty: "OKP", + crv: "X25519", + kid: "X25519-01", + x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + key_ops: [], + }; + + // assert + await assertRejects( + () => kemContext.importKey("jwk", jwk, true), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with private raw key(OKP/448) with 'jwk'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + const cryptoApi = await loadCrypto(); + const rawKey = new Uint8Array(56); + cryptoApi.getRandomValues(rawKey); + + // assert + await assertRejects( + () => kemContext.importKey("jwk", rawKey.buffer, false), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with private JWK(OKP/448) with 'raw'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + 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), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'kty'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + 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), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'crv'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + 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), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid private JWK(OKP/X448) without 'd'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + 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), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid public JWK(OKP/X448) without 'x'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + 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), + errors.DeserializeError, + ); + }); + + it("should throw DeserializeError with invalid public JWK(X448) with 'd'", async () => { + const api = await loadSubtleCrypto(); + const kemContext = new DhkemX448HkdfSha512(api); + + 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), + errors.DeserializeError, + ); + }); + it("should throw DeserializeError with invalid DhkemSecp256K1HkdfSha256 private key", async () => { const api = await loadSubtleCrypto(); const kemContext = new DhkemSecp256K1HkdfSha256(api);