From 674281915910b3533b9653819a0ecffdc9cada51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Mon, 29 Jul 2019 12:56:03 +0200 Subject: [PATCH 1/8] Add basic structure of prototype --- .babelrc | 12 +++ .editorconfig | 9 ++ .eslintrc.json | 36 +++++++ .gitignore | 1 + .travis.yml | 7 ++ README.md | 45 +++++++++ lib/algorithms.js | 37 +++++++ lib/errors.js | 11 +++ lib/index.js | 11 +++ lib/random.js | 11 +++ lib/subtle.js | 220 +++++++++++++++++++++++++++++++++++++++++ lib/util.js | 20 ++++ package.json | 25 +++++ test/default-export.js | 17 ++++ test/random.js | 26 +++++ test/subtle.js | 21 ++++ 16 files changed, 509 insertions(+) create mode 100644 .babelrc create mode 100644 .editorconfig create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100644 lib/algorithms.js create mode 100644 lib/errors.js create mode 100644 lib/index.js create mode 100644 lib/random.js create mode 100644 lib/subtle.js create mode 100644 lib/util.js create mode 100644 package.json create mode 100644 test/default-export.js create mode 100644 test/random.js create mode 100644 test/subtle.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..d92bc17 --- /dev/null +++ b/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "12" + } + } + ] + ] +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ccd39f5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[**.{js,json}] +indent_style = space +indent_size = 2 + +[.babelrc] +indent_style = space +indent_size = 2 diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..41dcc0c --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,36 @@ +{ + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": { + "array-bracket-spacing": ["error", "never"], + "comma-dangle": ["error", "never"], + "eol-last": ["error", "always"], + "indent": ["error", 2, { + "FunctionDeclaration": { + "parameters": "first" + }, + "CallExpression": { + "arguments": "first" + }, + "VariableDeclarator": "first" + }], + "max-len": ["error", 80], + "no-var": "error", + "prefer-const": "error", + "quotes": ["error", "single"], + "semi": ["error", "always"], + "space-infix-ops": ["error"] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..851b757 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - "12" + +script: + - npm run lint + - npm test diff --git a/README.md b/README.md new file mode 100644 index 0000000..019588b --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# WebCrypto Prototype for Node.js + +This is a partial and experimental WebCrypto implementation for the Node.js +platform. + +## Asynchonicity + +The WebCrypto specification requires almost all operations to be completed +asynchronously, however, Node.js implements very few operations asynchronously. +Usually, this is not a problem, since most cryptographic functions are +incredibly fast compared to the overhead that comes with asynchronicity, +and because Node.js implements most cryptographic features through efficient +streaming interfaces. WebCrypto has no streaming interfaces but only one-shot +APIs. Encrypting, hashing, signing or verifying large amounts of data is thus +difficult in WebCrypto without underlying asynchronous APIs. + +This is not considered to be a deficiency of Node.js, but rather an unfortunate +design conflict. More efficient alternatives will be explored at a later point. + +## Development + +### Structure + +The main export of this package is implemented in `lib/index.js` and represents +the `Crypto` interface as defined in section 10 of the WebCrypto specification. +It contains two members: + +- The `subtle` attribute is implemented in `lib/subtle.js`, including all + methods described in section 14.3 of the WebCrypto specification. These + methods usually delegate work to one or more cryptographic operations + that are listed in section 18.2.2 and implemented in `lib/algorithms/`. +- The `getRandomValues` function is implemented in `lib/random.js`. + +### Tests + +The `test` directory contains a small number of unit tests. All of these tests +are required to pass after each commit. You can run unit tests using `npm test`. + +It is our intention to add Web Platform Tests (WPT) at some point. When this +happens, not all WPTs are required to pass, but if a test passes, it must not be +broken by a later commit. + +### Linting + +This repository uses ESLint. Use `npm run lint` to check the code. diff --git a/lib/algorithms.js b/lib/algorithms.js new file mode 100644 index 0000000..8eaeb5a --- /dev/null +++ b/lib/algorithms.js @@ -0,0 +1,37 @@ +const algorithms = [ +]; + +function objectFromArray(array, fn) { + const obj = {}; + for (const val of array) + fn(obj, val); + return obj; +} + +const supportedAlgorithms = objectFromArray([ + // This corresponds to section 18.2.2 of the WebCrypto spec. + 'encrypt', + 'decrypt', + 'sign', + 'verify', + 'deriveBits', + 'wrapKey', + 'unwrapKey', + 'digest', + 'generateKey', + 'importKey', + 'exportKey', + 'get key length', + + // The following APIs are for internal use only. + 'get hash function' +], (opsByName, op) => { + opsByName[op] = objectFromArray(algorithms, (algsByName, alg) => { + if (typeof alg[op] === 'function') + algsByName[alg.name.toLowerCase()] = alg; + }); +}); + +export function getAlgorithmImplementation(name, op) { + return supportedAlgorithms[op][name.toLowerCase()]; +} diff --git a/lib/errors.js b/lib/errors.js new file mode 100644 index 0000000..193dcd3 --- /dev/null +++ b/lib/errors.js @@ -0,0 +1,11 @@ +export class NotSupportedError extends Error {} +NotSupportedError.prototype.name = 'NotSupportedError'; + +export class InvalidAccessError extends Error {} +InvalidAccessError.prototype.name = 'InvalidAccessError'; + +export class OperationError extends Error {} +OperationError.prototype.name = 'OperationError'; + +export class QuotaExceededError extends Error {} +QuotaExceededError.prototype.name = 'QuotaExceededError'; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..09e630b --- /dev/null +++ b/lib/index.js @@ -0,0 +1,11 @@ +import { getRandomValues } from './random.js'; +export { getRandomValues } from './random.js'; + +import subtle_ from './subtle.js'; +export const subtle = subtle_; + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#crypto-interface +export default { + subtle, + getRandomValues +}; diff --git a/lib/random.js b/lib/random.js new file mode 100644 index 0000000..da33049 --- /dev/null +++ b/lib/random.js @@ -0,0 +1,11 @@ +import { randomFillSync } from 'crypto'; +import { QuotaExceededError } from './errors.js'; + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#dfn-Crypto-method-getRandomValues +export function getRandomValues(array) { + if (array.byteLength > 65536) + throw new QuotaExceededError(); + + randomFillSync(array); + return array; +} diff --git a/lib/subtle.js b/lib/subtle.js new file mode 100644 index 0000000..1fccaaa --- /dev/null +++ b/lib/subtle.js @@ -0,0 +1,220 @@ +import { getAlgorithmImplementation } from './algorithms.js'; +import { + InvalidAccessError, + NotSupportedError +} from './errors.js'; +import { toBuffer } from './util.js'; + +const kType = Symbol('kType'); +const kAlgorithm = Symbol('kAlgorithm'); +const kExtractable = Symbol('kExtractable'); +const kUsages = Symbol('kUsages'); + +export const kKeyMaterial = Symbol('kKeyMaterial'); + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#cryptokey-interface +export class CryptoKey { + constructor(type, algorithm, extractable, usages, keyMaterial) { + this[kType] = type; + this[kAlgorithm] = algorithm; + this[kExtractable] = extractable; + this[kUsages] = new Set(usages); + this[kKeyMaterial] = keyMaterial; + } + + get type() { + return this[kType]; + } + + get extractable() { + return this[kExtractable]; + } + + get algorithm() { + return this[kAlgorithm]; + } + + get usages() { + return [...this[kUsages]]; + } +} + +function requireKeyUsage(key, usage) { + if (!key[kUsages].has(usage)) + throw new InvalidAccessError(); +} + +export function getAlgorithm(alg, op) { + if (typeof alg !== 'string') { + if (typeof alg !== 'object') + throw new SyntaxError(); + const { name } = alg; + if (typeof name !== 'string') + throw new SyntaxError(); + return getAlgorithm(alg.name, op); + } + + const impl = getAlgorithmImplementation(alg.toLowerCase(), op); + if (impl === undefined) + throw new NotSupportedError(); + return impl; +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-encrypt +export async function encrypt(algorithm, key, data) { + const alg = getAlgorithm(algorithm, 'encrypt'); + if (key.algorithm.name !== alg.name) + throw new InvalidAccessError(); + + requireKeyUsage(key, 'encrypt'); + const buffer = toBuffer(data); + return alg.encrypt(algorithm, key, buffer); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-decrypt +export async function decrypt(algorithm, key, data) { + const alg = getAlgorithm(algorithm, 'decrypt'); + if (key.algorithm.name !== alg.name) + throw new InvalidAccessError(); + + requireKeyUsage(key, 'decrypt'); + const buffer = toBuffer(data); + return alg.decrypt(algorithm, key, buffer); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-sign +export async function sign(algorithm, key, data) { + const alg = getAlgorithm(algorithm, 'sign'); + if (alg.name !== key[kAlgorithm].name) + throw new InvalidAccessError(); + + requireKeyUsage(key, 'sign'); + return alg.sign(algorithm, key, data); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-verify +export async function verify(algorithm, key, signature, data) { + const alg = getAlgorithm(algorithm, 'verify'); + if (alg.name !== key[kAlgorithm].name) + throw new InvalidAccessError(); + + requireKeyUsage(key, 'verify'); + return alg.verify(algorithm, key, signature, data); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-digest +export async function digest(algorithm, data) { + const buffer = toBuffer(data); + const alg = getAlgorithm(algorithm, 'digest'); + return alg.digest(algorithm, buffer); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey +export async function generateKey(algorithm, extractable, keyUsages) { + const alg = getAlgorithm(algorithm, 'generateKey'); + return alg.generateKey(algorithm, extractable, keyUsages); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveKey +export async function deriveKey(algorithm, baseKey, derivedKeyType, extractable, + keyUsages) { + const alg = getAlgorithm(algorithm, 'deriveBits'); + const keyAlg = getAlgorithm(derivedKeyType, 'get key length'); + const length = await keyAlg['get key length'](derivedKeyType); + requireKeyUsage(baseKey, 'deriveKey'); + const bits = await alg.deriveBits(algorithm, baseKey, length, extractable, + keyUsages); + return keyAlg.importKey('raw', bits, derivedKeyType, extractable, keyUsages); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveBits +export async function deriveBits(algorithm, key, length) { + const alg = getAlgorithm(algorithm, 'deriveBits'); + requireKeyUsage(key, 'deriveBits'); + return alg.deriveBits(algorithm, key, length); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-importKey +export async function importKey(keyFormat, keyData, algorithm, extractable, + keyUsages) { + const alg = getAlgorithm(algorithm, 'importKey'); + return alg.importKey(keyFormat, keyData, algorithm, extractable, keyUsages); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-exportKey +export async function exportKey(format, key) { + const alg = getAlgorithm(key.algorithm, 'exportKey'); + if (!key.extractable) + throw new InvalidAccessError(); + return alg.exportKey(format, key); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-wrapKey +export async function wrapKey(format, key, wrappingKey, wrapAlgorithm) { + let wrapFn, alg; + try { + alg = getAlgorithm(wrapAlgorithm, wrapFn = 'wrapKey'); + } catch (err) { + alg = getAlgorithm(wrapAlgorithm, wrapFn = 'encrypt'); + } + + if (wrappingKey[kAlgorithm].name !== alg.name) + throw new InvalidAccessError(); + + requireKeyUsage(wrappingKey, 'wrapKey'); + + const exportAlg = getAlgorithm(key.algorithm, 'exportKey'); + if (!key[kExtractable]) + throw new InvalidAccessError(); + + let bytes = exportAlg.exportKey(format, key); + if (format === 'jwk') + bytes = Buffer.from(JSON.stringify(bytes), 'utf8'); + + return alg[wrapFn](wrapAlgorithm, wrappingKey, bytes); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-unwrapKey +export async function unwrapKey(format, wrappedKey, unwrappingKey, + unwrapAlgorithm, unwrappedKeyAlgorithm, + extractable, keyUsages) { + let unwrapFn, alg; + try { + alg = getAlgorithm(unwrapAlgorithm, unwrapFn = 'unwrapKey'); + } catch (err) { + alg = getAlgorithm(unwrapAlgorithm, unwrapFn = 'decrypt'); + } + + const importAlg = getAlgorithm(unwrappingKey.algorithm, 'importKey'); + + if (unwrappingKey[kAlgorithm].name !== alg.name) + throw new InvalidAccessError(); + + requireKeyUsage(unwrappingKey, 'unwrapKey'); + + let key = await alg[unwrapFn](unwrapAlgorithm, unwrappingKey, wrappedKey); + if (format === 'jwk') + key = JSON.parse(key.toString('utf8')); + + return importAlg.importKey(format, key, unwrappedKeyAlgorithm, extractable, + keyUsages); +} + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#subtlecrypto-interface +export default { + encrypt, + decrypt, + sign, + verify, + digest, + + generateKey, + deriveKey, + deriveBits, + + importKey, + exportKey, + + wrapKey, + unwrapKey +}; diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..707f57c --- /dev/null +++ b/lib/util.js @@ -0,0 +1,20 @@ +import { getAlgorithm } from './subtle.js'; + +export function toBuffer(source) { + if (ArrayBuffer.isView(source)) { + return Buffer.from(source.buffer, source.byteOffset, source.byteLength); + } else { + return Buffer.from(source); + } +} + +export function opensslHashFunctionName(algorithm) { + return getAlgorithm(algorithm, 'get hash function')['get hash function'](); +} + +export function limitUsages(usages, allowed) { + for (const usage of usages) { + if (!allowed.includes(usage)) + throw new SyntaxError(); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f61bca1 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "main": "./lib/index.js", + "type": "module", + "devDependencies": { + "@babel/cli": "^7.5.5", + "@babel/core": "^7.5.5", + "@babel/node": "^7.5.5", + "@babel/preset-env": "^7.5.5", + "@babel/register": "^7.5.5", + "eslint": "^6.1.0", + "mocha": "^6.2.0" + }, + "scripts": { + "test": "mocha --require @babel/register --recursive ./test/", + "lint": "eslint lib test" + }, + "license": "MIT", + "private": true, + "author": "Tobias Nießen ", + "bugs": "https://github.com/nodejs/webcrypto/issues", + "repository": { + "type" : "git", + "url" : "https://github.com/nodejs/webcrypto.git" + } +} diff --git a/test/default-export.js b/test/default-export.js new file mode 100644 index 0000000..f2a820a --- /dev/null +++ b/test/default-export.js @@ -0,0 +1,17 @@ +import assert from 'assert'; + +import crypto from '../'; + +describe('Default export', () => { + it('should have getRandomBytes', () => { + assert.strictEqual(typeof crypto.getRandomValues, 'function'); + }); + + it('should have subtle', () => { + assert.strictEqual(typeof crypto.subtle, 'object'); + }); + + it('should not have any other properties', () => { + assert.strictEqual(Object.keys(crypto).length, 2); + }); +}); diff --git a/test/random.js b/test/random.js new file mode 100644 index 0000000..9b38777 --- /dev/null +++ b/test/random.js @@ -0,0 +1,26 @@ +import assert from 'assert'; + +import { getRandomValues } from '../'; + +describe('crypto.getRandomBytes', () => { + it('should exist', () => { + assert.strictEqual(typeof getRandomValues, 'function'); + }); + + it('should return the input parameter', () => { + const buf = Buffer.alloc(1024); + assert.strictEqual(getRandomValues(buf), buf); + }); + + it('should overwrite the buffer', () => { + const zero = Buffer.alloc(1024, 0); + const buf = getRandomValues(Buffer.alloc(1024, 0)); + assert(!buf.equals(zero)); + }); + + it('should produce a different output each time', () => { + const buf1 = getRandomValues(Buffer.alloc(1024)); + const buf2 = getRandomValues(Buffer.alloc(1024)); + assert(!buf1.equals(buf2)); + }); +}); diff --git a/test/subtle.js b/test/subtle.js new file mode 100644 index 0000000..4327580 --- /dev/null +++ b/test/subtle.js @@ -0,0 +1,21 @@ +import assert from 'assert'; + +import { subtle } from '../'; + +describe('crypto.subtle', () => { + const fns = [ + 'encrypt', 'decrypt', 'sign', 'verify', 'digest', + 'generateKey', 'deriveKey', 'deriveBits', + 'importKey', 'exportKey', + 'wrapKey', 'unwrapKey' + ]; + + it('should have all SubtleCrypto functions', () => { + for (const key of fns) + assert.strictEqual(typeof subtle[key], 'function'); + }); + + it('should not have any other properties', () => { + assert.strictEqual(Object.keys(subtle).length, fns.length); + }); +}); From 106b235ae2fdfda1c0e99cb3e6dd8a3c6f25bc02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Mon, 29 Jul 2019 13:03:25 +0200 Subject: [PATCH 2/8] Add SHA prototype --- lib/algorithms.js | 6 ++++++ lib/algorithms/sha.js | 20 +++++++++++++++++++ test/algorithms/sha.js | 45 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 lib/algorithms/sha.js create mode 100644 test/algorithms/sha.js diff --git a/lib/algorithms.js b/lib/algorithms.js index 8eaeb5a..e7b6db4 100644 --- a/lib/algorithms.js +++ b/lib/algorithms.js @@ -1,4 +1,10 @@ +import { SHA_1, SHA_256, SHA_384, SHA_512 } from './algorithms/sha.js'; + const algorithms = [ + SHA_1, + SHA_256, + SHA_384, + SHA_512 ]; function objectFromArray(array, fn) { diff --git a/lib/algorithms/sha.js b/lib/algorithms/sha.js new file mode 100644 index 0000000..e653767 --- /dev/null +++ b/lib/algorithms/sha.js @@ -0,0 +1,20 @@ +import { createHash } from 'crypto'; + +function implement(name, opensslName) { + return { + name, + + digest(params, data) { + return createHash(opensslName).update(data).digest(); + }, + + 'get hash function'() { + return opensslName; + } + }; +} + +export const SHA_1 = implement('SHA-1', 'sha1'); +export const SHA_256 = implement('SHA-256', 'sha256'); +export const SHA_384 = implement('SHA-384', 'sha384'); +export const SHA_512 = implement('SHA-512', 'sha512'); diff --git a/test/algorithms/sha.js b/test/algorithms/sha.js new file mode 100644 index 0000000..78d383c --- /dev/null +++ b/test/algorithms/sha.js @@ -0,0 +1,45 @@ +import assert from 'assert'; + +import { subtle } from '../../lib'; + +describe('SHA', () => { + + it('should support SHA-1', async () => { + const data = Buffer.from('Hello world', 'utf8'); + const digest = await subtle.digest('SHA-1', data); + assert(Buffer.isBuffer(digest)); + assert.strictEqual(digest.toString('hex'), + '7b502c3a1f48c8609ae212cdfb639dee39673f5e'); + }); + + it('should support SHA-256', async () => { + const data = Buffer.from('Hello world', 'utf8'); + const digest = await subtle.digest('SHA-256', data); + assert(Buffer.isBuffer(digest)); + assert.strictEqual(digest.toString('hex'), + '64ec88ca00b268e5ba1a35678a1b5316' + + 'd212f4f366b2477232534a8aeca37f3c'); + }); + + it('should support SHA-384', async () => { + const data = Buffer.from('Hello world', 'utf8'); + const digest = await subtle.digest('SHA-384', data); + assert(Buffer.isBuffer(digest)); + assert.strictEqual(digest.toString('hex'), + '9203b0c4439fd1e6ae5878866337b7c5' + + '32acd6d9260150c80318e8ab8c27ce33' + + '0189f8df94fb890df1d298ff360627e1'); + }); + + it('should support SHA-512', async () => { + const data = Buffer.from('Hello world', 'utf8'); + const digest = await subtle.digest('SHA-512', data); + assert(Buffer.isBuffer(digest)); + assert.strictEqual(digest.toString('hex'), + 'b7f783baed8297f0db917462184ff4f0' + + '8e69c2d5e5f79a942600f9725f58ce1f' + + '29c18139bf80b06c0fff2bdd34738452' + + 'ecf40c488c22a7e3d80cdf6f9c1c0d47'); + }); + +}); From 1f3a881ddd377a2d2d441d0446fdc4bc3479f1e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Mon, 29 Jul 2019 13:12:07 +0200 Subject: [PATCH 3/8] Add AES prototype --- lib/algorithms.js | 6 + lib/algorithms/aes.js | 232 +++++++++++++++++++++++++++++++++++++ test/algorithms/aes.js | 252 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 490 insertions(+) create mode 100644 lib/algorithms/aes.js create mode 100644 test/algorithms/aes.js diff --git a/lib/algorithms.js b/lib/algorithms.js index e7b6db4..ecfea6d 100644 --- a/lib/algorithms.js +++ b/lib/algorithms.js @@ -1,6 +1,12 @@ +import { AES_CTR, AES_CBC, AES_GCM, AES_KW } from './algorithms/aes.js'; import { SHA_1, SHA_256, SHA_384, SHA_512 } from './algorithms/sha.js'; const algorithms = [ + AES_CTR, + AES_CBC, + AES_GCM, + AES_KW, + SHA_1, SHA_256, SHA_384, diff --git a/lib/algorithms/aes.js b/lib/algorithms/aes.js new file mode 100644 index 0000000..1144162 --- /dev/null +++ b/lib/algorithms/aes.js @@ -0,0 +1,232 @@ +import { + createCipheriv, + createDecipheriv, + createSecretKey, + randomBytes as randomBytesCallback +} from 'crypto'; +import { promisify } from 'util'; + +import { NotSupportedError, OperationError } from '../errors.js'; +import { kKeyMaterial, CryptoKey } from '../subtle.js'; +import { limitUsages, toBuffer } from '../util.js'; + +const randomBytes = promisify(randomBytesCallback); + +const aesBase = { + async generateKey(algorithm, extractable, usages) { + limitUsages(usages, ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']); + + const { length } = algorithm; + if (length !== 128 && length !== 192 && length !== 256) + throw new OperationError(); + + const key = createSecretKey(await randomBytes(length >> 3)); + return new CryptoKey('secret', { name: this.name, length }, extractable, + usages, key); + }, + + importKey(keyFormat, keyData, params, extractable, keyUsages) { + if (keyFormat !== 'raw') + throw new NotSupportedError(); + + limitUsages(keyUsages, ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']); + + const buf = toBuffer(keyData); + if (buf.length !== 16 && buf.length !== 24 && buf.length !== 32) + throw new OperationError(); + + return new CryptoKey('secret', { name: this.name, length: buf.length << 3 }, + extractable, keyUsages, + createSecretKey(toBuffer(keyData))); + }, + + exportKey(format, key) { + if (format !== 'raw') + throw new NotSupportedError(); + return key[kKeyMaterial].export(); + }, + + 'get key length'(algorithm) { + const { length } = algorithm; + if (length !== 128 && length !== 192 && length !== 256) + throw new OperationError(); + return length; + } +}; + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#aes-ctr +export const AES_CTR = { + name: 'AES-CTR', + ...aesBase, + + _doCipher(iv, length, data, fn) { + if (length === 128) { + // Fast path for the default 128-bit length. + return fn(data, iv); + } else { + // WebCrypto has a non-standard feature which allows to specify the number + // of bits that are used as the counter. This feature is not available in + // Node.js or OpenSSL and thus needs to be simulated: We calculate when + // an overflow would occur that would violate the given "length" restraint + // and restart encryption at those points with a different IV. + + let nBlocksBeforeOverflow = 1; + for (let i = 0; i < length; i++) { + if ((iv[15 - Math.floor(i / 8)] & (1 << (i % 8))) === 0) { + nBlocksBeforeOverflow += 2 ** i; + + if (nBlocksBeforeOverflow >= data.length / 16) + return fn(data, iv); + } + } + + const overflowIV = Buffer.from(iv); + if (length >= 8) + overflowIV.fill(0, 16 - Math.floor(length / 8), 16); + overflowIV[15 - Math.floor(length / 8)] &= (0xff << (length % 8)) & 0xff; + + let result = fn(data.slice(0, nBlocksBeforeOverflow * 16), iv); + const blocksPerCycle = 2 ** length; + const nBlocks = Math.ceil(data.length / 16); + for (let i = nBlocksBeforeOverflow; i < nBlocks; i += blocksPerCycle) { + result = Buffer.concat([ + result, + fn(data.slice(16 * i, 16 * (i + blocksPerCycle)), overflowIV) + ]); + } + return result; + } + }, + + encrypt(params, key, data) { + const { counter, length } = params; + + const iv = toBuffer(counter); + if (iv.length !== 16) + throw new OperationError(); + + if (length === 0 || length > 128) + throw new OperationError(); + + const secretKey = key[kKeyMaterial]; + const cipher = `aes-${secretKey.symmetricKeySize << 3}-ctr`; + + return this._doCipher(iv, length, data, (data, iv) => { + return createCipheriv(cipher, secretKey, iv).update(data); + }); + }, + + decrypt(params, key, data) { + const { counter, length } = params; + + const iv = toBuffer(counter); + if (iv.length !== 16) + throw new OperationError(); + + if (length === 0 || length > 128) + throw new OperationError(); + + const secretKey = key[kKeyMaterial]; + const cipher = `aes-${secretKey.symmetricKeySize << 3}-ctr`; + + return this._doCipher(iv, length, data, (data, iv) => { + return createDecipheriv(cipher, secretKey, iv).update(data); + }); + } +}; + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#aes-cbc +export const AES_CBC = { + name: 'AES-CBC', + ...aesBase, + + encrypt(params, key, data) { + let { iv } = params; + + iv = toBuffer(iv); + if (iv.length !== 16) + throw new OperationError(); + + const secretKey = key[kKeyMaterial]; + const cipher = `aes-${secretKey.symmetricKeySize << 3}-cbc`; + + const c = createCipheriv(cipher, secretKey, iv); + return Buffer.concat([c.update(data), c.final()]); + }, + + decrypt(params, key, data) { + let { iv } = params; + + iv = toBuffer(iv); + if (iv.length !== 16) + throw new OperationError(); + + const secretKey = key[kKeyMaterial]; + const cipher = `aes-${secretKey.symmetricKeySize << 3}-cbc`; + + const c = createDecipheriv(cipher, secretKey, iv); + return Buffer.concat([c.update(data), c.final()]); + } +}; + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm +export const AES_GCM = { + name: 'AES-GCM', + ...aesBase, + + encrypt(params, key, data) { + const { iv, tagLength, additionalData } = params; + + const ivBuf = toBuffer(iv); + const secretKey = key[kKeyMaterial]; + const cipher = `aes-${secretKey.symmetricKeySize << 3}-gcm`; + + const authTagLength = tagLength === undefined ? 16 : tagLength >> 3; + const c = createCipheriv(cipher, secretKey, ivBuf, { authTagLength }); + if (additionalData !== undefined) + c.setAAD(additionalData); + return Buffer.concat([c.update(data), c.final(), c.getAuthTag()]); + }, + + decrypt(params, key, data) { + const { iv, tagLength, additionalData } = params; + + const ivBuf = toBuffer(iv); + const secretKey = key[kKeyMaterial]; + const cipher = `aes-${secretKey.symmetricKeySize << 3}-gcm`; + + const authTagLength = tagLength === undefined ? 16 : tagLength >> 3; + const c = createDecipheriv(cipher, secretKey, ivBuf, { authTagLength }); + if (additionalData !== undefined) + c.setAAD(additionalData); + c.setAuthTag(data.slice(data.byteLength - authTagLength, data.length)); + return Buffer.concat([ + c.update(data.slice(0, data.byteLength - authTagLength)), + c.final() + ]); + } +}; + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#aes-kw +export const AES_KW = { + name: 'AES-KW', + ...aesBase, + + defaultIV: Buffer.from('A6A6A6A6A6A6A6A6', 'hex'), + + async wrapKey(params, key, data) { + const secretKey = key[kKeyMaterial]; + const cipher = `aes${secretKey.symmetricKeySize << 3}-wrap`; + + const c = createCipheriv(cipher, secretKey, this.defaultIV); + return Buffer.concat([c.update(data), c.final()]); + }, + + async unwrapKey(params, key, data) { + const secretKey = key[kKeyMaterial]; + const cipher = `aes${secretKey.symmetricKeySize << 3}-wrap`; + + const c = createDecipheriv(cipher, secretKey, this.defaultIV); + return Buffer.concat([c.update(data), c.final()]); + } +}; diff --git a/test/algorithms/aes.js b/test/algorithms/aes.js new file mode 100644 index 0000000..2d990c4 --- /dev/null +++ b/test/algorithms/aes.js @@ -0,0 +1,252 @@ +import assert from 'assert'; +import { randomBytes } from 'crypto'; + +import { subtle } from '../../lib'; + +function twice(buf) { + return Buffer.concat([buf, buf], buf.length * 2); +} + +function testGenImportExport(name) { + return async () => { + const key1 = await subtle.generateKey({ name, length: 192 }, true, + ['encrypt', 'decrypt']); + assert.strictEqual(key1.algorithm.name, name); + const key2 = await subtle.generateKey({ name, length: 192 }, true, + ['encrypt', 'decrypt']); + assert.strictEqual(key2.algorithm.name, name); + const key3 = await subtle.generateKey({ name, length: 256 }, true, + ['encrypt', 'decrypt']); + assert.strictEqual(key3.algorithm.name, name); + + const expKey1 = await subtle.exportKey('raw', key1); + assert(Buffer.isBuffer(expKey1)); + assert.strictEqual(expKey1.length, 24); + const expKey2 = await subtle.exportKey('raw', key2); + assert(Buffer.isBuffer(expKey2)); + assert.strictEqual(expKey2.length, 24); + const expKey3 = await subtle.exportKey('raw', key3); + assert(Buffer.isBuffer(expKey3)); + assert.strictEqual(expKey3.length, 32); + + assert.notDeepEqual(expKey1, expKey2); + + const impKey1 = await subtle.importKey('raw', expKey1, name, true, + ['encrypt', 'decrypt']); + const impKey2 = await subtle.importKey('raw', expKey2, name, true, + ['encrypt', 'decrypt']); + const impKey3 = await subtle.importKey('raw', expKey3, name, true, + ['encrypt', 'decrypt']); + + assert.deepEqual(await subtle.exportKey('raw', impKey1), expKey1); + assert.deepEqual(await subtle.exportKey('raw', impKey2), expKey2); + assert.deepEqual(await subtle.exportKey('raw', impKey3), expKey3); + }; +} + +describe('AES-CTR', () => { + it('should generate, import and export keys', + testGenImportExport('AES-CTR')); + + it('should encrypt and decrypt', async () => { + const keyData = Buffer.from('36adfe538cc234279e4cbb29e1f27af5', 'hex'); + const counter = Buffer.from('2159e5bd415791990e52b5c825572994', 'hex'); + const length = 128; + + const key = await subtle.importKey('raw', keyData, 'AES-CTR', false, + ['encrypt', 'decrypt']); + + const plaintext = Buffer.from('Hello WebCrypto!', 'utf8'); + const ciphertext = await subtle.encrypt({ + name: 'AES-CTR', + counter, + length + }, key, plaintext); + assert.deepEqual(ciphertext, + Buffer.from('0bdbe0f2de637f43b9d86f8bb0ba5f05', 'hex')); + + const deciphered = await subtle.decrypt({ + name: 'AES-CTR', + counter, + length + }, key, ciphertext); + assert.deepEqual(deciphered, plaintext); + }); + + it('should handle the "length" parameter', async () => { + const blockSize = 16; + const key = await subtle.generateKey({ name: 'AES-CTR', length: 192 }, + false, ['encrypt', 'decrypt']); + + // In this case, only the last bit of the IV will be flipped between blocks, + // meaning that every second block will be XOR'd with the same bit stream. + const plaintext = twice(randomBytes(2 * blockSize)); + let counter = randomBytes(blockSize); + let length = 1; + let ciphertext = await subtle.encrypt({ name: 'AES-CTR', counter, length }, + key, plaintext); + assert.strictEqual(ciphertext.length, 4 * blockSize); + const encryptedFirstHalf = ciphertext.slice(0, 2 * blockSize); + const encryptedSecondHalf = ciphertext.slice(2 * blockSize); + assert.deepEqual(encryptedFirstHalf, encryptedSecondHalf); + + let decrypted = await subtle.decrypt({ name: 'AES-CTR', counter, length }, + key, ciphertext); + assert.deepEqual(decrypted, plaintext); + + // This is slightly more tricky: We allow incrementing the last 127 bits, + // which will not lead to any repetitions that we could test for. However, + // we can pick an IV that will cause an overflow, which would usually cause + // the MSB to be flipped, but since the MSB is not within the last 127 bits, + // this cannot happen. We just need to verify that the second block was + // encrypted using the correct IV (with an unmodified MSB). + counter = Buffer.from('7fffffffffffffffffffffffffffffff', 'hex'); + length = 127; + ciphertext = await subtle.encrypt({ name: 'AES-CTR', counter, length }, + key, plaintext); + const expectedIV = Buffer.from('00000000000000000000000000000000', 'hex'); + const expectedSecondBlock = await subtle.encrypt({ + name: 'AES-CTR', + counter: expectedIV, + length: 128 + }, key, plaintext.slice(blockSize, 2 * blockSize)); + assert.deepEqual(ciphertext.slice(blockSize, 2 * blockSize), + expectedSecondBlock); + + decrypted = await subtle.decrypt({ name: 'AES-CTR', counter, length }, key, + ciphertext); + assert.deepEqual(decrypted, plaintext); + }); +}); + +describe('AES-CBC', () => { + it('should generate, import and export keys', + testGenImportExport('AES-CBC')); + + it('should encrypt and decrypt', async () => { + const keyData = Buffer.from('36adfe538cc234279e4cbb29e1f27af5', 'hex'); + const iv = Buffer.from('2159e5bd415791990e52b5c825572994', 'hex'); + + const key = await subtle.importKey('raw', keyData, 'AES-CBC', false, + ['encrypt', 'decrypt']); + + const plaintext = Buffer.from('Hello WebCrypto!', 'utf8'); + const ciphertext = await subtle.encrypt({ + name: 'AES-CBC', + iv + }, key, plaintext); + assert.deepEqual(ciphertext, + Buffer.from('8bb6173879b0f7a8899397e0fde3a3c88c69e86b18' + + 'eb74f8629be60287c89552', 'hex')); + + const deciphered = await subtle.decrypt({ + name: 'AES-CBC', + iv + }, key, ciphertext); + assert.deepEqual(deciphered, plaintext); + }); +}); + +describe('AES-GCM', () => { + it('should generate, import and export keys', + testGenImportExport('AES-GCM')); + + it('should encrypt and decrypt', async () => { + const keyData = Buffer.from('36adfe538cc234279e4cbb29e1f27af5', 'hex'); + const iv = Buffer.from('2159e5bd415791990e52b5c825572994', 'hex'); + + const key = await subtle.importKey('raw', keyData, 'AES-GCM', false, + ['encrypt', 'decrypt']); + + const plaintext = Buffer.from('Hello WebCrypto!', 'utf8'); + const ciphertext = await subtle.encrypt({ + name: 'AES-GCM', + iv + }, key, plaintext); + assert.deepEqual(ciphertext, + Buffer.from('7080337fe4a1f8d8d96fa061ccfdb8cda6dacbf3f2' + + '7ef1dc85190feddc4befdd', 'hex')); + + const deciphered = await subtle.decrypt({ + name: 'AES-GCM', + iv + }, key, ciphertext); + assert.deepEqual(deciphered, plaintext); + }); + + it('should handle the "tagLength" parameter', async () => { + const keyData = Buffer.from('36adfe538cc234279e4cbb29e1f27af5', 'hex'); + const iv = Buffer.from('2159e5bd415791990e52b5c825572994', 'hex'); + + const key = await subtle.importKey('raw', keyData, 'AES-GCM', false, + ['encrypt', 'decrypt']); + + const plaintext = Buffer.from('Hello WebCrypto!', 'utf8'); + const ciphertext = await subtle.encrypt({ + name: 'AES-GCM', + iv, + tagLength: 112 + }, key, plaintext); + assert.deepEqual(ciphertext, + Buffer.from('7080337fe4a1f8d8d96fa061ccfdb8cda6dacbf3f2' + + '7ef1dc85190feddc4b', 'hex')); + + const deciphered = await subtle.decrypt({ + name: 'AES-GCM', + iv, + tagLength: 112 + }, key, ciphertext); + assert.deepEqual(deciphered, plaintext); + }); + + it('should support all IV lengths', async () => { + const keyData = Buffer.from('36adfe538cc234279e4cbb29e1f27af5', 'hex'); + const iv = twice(Buffer.from('2159e5bd415791990e52b5c825572994', 'hex')); + + const key = await subtle.importKey('raw', keyData, 'AES-GCM', false, + ['encrypt', 'decrypt']); + + const plaintext = Buffer.from('Hello WebCrypto!', 'utf8'); + const ciphertext = await subtle.encrypt({ + name: 'AES-GCM', + iv, + tagLength: 112 + }, key, plaintext); + assert.deepEqual(ciphertext, + Buffer.from('2f136ce56f36acf081476d227c0fb89ed4e0fcd07b' + + '3b8de9d412f99a2c2d', 'hex')); + + const deciphered = await subtle.decrypt({ + name: 'AES-GCM', + iv, + tagLength: 112 + }, key, ciphertext); + assert.deepEqual(deciphered, plaintext); + }); +}); + +describe('AES-KW', () => { + it('should generate, import and export keys', + testGenImportExport('AES-KW')); + + it('should wrap and unwrap keys', async () => { + const wrappingKey = await subtle.generateKey({ + name: 'AES-KW', + length: 192 + }, false, ['wrapKey', 'unwrapKey']); + const keyToWrap = await subtle.generateKey({ + name: 'AES-CBC', + length: 256 + }, true, ['encrypt', 'decrypt']); + const wrappedKey = await subtle.wrapKey('raw', keyToWrap, wrappingKey, + 'AES-KW'); + assert(Buffer.isBuffer(wrappedKey)); + assert.strictEqual(wrappedKey.length, (256 + 64) / 8); + + const unwrappedKey = await subtle.unwrapKey('raw', wrappedKey, wrappingKey, + 'AES-KW', 'AES-CBC', true, + ['encrypt', 'decrypt']); + assert.deepEqual(await subtle.exportKey('raw', unwrappedKey), + await subtle.exportKey('raw', keyToWrap)); + }); +}); From 369a8010accbaded6eb7679a54412f7cb950822c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Mon, 29 Jul 2019 13:13:59 +0200 Subject: [PATCH 4/8] Add PBKDF2 prototype --- lib/algorithms.js | 3 +++ lib/algorithms/pbkdf2.js | 41 ++++++++++++++++++++++++++++ test/algorithms/pbkdf2.js | 56 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 lib/algorithms/pbkdf2.js create mode 100644 test/algorithms/pbkdf2.js diff --git a/lib/algorithms.js b/lib/algorithms.js index ecfea6d..c904df6 100644 --- a/lib/algorithms.js +++ b/lib/algorithms.js @@ -1,4 +1,5 @@ import { AES_CTR, AES_CBC, AES_GCM, AES_KW } from './algorithms/aes.js'; +import { PBKDF2 } from './algorithms/pbkdf2.js'; import { SHA_1, SHA_256, SHA_384, SHA_512 } from './algorithms/sha.js'; const algorithms = [ @@ -7,6 +8,8 @@ const algorithms = [ AES_GCM, AES_KW, + PBKDF2, + SHA_1, SHA_256, SHA_384, diff --git a/lib/algorithms/pbkdf2.js b/lib/algorithms/pbkdf2.js new file mode 100644 index 0000000..4fd0292 --- /dev/null +++ b/lib/algorithms/pbkdf2.js @@ -0,0 +1,41 @@ +import { pbkdf2 } from 'crypto'; + +import { OperationError, NotSupportedError } from '../errors.js'; +import { kKeyMaterial, CryptoKey } from '../subtle.js'; +import { limitUsages, opensslHashFunctionName } from '../util.js'; + +export const PBKDF2 = { + name: 'PBKDF2', + + deriveBits(params, key, length) { + const { hash, salt, iterations } = params; + + if (length === null || length % 8 !== 0) + throw new OperationError(); + length >>= 3; + + const hashFn = opensslHashFunctionName(hash); + + const keyDerivationKey = key[kKeyMaterial]; + return new Promise((resolve, reject) => { + pbkdf2(keyDerivationKey, salt, iterations, length, hashFn, (err, key) => { + if (err) + return reject(err); + resolve(key); + }); + }); + }, + + importKey(keyFormat, keyData, params, extractable, keyUsages) { + if (keyFormat !== 'raw') + throw new NotSupportedError(); + + limitUsages(keyUsages, ['deriveKey', 'deriveBits']); + + if (extractable !== false) + throw new SyntaxError(); + + return new CryptoKey('secret', { name: 'PBKDF2' }, extractable, keyUsages, + Buffer.from(keyData)); + } +}; diff --git a/test/algorithms/pbkdf2.js b/test/algorithms/pbkdf2.js new file mode 100644 index 0000000..91ef5da --- /dev/null +++ b/test/algorithms/pbkdf2.js @@ -0,0 +1,56 @@ +import assert from 'assert'; + +import { subtle } from '../../lib'; + +describe('PBKDF2', () => { + it('should import keys', async () => { + const keyBuffer = Buffer.from('passphrase', 'utf8'); + const key = await subtle.importKey('raw', keyBuffer, 'PBKDF2', false, + ['deriveBits']); + assert.strictEqual(key.algorithm.name, 'PBKDF2'); + assert.strictEqual(key.type, 'secret'); + assert.strictEqual(key.extractable, false); + assert.deepEqual(key.usages, ['deriveBits']); + }); + + it('should produce correct outputs', async () => { + const keyBuffer = Buffer.from('passphrase', 'utf8'); + const key = await subtle.importKey('raw', keyBuffer, 'PBKDF2', false, + ['deriveBits']); + + const bits = await subtle.deriveBits({ + name: 'PBKDF2', + iterations: 1000, + hash: 'SHA-512', + salt: Buffer.from('Hello world', 'utf8') + }, key, 256); + + assert(Buffer.isBuffer(bits)); + assert.strictEqual(bits.toString('hex'), + '5552743c1053eeb1c91c1b33c806efd6' + + '2585e90c932bcdad4814a572537bdef5'); + }); + + it('should produce correct keys', async () => { + const keyBuffer = Buffer.from('passphrase', 'utf8'); + const key = await subtle.importKey('raw', keyBuffer, 'PBKDF2', false, + ['deriveKey']); + + const derivedKey = await subtle.deriveKey({ + name: 'PBKDF2', + iterations: 1000, + hash: 'SHA-512', + salt: Buffer.from('Hello world', 'utf8') + }, key, { + name: 'AES-CTR', + length: 256 + }, true, ['encrypt', 'decrypt']); + + assert.strictEqual(derivedKey.type, 'secret'); + + const bits = await subtle.exportKey('raw', derivedKey); + assert.strictEqual(bits.toString('hex'), + '5552743c1053eeb1c91c1b33c806efd6' + + '2585e90c932bcdad4814a572537bdef5'); + }); +}); From 6a48ccb65fa645d7fcba0cdc36ae3af593b067fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Mon, 29 Jul 2019 13:17:40 +0200 Subject: [PATCH 5/8] Add HKDF prototype --- lib/algorithms.js | 3 +++ lib/algorithms/hkdf.js | 51 ++++++++++++++++++++++++++++++++++++++ test/algorithms/hkdf.js | 55 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 lib/algorithms/hkdf.js create mode 100644 test/algorithms/hkdf.js diff --git a/lib/algorithms.js b/lib/algorithms.js index c904df6..d4636c0 100644 --- a/lib/algorithms.js +++ b/lib/algorithms.js @@ -1,4 +1,5 @@ import { AES_CTR, AES_CBC, AES_GCM, AES_KW } from './algorithms/aes.js'; +import { HKDF } from './algorithms/hkdf.js'; import { PBKDF2 } from './algorithms/pbkdf2.js'; import { SHA_1, SHA_256, SHA_384, SHA_512 } from './algorithms/sha.js'; @@ -8,6 +9,8 @@ const algorithms = [ AES_GCM, AES_KW, + HKDF, + PBKDF2, SHA_1, diff --git a/lib/algorithms/hkdf.js b/lib/algorithms/hkdf.js new file mode 100644 index 0000000..6882d15 --- /dev/null +++ b/lib/algorithms/hkdf.js @@ -0,0 +1,51 @@ +import { createHmac } from 'crypto'; + +import { OperationError, NotSupportedError } from '../errors.js'; +import { kKeyMaterial, CryptoKey } from '../subtle.js'; +import { limitUsages, opensslHashFunctionName, toBuffer } from '../util.js'; + +function hmac(hash, key, data) { + return createHmac(hash, key).update(data).digest(); +} + +export const HKDF = { + name: 'HKDF', + + deriveBits(params, key, length) { + if (length === null) + throw new OperationError(); + length >>= 3; + + const hashFn = opensslHashFunctionName(params.hash); + + const keyDerivationKey = key[kKeyMaterial]; + const hmacKey = toBuffer(params.salt); + const pseudoRandomKey = hmac(hashFn, hmacKey, keyDerivationKey); + const hashLen = pseudoRandomKey.length; + const N = Math.ceil(length / hashLen); + + const blocks = new Array(N); + let t = Buffer.alloc(0); + const info = toBuffer(params.info); + for (let i = 0; i < N; i++) { + const data = Buffer.concat([t, info, Buffer.from([i + 1])], + t.length + info.length + 1); + t = blocks[i] = hmac(hashFn, pseudoRandomKey, data); + } + const all = Buffer.concat(blocks, N * hashLen); + return all.slice(0, length); + }, + + importKey(keyFormat, keyData, params, extractable, keyUsages) { + if (keyFormat !== 'raw') + throw new NotSupportedError(); + + limitUsages(keyUsages, ['deriveKey', 'deriveBits']); + + if (extractable !== false) + throw new SyntaxError(); + + return new CryptoKey('secret', { name: 'HKDF' }, extractable, keyUsages, + Buffer.from(keyData)); + } +}; diff --git a/test/algorithms/hkdf.js b/test/algorithms/hkdf.js new file mode 100644 index 0000000..434a355 --- /dev/null +++ b/test/algorithms/hkdf.js @@ -0,0 +1,55 @@ +import assert from 'assert'; + +import { subtle } from '../../lib'; + +describe('HKDF', () => { + it('should import keys', async () => { + const keyBuffer = Buffer.from('passphrase', 'utf8'); + const key = await subtle.importKey('raw', keyBuffer, 'HKDF', false, + ['deriveBits']); + assert.strictEqual(key.algorithm.name, 'HKDF'); + assert.strictEqual(key.type, 'secret'); + assert.strictEqual(key.extractable, false); + assert.deepEqual(key.usages, ['deriveBits']); + }); + + it('should produce correct outputs', async () => { + const keyBuffer = Buffer.from('passphrase', 'utf8'); + const key = await subtle.importKey('raw', keyBuffer, 'HKDF', false, + ['deriveBits']); + + const bits = await subtle.deriveBits({ + name: 'HKDF', + hash: 'SHA-384', + salt: Buffer.from('b19a9d6d7f7d2e9e', 'hex'), + info: Buffer.alloc(0) + }, key, 128); + + assert(Buffer.isBuffer(bits)); + assert.strictEqual(bits.toString('hex'), + '4b52236af0e6516384e531e618c95b96'); + }); + + it('should produce correct keys', async () => { + const keyBuffer = Buffer.from('passphrase', 'utf8'); + const key = await subtle.importKey('raw', keyBuffer, 'HKDF', false, + ['deriveKey']); + + const derivedKey = await subtle.deriveKey({ + name: 'HKDF', + hash: 'SHA-1', + salt: Buffer.from('b19a9d6d7f7d2e9e', 'hex'), + info: Buffer.alloc(0) + }, key, { + name: 'AES-CTR', + length: 256 + }, true, ['encrypt', 'decrypt']); + + assert.strictEqual(derivedKey.type, 'secret'); + + const bits = await subtle.exportKey('raw', derivedKey); + assert.strictEqual(bits.toString('hex'), + '70d38acbfd289f4869069d254e7addff' + + 'c1eec6cf90dc0f8f1598b97828f23b3f'); + }); +}); From 36d52c4fa61e0f6fce114d0aec1e4c1c305ca38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Wed, 31 Jul 2019 19:10:21 +0200 Subject: [PATCH 6/8] Remove babel, refactor ESM to CJS --- .babelrc | 12 ----- .editorconfig | 4 -- lib/algorithms.js | 29 +++++++++--- lib/algorithms/aes.js | 24 +++++----- lib/algorithms/hkdf.js | 16 +++++-- lib/algorithms/pbkdf2.js | 12 +++-- lib/algorithms/sha.js | 12 +++-- lib/errors.js | 17 +++++-- lib/index.js | 13 +++-- lib/key.js | 52 ++++++++++++++++++++ lib/random.js | 11 +++-- lib/subtle.js | 99 +++++++++------------------------------ lib/util.js | 19 ++++---- package.json | 8 +--- test/algorithms/aes.js | 8 ++-- test/algorithms/hkdf.js | 6 ++- test/algorithms/pbkdf2.js | 6 ++- test/algorithms/sha.js | 6 ++- test/default-export.js | 6 ++- test/random.js | 6 ++- test/subtle.js | 6 ++- 21 files changed, 202 insertions(+), 170 deletions(-) delete mode 100644 .babelrc create mode 100644 lib/key.js diff --git a/.babelrc b/.babelrc deleted file mode 100644 index d92bc17..0000000 --- a/.babelrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "node": "12" - } - } - ] - ] -} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index ccd39f5..3ce84f6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,3 @@ root = true [**.{js,json}] indent_style = space indent_size = 2 - -[.babelrc] -indent_style = space -indent_size = 2 diff --git a/lib/algorithms.js b/lib/algorithms.js index d4636c0..d8639e4 100644 --- a/lib/algorithms.js +++ b/lib/algorithms.js @@ -1,7 +1,10 @@ -import { AES_CTR, AES_CBC, AES_GCM, AES_KW } from './algorithms/aes.js'; -import { HKDF } from './algorithms/hkdf.js'; -import { PBKDF2 } from './algorithms/pbkdf2.js'; -import { SHA_1, SHA_256, SHA_384, SHA_512 } from './algorithms/sha.js'; +'use strict'; + +const { AES_CTR, AES_CBC, AES_GCM, AES_KW } = require('./algorithms/aes'); +const { HKDF } = require('./algorithms/hkdf'); +const { PBKDF2 } = require('./algorithms/pbkdf2'); +const { SHA_1, SHA_256, SHA_384, SHA_512 } = require('./algorithms/sha'); +const { NotSupportedError } = require('./errors'); const algorithms = [ AES_CTR, @@ -50,6 +53,20 @@ const supportedAlgorithms = objectFromArray([ }); }); -export function getAlgorithmImplementation(name, op) { - return supportedAlgorithms[op][name.toLowerCase()]; +function getAlgorithm(alg, op) { + if (typeof alg !== 'string') { + if (typeof alg !== 'object') + throw new SyntaxError(); + const { name } = alg; + if (typeof name !== 'string') + throw new SyntaxError(); + return getAlgorithm(alg.name, op); + } + + const impl = supportedAlgorithms[op][alg.toLowerCase()]; + if (impl === undefined) + throw new NotSupportedError(); + return impl; } + +module.exports.getAlgorithm = getAlgorithm; diff --git a/lib/algorithms/aes.js b/lib/algorithms/aes.js index 1144162..668467b 100644 --- a/lib/algorithms/aes.js +++ b/lib/algorithms/aes.js @@ -1,14 +1,16 @@ -import { +'use strict'; + +const { createCipheriv, createDecipheriv, createSecretKey, - randomBytes as randomBytesCallback -} from 'crypto'; -import { promisify } from 'util'; + randomBytes: randomBytesCallback +} = require('crypto'); +const { promisify } = require('util'); -import { NotSupportedError, OperationError } from '../errors.js'; -import { kKeyMaterial, CryptoKey } from '../subtle.js'; -import { limitUsages, toBuffer } from '../util.js'; +const { NotSupportedError, OperationError } = require('../errors'); +const { kKeyMaterial, CryptoKey } = require('../key'); +const { limitUsages, toBuffer } = require('../util'); const randomBytes = promisify(randomBytesCallback); @@ -55,7 +57,7 @@ const aesBase = { }; // Spec: https://www.w3.org/TR/WebCryptoAPI/#aes-ctr -export const AES_CTR = { +module.exports.AES_CTR = { name: 'AES-CTR', ...aesBase, @@ -136,7 +138,7 @@ export const AES_CTR = { }; // Spec: https://www.w3.org/TR/WebCryptoAPI/#aes-cbc -export const AES_CBC = { +module.exports.AES_CBC = { name: 'AES-CBC', ...aesBase, @@ -170,7 +172,7 @@ export const AES_CBC = { }; // Spec: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm -export const AES_GCM = { +module.exports.AES_GCM = { name: 'AES-GCM', ...aesBase, @@ -208,7 +210,7 @@ export const AES_GCM = { }; // Spec: https://www.w3.org/TR/WebCryptoAPI/#aes-kw -export const AES_KW = { +module.exports.AES_KW = { name: 'AES-KW', ...aesBase, diff --git a/lib/algorithms/hkdf.js b/lib/algorithms/hkdf.js index 6882d15..5c8d619 100644 --- a/lib/algorithms/hkdf.js +++ b/lib/algorithms/hkdf.js @@ -1,14 +1,20 @@ -import { createHmac } from 'crypto'; +'use strict'; -import { OperationError, NotSupportedError } from '../errors.js'; -import { kKeyMaterial, CryptoKey } from '../subtle.js'; -import { limitUsages, opensslHashFunctionName, toBuffer } from '../util.js'; +const { createHmac } = require('crypto'); + +const { OperationError, NotSupportedError } = require('../errors'); +const { kKeyMaterial, CryptoKey } = require('../key'); +const { + limitUsages, + opensslHashFunctionName, + toBuffer +} = require('../util'); function hmac(hash, key, data) { return createHmac(hash, key).update(data).digest(); } -export const HKDF = { +module.exports.HKDF = { name: 'HKDF', deriveBits(params, key, length) { diff --git a/lib/algorithms/pbkdf2.js b/lib/algorithms/pbkdf2.js index 4fd0292..d7d5663 100644 --- a/lib/algorithms/pbkdf2.js +++ b/lib/algorithms/pbkdf2.js @@ -1,10 +1,12 @@ -import { pbkdf2 } from 'crypto'; +'use strict'; -import { OperationError, NotSupportedError } from '../errors.js'; -import { kKeyMaterial, CryptoKey } from '../subtle.js'; -import { limitUsages, opensslHashFunctionName } from '../util.js'; +const { pbkdf2 } = require('crypto'); -export const PBKDF2 = { +const { OperationError, NotSupportedError } = require('../errors'); +const { kKeyMaterial, CryptoKey } = require('../key'); +const { limitUsages, opensslHashFunctionName } = require('../util'); + +module.exports.PBKDF2 = { name: 'PBKDF2', deriveBits(params, key, length) { diff --git a/lib/algorithms/sha.js b/lib/algorithms/sha.js index e653767..b7b0978 100644 --- a/lib/algorithms/sha.js +++ b/lib/algorithms/sha.js @@ -1,4 +1,6 @@ -import { createHash } from 'crypto'; +'use strict'; + +const { createHash } = require('crypto'); function implement(name, opensslName) { return { @@ -14,7 +16,7 @@ function implement(name, opensslName) { }; } -export const SHA_1 = implement('SHA-1', 'sha1'); -export const SHA_256 = implement('SHA-256', 'sha256'); -export const SHA_384 = implement('SHA-384', 'sha384'); -export const SHA_512 = implement('SHA-512', 'sha512'); +module.exports.SHA_1 = implement('SHA-1', 'sha1'); +module.exports.SHA_256 = implement('SHA-256', 'sha256'); +module.exports.SHA_384 = implement('SHA-384', 'sha384'); +module.exports.SHA_512 = implement('SHA-512', 'sha512'); diff --git a/lib/errors.js b/lib/errors.js index 193dcd3..51b604f 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -1,11 +1,20 @@ -export class NotSupportedError extends Error {} +'use strict'; + +class NotSupportedError extends Error {} NotSupportedError.prototype.name = 'NotSupportedError'; -export class InvalidAccessError extends Error {} +class InvalidAccessError extends Error {} InvalidAccessError.prototype.name = 'InvalidAccessError'; -export class OperationError extends Error {} +class OperationError extends Error {} OperationError.prototype.name = 'OperationError'; -export class QuotaExceededError extends Error {} +class QuotaExceededError extends Error {} QuotaExceededError.prototype.name = 'QuotaExceededError'; + +module.exports = { + NotSupportedError, + InvalidAccessError, + OperationError, + QuotaExceededError +}; diff --git a/lib/index.js b/lib/index.js index 09e630b..8d0fa6a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,11 +1,10 @@ -import { getRandomValues } from './random.js'; -export { getRandomValues } from './random.js'; +'use strict'; -import subtle_ from './subtle.js'; -export const subtle = subtle_; +const { getRandomValues } = require('./random'); +const subtle = require('./subtle'); // Spec: https://www.w3.org/TR/WebCryptoAPI/#crypto-interface -export default { - subtle, - getRandomValues +module.exports = { + getRandomValues, + subtle }; diff --git a/lib/key.js b/lib/key.js new file mode 100644 index 0000000..4a2b3db --- /dev/null +++ b/lib/key.js @@ -0,0 +1,52 @@ +'use strict'; + +const { InvalidAccessError } = require('./errors'); + +const kType = Symbol('kType'); +const kAlgorithm = Symbol('kAlgorithm'); +const kExtractable = Symbol('kExtractable'); +const kUsages = Symbol('kUsages'); + +const kKeyMaterial = Symbol('kKeyMaterial'); + +// Spec: https://www.w3.org/TR/WebCryptoAPI/#cryptokey-interface +class CryptoKey { + constructor(type, algorithm, extractable, usages, keyMaterial) { + this[kType] = type; + this[kAlgorithm] = algorithm; + this[kExtractable] = extractable; + this[kUsages] = new Set(usages); + this[kKeyMaterial] = keyMaterial; + } + + get type() { + return this[kType]; + } + + get extractable() { + return this[kExtractable]; + } + + get algorithm() { + return this[kAlgorithm]; + } + + get usages() { + return [...this[kUsages]]; + } +} + +function requireKeyUsage(key, usage) { + if (!key[kUsages].has(usage)) + throw new InvalidAccessError(); +} + +module.exports = { + kType, + kAlgorithm, + kExtractable, + kUsages, + kKeyMaterial, + CryptoKey, + requireKeyUsage +}; diff --git a/lib/random.js b/lib/random.js index da33049..5bc1f15 100644 --- a/lib/random.js +++ b/lib/random.js @@ -1,11 +1,14 @@ -import { randomFillSync } from 'crypto'; -import { QuotaExceededError } from './errors.js'; +'use strict'; + +const { randomFillSync } = require('crypto'); + +const { QuotaExceededError } = require('./errors'); // Spec: https://www.w3.org/TR/WebCryptoAPI/#dfn-Crypto-method-getRandomValues -export function getRandomValues(array) { +module.exports.getRandomValues = (array) => { if (array.byteLength > 65536) throw new QuotaExceededError(); randomFillSync(array); return array; -} +}; diff --git a/lib/subtle.js b/lib/subtle.js index 1fccaaa..8d74952 100644 --- a/lib/subtle.js +++ b/lib/subtle.js @@ -1,67 +1,12 @@ -import { getAlgorithmImplementation } from './algorithms.js'; -import { - InvalidAccessError, - NotSupportedError -} from './errors.js'; -import { toBuffer } from './util.js'; - -const kType = Symbol('kType'); -const kAlgorithm = Symbol('kAlgorithm'); -const kExtractable = Symbol('kExtractable'); -const kUsages = Symbol('kUsages'); - -export const kKeyMaterial = Symbol('kKeyMaterial'); - -// Spec: https://www.w3.org/TR/WebCryptoAPI/#cryptokey-interface -export class CryptoKey { - constructor(type, algorithm, extractable, usages, keyMaterial) { - this[kType] = type; - this[kAlgorithm] = algorithm; - this[kExtractable] = extractable; - this[kUsages] = new Set(usages); - this[kKeyMaterial] = keyMaterial; - } - - get type() { - return this[kType]; - } - - get extractable() { - return this[kExtractable]; - } - - get algorithm() { - return this[kAlgorithm]; - } - - get usages() { - return [...this[kUsages]]; - } -} - -function requireKeyUsage(key, usage) { - if (!key[kUsages].has(usage)) - throw new InvalidAccessError(); -} - -export function getAlgorithm(alg, op) { - if (typeof alg !== 'string') { - if (typeof alg !== 'object') - throw new SyntaxError(); - const { name } = alg; - if (typeof name !== 'string') - throw new SyntaxError(); - return getAlgorithm(alg.name, op); - } +'use strict'; - const impl = getAlgorithmImplementation(alg.toLowerCase(), op); - if (impl === undefined) - throw new NotSupportedError(); - return impl; -} +const { getAlgorithm } = require('./algorithms'); +const { InvalidAccessError } = require('./errors'); +const { kAlgorithm, kExtractable, requireKeyUsage } = require('./key'); +const { toBuffer } = require('./util'); // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-encrypt -export async function encrypt(algorithm, key, data) { +async function encrypt(algorithm, key, data) { const alg = getAlgorithm(algorithm, 'encrypt'); if (key.algorithm.name !== alg.name) throw new InvalidAccessError(); @@ -72,7 +17,7 @@ export async function encrypt(algorithm, key, data) { } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-decrypt -export async function decrypt(algorithm, key, data) { +async function decrypt(algorithm, key, data) { const alg = getAlgorithm(algorithm, 'decrypt'); if (key.algorithm.name !== alg.name) throw new InvalidAccessError(); @@ -83,7 +28,7 @@ export async function decrypt(algorithm, key, data) { } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-sign -export async function sign(algorithm, key, data) { +async function sign(algorithm, key, data) { const alg = getAlgorithm(algorithm, 'sign'); if (alg.name !== key[kAlgorithm].name) throw new InvalidAccessError(); @@ -93,7 +38,7 @@ export async function sign(algorithm, key, data) { } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-verify -export async function verify(algorithm, key, signature, data) { +async function verify(algorithm, key, signature, data) { const alg = getAlgorithm(algorithm, 'verify'); if (alg.name !== key[kAlgorithm].name) throw new InvalidAccessError(); @@ -103,21 +48,21 @@ export async function verify(algorithm, key, signature, data) { } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-digest -export async function digest(algorithm, data) { +async function digest(algorithm, data) { const buffer = toBuffer(data); const alg = getAlgorithm(algorithm, 'digest'); return alg.digest(algorithm, buffer); } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey -export async function generateKey(algorithm, extractable, keyUsages) { +async function generateKey(algorithm, extractable, keyUsages) { const alg = getAlgorithm(algorithm, 'generateKey'); return alg.generateKey(algorithm, extractable, keyUsages); } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveKey -export async function deriveKey(algorithm, baseKey, derivedKeyType, extractable, - keyUsages) { +async function deriveKey(algorithm, baseKey, derivedKeyType, extractable, + keyUsages) { const alg = getAlgorithm(algorithm, 'deriveBits'); const keyAlg = getAlgorithm(derivedKeyType, 'get key length'); const length = await keyAlg['get key length'](derivedKeyType); @@ -128,21 +73,21 @@ export async function deriveKey(algorithm, baseKey, derivedKeyType, extractable, } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveBits -export async function deriveBits(algorithm, key, length) { +async function deriveBits(algorithm, key, length) { const alg = getAlgorithm(algorithm, 'deriveBits'); requireKeyUsage(key, 'deriveBits'); return alg.deriveBits(algorithm, key, length); } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-importKey -export async function importKey(keyFormat, keyData, algorithm, extractable, - keyUsages) { +async function importKey(keyFormat, keyData, algorithm, extractable, + keyUsages) { const alg = getAlgorithm(algorithm, 'importKey'); return alg.importKey(keyFormat, keyData, algorithm, extractable, keyUsages); } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-exportKey -export async function exportKey(format, key) { +async function exportKey(format, key) { const alg = getAlgorithm(key.algorithm, 'exportKey'); if (!key.extractable) throw new InvalidAccessError(); @@ -150,7 +95,7 @@ export async function exportKey(format, key) { } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-wrapKey -export async function wrapKey(format, key, wrappingKey, wrapAlgorithm) { +async function wrapKey(format, key, wrappingKey, wrapAlgorithm) { let wrapFn, alg; try { alg = getAlgorithm(wrapAlgorithm, wrapFn = 'wrapKey'); @@ -175,9 +120,9 @@ export async function wrapKey(format, key, wrappingKey, wrapAlgorithm) { } // Spec: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-unwrapKey -export async function unwrapKey(format, wrappedKey, unwrappingKey, - unwrapAlgorithm, unwrappedKeyAlgorithm, - extractable, keyUsages) { +async function unwrapKey(format, wrappedKey, unwrappingKey, + unwrapAlgorithm, unwrappedKeyAlgorithm, + extractable, keyUsages) { let unwrapFn, alg; try { alg = getAlgorithm(unwrapAlgorithm, unwrapFn = 'unwrapKey'); @@ -201,7 +146,7 @@ export async function unwrapKey(format, wrappedKey, unwrappingKey, } // Spec: https://www.w3.org/TR/WebCryptoAPI/#subtlecrypto-interface -export default { +module.exports = { encrypt, decrypt, sign, diff --git a/lib/util.js b/lib/util.js index 707f57c..d088910 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,20 +1,23 @@ -import { getAlgorithm } from './subtle.js'; +'use strict'; -export function toBuffer(source) { +const algorithms = require('./algorithms'); + +module.exports.toBuffer = (source) => { if (ArrayBuffer.isView(source)) { return Buffer.from(source.buffer, source.byteOffset, source.byteLength); } else { return Buffer.from(source); } -} +}; -export function opensslHashFunctionName(algorithm) { - return getAlgorithm(algorithm, 'get hash function')['get hash function'](); -} +module.exports.opensslHashFunctionName = (algorithm) => { + const op = 'get hash function'; + return algorithms.getAlgorithm(algorithm, op)[op](); +}; -export function limitUsages(usages, allowed) { +module.exports.limitUsages = (usages, allowed) => { for (const usage of usages) { if (!allowed.includes(usage)) throw new SyntaxError(); } -} +}; diff --git a/package.json b/package.json index f61bca1..542b070 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,11 @@ { "main": "./lib/index.js", - "type": "module", "devDependencies": { - "@babel/cli": "^7.5.5", - "@babel/core": "^7.5.5", - "@babel/node": "^7.5.5", - "@babel/preset-env": "^7.5.5", - "@babel/register": "^7.5.5", "eslint": "^6.1.0", "mocha": "^6.2.0" }, "scripts": { - "test": "mocha --require @babel/register --recursive ./test/", + "test": "mocha --recursive ./test/", "lint": "eslint lib test" }, "license": "MIT", diff --git a/test/algorithms/aes.js b/test/algorithms/aes.js index 2d990c4..0676815 100644 --- a/test/algorithms/aes.js +++ b/test/algorithms/aes.js @@ -1,7 +1,9 @@ -import assert from 'assert'; -import { randomBytes } from 'crypto'; +'use strict'; -import { subtle } from '../../lib'; +const assert = require('assert'); +const { randomBytes } = require('crypto'); + +const { subtle } = require('../../'); function twice(buf) { return Buffer.concat([buf, buf], buf.length * 2); diff --git a/test/algorithms/hkdf.js b/test/algorithms/hkdf.js index 434a355..95fcaba 100644 --- a/test/algorithms/hkdf.js +++ b/test/algorithms/hkdf.js @@ -1,6 +1,8 @@ -import assert from 'assert'; +'use strict'; -import { subtle } from '../../lib'; +const assert = require('assert'); + +const { subtle } = require('../../'); describe('HKDF', () => { it('should import keys', async () => { diff --git a/test/algorithms/pbkdf2.js b/test/algorithms/pbkdf2.js index 91ef5da..7c47ad4 100644 --- a/test/algorithms/pbkdf2.js +++ b/test/algorithms/pbkdf2.js @@ -1,6 +1,8 @@ -import assert from 'assert'; +'use strict'; -import { subtle } from '../../lib'; +const assert = require('assert'); + +const { subtle } = require('../../'); describe('PBKDF2', () => { it('should import keys', async () => { diff --git a/test/algorithms/sha.js b/test/algorithms/sha.js index 78d383c..3709fd8 100644 --- a/test/algorithms/sha.js +++ b/test/algorithms/sha.js @@ -1,6 +1,8 @@ -import assert from 'assert'; +'use strict'; -import { subtle } from '../../lib'; +const assert = require('assert'); + +const { subtle } = require('../../'); describe('SHA', () => { diff --git a/test/default-export.js b/test/default-export.js index f2a820a..77df9ba 100644 --- a/test/default-export.js +++ b/test/default-export.js @@ -1,6 +1,8 @@ -import assert from 'assert'; +'use strict'; -import crypto from '../'; +const assert = require('assert'); + +const crypto = require('../'); describe('Default export', () => { it('should have getRandomBytes', () => { diff --git a/test/random.js b/test/random.js index 9b38777..5dd9a24 100644 --- a/test/random.js +++ b/test/random.js @@ -1,6 +1,8 @@ -import assert from 'assert'; +'use strict'; -import { getRandomValues } from '../'; +const assert = require('assert'); + +const { getRandomValues } = require('../'); describe('crypto.getRandomBytes', () => { it('should exist', () => { diff --git a/test/subtle.js b/test/subtle.js index 4327580..36eea10 100644 --- a/test/subtle.js +++ b/test/subtle.js @@ -1,6 +1,8 @@ -import assert from 'assert'; +'use strict'; -import { subtle } from '../'; +const assert = require('assert'); + +const { subtle } = require('../'); describe('crypto.subtle', () => { const fns = [ From 47db05e9a830ae6393f9eeee629a371e8de84077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Thu, 1 Aug 2019 11:56:42 +0200 Subject: [PATCH 7/8] Adopt Node.js linting rules --- .eslintignore | 1 + .eslintrc.js | 268 ++++++++++++++++++++++++++++++++++++++ .eslintrc.json | 36 ----- lib/subtle.js | 4 +- package.json | 7 +- test/.eslintrc.json | 5 + test/algorithms/aes.js | 60 ++++----- test/algorithms/hkdf.js | 6 +- test/algorithms/pbkdf2.js | 2 +- 9 files changed, 314 insertions(+), 75 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.js delete mode 100644 .eslintrc.json create mode 100644 test/.eslintrc.json diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..09a8422 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +!.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..25c4dcc --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,268 @@ +'use strict'; + +// This is a modified version of the Node.js core ESLint configuration. + +module.exports = { + root: true, + env: { + node: true, + es6: true + }, + parser: 'babel-eslint', + parserOptions: { sourceType: 'script' }, + rules: { + // ESLint built-in rules + // http://eslint.org/docs/rules + 'accessor-pairs': 'error', + 'array-callback-return': 'error', + 'arrow-parens': ['error', 'always'], + 'arrow-spacing': ['error', { before: true, after: true }], + 'block-scoped-var': 'error', + 'block-spacing': 'error', + 'brace-style': ['error', '1tbs', { allowSingleLine: true }], + 'capitalized-comments': ['error', 'always', { + line: { + // Ignore all lines that have less characters than 20 and all lines that + // start with something that looks like a variable name or code. + // eslint-disable-next-line max-len + ignorePattern: '.{0,20}$|[a-z]+ ?[0-9A-Z_.(/=:[#-]|std|http|ssh|ftp|(let|var|const) [a-z_A-Z0-9]+ =|[b-z] |[a-z]*[0-9].* ', + ignoreInlineComments: true, + ignoreConsecutiveComments: true + }, + block: { + ignorePattern: '.*' + } + }], + 'comma-dangle': ['error', 'never'], + 'comma-spacing': 'error', + 'comma-style': 'error', + 'computed-property-spacing': 'error', + 'constructor-super': 'error', + 'dot-location': ['error', 'property'], + 'dot-notation': 'error', + 'eol-last': 'error', + 'eqeqeq': ['error', 'smart'], + 'for-direction': 'error', + 'func-call-spacing': 'error', + 'func-name-matching': 'error', + 'func-style': ['error', 'declaration', { allowArrowFunctions: true }], + 'getter-return': 'error', + 'indent': ['error', 2, { + ArrayExpression: 'first', + CallExpression: { arguments: 'first' }, + FunctionDeclaration: { parameters: 'first' }, + FunctionExpression: { parameters: 'first' }, + MemberExpression: 'off', + ObjectExpression: 'first', + SwitchCase: 1 + }], + 'key-spacing': ['error', { mode: 'strict' }], + 'keyword-spacing': 'error', + 'linebreak-style': ['error', 'unix'], + 'max-len': ['error', { + code: 80, + ignorePattern: '^// Flags:', + ignoreRegExpLiterals: true, + ignoreUrls: true, + tabWidth: 2 + }], + 'new-parens': 'error', + 'no-async-promise-executor': 'error', + 'no-class-assign': 'error', + 'no-confusing-arrow': 'error', + 'no-const-assign': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-delete-var': 'error', + 'no-dupe-args': 'error', + 'no-dupe-class-members': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-duplicate-imports': 'error', + 'no-empty-character-class': 'error', + 'no-ex-assign': 'error', + 'no-extra-boolean-cast': 'error', + 'no-extra-parens': ['error', 'functions'], + 'no-extra-semi': 'error', + 'no-fallthrough': 'error', + 'no-func-assign': 'error', + 'no-global-assign': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-lonely-if': 'error', + 'no-misleading-character-class': 'error', + 'no-mixed-requires': 'error', + 'no-mixed-spaces-and-tabs': 'error', + 'no-multi-spaces': ['error', { ignoreEOLComments: true }], + 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 0, maxBOF: 0 }], + 'no-new-require': 'error', + 'no-new-symbol': 'error', + 'no-obj-calls': 'error', + 'no-octal': 'error', + 'no-path-concat': 'error', + 'no-proto': 'error', + 'no-redeclare': ['error', { 'builtinGlobals': false }], + 'no-restricted-modules': ['error', 'sys'], + /* eslint-disable max-len */ + 'no-restricted-properties': [ + 'error', + { + object: 'assert', + property: 'deepEqual', + message: 'Use `assert.deepStrictEqual()`.' + }, + { + object: 'assert', + property: 'notDeepEqual', + message: 'Use `assert.notDeepStrictEqual()`.' + }, + { + object: 'assert', + property: 'equal', + message: 'Use `assert.strictEqual()` rather than `assert.equal()`.' + }, + { + object: 'assert', + property: 'notEqual', + message: 'Use `assert.notStrictEqual()` rather than `assert.notEqual()`.' + }, + { + property: '__defineGetter__', + message: '__defineGetter__ is deprecated.' + }, + { + property: '__defineSetter__', + message: '__defineSetter__ is deprecated.' + } + ], + 'no-restricted-syntax': [ + 'error', + { + selector: "CallExpression[callee.property.name='deepStrictEqual'][arguments.2.type='Literal']", + message: 'Do not use a literal for the third argument of assert.deepStrictEqual().' + }, + { + selector: "CallExpression[callee.property.name='doesNotThrow']", + message: 'Do not use `assert.doesNotThrow()`. Write the code without the wrapper and add a comment instead.' + }, + { + selector: "CallExpression[callee.property.name='doesNotReject']", + message: 'Do not use `assert.doesNotReject()`. Write the code without the wrapper and add a comment instead.' + }, + { + selector: "CallExpression[callee.property.name='rejects'][arguments.length<2]", + message: '`assert.rejects()` must be invoked with at least two arguments.' + }, + { + selector: "CallExpression[callee.property.name='strictEqual'][arguments.2.type='Literal']", + message: 'Do not use a literal for the third argument of assert.strictEqual().' + }, + { + selector: "CallExpression[callee.property.name='throws'][arguments.1.type='Literal']:not([arguments.1.regex])", + message: 'Use an object as second argument of `assert.throws()`.' + }, + { + selector: "CallExpression[callee.property.name='throws'][arguments.length<2]", + message: '`assert.throws()` must be invoked with at least two arguments.' + }, + { + selector: "CallExpression[callee.name='setTimeout'][arguments.length<2]", + message: '`setTimeout()` must be invoked with at least two arguments.' + }, + { + selector: "CallExpression[callee.name='setInterval'][arguments.length<2]", + message: '`setInterval()` must be invoked with at least two arguments.' + }, + { + selector: 'ThrowStatement > CallExpression[callee.name=/Error$/]', + message: 'Use `new` keyword when throwing an `Error`.' + }, + { + selector: "CallExpression[callee.property.name='notDeepStrictEqual'][arguments.0.type='Literal']:not([arguments.1.type='Literal']):not([arguments.1.type='ObjectExpression']):not([arguments.1.type='ArrayExpression']):not([arguments.1.type='UnaryExpression'])", + message: 'The first argument should be the `actual`, not the `expected` value.' + }, + { + selector: "CallExpression[callee.property.name='notStrictEqual'][arguments.0.type='Literal']:not([arguments.1.type='Literal']):not([arguments.1.type='ObjectExpression']):not([arguments.1.type='ArrayExpression']):not([arguments.1.type='UnaryExpression'])", + message: 'The first argument should be the `actual`, not the `expected` value.' + }, + { + selector: "CallExpression[callee.property.name='deepStrictEqual'][arguments.0.type='Literal']:not([arguments.1.type='Literal']):not([arguments.1.type='ObjectExpression']):not([arguments.1.type='ArrayExpression']):not([arguments.1.type='UnaryExpression'])", + message: 'The first argument should be the `actual`, not the `expected` value.' + }, + { + selector: "CallExpression[callee.property.name='strictEqual'][arguments.0.type='Literal']:not([arguments.1.type='Literal']):not([arguments.1.type='ObjectExpression']):not([arguments.1.type='ArrayExpression']):not([arguments.1.type='UnaryExpression'])", + message: 'The first argument should be the `actual`, not the `expected` value.' + } + ], + /* eslint-enable max-len */ + 'no-return-await': 'error', + 'no-self-assign': 'error', + 'no-self-compare': 'error', + 'no-shadow-restricted-names': 'error', + 'no-tabs': 'error', + 'no-template-curly-in-string': 'error', + 'no-this-before-super': 'error', + 'no-throw-literal': 'error', + 'no-trailing-spaces': 'error', + 'no-undef': ['error', { typeof: true }], + 'no-undef-init': 'error', + 'no-unexpected-multiline': 'error', + 'no-unreachable': 'error', + 'no-unsafe-finally': 'error', + 'no-unsafe-negation': 'error', + 'no-unused-labels': 'error', + 'no-unused-vars': ['error', { args: 'none', caughtErrors: 'all' }], + 'no-use-before-define': ['error', { + classes: true, + functions: false, + variables: false + }], + 'no-useless-call': 'error', + 'no-useless-catch': 'error', + 'no-useless-concat': 'error', + 'no-useless-constructor': 'error', + 'no-useless-escape': 'error', + 'no-useless-return': 'error', + 'no-void': 'error', + 'no-whitespace-before-property': 'error', + 'no-with': 'error', + 'object-curly-spacing': ['error', 'always'], + 'one-var': ['error', { initialized: 'never' }], + 'one-var-declaration-per-line': 'error', + 'operator-linebreak': ['error', 'after'], + 'prefer-const': ['error', { ignoreReadBeforeAssign: true }], + 'quotes': ['error', 'single', { avoidEscape: true }], + 'quote-props': ['error', 'consistent'], + 'rest-spread-spacing': 'error', + 'semi': 'error', + 'semi-spacing': 'error', + 'space-before-blocks': ['error', 'always'], + 'space-before-function-paren': ['error', { + anonymous: 'never', + named: 'never', + asyncArrow: 'always' + }], + 'space-in-parens': ['error', 'never'], + 'space-infix-ops': 'error', + 'space-unary-ops': 'error', + 'spaced-comment': ['error', 'always', { + 'block': { 'balanced': true }, + 'exceptions': ['-'] + }], + 'strict': ['error', 'global'], + 'symbol-description': 'error', + 'template-curly-spacing': 'error', + 'unicode-bom': 'error', + 'use-isnan': 'error', + 'valid-typeof': 'error' + }, + globals: { + Atomics: 'readable', + BigInt: 'readable', + BigInt64Array: 'readable', + BigUint64Array: 'readable', + TextEncoder: 'readable', + TextDecoder: 'readable', + queueMicrotask: 'readable' + } +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 41dcc0c..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "env": { - "es6": true, - "node": true, - "mocha": true - }, - "extends": "eslint:recommended", - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "rules": { - "array-bracket-spacing": ["error", "never"], - "comma-dangle": ["error", "never"], - "eol-last": ["error", "always"], - "indent": ["error", 2, { - "FunctionDeclaration": { - "parameters": "first" - }, - "CallExpression": { - "arguments": "first" - }, - "VariableDeclarator": "first" - }], - "max-len": ["error", 80], - "no-var": "error", - "prefer-const": "error", - "quotes": ["error", "single"], - "semi": ["error", "always"], - "space-infix-ops": ["error"] - } -} \ No newline at end of file diff --git a/lib/subtle.js b/lib/subtle.js index 8d74952..8e82837 100644 --- a/lib/subtle.js +++ b/lib/subtle.js @@ -99,7 +99,7 @@ async function wrapKey(format, key, wrappingKey, wrapAlgorithm) { let wrapFn, alg; try { alg = getAlgorithm(wrapAlgorithm, wrapFn = 'wrapKey'); - } catch (err) { + } catch { alg = getAlgorithm(wrapAlgorithm, wrapFn = 'encrypt'); } @@ -126,7 +126,7 @@ async function unwrapKey(format, wrappedKey, unwrappingKey, let unwrapFn, alg; try { alg = getAlgorithm(unwrapAlgorithm, unwrapFn = 'unwrapKey'); - } catch (err) { + } catch { alg = getAlgorithm(unwrapAlgorithm, unwrapFn = 'decrypt'); } diff --git a/package.json b/package.json index 542b070..2ba7768 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,20 @@ { "main": "./lib/index.js", "devDependencies": { + "babel-eslint": "^10.0.2", "eslint": "^6.1.0", "mocha": "^6.2.0" }, "scripts": { "test": "mocha --recursive ./test/", - "lint": "eslint lib test" + "lint": "eslint ." }, "license": "MIT", "private": true, "author": "Tobias Nießen ", "bugs": "https://github.com/nodejs/webcrypto/issues", "repository": { - "type" : "git", - "url" : "https://github.com/nodejs/webcrypto.git" + "type": "git", + "url": "https://github.com/nodejs/webcrypto.git" } } diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 0000000..b2916b9 --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "env": { + "mocha": true + } +} \ No newline at end of file diff --git a/test/algorithms/aes.js b/test/algorithms/aes.js index 0676815..810eb92 100644 --- a/test/algorithms/aes.js +++ b/test/algorithms/aes.js @@ -31,7 +31,7 @@ function testGenImportExport(name) { assert(Buffer.isBuffer(expKey3)); assert.strictEqual(expKey3.length, 32); - assert.notDeepEqual(expKey1, expKey2); + assert.notDeepStrictEqual(expKey1, expKey2); const impKey1 = await subtle.importKey('raw', expKey1, name, true, ['encrypt', 'decrypt']); @@ -40,9 +40,9 @@ function testGenImportExport(name) { const impKey3 = await subtle.importKey('raw', expKey3, name, true, ['encrypt', 'decrypt']); - assert.deepEqual(await subtle.exportKey('raw', impKey1), expKey1); - assert.deepEqual(await subtle.exportKey('raw', impKey2), expKey2); - assert.deepEqual(await subtle.exportKey('raw', impKey3), expKey3); + assert.deepStrictEqual(await subtle.exportKey('raw', impKey1), expKey1); + assert.deepStrictEqual(await subtle.exportKey('raw', impKey2), expKey2); + assert.deepStrictEqual(await subtle.exportKey('raw', impKey3), expKey3); }; } @@ -64,15 +64,15 @@ describe('AES-CTR', () => { counter, length }, key, plaintext); - assert.deepEqual(ciphertext, - Buffer.from('0bdbe0f2de637f43b9d86f8bb0ba5f05', 'hex')); + assert.strictEqual(ciphertext.toString('hex'), + '0bdbe0f2de637f43b9d86f8bb0ba5f05'); const deciphered = await subtle.decrypt({ name: 'AES-CTR', counter, length }, key, ciphertext); - assert.deepEqual(deciphered, plaintext); + assert.deepStrictEqual(deciphered, plaintext); }); it('should handle the "length" parameter', async () => { @@ -90,11 +90,11 @@ describe('AES-CTR', () => { assert.strictEqual(ciphertext.length, 4 * blockSize); const encryptedFirstHalf = ciphertext.slice(0, 2 * blockSize); const encryptedSecondHalf = ciphertext.slice(2 * blockSize); - assert.deepEqual(encryptedFirstHalf, encryptedSecondHalf); + assert.deepStrictEqual(encryptedFirstHalf, encryptedSecondHalf); let decrypted = await subtle.decrypt({ name: 'AES-CTR', counter, length }, key, ciphertext); - assert.deepEqual(decrypted, plaintext); + assert.deepStrictEqual(decrypted, plaintext); // This is slightly more tricky: We allow incrementing the last 127 bits, // which will not lead to any repetitions that we could test for. However, @@ -112,12 +112,12 @@ describe('AES-CTR', () => { counter: expectedIV, length: 128 }, key, plaintext.slice(blockSize, 2 * blockSize)); - assert.deepEqual(ciphertext.slice(blockSize, 2 * blockSize), - expectedSecondBlock); + assert.deepStrictEqual(ciphertext.slice(blockSize, 2 * blockSize), + expectedSecondBlock); decrypted = await subtle.decrypt({ name: 'AES-CTR', counter, length }, key, ciphertext); - assert.deepEqual(decrypted, plaintext); + assert.deepStrictEqual(decrypted, plaintext); }); }); @@ -137,15 +137,15 @@ describe('AES-CBC', () => { name: 'AES-CBC', iv }, key, plaintext); - assert.deepEqual(ciphertext, - Buffer.from('8bb6173879b0f7a8899397e0fde3a3c88c69e86b18' + - 'eb74f8629be60287c89552', 'hex')); + assert.strictEqual(ciphertext.toString('hex'), + '8bb6173879b0f7a8899397e0fde3a3c88c69e86b18' + + 'eb74f8629be60287c89552'); const deciphered = await subtle.decrypt({ name: 'AES-CBC', iv }, key, ciphertext); - assert.deepEqual(deciphered, plaintext); + assert.deepStrictEqual(deciphered, plaintext); }); }); @@ -165,15 +165,15 @@ describe('AES-GCM', () => { name: 'AES-GCM', iv }, key, plaintext); - assert.deepEqual(ciphertext, - Buffer.from('7080337fe4a1f8d8d96fa061ccfdb8cda6dacbf3f2' + - '7ef1dc85190feddc4befdd', 'hex')); + assert.strictEqual(ciphertext.toString('hex'), + '7080337fe4a1f8d8d96fa061ccfdb8cda6dacbf3f2' + + '7ef1dc85190feddc4befdd'); const deciphered = await subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext); - assert.deepEqual(deciphered, plaintext); + assert.deepStrictEqual(deciphered, plaintext); }); it('should handle the "tagLength" parameter', async () => { @@ -189,16 +189,16 @@ describe('AES-GCM', () => { iv, tagLength: 112 }, key, plaintext); - assert.deepEqual(ciphertext, - Buffer.from('7080337fe4a1f8d8d96fa061ccfdb8cda6dacbf3f2' + - '7ef1dc85190feddc4b', 'hex')); + assert.strictEqual(ciphertext.toString('hex'), + '7080337fe4a1f8d8d96fa061ccfdb8cda6dacbf3f2' + + '7ef1dc85190feddc4b'); const deciphered = await subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 112 }, key, ciphertext); - assert.deepEqual(deciphered, plaintext); + assert.deepStrictEqual(deciphered, plaintext); }); it('should support all IV lengths', async () => { @@ -214,16 +214,16 @@ describe('AES-GCM', () => { iv, tagLength: 112 }, key, plaintext); - assert.deepEqual(ciphertext, - Buffer.from('2f136ce56f36acf081476d227c0fb89ed4e0fcd07b' + - '3b8de9d412f99a2c2d', 'hex')); + assert.strictEqual(ciphertext.toString('hex'), + '2f136ce56f36acf081476d227c0fb89ed4e0fcd07b' + + '3b8de9d412f99a2c2d'); const deciphered = await subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 112 }, key, ciphertext); - assert.deepEqual(deciphered, plaintext); + assert.deepStrictEqual(deciphered, plaintext); }); }); @@ -248,7 +248,7 @@ describe('AES-KW', () => { const unwrappedKey = await subtle.unwrapKey('raw', wrappedKey, wrappingKey, 'AES-KW', 'AES-CBC', true, ['encrypt', 'decrypt']); - assert.deepEqual(await subtle.exportKey('raw', unwrappedKey), - await subtle.exportKey('raw', keyToWrap)); + assert.deepStrictEqual(await subtle.exportKey('raw', unwrappedKey), + await subtle.exportKey('raw', keyToWrap)); }); }); diff --git a/test/algorithms/hkdf.js b/test/algorithms/hkdf.js index 95fcaba..5245519 100644 --- a/test/algorithms/hkdf.js +++ b/test/algorithms/hkdf.js @@ -12,7 +12,7 @@ describe('HKDF', () => { assert.strictEqual(key.algorithm.name, 'HKDF'); assert.strictEqual(key.type, 'secret'); assert.strictEqual(key.extractable, false); - assert.deepEqual(key.usages, ['deriveBits']); + assert.deepStrictEqual(key.usages, ['deriveBits']); }); it('should produce correct outputs', async () => { @@ -28,8 +28,8 @@ describe('HKDF', () => { }, key, 128); assert(Buffer.isBuffer(bits)); - assert.strictEqual(bits.toString('hex'), - '4b52236af0e6516384e531e618c95b96'); + assert.deepStrictEqual(bits.toString('hex'), + '4b52236af0e6516384e531e618c95b96'); }); it('should produce correct keys', async () => { diff --git a/test/algorithms/pbkdf2.js b/test/algorithms/pbkdf2.js index 7c47ad4..a61515e 100644 --- a/test/algorithms/pbkdf2.js +++ b/test/algorithms/pbkdf2.js @@ -12,7 +12,7 @@ describe('PBKDF2', () => { assert.strictEqual(key.algorithm.name, 'PBKDF2'); assert.strictEqual(key.type, 'secret'); assert.strictEqual(key.extractable, false); - assert.deepEqual(key.usages, ['deriveBits']); + assert.deepStrictEqual(key.usages, ['deriveBits']); }); it('should produce correct outputs', async () => { From fbd661bc650cb8f93918eb607f0cd6970f27413a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sat, 10 Aug 2019 14:21:43 +0200 Subject: [PATCH 8/8] Minor fixes regarding the initial prototype --- README.md | 9 ++++----- lib/algorithms.js | 1 + package.json | 3 +++ test/.eslintrc.json | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 019588b..4732522 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,13 @@ streaming interfaces. WebCrypto has no streaming interfaces but only one-shot APIs. Encrypting, hashing, signing or verifying large amounts of data is thus difficult in WebCrypto without underlying asynchronous APIs. -This is not considered to be a deficiency of Node.js, but rather an unfortunate -design conflict. More efficient alternatives will be explored at a later point. - ## Development ### Structure The main export of this package is implemented in `lib/index.js` and represents -the `Crypto` interface as defined in section 10 of the WebCrypto specification. -It contains two members: +the `Crypto` interface as defined in section 10 of the +[WebCrypto specification][]. It contains two members: - The `subtle` attribute is implemented in `lib/subtle.js`, including all methods described in section 14.3 of the WebCrypto specification. These @@ -43,3 +40,5 @@ broken by a later commit. ### Linting This repository uses ESLint. Use `npm run lint` to check the code. + +[WebCrypto specification]: https://www.w3.org/TR/WebCryptoAPI/ diff --git a/lib/algorithms.js b/lib/algorithms.js index d8639e4..9a052de 100644 --- a/lib/algorithms.js +++ b/lib/algorithms.js @@ -29,6 +29,7 @@ function objectFromArray(array, fn) { return obj; } +// Spec: https://www.w3.org/TR/WebCryptoAPI/#dfn-supportedAlgorithms const supportedAlgorithms = objectFromArray([ // This corresponds to section 18.2.2 of the WebCrypto spec. 'encrypt', diff --git a/package.json b/package.json index 2ba7768..1b943c0 100644 --- a/package.json +++ b/package.json @@ -16,5 +16,8 @@ "repository": { "type": "git", "url": "https://github.com/nodejs/webcrypto.git" + }, + "engines": { + "node": "^12.x" } } diff --git a/test/.eslintrc.json b/test/.eslintrc.json index b2916b9..7eeefc3 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -2,4 +2,4 @@ "env": { "mocha": true } -} \ No newline at end of file +}