Skip to content

Commit

Permalink
Add tests for Pbkdf2 crypto primitive
Browse files Browse the repository at this point in the history
Signed-off-by: Frank Hinek <[email protected]>
  • Loading branch information
frankhinek committed Nov 6, 2023
1 parent 26daadb commit 4f008ee
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 5 deletions.
8 changes: 3 additions & 5 deletions packages/crypto/src/crypto-primitives/pbkdf2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,18 @@ export class Pbkdf2 {
// Convert length from bits to bytes.
const length = options.length / 8;

// Dynamically import node:crypto.
// Dynamically import the `crypto` package.
const { pbkdf2 } = await import('node:crypto');

return new Promise((resolve, reject) => {
return new Promise((resolve) => {
pbkdf2(
password,
salt,
iterations,
length,
hash,
(err, derivedKey) => {
if (err) {
reject(err);
} else {
if (!err) {
resolve(new Uint8Array(derivedKey));
}
}
Expand Down
121 changes: 121 additions & 0 deletions packages/crypto/tests/crypto-primitives.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { BytesKeyPair } from '../src/types/crypto-key.js';

import sinon from 'sinon';
import chai, { expect } from 'chai';
import { Convert } from '@web5/common';
import chaiAsPromised from 'chai-as-promised';
Expand All @@ -13,6 +14,7 @@ import {
AesGcm,
ConcatKdf,
Ed25519,
Pbkdf2,
Secp256k1,
X25519,
XChaCha20,
Expand Down Expand Up @@ -525,6 +527,125 @@ describe('Cryptographic Primitive Implementations', () => {
});
});

describe.only('Pbkdf2', () => {

Check warning on line 530 in packages/crypto/tests/crypto-primitives.spec.ts

View workflow job for this annotation

GitHub Actions / test-with-node

Unexpected exclusive mocha test
const password = Convert.string('password').toUint8Array();
const salt = Convert.string('salt').toUint8Array();
const iterations = 1;
const length = 256; // 32 bytes

describe('deriveKey', () => {
it('successfully derives a key using WebCrypto, if available', async () => {
const subtleDeriveBitsSpy = sinon.spy(crypto.subtle, 'deriveBits');

const derivedKey = await Pbkdf2.deriveKey({ hash: 'SHA-256', password, salt, iterations, length });

expect(derivedKey).to.be.instanceOf(Uint8Array);
expect(derivedKey.byteLength).to.equal(length / 8);
expect(subtleDeriveBitsSpy.called).to.be.true;

subtleDeriveBitsSpy.restore();
});

it('successfully derives a key using node:crypto when WebCrypto is not supported', async function () {
// Skip test in web browsers since node:crypto is not available.
if (typeof window !== 'undefined') this.skip();

// Ensure that WebCrypto is not available for this test.
sinon.stub(crypto, 'subtle').value(null);

// @ts-expect-error because we're spying on a private method.
const nodeCryptoDeriveKeySpy = sinon.spy(Pbkdf2, 'deriveKeyWithNodeCrypto');

const derivedKey = await Pbkdf2.deriveKey({ hash: 'SHA-256', password, salt, iterations, length });

expect(derivedKey).to.be.instanceOf(Uint8Array);
expect(derivedKey.byteLength).to.equal(length / 8);
expect(nodeCryptoDeriveKeySpy.called).to.be.true;

nodeCryptoDeriveKeySpy.restore();
sinon.restore();
});

it('derives the same value with node:crypto and WebCrypto', async function () {
// Skip test in web browsers since node:crypto is not available.
if (typeof window !== 'undefined') this.skip();

const options = { hash: 'SHA-256', password, salt, iterations, length };

// @ts-expect-error because we're testing a private method.
const webCryptoDerivedKey = await Pbkdf2.deriveKeyWithNodeCrypto(options);
// @ts-expect-error because we're testing a private method.
const nodeCryptoDerivedKey = await Pbkdf2.deriveKeyWithWebCrypto(options);

expect(webCryptoDerivedKey).to.deep.equal(nodeCryptoDerivedKey);
});

const hashFunctions: ('SHA-256' | 'SHA-384' | 'SHA-512')[] = ['SHA-256', 'SHA-384', 'SHA-512'];
hashFunctions.forEach(hash => {
it(`handles ${hash} hash function`, async () => {
const options = { hash, password, salt, iterations, length };

const derivedKey = await Pbkdf2.deriveKey(options);
expect(derivedKey).to.be.instanceOf(Uint8Array);
expect(derivedKey.byteLength).to.equal(length / 8);
});
});

it('throws an error when an invalid hash function is used with WebCrypto', async () => {
const options = {
hash: 'SHA-2' as const, password, salt, iterations, length
};

// @ts-expect-error for testing purposes
await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error);
});

it('throws an error when an invalid hash function is used with node:crypto', async function () {
// Skip test in web browsers since node:crypto is not available.
if (typeof window !== 'undefined') this.skip();

// Ensure that WebCrypto is not available for this test.
sinon.stub(crypto, 'subtle').value(null);

const options = {
hash: 'SHA-2' as const, password, salt, iterations, length
};

// @ts-expect-error for testing purposes
await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error);

sinon.restore();
});

it('throws an error when iterations count is not a positive number with WebCrypto', async () => {
const options = {
hash : 'SHA-256' as const, password, salt,
iterations : -1, length
};

// Every browser throws a different error message so a specific message cannot be checked.
await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error);
});

it('throws an error when iterations count is not a positive number with node:crypto', async function () {
// Skip test in web browsers since node:crypto is not available.
if (typeof window !== 'undefined') this.skip();

// Ensure that WebCrypto is not available for this test.
sinon.stub(crypto, 'subtle').value(null);

const options = {
hash : 'SHA-256' as const, password, salt,
iterations : -1, length
};

await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error, 'out of range');

sinon.restore();
});
});
});

describe('Secp256k1', () => {
describe('convertPublicKey method', () => {
it('converts an uncompressed public key to a compressed format', async () => {
Expand Down

0 comments on commit 4f008ee

Please sign in to comment.