Skip to content

Commit

Permalink
Crypto: Implement generation of RSA keys
Browse files Browse the repository at this point in the history
See #3.
  • Loading branch information
gnarea committed Jun 22, 2019
1 parent ff7b05e commit 6211d8c
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 19 deletions.
58 changes: 50 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
"engines": {
"node": ">=8.9"
},
"dependencies": {},
"dependencies": {
"node-webcrypto-ossl": "^1.0.47",
"pkijs": "^2.1.78"
},
"devDependencies": {
"@types/jest": "^24.0.15",
"codecov": "^3.5.0",
Expand Down
68 changes: 68 additions & 0 deletions src/lib/crypto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { generateRsaKeys } from './crypto';

describe('generateRsaKeys', () => {
test('Keys should be RSA', async () => {
const keyPair = await generateRsaKeys();

expect(keyPair.publicKey.algorithm.name).toMatch(/^RSA-/);
expect(keyPair.privateKey.algorithm.name).toMatch(/^RSA-/);
});

test('Keys should be extractable', async () => {
const keyPair = await generateRsaKeys();

expect(keyPair.publicKey.extractable).toBe(true);
expect(keyPair.privateKey.extractable).toBe(true);
});

describe('Modulus', () => {
test('Default modulus should be 2048', async () => {
const keyPair = await generateRsaKeys();
// @ts-ignore
expect(keyPair.publicKey.algorithm.modulusLength).toBe(2048);
// @ts-ignore
expect(keyPair.privateKey.algorithm.modulusLength).toBe(2048);
});

test('Modulus > 2048 should be supported', async () => {
const modulus = 3072;
const keyPair = await generateRsaKeys({ modulus });
// @ts-ignore
expect(keyPair.publicKey.algorithm.modulusLength).toBe(modulus);
// @ts-ignore
expect(keyPair.privateKey.algorithm.modulusLength).toBe(modulus);
});

test('Modulus < 2048 should not supported', async () => {
await expect(generateRsaKeys({ modulus: 1024 })).rejects.toThrow(
'RSA modulus must be => 2048 per RS-018 (got 1024)'
);
});
});

describe('Hashing algorithm', () => {
test('SHA-256 should be used by default', async () => {
const keyPair = await generateRsaKeys();
// @ts-ignore
expect(keyPair.publicKey.algorithm.hash.name).toBe('SHA-256');
// @ts-ignore
expect(keyPair.privateKey.algorithm.hash.name).toBe('SHA-256');
});

['SHA-384', 'SHA-512'].forEach(hashingAlgorithm => {
test(`${hashingAlgorithm} should be supported`, async () => {
const keyPair = await generateRsaKeys({ hashingAlgorithm });
// @ts-ignore
expect(keyPair.publicKey.algorithm.hash.name).toBe(hashingAlgorithm);
// @ts-ignore
expect(keyPair.privateKey.algorithm.hash.name).toBe(hashingAlgorithm);
});
});

test('SHA-1 should not be supported', async () => {
await expect(
generateRsaKeys({ hashingAlgorithm: 'SHA-1' })
).rejects.toThrow('SHA-1 is disallowed by RS-018');
});
});
});
39 changes: 39 additions & 0 deletions src/lib/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import WebCrypto from 'node-webcrypto-ossl';
import { CryptoEngine, getAlgorithmParameters, setEngine } from 'pkijs';

const webcrypto = new WebCrypto();
const cryptoEngine = new CryptoEngine({
crypto: webcrypto,
name: 'nodeEngine',
subtle: webcrypto.subtle
});
setEngine('nodeEngine', webcrypto, cryptoEngine);

/**
* Generate an RSA symmetric key
*
* @param modulus The RSA modulus for the keys (2048 or greater).
* @param hashingAlgorithm The hashing algorithm (e.g., SHA-256, SHA-384, SHA-512).
* @throws Error If the modulus or the hashing algorithm is disallowed by RS-018.
*/
export async function generateRsaKeys({
modulus = 2048,
hashingAlgorithm = 'SHA-256'
} = {}): Promise<CryptoKeyPair> {
if (modulus < 2048) {
throw new Error(`RSA modulus must be => 2048 per RS-018 (got ${modulus})`);
}

// RS-018 disallows MD5 and SHA-1, but only SHA-1 is supported in WebCrypto
if (hashingAlgorithm === 'SHA-1') {
throw new Error('SHA-1 is disallowed by RS-018');
}

const algorithm = getAlgorithmParameters('RSA-PSS', 'generatekey');
// tslint:disable-next-line:no-object-mutation
algorithm.algorithm.hash.name = hashingAlgorithm;
// tslint:disable-next-line:no-object-mutation
algorithm.algorithm.modulusLength = modulus;

return cryptoEngine.generateKey(algorithm.algorithm, true, algorithm.usages);
}
2 changes: 2 additions & 0 deletions src/types/pkijs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @types/pkijs didn't work becuase it didn't define the "pkijs" module.
declare module 'pkijs';
7 changes: 5 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@
// "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
// "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,

"lib": ["es2017"],
"lib": [
"es2017",
"dom"
],
"types": ["node", "jest"],
"typeRoots": ["node_modules/@types"]
"typeRoots": ["node_modules/@types", "src/types"]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules/**"],
Expand Down
9 changes: 1 addition & 8 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,7 @@
"no-method-signature": true,

// Functional style rules
"no-this": true,
"no-class": true,
"no-mixed-interface": true,
"no-expression-statement": [
true,
{ "ignore-prefix": ["console.", "process.exit"] }
],
"no-if-statement": true
"no-mixed-interface": true
/* end tslint-immutable rules */
}
}

0 comments on commit 6211d8c

Please sign in to comment.