diff --git a/README.md b/README.md index 259ad07..fb577b2 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ npm install @peculiar/webcrypto | AES-CBC | X | | X | | X | X | | | AES-CTR | X | | X | | X | X | | | AES-GCM | X | | X | | X | X | | +| AES-KW | X | | X | | | X | | | ECDSA1 | X | | X | X | | | | | ECDH1 | X | | X | | | | X | | PBKDF2 | | | X | | | | X | diff --git a/src/mechs/aes/aes_kw.ts b/src/mechs/aes/aes_kw.ts new file mode 100644 index 0000000..87a3631 --- /dev/null +++ b/src/mechs/aes/aes_kw.ts @@ -0,0 +1,40 @@ +import * as core from "webcrypto-core"; +import { AesCrypto } from "./crypto"; +import { AesCryptoKey } from "./key"; + +export class AesKwProvider extends core.AesKwProvider { + + public async onGenerateKey(algorithm: AesKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + return await AesCrypto.generateKey( + { + name: this.name, + length: algorithm.length, + }, + extractable, + keyUsages, + ); + } + + public async onExportKey(format: KeyFormat, key: AesCryptoKey): Promise { + return AesCrypto.exportKey(format, key); + } + + public async onImportKey(format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise { + return AesCrypto.importKey(format, keyData, { name: algorithm.name }, extractable, keyUsages); + } + + public async onEncrypt(algorithm: AesKeyAlgorithm, key: AesCryptoKey, data: ArrayBuffer): Promise { + return AesCrypto.encrypt(algorithm, key, new Uint8Array(data)); + } + + public async onDecrypt(algorithm: AesKeyAlgorithm, key: AesCryptoKey, data: ArrayBuffer): Promise { + return AesCrypto.decrypt(algorithm, key, new Uint8Array(data)); + } + + public checkCryptoKey(key: CryptoKey, keyUsage?: KeyUsage) { + super.checkCryptoKey(key, keyUsage); + if (!(key instanceof AesCryptoKey)) { + throw new TypeError("key: Is not a AesCryptoKey"); + } + } +} diff --git a/src/mechs/aes/crypto.ts b/src/mechs/aes/crypto.ts index 6d6efa1..eca0cfc 100644 --- a/src/mechs/aes/crypto.ts +++ b/src/mechs/aes/crypto.ts @@ -74,6 +74,8 @@ export class AesCrypto { return this.encryptAesCTR(algorithm as AesCtrParams, key, Buffer.from(data)); case "AES-GCM": return this.encryptAesGCM(algorithm as AesGcmParams, key, Buffer.from(data)); + case "AES-KW": + return this.encryptAesKW(algorithm as AesKeyAlgorithm, key, Buffer.from(data)); default: throw new core.OperationError("algorithm: Is not recognized"); } @@ -91,6 +93,8 @@ export class AesCrypto { return this.decryptAesCTR(algorithm as AesCtrParams, key, Buffer.from(data)); case "AES-GCM": return this.decryptAesGCM(algorithm as AesGcmParams, key, Buffer.from(data)); + case "AES-KW": + return this.decryptAesKW(algorithm as AesKeyAlgorithm, key, Buffer.from(data)); default: throw new core.OperationError("algorithm: Is not recognized"); } @@ -153,4 +157,20 @@ export class AesCrypto { return new Uint8Array(dec).buffer; } + public static async encryptAesKW(algorithm: AesKeyAlgorithm, key: AesCryptoKey, data: Buffer) { + const iv = Buffer.from("A6A6A6A6A6A6A6A6", "hex"); + const cipher = crypto.createCipheriv(`id-aes${key.algorithm.length}-wrap`, key.data, iv); + let enc = cipher.update(data); + enc = Buffer.concat([enc, cipher.final()]); + const res = new Uint8Array(enc).buffer; + return res; + } + + public static async decryptAesKW(algorithm: AesKeyAlgorithm, key: AesCryptoKey, data: Buffer) { + const iv = Buffer.from("A6A6A6A6A6A6A6A6", "'hex"); + const decipher = crypto.createDecipheriv(`id-aes${key.algorithm.length}-wrap`, key.data, iv); + let dec = decipher.update(data); + dec = Buffer.concat([dec, decipher.final()]); + return new Uint8Array(dec).buffer; + } } diff --git a/src/mechs/aes/index.ts b/src/mechs/aes/index.ts index 596cea8..c2971ec 100644 --- a/src/mechs/aes/index.ts +++ b/src/mechs/aes/index.ts @@ -3,3 +3,4 @@ export * from "./aes_cbc"; export * from "./aes_cmac"; export * from "./aes_ctr"; export * from "./aes_gcm"; +export * from "./aes_kw"; diff --git a/src/mechs/aes/key.ts b/src/mechs/aes/key.ts index d0b553f..2da5be6 100644 --- a/src/mechs/aes/key.ts +++ b/src/mechs/aes/key.ts @@ -18,6 +18,8 @@ export class AesCryptoKey extends SymmetricKey { return `A${this.algorithm.length}CTR`; case "AES-GCM": return `A${this.algorithm.length}GCM`; + case "AES-KW": + return `A${this.algorithm.length}KW`; default: throw new core.AlgorithmError("Unsupported algorithm name"); } diff --git a/src/subtle.ts b/src/subtle.ts index 9573405..fdb1ecd 100644 --- a/src/subtle.ts +++ b/src/subtle.ts @@ -1,6 +1,6 @@ import * as core from "webcrypto-core"; import { - AesCbcProvider, AesCtrProvider, AesGcmProvider, + AesCbcProvider, AesCtrProvider, AesGcmProvider, AesKwProvider, DesCbcProvider, DesEde3CbcProvider, EcdhProvider, EcdsaProvider, HmacProvider, @@ -17,6 +17,7 @@ export class SubtleCrypto extends core.SubtleCrypto { this.providers.set(new AesCbcProvider()); this.providers.set(new AesCtrProvider()); this.providers.set(new AesGcmProvider()); + this.providers.set(new AesKwProvider()); //#endregion //#region DES diff --git a/test/aes.ts b/test/aes.ts index 441a89e..73ff190 100644 --- a/test/aes.ts +++ b/test/aes.ts @@ -529,6 +529,183 @@ context("AES", () => { }, //#endregion + //#region AES-KW + { + name: "AES-128-KW", + actions: { + generateKey: [ + { + algorithm: { name: "AES-KW", length: 128 } as AesKeyGenParams, + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"] as KeyUsage[], + }, + ], + wrapKey: [ + { + key: { + format: "raw", + algorithm: "AES-KW", + data: Buffer.from("000102030405060708090A0B0C0D0E0F", "hex"), + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + wKey: { + format: "raw" as KeyFormat, + data: Buffer.from("00112233445566778899AABBCCDDEEFF", "hex"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + algorithm: { + name: "AES-KW", + }, + wrappedKey: Buffer.from("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5", "hex") + }, + ], + import: [ + { + name: "raw", + format: "raw" as KeyFormat, + data: Buffer.from("1234567890abcdef12345678"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"] as KeyUsage[], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A192KW", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4", + ext: true, + key_ops: ["wrapKey", "unwrapKey"], + }, + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-192-KW", + actions: { + generateKey: [ + { + algorithm: { name: "AES-KW", length: 192 } as AesKeyGenParams, + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"] as KeyUsage[], + }, + ], + wrapKey: [ + { + key: { + format: "raw", + algorithm: "AES-KW", + data: Buffer.from("000102030405060708090A0B0C0D0E0F1011121314151617", "hex"), + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + wKey: { + format: "raw" as KeyFormat, + data: Buffer.from("00112233445566778899AABBCCDDEEFF0001020304050607", "hex"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + algorithm: { + name: "AES-KW", + }, + wrappedKey: Buffer.from("031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2", "hex") + }, + ], + import: [ + { + name: "raw", + format: "raw" as KeyFormat, + data: Buffer.from("1234567890abcdef12345678"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"] as KeyUsage[], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A192KW", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4", + ext: true, + key_ops: ["wrapKey", "unwrapKey"], + }, + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-256-KW", + actions: { + generateKey: [ + { + algorithm: { name: "AES-KW", length: 256 } as AesKeyGenParams, + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + wrapKey: [ + { + key: { + format: "raw", + algorithm: "AES-KW", + data: Buffer.from("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", "hex"), + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + wKey: { + format: "raw" as KeyFormat, + data: Buffer.from("00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F", "hex"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + algorithm: { + name: "AES-KW", + }, + wrappedKey: Buffer.from("28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21", "hex"), + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: Buffer.from("1234567890abcdef1234567890abcdef"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A256KW", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWY", + ext: true, + key_ops: ["wrapKey", "unwrapKey"], + }, + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + }, + }, + //#endregion + ]); });