diff --git a/README.md b/README.md index fb577b2..7bdb44b 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ npm install @peculiar/webcrypto | AES-KW | X | | X | | | X | | | ECDSA1 | X | | X | X | | | | | ECDH1 | X | | X | | | | X | +| HKDF | | | X | | | | X | | PBKDF2 | | | X | | | | X | | DES-CBC2| X | | X | | X | X | | | DES-EDE3-CBC2| X | | X | | X | X | | diff --git a/package-lock.json b/package-lock.json index df6979e..29a3bcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -250,7 +250,14 @@ "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.21.tgz", "integrity": "sha512-yfRxOLyqiUvtn0pO7t//tdEet9ZWBWw5nrsHkdSfC5lLKOHYvhdPcULPD3KANSfGCOqHWXxndmOmXf0tZWjNPg==", "requires": { - "pvutils": "latest" + "pvutils": "^1.0.17" + }, + "dependencies": { + "pvutils": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz", + "integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==" + } } }, "assert-plus": { @@ -2535,11 +2542,6 @@ "tslib": "^1.9.3" } }, - "pvutils": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.16.tgz", - "integrity": "sha512-+8C8cKbON+UH3GxGRdF0xHwiaVhq0+lsHcUgN/MZLCLtvcYhVP3fQPhrnkucK2v/RqRuq4pw1jjyF9PwW9f7mA==" - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -3153,9 +3155,9 @@ } }, "webcrypto-core": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.0.7.tgz", - "integrity": "sha512-6IVimwLJxNy7fa9yUduvR0qUcVCNE2t+HK9HRN3JAlqbhgRYoSQg8KtR7I0BwRST03kUG6fusYpn7r2feKNFkg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.0.10.tgz", + "integrity": "sha512-nTc37SyfR05Lg/t45uIowbJtNXi5p06zV4WX8x+ZA6FSnCrRzdiAuk/rz1OjIcAKsyMRQw3hcmgFceoTxH9T3w==", "requires": { "pvtsutils": "^1.0.3", "tslib": "^1.9.3" diff --git a/package.json b/package.json index cf943ee..8fcc210 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "asn1js": "^2.0.21", "pvtsutils": "^1.0.3", "tslib": "^1.9.3", - "webcrypto-core": "^1.0.7" + "webcrypto-core": "^1.0.10" }, "nyc": { "extension": [ diff --git a/src/mechs/hkdf/hkdf.ts b/src/mechs/hkdf/hkdf.ts new file mode 100644 index 0000000..7da2545 --- /dev/null +++ b/src/mechs/hkdf/hkdf.ts @@ -0,0 +1,71 @@ +import * as core from "webcrypto-core"; +import { HmacCryptoKey } from "../hmac/key"; +import { HkdfCryptoKey } from "./key"; +import { BufferSourceConverter, CryptoKey } from "webcrypto-core"; +import crypto from "crypto"; + +export class HkdfProvider extends core.HkdfProvider { + + private normalizeHash(hash: HashAlgorithmIdentifier): Algorithm { + if (typeof hash === "string") { + hash = {name: hash}; + } + + this.checkHashAlgorithm(hash, this.hashAlgorithms); + return hash; + } + + public async onImportKey(format: KeyFormat, keyData: ArrayBuffer, algorithm: HmacImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + if (format.toLowerCase() !== "raw") { + throw new core.OperationError("Operation not supported"); + } + + const key: HkdfCryptoKey = new HkdfCryptoKey(); + key.data = Buffer.from(keyData); + key.algorithm = { name: this.name }; + key.extractable = extractable; + key.usages = keyUsages; + return key; + } + + public async onExportKey(format: KeyFormat, key: HmacCryptoKey): Promise { + switch (format.toLowerCase()) { + case "raw": + return new Uint8Array(key.data).buffer; + default: + throw new core.OperationError("format: Must be 'raw'"); + } + } + + public async onDeriveBits(params: HkdfParams, baseKey: HkdfCryptoKey, length: number): Promise { + const hash = this.normalizeHash(params.hash).name.replace("-", ""); + const hashLength = crypto.createHash(hash).digest().length; + + const byteLength = length / 8; + const info = BufferSourceConverter.toUint8Array(params.info); + + const PRK = crypto.createHmac(hash, BufferSourceConverter.toUint8Array(params.salt)) + .update(BufferSourceConverter.toUint8Array(baseKey.data)) + .digest(); + + let blocks = [Buffer.alloc(0)]; + const blockCount = Math.ceil(byteLength / hashLength) + 1; // Includes empty buffer + for (let i=1; i { + + const crypto = new Crypto(); + + testCrypto(crypto, [ + { + name: "HKDF", + actions: { + import: [ + { + name: "raw", + format: "raw", + data: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"), + algorithm: { + name: "HKDF", + }, + extractable: false, + keyUsages: ["deriveBits", "deriveKey"], + }, + ], + deriveBits: [ + { + key: { + format: "raw", + data: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"), + algorithm: { + name: "HKDF", + }, + extractable: false, + keyUsages: ["deriveBits"], + }, + algorithm: { + name: "HKDF", + hash: {name: "SHA-256"}, + salt: Buffer.from("000102030405060708090a0b0c", "hex"), + info: Buffer.from("f0f1f2f3f4f5f6f7f8f9", "hex"), + } as HkdfParams, + data: Buffer.from("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", "hex"), + length: 42 * 8, + }, + { + key: { + format: "raw", + data: Buffer.from("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f", "hex"), + algorithm: { + name: "HKDF", + }, + extractable: false, + keyUsages: ["deriveBits"], + }, + algorithm: { + name: "HKDF", + hash: "SHA-256", + salt: Buffer.from("606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf", "hex"), + info: Buffer.from("b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "hex"), + } as HkdfParams, + data: Buffer.from("b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87", "hex"), + length: 82 * 8, + }, + { + key: { + format: "raw", + data: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"), + algorithm: { + name: "HKDF", + }, + extractable: false, + keyUsages: ["deriveBits"], + }, + algorithm: { + name: "HKDF", + hash: "SHA-256", + salt: Buffer.from([]), + info: Buffer.from([]), + } as HkdfParams, + data: Buffer.from("8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8", "hex"), + length: 42 * 8, + }, + ], + }, + }, + ]); +});