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
+
]);
});