Implement HKDF
This commit is contained in:
parent
04c1016124
commit
7d4786e09c
|
@ -43,6 +43,7 @@ npm install @peculiar/webcrypto
|
||||||
| AES-KW | X | | X | | | X | |
|
| AES-KW | X | | X | | | X | |
|
||||||
| ECDSA<sup>1</sup> | X | | X | X | | | |
|
| ECDSA<sup>1</sup> | X | | X | X | | | |
|
||||||
| ECDH<sup>1</sup> | X | | X | | | | X |
|
| ECDH<sup>1</sup> | X | | X | | | | X |
|
||||||
|
| HKDF | | | X | | | | X |
|
||||||
| PBKDF2 | | | X | | | | X |
|
| PBKDF2 | | | X | | | | X |
|
||||||
| DES-CBC<sup>2</sup>| X | | X | | X | X | |
|
| DES-CBC<sup>2</sup>| X | | X | | X | X | |
|
||||||
| DES-EDE3-CBC<sup>2</sup>| X | | X | | X | X | |
|
| DES-EDE3-CBC<sup>2</sup>| X | | X | | X | X | |
|
||||||
|
|
|
@ -250,7 +250,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.21.tgz",
|
||||||
"integrity": "sha512-yfRxOLyqiUvtn0pO7t//tdEet9ZWBWw5nrsHkdSfC5lLKOHYvhdPcULPD3KANSfGCOqHWXxndmOmXf0tZWjNPg==",
|
"integrity": "sha512-yfRxOLyqiUvtn0pO7t//tdEet9ZWBWw5nrsHkdSfC5lLKOHYvhdPcULPD3KANSfGCOqHWXxndmOmXf0tZWjNPg==",
|
||||||
"requires": {
|
"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": {
|
"assert-plus": {
|
||||||
|
@ -2535,11 +2542,6 @@
|
||||||
"tslib": "^1.9.3"
|
"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": {
|
"qs": {
|
||||||
"version": "6.5.2",
|
"version": "6.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||||
|
@ -3153,9 +3155,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"webcrypto-core": {
|
"webcrypto-core": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.0.10.tgz",
|
||||||
"integrity": "sha512-6IVimwLJxNy7fa9yUduvR0qUcVCNE2t+HK9HRN3JAlqbhgRYoSQg8KtR7I0BwRST03kUG6fusYpn7r2feKNFkg==",
|
"integrity": "sha512-nTc37SyfR05Lg/t45uIowbJtNXi5p06zV4WX8x+ZA6FSnCrRzdiAuk/rz1OjIcAKsyMRQw3hcmgFceoTxH9T3w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"pvtsutils": "^1.0.3",
|
"pvtsutils": "^1.0.3",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
"asn1js": "^2.0.21",
|
"asn1js": "^2.0.21",
|
||||||
"pvtsutils": "^1.0.3",
|
"pvtsutils": "^1.0.3",
|
||||||
"tslib": "^1.9.3",
|
"tslib": "^1.9.3",
|
||||||
"webcrypto-core": "^1.0.7"
|
"webcrypto-core": "^1.0.10"
|
||||||
},
|
},
|
||||||
"nyc": {
|
"nyc": {
|
||||||
"extension": [
|
"extension": [
|
||||||
|
|
|
@ -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<CryptoKey> {
|
||||||
|
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<JsonWebKey | ArrayBuffer> {
|
||||||
|
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<ArrayBuffer> {
|
||||||
|
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<blockCount; ++i) {
|
||||||
|
blocks.push(
|
||||||
|
crypto.createHmac(hash, PRK)
|
||||||
|
.update(Buffer.concat([blocks[i - 1], info, Buffer.from([i])]))
|
||||||
|
.digest()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.concat(blocks).slice(0, byteLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkCryptoKey(key: CryptoKey, keyUsage?: KeyUsage) {
|
||||||
|
super.checkCryptoKey(key, keyUsage);
|
||||||
|
if (!(key instanceof HkdfCryptoKey)) {
|
||||||
|
throw new TypeError("key: Is not HKDF CryptoKey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./hkdf";
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { CryptoKey } from "../../keys";
|
||||||
|
|
||||||
|
export class HkdfCryptoKey extends CryptoKey {
|
||||||
|
|
||||||
|
public data!: Buffer;
|
||||||
|
|
||||||
|
public algorithm!: KeyAlgorithm;
|
||||||
|
}
|
|
@ -5,3 +5,4 @@ export * from "./ec";
|
||||||
export * from "./sha";
|
export * from "./sha";
|
||||||
export * from "./pbkdf";
|
export * from "./pbkdf";
|
||||||
export * from "./hmac";
|
export * from "./hmac";
|
||||||
|
export * from "./hkdf";
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
DesCbcProvider, DesEde3CbcProvider,
|
DesCbcProvider, DesEde3CbcProvider,
|
||||||
EcdhProvider, EcdsaProvider,
|
EcdhProvider, EcdsaProvider,
|
||||||
HmacProvider,
|
HmacProvider,
|
||||||
Pbkdf2Provider,
|
Pbkdf2Provider, HkdfProvider,
|
||||||
RsaOaepProvider, RsaPssProvider, RsaSsaProvider,
|
RsaOaepProvider, RsaPssProvider, RsaSsaProvider,
|
||||||
Sha1Provider, Sha256Provider, Sha384Provider, Sha512Provider,
|
Sha1Provider, Sha256Provider, Sha384Provider, Sha512Provider,
|
||||||
} from "./mechs";
|
} from "./mechs";
|
||||||
|
@ -50,5 +50,9 @@ export class SubtleCrypto extends core.SubtleCrypto {
|
||||||
//#region HMAC
|
//#region HMAC
|
||||||
this.providers.set(new HmacProvider());
|
this.providers.set(new HmacProvider());
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region HKDF
|
||||||
|
this.providers.set(new HkdfProvider());
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,11 @@ export function testCrypto(crypto: Crypto, params: ITestParams[]) {
|
||||||
action.extractable,
|
action.extractable,
|
||||||
action.keyUsages);
|
action.keyUsages);
|
||||||
|
|
||||||
|
// Can't continue if key is not exctractable.
|
||||||
|
if (!action.extractable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const exportedData = await crypto.subtle.exportKey(
|
const exportedData = await crypto.subtle.exportKey(
|
||||||
action.format,
|
action.format,
|
||||||
importedKey);
|
importedKey);
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { Convert } from "pvtsutils";
|
||||||
|
import { Crypto } from "../src";
|
||||||
|
import { testCrypto } from "./helper";
|
||||||
|
|
||||||
|
context("HKDF", () => {
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
Reference in New Issue