| /*! | ||
| * Copyright (c) 2020 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| /** | ||
| * General purpose key generation driver for Linked Data cryptographic key | ||
| * pairs. | ||
| * | ||
| * @param {Map} [suites] - Optional map of supported suites, by suite id. | ||
| */ | ||
| class CryptoLD { | ||
| constructor({suites} = {}) { | ||
| this.suites = suites || new Map(); | ||
| } | ||
| /** | ||
| * Installs support for a key type (suite). | ||
| * | ||
| * @param {LDKeyPair} keyPairLib - Conforming key pair library for a suite. | ||
| */ | ||
| use(keyPairLib) { | ||
| this.suites.set(keyPairLib.type, keyPairLib); | ||
| } | ||
| /** | ||
| * Generates a public/private LDKeyPair. | ||
| * | ||
| * @param {string} type - Key suite id ('Ed25519VerificationKey2018'). | ||
| * | ||
| * @param {object} [options] - Optional suite-specific key options. | ||
| * @param {string} [options.controller] - Controller DID or URL for the | ||
| * generated key pair. If present, used to auto-initialize the key.id. | ||
| * | ||
| * @returns {Promise<LDKeyPair>} | ||
| */ | ||
| async generate({type, ...options} = {}) { | ||
| if(!type) { | ||
| throw new TypeError('A key type is required to generate.'); | ||
| } | ||
| if(!this._installed({type})) { | ||
| throw new TypeError(`Support for key type "${type}" is not installed.`); | ||
| } | ||
| return await this.suites.get(type).generate(options); | ||
| } | ||
| /** | ||
| * Imports a public/private key pair from serialized data. | ||
| * | ||
| * @param {object} serialized - Serialized key object. | ||
| * | ||
| * @throws {Error} - On missing or invalid serialized key data. | ||
| * | ||
| * @returns {Promise<LDKeyPair>} | ||
| */ | ||
| async from(serialized = {}) { | ||
| const type = serialized && serialized.type; | ||
| if(!type) { | ||
| throw new TypeError('Missing key type.'); | ||
| } | ||
| if(!this._installed({type})) { | ||
| throw new Error(`Support for key type "${type}" is not installed.`); | ||
| } | ||
| return this.suites.get(type).from(serialized); | ||
| } | ||
| /** | ||
| * Tests if a given key type is currently installed. | ||
| * | ||
| * @param {string} [type] - Key suite id ('Ed25519VerificationKey2018'). | ||
| * @private | ||
| */ | ||
| _installed({type}) { | ||
| return this.suites.has(type); | ||
| } | ||
| } | ||
| module.exports = { | ||
| CryptoLD | ||
| }; |
| /*! | ||
| * Copyright (c) 2020 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const {LDKeyPair} = require('./LDKeyPair'); | ||
| class LDVerifierKeyPair extends LDKeyPair { | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * Returns a signer object for use with | ||
| * [jsonld-signatures]{@link https://github.com/digitalbazaar/jsonld-signatures}. | ||
| * @example | ||
| * > const signer = keyPair.signer(); | ||
| * > signer | ||
| * { sign: [AsyncFunction: sign] } | ||
| * > signer.sign({data}); | ||
| * | ||
| * @returns {{sign: Function}} A signer for json-ld usage. | ||
| */ | ||
| /* eslint-enable */ | ||
| signer() { | ||
| return { | ||
| async sign({/* data */}) { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| }; | ||
| } | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * Returns a verifier object for use with | ||
| * [jsonld-signatures]{@link https://github.com/digitalbazaar/jsonld-signatures}. | ||
| * @example | ||
| * > const verifier = keyPair.verifier(); | ||
| * > verifier | ||
| * { verify: [AsyncFunction: verify] } | ||
| * > verifier.verify(key); | ||
| * | ||
| * @returns {{verify: Function}} Used to verify jsonld-signatures. | ||
| */ | ||
| /* eslint-enable */ | ||
| verifier() { | ||
| return { | ||
| async verify({/* data, signature */}) { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| module.exports = { | ||
| LDVerifierKeyPair | ||
| }; |
+42
-5
| # crypto-ld ChangeLog | ||
| ## 3.8.0 - 2020-06-23 | ||
| ## 4.0.0 - 2020-08-01 | ||
| ### Added | ||
| - Setup CI and coverage workflow. | ||
| - Add node 14 to CI. | ||
| - Use sodium-native@3.2.0. | ||
| ### Changed | ||
| - Implement chai-like `.use()` API for installing and specifying individual key | ||
| types. | ||
| - **BREAKING**: Extracted bundled Ed25519 and RSA key suites to their own | ||
| libraries. | ||
| - **BREAKING**: Remove deprecated `.owner` instance property | ||
| - **BREAKING**: Remove deprecated `.passphrase` instance property, and the `encrypt()` and | ||
| `decrypt()` methods (these are no longer used). | ||
| - **BREAKING**: Remove deprecated/unused `publicKey` and `privateKey` properties. | ||
| - **BREAKING**: Rename `.publicNode()` to `.export({publicKey: true})`. | ||
| - **BREAKING**: `.export()` now requires explicitly stating whether you're | ||
| exporting public or private key material. | ||
| - **BREAKING**: Changed `verifyFingerprint()` to used named params. | ||
| - **BREAKING**: Changed `addPublicKey()` and `addPrivateKey()` to used named params. | ||
| ### 4.0.0 - Purpose | ||
| The previous design (`v3.7` and earlier) bundled two key types with this | ||
| library (RSA and Ed25519), which resulted in extraneous code and bundle size | ||
| for projects that only used one of them (or used some other suite). The | ||
| decision was made to extract those bundled suites to their own repositories, | ||
| and to add a builder-style `.use()` API to `crypto-ld` so that client code | ||
| could select just the suites they needed. | ||
| Since this was a comprehensive breaking change in usage, this also gave an | ||
| opportunity to clean up and streamline the existing API, change function | ||
| signatures to be consistent (for example, to consistently used named parameters), | ||
| and to remove deprecated and unused APIs and properties. | ||
| ### Upgrading from v3.7.0 | ||
| Since this is a comprehensive breaking change, you will need to audit and change | ||
| pretty much all usage of `crypto-ld` and compatible key pairs. Specifically: | ||
| * Ed25519 and RSA keys are no longer imported from `crypto-ld`, they'll need to | ||
| be imported from their own packages. | ||
| * Since key suites have been decoupled from `crypto-ld`, it means that this | ||
| library should only be used when a project is using _multiple_ key suites. | ||
| If you're just using a single suite, then you can use that suite directly, | ||
| without `crypto-ld`. | ||
| * Most function param signatures have been changed to use `{}` style named params. | ||
| ## 3.7.0 - 2019-09-06 | ||
@@ -11,0 +48,0 @@ |
+8
-39
| /* | ||
| * Copyright (c) 2018-2019 Digital Bazaar, Inc. All rights reserved. | ||
| * Copyright (c) 2018-2020 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const {CryptoLD} = require('./CryptoLD'); | ||
| const {LDKeyPair} = require('./LDKeyPair'); | ||
| const {LDVerifierKeyPair} = require('./LDVerifierKeyPair'); | ||
| module.exports = { | ||
| Ed25519KeyPair: require('./Ed25519KeyPair'), | ||
| LDKeyPair: require('./LDKeyPair'), | ||
| RSAKeyPair: require('./RSAKeyPair'), | ||
| CryptoLD, | ||
| LDKeyPair, | ||
| LDVerifierKeyPair | ||
| }; | ||
| /** | ||
| * [JSON Web encryption]{@link https://tools.ietf.org/html/rfc7516} | ||
| * @typedef {Object} JWE | ||
| * @property {string} unprotected - A header for the jwe. | ||
| * @property {string} iv - A base64 url. | ||
| * @property {string} ciphertext - A base64 url. | ||
| * @property {string} tag - A base64 url. | ||
| */ | ||
| /** | ||
| * PSS Object | ||
| * @typedef {Object} PSS | ||
| * @property encode {Function} | ||
| * @property verify {Function} | ||
| */ | ||
| /** | ||
| * KeyPair Options. | ||
| * @typedef {Object} KeyPairOptions | ||
| * @property {string} passphrase - For encrypting the private key. | ||
| * @property {string} id - Key Id. | ||
| * @property {string} controller - | ||
| * DID of the person/entity controlling this key. | ||
| * @property {string} owner - DID or URI of owner. DEPRECATED, use | ||
| * `controller` instead. | ||
| */ | ||
| /** | ||
| * Serialized LD Key. | ||
| * @typedef {Object} SerializedLdKey | ||
| * @property {Ed25519VerificationKey2018|RsaVerificationKey2018} | ||
| * type - The Encryption type. | ||
| * @property {string} passphrase - The passphrase to generate the pair. | ||
| */ |
+93
-156
| /*! | ||
| * Copyright (c) 2018-2019 Digital Bazaar, Inc. All rights reserved. | ||
| * Copyright (c) 2018-2020 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const forge = require('node-forge'); | ||
| const {util: {binary: {base58}}} = forge; | ||
| /** | ||
| * When adding support for a new suite type for `crypto-ld`, developers should | ||
| * do the following: | ||
| * | ||
| * 1. Create their own npm package / github repo, such as `example-key-pair`. | ||
| * 2. Subclass either LDVerifierKeyPair (for signature-related suites), or | ||
| * LDKeyPair (for all other types, such as key-agreement-related). | ||
| * 3. Add to the key type table in the `crypto-ld` README.md (that's this repo). | ||
| */ | ||
| class LDKeyPair { | ||
| /** | ||
| * Note: Actual key material | ||
| * (like `publicKeyBase58` for Ed25519 or | ||
| * `publicKeyPem` for RSA) is handled in the subclass. | ||
| * An LDKeyPair can encrypt private key material. | ||
| * @classdesc The Abstract Base Class on which KeyPairs are based. | ||
| * @example | ||
| * // LDKeyPair is an Abstract Class and should only | ||
| * // be used as a base class for other KeyPairs. | ||
| * @param {KeyPairOptions} [options={}] - | ||
| * See [KeyPairOptions]{@link ./index.md#KeyPairOptions}. | ||
| * @param {string} [options.passphrase=null] - For encrypting the private key. | ||
| * @param {string} options.id - The Key id. | ||
| * @param {string} options.controller - DID of the person/entity controlling | ||
| * this key. | ||
| * @param {string} [options.owner] - DID or URI of owner. DEPRECATED, use | ||
| * `controller` instead. | ||
| * Creates a public/private key pair instance. This is an abstract base class, | ||
| * actual key material and suite-specific methods are handled in the subclass. | ||
| * | ||
| * To generate or import a key pair, use the `cryptoLd` instance. | ||
| * @see CryptoLD.js | ||
| * | ||
| * @param {string} id - The Key id, typically composed of controller | ||
| * URL and key fingerprint as hash fragment. | ||
| * @param {string} controller - DID/URL of the person/entity | ||
| * controlling this key. | ||
| */ | ||
| constructor(options = {}) { | ||
| this.passphrase = options.passphrase || null; | ||
| this.id = options.id; | ||
| this.controller = options.controller; | ||
| this.owner = options.owner; | ||
| constructor({id, controller} = {}) { | ||
| this.id = id; | ||
| this.controller = controller; | ||
| // this.type is set in subclass constructor | ||
| // this.publicKey* and this.privateKey* is handled in sub-classes | ||
| } | ||
| /** | ||
| * @abstract | ||
| * @interface | ||
| * @readonly | ||
| * Returns the public key. | ||
| * @throws If not implemented by the subclass. | ||
| * Generates a new public/private key pair instance. | ||
| * Note that this method is not typically called directly by client code, | ||
| * but instead is used through a `cryptoLd` instance. | ||
| * | ||
| * @returns {string} A public key. | ||
| */ | ||
| get publicKey() { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| /** | ||
| * @abstract | ||
| * @interface | ||
| * @readonly | ||
| * Returns the private key. | ||
| * @throws If not implemented by the subclass. | ||
| * @param {object} options - Suite-specific options for the KeyPair. For | ||
| * common options, see the `LDKeyPair.constructor()` docstring. | ||
| * | ||
| * @returns {string} A private key. | ||
| * @returns {Promise<LDKeyPair>} An LDKeyPair instance. | ||
| */ | ||
| get privateKey() { | ||
| static async generate(/* options */) { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| /** | ||
| * Generates an LdKeyPair using SerializedLdKey options. | ||
| * @param {SerializedLdKey} options - Options for generating the KeyPair. | ||
| * @example | ||
| * > const options = { | ||
| * type: 'RsaVerificationKey2018', | ||
| * passphrase: 'Test1234' | ||
| * }; | ||
| * > const keyPair = await LDKeyPair.generate(options); | ||
| * | ||
| * @returns {Promise<LDKeyPair>} An LDKeyPair. | ||
| * @throws Unsupported Key Type. | ||
| * @see [SerializedLdKey]{@link ./index.md#SerializedLdKey} | ||
| */ | ||
| static async generate(options) { | ||
| switch(options.type) { | ||
| case 'Ed25519VerificationKey2018': | ||
| const Ed25519KeyPair = require('./Ed25519KeyPair'); | ||
| return Ed25519KeyPair.generate(options); | ||
| case 'RsaVerificationKey2018': | ||
| const RSAKeyPair = require('./RSAKeyPair'); | ||
| return RSAKeyPair.generate(options); | ||
| default: | ||
| throw new Error(`Unsupported Key Type: ${options.type}`); | ||
| } | ||
| } | ||
| /** | ||
| * Generates a KeyPair from some options. | ||
| * @param {SerializedLdKey} options - Will generate a key pair | ||
| * @param {object} options - Will generate a key pair | ||
| * in multiple different formats. | ||
| * @see [SerializedLdKey]{@link ./index.md#SerializedLdKey} | ||
| * @example | ||
| * > const options = { | ||
| * type: 'Ed25519VerificationKey2018', | ||
| * passphrase: 'Test1234' | ||
| * type: 'Ed25519VerificationKey2018' | ||
| * }; | ||
@@ -104,106 +61,86 @@ * > const edKeyPair = await LDKeyPair.from(options); | ||
| */ | ||
| static async from(options) { | ||
| switch(options.type) { | ||
| case 'Ed25519VerificationKey2018': | ||
| const Ed25519KeyPair = require('./Ed25519KeyPair'); | ||
| return Ed25519KeyPair.from(options); | ||
| case 'RsaVerificationKey2018': | ||
| const RSAKeyPair = require('./RSAKeyPair'); | ||
| return RSAKeyPair.from(options); | ||
| default: | ||
| throw new Error(`Unsupported Key Type: ${options.type}`); | ||
| } | ||
| static async from(/* options */) { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| /** | ||
| * Creates an instance of LDKeyPair from a key fingerprint. | ||
| * Note: Only key types that use their full public key in the fingerprint | ||
| * are supported (so, currently, only 'ed25519'). | ||
| * Exports the serialized representation of the KeyPair | ||
| * and other information that json-ld Signatures can use to form a proof. | ||
| * | ||
| * @param {string} fingerprint | ||
| * @returns {LDKeyPair} | ||
| * @throws Unsupported Fingerprint Type. | ||
| * @param {boolean} [publicKey] - Export public key material? | ||
| * @param {boolean} [privateKey] - Export private key material? | ||
| * | ||
| * @returns {object} A public key object | ||
| * information used in verification methods by signatures. | ||
| */ | ||
| static fromFingerprint({fingerprint}) { | ||
| // skip leading `z` that indicates base58 encoding | ||
| const buffer = base58.decode(fingerprint.substr(1)); | ||
| // buffer is: 0xed 0x01 <public key bytes> | ||
| if(buffer[0] === 0xed && buffer[1] === 0x01) { | ||
| const Ed25519KeyPair = require('./Ed25519KeyPair'); | ||
| return new Ed25519KeyPair({ | ||
| publicKeyBase58: base58.encode(buffer.slice(2)) | ||
| }); | ||
| export({publicKey = false, privateKey = false} = {}) { | ||
| if(!publicKey && !privateKey) { | ||
| throw new Error( | ||
| 'Export requires specifying either "publicKey" or "privateKey".'); | ||
| } | ||
| const key = { | ||
| id: this.id, | ||
| type: this.type, | ||
| controller: this.controller | ||
| }; | ||
| if(publicKey) { | ||
| this.addPublicKey({key}); // Subclass-specific | ||
| } | ||
| if(privateKey) { | ||
| this.addPrivateKey({key}); // Subclass-specific | ||
| } | ||
| return key; | ||
| } | ||
| throw new Error(`Unsupported Fingerprint Type: ${fingerprint}`); | ||
| /** | ||
| * Adds the suite-specific public key material, serialized to string, to | ||
| * the exported public key node. | ||
| * @param {object} key - Public key object. | ||
| * @returns {object} | ||
| */ | ||
| addPublicKey(/* {key} */) { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| /** | ||
| * Generates a | ||
| * [pdkdf2]{@link https://en.wikipedia.org/wiki/PBKDF2} key. | ||
| * @param {string} password - The password for the key. | ||
| * @param {string} salt - Noise used to randomize the key. | ||
| * @param {number} iterations - The number of times to run the algorithm. | ||
| * @param {number} keySize - The byte length of the key. | ||
| * @example | ||
| * > const key = await LdKeyPair.pbkdf2('Test1234', salt, 10, 32); | ||
| * Adds the suite-specific private key material, serialized to string, to | ||
| * the exported public key node. | ||
| * | ||
| * @returns {Promise<Object>} A promise that resolves to a pdkdf2 key. | ||
| * @see https://github.com/digitalbazaar/forge#pkcs5 | ||
| * @param {object} key - Private key object. | ||
| * @returns {object} | ||
| */ | ||
| static async pbkdf2(password, salt, iterations, keySize) { | ||
| return new Promise((resolve, reject) => { | ||
| forge.pkcs5.pbkdf2(password, salt, iterations, keySize, (err, key) => | ||
| err ? reject(err) : resolve(key)); | ||
| }); | ||
| addPrivateKey(/* {key} */) { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| /** | ||
| * Contains the encryption type & public key for the KeyPair | ||
| * and other information that json-ld Signatures can use to form a proof. | ||
| * @param {Object} [options={}] - Needs either a controller or owner. | ||
| * @param {string} [options.controller=this.controller] - DID of the | ||
| * person/entity controlling this key pair. | ||
| * @param {string} [options.owner=this.owner] - DID of key owner. | ||
| * Deprecated term, use `controller`. | ||
| * @example | ||
| * > ldKeyPair.publicNode(); | ||
| * {id: 'test-keypair-id', owner: 'did:uuid:example'} | ||
| * Returns the public key fingerprint, multibase+multicodec encoded. The | ||
| * specific fingerprint method is determined by the key suite, and is often | ||
| * either a hash of the public key material (such as with RSA), or the | ||
| * full encoded public key (for key types with sufficiently short | ||
| * representations, such as ed25519). | ||
| * This is frequently used in initializing the key id, or generating some | ||
| * types of cryptonym DIDs. | ||
| * | ||
| * @returns {Object} A public node with | ||
| * information used in verification methods by signatures. | ||
| * @returns {string} | ||
| */ | ||
| publicNode({controller = this.controller, owner = this.owner} = {}) { | ||
| const publicNode = { | ||
| id: this.id, | ||
| type: this.type, | ||
| }; | ||
| if(controller) { | ||
| publicNode.controller = controller; | ||
| } | ||
| if(owner) { | ||
| publicNode.owner = owner; | ||
| } | ||
| this.addEncodedPublicKey(publicNode); // Subclass-specific | ||
| return publicNode; | ||
| fingerprint() { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| // publicKeyPem, publicKeyJwk, publicKeyHex, publicKeyBase64, publicKeyBase58 | ||
| /** | ||
| * Exports the publicNode with an encrypted private key attached. | ||
| * @example | ||
| * > const withPrivateKey = await edKeyPair.export(); | ||
| * Verifies that a given key fingerprint matches the public key material | ||
| * belonging to this key pair. | ||
| * | ||
| * @returns {KeyPairOptions} A public node with encrypted private key. | ||
| * @see [KeyPairOptions]{@link ./index.md#KeyPairOptions} | ||
| * @param {string} fingerprint - Public key fingerprint. | ||
| * | ||
| * @returns {{verified: boolean}} | ||
| */ | ||
| async export() { | ||
| const keyNode = this.publicNode(); | ||
| return this.addEncryptedPrivateKey(keyNode); // Subclass-specific | ||
| verifyFingerprint(/* {fingerprint} */) { | ||
| throw new Error('Abstract method, must be implemented in subclass.'); | ||
| } | ||
| } | ||
| module.exports = LDKeyPair; | ||
| module.exports = { | ||
| LDKeyPair | ||
| }; |
+27
-52
| { | ||
| "name": "crypto-ld", | ||
| "version": "3.8.0", | ||
| "description": "A library for managing cryptographic keys using Linked Data.", | ||
| "version": "4.0.0", | ||
| "description": "A Javascript library for generating and performing common operations on Linked Data cryptographic key pairs.", | ||
| "homepage": "https://github.com/digitalbazaar/crypto-ld", | ||
@@ -24,8 +24,3 @@ "author": { | ||
| ], | ||
| "dependencies": { | ||
| "base64url-universal": "^1.0.1", | ||
| "bs58": "^4.0.1", | ||
| "node-forge": "~0.9.0", | ||
| "semver": "^6.2.0" | ||
| }, | ||
| "dependencies": {}, | ||
| "optionalDependencies": { | ||
@@ -35,35 +30,25 @@ "sodium-native": "^3.2.0" | ||
| "devDependencies": { | ||
| "@babel/core": "^7.2.2", | ||
| "@babel/plugin-proposal-object-rest-spread": "^7.3.1", | ||
| "@babel/plugin-transform-modules-commonjs": "^7.2.0", | ||
| "@babel/plugin-transform-runtime": "^7.2.0", | ||
| "@babel/preset-env": "^7.3.1", | ||
| "@babel/runtime": "^7.3.1", | ||
| "babel-loader": "^8.0.5", | ||
| "benchmark": "^2.1.4", | ||
| "chai": "^4.1.2", | ||
| "core-js": "^2.6.3", | ||
| "cross-env": "^5.1.3", | ||
| "eslint": "^6.8.0", | ||
| "eslint-config-digitalbazaar": "^2.0.0", | ||
| "jsdoc-to-markdown": "^4.0.1", | ||
| "karma": "^4.0.1", | ||
| "karma-babel-preprocessor": "^8.0.0", | ||
| "karma-chrome-launcher": "^2.2.0", | ||
| "karma-edge-launcher": "^0.4.2", | ||
| "karma-firefox-launcher": "^1.1.0", | ||
| "karma-ie-launcher": "^1.0.0", | ||
| "karma-mocha": "^1.3.0", | ||
| "@babel/core": "^7.10.2", | ||
| "@babel/plugin-transform-modules-commonjs": "^7.10.1", | ||
| "@babel/plugin-transform-runtime": "^7.10.1", | ||
| "@babel/preset-env": "^7.10.2", | ||
| "@babel/runtime": "^7.10.2", | ||
| "babel-loader": "^8.1.0", | ||
| "chai": "^4.2.0", | ||
| "cross-env": "^7.0.2", | ||
| "eslint": "^7.2.0", | ||
| "eslint-config-digitalbazaar": "^2.5.0", | ||
| "eslint-plugin-jsdoc": "^27.0.4", | ||
| "karma": "^5.0.9", | ||
| "karma-babel-preprocessor": "^8.0.1", | ||
| "karma-chai": "^0.1.0", | ||
| "karma-chrome-launcher": "^3.1.0", | ||
| "karma-mocha": "^2.0.1", | ||
| "karma-mocha-reporter": "^2.2.5", | ||
| "karma-safari-launcher": "^1.0.0", | ||
| "karma-sourcemap-loader": "^0.3.7", | ||
| "karma-tap-reporter": "0.0.6", | ||
| "karma-webpack": "^3.0.5", | ||
| "mocha": "^6.0.0", | ||
| "karma-webpack": "^4.0.2", | ||
| "mocha": "^7.2.0", | ||
| "mocha-lcov-reporter": "^1.3.0", | ||
| "multibase": "^0.6.0", | ||
| "multicodec": "^0.5.0", | ||
| "multihashes": "^0.4.14", | ||
| "nyc": "^15.0.0", | ||
| "webpack": "^4.29.0" | ||
| "nyc": "^15.1.0", | ||
| "webpack": "^4.43.0" | ||
| }, | ||
@@ -79,11 +64,2 @@ "nyc": { | ||
| }, | ||
| "browser": { | ||
| "bs58": false, | ||
| "crypto": false, | ||
| "sodium-native": false, | ||
| "util": false, | ||
| "semver": false, | ||
| "./lib/ed25519PublicKeyNode12.js": false, | ||
| "./lib/ed25519PrivateKeyNode12.js": false | ||
| }, | ||
| "engines": { | ||
@@ -100,5 +76,5 @@ "node": ">=8.3.0" | ||
| "scripts": { | ||
| "test": "npm run test-node", | ||
| "test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 30000 -A -R ${REPORTER:-spec} tests/*.spec.js", | ||
| "test-karma": "karma start", | ||
| "test": "npm run lint && npm run test-node && npm run test-karma", | ||
| "test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 30000 -A -R ${REPORTER:-spec} tests/**/*.spec.js", | ||
| "test-karma": "karma start tests/karma.conf.js", | ||
| "benchmark": "node benchmark/benchmark.js", | ||
@@ -108,5 +84,4 @@ "coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text-summary npm run test-node", | ||
| "coverage-report": "nyc report", | ||
| "lint": "eslint lib tests", | ||
| "generate-docs": "node docs/generate.js" | ||
| "lint": "eslint lib tests" | ||
| } | ||
| } |
+133
-81
@@ -1,6 +0,6 @@ | ||
| # Crypto LD (Linked Data) _(crypto-ld)_ | ||
| # Cryptographic Key Pair Library for Linked Data _(crypto-ld)_ | ||
| [](https://travis-ci.org/digitalbazaar/crypto-ld) | ||
| [](https://github.com/digitalbazaar/crypto-ld/actions?query=workflow%3A%22Node.js+CI%22) | ||
| > A Javascript library for cryptographic operations using Linked Data | ||
| > A Javascript library for generating and performing common operations on Linked Data cryptographic key pairs. | ||
@@ -13,3 +13,2 @@ ## Table of Contents | ||
| - [Usage](#usage) | ||
| - [API](#api-documentation) | ||
| - [Contribute](#contribute) | ||
@@ -21,58 +20,40 @@ - [Commercial Support](#commercial-support) | ||
| See also (related specs): | ||
| ### Supported Key Types | ||
| * [Linked Data Proofs 1.0](https://w3c-dvcg.github.io/ld-proofs/) | ||
| * [Linked Data Cryptographic Suite Registry](https://w3c-ccg.github.io/ld-cryptosuite-registry/) | ||
| This library provides general Linked Data cryptographic key generation | ||
| functionality, but does not support any individual key type by default. | ||
| As a developer, in order to use this library, you will need to make the | ||
| following decisions, constrained by your use case: | ||
| To use it, you must [install individual driver libraries](#usage) for each | ||
| cryptographic key type. The following libraries are currently supported. | ||
| 1. [Which key type](#choosing-key-type) and suite to use? | ||
| 2. What IDs will you give your keys? We recommend the following pattern: | ||
| `<did or url>#<key fingerprint>`. (See Exporting Key Pair section below | ||
| for an example of this.) | ||
| 3. (Not required, but highly recommended) What is your [Private Key Storage](#private-key-storage) | ||
| strategy? (KMS, file system, secure wallet) | ||
| | Type | Crypto Suite | Library | Usage | | ||
| |-------------|--------------|---------|-------| | ||
| | `Ed25519` | [Ed25519VerificationKey2018](https://w3c-ccg.github.io/ld-cryptosuite-registry/#ed25519) | [`ed25519-verification-key-2018`](https://github.com/digitalbazaar/ed25519-verification-key-2018) | Signatures, VCs, zCaps, DIDAuth | | ||
| | `Secp256k1` | [EcdsaSecp256k1VerificationKey2019](https://w3c-ccg.github.io/ld-cryptosuite-registry/#secp256k1) | [`ecdsa-secp256k1-verification-key-2019`](https://github.com/digitalbazaar/ecdsa-secp256k1-verification-key-2019) | Signatures, VCs, zCaps, DIDAuth, HD Wallets | | ||
| | `RSA` | [RsaVerificationKey2018](https://w3c-ccg.github.io/ld-cryptosuite-registry/#rsasignature2018) | [`rsa-verification-key-2018`](https://github.com/digitalbazaar/rsa-verification-key-2018) | Signatures, VCs | | ||
| | `X25519/Curve25519` | X25519KeyAgreementKey2019 | [`x25519-key-agreement-key-2019`](https://github.com/digitalbazaar/x25519-key-agreement-key-2019) | [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) key agreement, JWE/CWE encryption with [`minimal-cipher`](https://github.com/digitalbazaar/minimal-cipher) | | ||
| ### Supported Key Types | ||
| See also (related specs): | ||
| This library supports the following key types (used primarily for the purpose | ||
| of digital signatures): | ||
| * [Linked Data Cryptographic Suite Registry](https://w3c-ccg.github.io/ld-cryptosuite-registry/) | ||
| * [Linked Data Proofs 1.0](https://w3c-ccg.github.io/ld-proofs/) | ||
| * [Ed25519](https://w3c-ccg.github.io/ld-cryptosuite-registry/#ed25519) | ||
| * [RSA](https://w3c-ccg.github.io/ld-cryptosuite-registry/#rsa) | ||
| #### Choosing a Key Type | ||
| These key pairs can be used for general purpose digital signatures using the | ||
| For digital signatures using the | ||
| [`jsonld-signatures`](https://github.com/digitalbazaar/jsonld-signatures), | ||
| signing of Verifiable Credentials using [`vc-js`](https://github.com/digitalbazaar/vc-js), | ||
| and other purposes. | ||
| authorization capabilities, and DIDAuth operations: | ||
| Additional key types are available (using the same API as this library) at the | ||
| following repos: | ||
| * Prefer **Ed25519VerificationKey2018** type keys, by default. | ||
| * Use **EcdsaSepc256k1** keys if your use case requires it (for example, if | ||
| you're developing for a Bitcoin-based or Ethereum-based ledger), or if you | ||
| require Hierarchical Deterministic (HD) wallet functionality. | ||
| * Only use RSA keys when interfacing with systems that require them. | ||
| For key agreement protocols for encryption operations: | ||
| * [EcdsaSecp256k1](https://w3c-dvcg.github.io/lds-ecdsa-secp256k1-2019/) at | ||
| [`secp256k1-key-pair`](https://github.com/digitalbazaar/secp256k1-key-pair/) | ||
| * Curve25519 at [`x25519-key-pair`](https://github.com/digitalbazaar/x25519-key-pair) | ||
| (for use with [`minimal-cipher`](https://github.com/digitalbazaar/minimal-cipher)) | ||
| * Use **Curve25519** with the [`minimal-cipher`](https://github.com/digitalbazaar/minimal-cipher) | ||
| library. | ||
| #### Choosing Key Type | ||
| TODO: Add design considerations for choosing key types / cryptographic | ||
| algorithms for various purposes. For now: | ||
| * Use **Ed25519** keys if you can | ||
| * Use **EcdsaSepc256k1** keys if you must (for example, if you're developing for | ||
| a Bitcoin-based or Ethereum-based ledger) | ||
| * You _can_ use RSA keys to sign, if your use case requires it. | ||
| * Use **Curve25519** for key agreement protocols. | ||
| #### Private Key Storage | ||
| Where to store the private keys? | ||
| TODO: Add a brief discussion of where to store the private keys. Point to | ||
| several recommended Wallet or KMS libraries. | ||
| Use `await keyPair.export()` | ||
| ## Security | ||
@@ -85,4 +66,3 @@ | ||
| - Node.js 8.3+ required. | ||
| - Node.js 10.12.0+ is highly recommended due to RSA key generation speed. | ||
| - Node.js 10.12.0+ is required. | ||
@@ -99,68 +79,140 @@ To install locally (for development): | ||
| ### Generating a new key pair | ||
| ### Installing Support for Key Types | ||
| Ed25519: | ||
| In order to use this library, you will need to import and install driver | ||
| libraries for key types you'll be working with via the `use()` method. | ||
| To use the library with one or more supported suites: | ||
| ```js | ||
| const {Ed25519KeyPair} = require('crypto-ld'); | ||
| import {Ed25519VerificationKey2018} from 'ed25519-verification-key-2018'; | ||
| import {RsaVerificationKey2018} from 'rsa-verification-key-2018'; | ||
| import {EcdsaSecp256k1VerificationKey2019} from 'ecdsa-secp256k1-verification-key-2019'; | ||
| import {X25519KeyAgreementKey2019} from 'x25519-key-agreement-key-2019'; | ||
| const keyPair = await Ed25519KeyPair.generate(); | ||
| import {CryptoLD} from 'crypto-ld'; | ||
| const cryptoLd = new CryptoLD(); | ||
| cryptoLd.use(Ed25519VerificationKey2018); | ||
| cryptoLd.use(RsaVerificationKey2018); | ||
| cryptoLd.use(EcdsaSecp256k1VerificationKey2019); | ||
| cryptoLd.use(X25519KeyAgreementKey2019); | ||
| const edKeyPair = await cryptoLd.generate({type: 'Ed25519VerificationKey2018'}); | ||
| const rsaKeyPair = await cryptoLd.generate({type: 'RsaVerificationKey2018'}); | ||
| ``` | ||
| RSA: | ||
| ### Generating a new public/private key pair | ||
| To generate a new public/private key pair: `cryptoLd.generate(options)`: | ||
| * `{string} [type]` Suite name, required. | ||
| * `{string} [controller]` Optional controller URI or DID to initialize the | ||
| generated key. (This will also init the key id.) | ||
| * `{string} [seed]` Optional deterministic seed value (only supported by some | ||
| key types, such as `ed25519`) from which to generate the key. | ||
| ### Importing a key pair from storage | ||
| To create an instance of a public/private key pair from data imported from | ||
| storage, use `cryptoLd.from()`: | ||
| ```js | ||
| const {RSAKeyPair} = require('crypto-ld'); | ||
| const serializedKeyPair = { ... }; | ||
| const keyPair = await RSAKeyPair.generate(); | ||
| const keyPair = await cryptoLd.from(serializedKeyPair); | ||
| ``` | ||
| ### Exporting a public/private key pair | ||
| Note that only installed key types are supported, if you try to create a | ||
| key pair via `from()` for an unsupported type, an error will be thrown. | ||
| ### Common individual key pair operations | ||
| The full range of operations will depend on key type. Here are some common | ||
| operations supported by all key types. | ||
| #### Exporting the public key only | ||
| To export just the public key of a pair - use `export()`: | ||
| ```js | ||
| const edKeyPair = await Ed25519KeyPair.generate(); | ||
| edKeyPair.id = 'did:ex:123#' + edKeyPair.fingerprint(); | ||
| console.log(await edKeyPair.export()) | ||
| /* -> | ||
| await keyPair.export({publicKey: true}); | ||
| // -> | ||
| { | ||
| id: 'did:ex:123#z6MkumafR1duPR5FZgbVu8nzX3VyhULoXNpq9rpjhfaiMQmx', | ||
| controller: 'did:ex:123', | ||
| type: 'Ed25519VerificationKey2018', | ||
| publicKeyBase58: 'GKKcpmPU3sanTBkoDZq9fwwysu4x7VaUTquosPchSBza', | ||
| privateKeyBase58: | ||
| '3cEzNVGdLoujfhWXqrbo1FgYy9GHA5GXYvB4KixHVuQoRbWbHTJP7XTkj6LqXeiFhw79v85E4wjPQc8WcdyzntcA' | ||
| publicKeyBase58: 'GKKcpmPU3sanTBkoDZq9fwwysu4x7VaUTquosPchSBza' | ||
| } | ||
| */ | ||
| ``` | ||
| #### Exporting the full public-private key pair | ||
| To export the full key pair, including private key (warning: this should be a | ||
| carefully considered operation, best left to dedicated Key Management Systems): | ||
| ```js | ||
| await keyPair.export({publicKey: true, privateKey: true}); | ||
| // -> | ||
| { | ||
| id: 'did:ex:123#z6Mks8wJbzhWdmkQZgw7z2qHwaxPVnFsFmEZSXzGkLkvhMvL', | ||
| controller: 'did:ex:123', | ||
| type: 'Ed25519VerificationKey2018', | ||
| publicKeyBase58: 'DggG1kT5JEFwTC6RJTsT6VQPgCz1qszCkX5Lv4nun98x', | ||
| privateKeyBase58: 'sSicNq6YBSzafzYDAcuduRmdHtnrZRJ7CbvjzdQhC45ewwvQeuqbM2dNwS9RCf6buUJGu6N3rBy6oLSpMwha8tc' | ||
| } | ||
| ``` | ||
| ### Importing a key pair from storage | ||
| #### Generating and verifying key fingerprint | ||
| If you know what type of key you're expecting, use its appropriate class: | ||
| To generate a fingerprint: | ||
| ```js | ||
| const serializedKeyPair = JSON.stringify(await keyPair.export()); | ||
| // later | ||
| const keyPair = await Ed25519KeyPair.from(JSON.parse(serializedKeyPair)); | ||
| keyPair.fingerprint(); | ||
| // -> | ||
| 'z6Mks8wJbzhWdmkQZgw7z2qHwaxPVnFsFmEZSXzGkLkvhMvL' | ||
| ``` | ||
| If you do not know which key type to expect, `LDKeyPair.from()` will route | ||
| based on type: | ||
| To verify a fingerprint: | ||
| ```js | ||
| const {LDKeyPair} = require('crypto-ld'); | ||
| keyPair.verifyFingerprint({ | ||
| fingerprint: 'z6Mks8wJbzhWdmkQZgw7z2qHwaxPVnFsFmEZSXzGkLkvhMvL' | ||
| }); | ||
| // -> | ||
| { valid: true } | ||
| ``` | ||
| // serializedKeyPair contains a serialized Ed25519KeyPair | ||
| const keyPair = await LDKeyPair.from(JSON.parse(serializedKeyPair)); | ||
| ### Operations on signature-related key pairs | ||
| For key pairs that are related to signature and verification (that extend from | ||
| the `LDVerifierKeyPair` class), two additional operations must be supported: | ||
| #### Creating a signer function | ||
| In order to perform a cryptographic signature, you need to create a `sign` | ||
| function, and then invoke it. | ||
| ```js | ||
| const keyPair = cryptoLd.generate({type: 'Ed25519VerificationKey2018'}); | ||
| const {sign} = keyPair.signer(); | ||
| const data = 'test data to sign'; | ||
| const signatureValue = await sign({data}); | ||
| ``` | ||
| ## API Documentation | ||
| #### Creating a verifier function | ||
| See [LD Key Pair Documentation](/docs/LDKeyPair.md) | ||
| In order to verify a cryptographic signature, you need to create a `verify` | ||
| function, and then invoke it (passing it the data to verify, and the signature). | ||
| See [Ed25519 Key Pair Documentation](/docs/Ed25519KeyPair.md) | ||
| ```js | ||
| const keyPair = cryptoLd.generate({type: 'Ed25519VerificationKey2018'}); | ||
| See [RSA Key Pair Documentation](/docs/RSAKeyPair.md) | ||
| const {verify} = keyPair.verifier(); | ||
| See [Type Documentation](/docs/index.md) | ||
| const {valid} = await verify({data, signature}); | ||
| ``` | ||
@@ -167,0 +219,0 @@ ## Contribute |
| /*! | ||
| * Copyright (c) 2018-2019 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const env = require('./env'); | ||
| const forge = require('node-forge'); | ||
| const base64url = require('base64url-universal'); | ||
| const {pki: {ed25519}, util: {binary: {base58}}} = forge; | ||
| const util = require('./util'); | ||
| const LDKeyPair = require('./LDKeyPair'); | ||
| class Ed25519KeyPair extends LDKeyPair { | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * An implementation of | ||
| * [Ed25519 Signature 2018]{@link https://w3c-dvcg.github.io/lds-ed25519-2018/} | ||
| * for | ||
| * [jsonld-signatures.]{@link https://github.com/digitalbazaar/jsonld-signatures} | ||
| * @example | ||
| * > const privateKeyBase58 = | ||
| * '3Mmk4UzTRJTEtxaKk61LxtgUxAa2Dg36jF6VogPtRiKvfpsQWKPCLesKSV182RMmvM' | ||
| * + 'JKk6QErH3wgdHp8itkSSiF'; | ||
| * > const options = { | ||
| * publicKeyBase58: 'GycSSui454dpYRKiFdsQ5uaE8Gy3ac6dSMPcAoQsk8yq', | ||
| * privateKeyBase58 | ||
| * }; | ||
| * > const EDKey = new Ed25519KeyPair(options); | ||
| * > EDKey | ||
| * Ed25519KeyPair { ... | ||
| * @param {KeyPairOptions} options - Base58 keys plus | ||
| * other options most follow | ||
| * [KeyPairOptions]{@link ./index.md#KeyPairOptions}. | ||
| * @param {string} options.publicKeyBase58 - Base58 encoded Public Key | ||
| * unencoded is 32-bytes. | ||
| * @param {string} options.privateKeyBase58 - Base58 Private Key | ||
| * unencoded is 64-bytes. | ||
| */ | ||
| /* eslint-enable */ | ||
| constructor(options = {}) { | ||
| super(options); | ||
| this.type = 'Ed25519VerificationKey2018'; | ||
| this.privateKeyBase58 = options.privateKeyBase58; | ||
| this.publicKeyBase58 = options.publicKeyBase58; | ||
| } | ||
| /** | ||
| * Returns the Base58 encoded public key. | ||
| * @implements {LDKeyPair#publicKey} | ||
| * @readonly | ||
| * | ||
| * @returns {string} The Base58 encoded public key. | ||
| * @see [publicKey]{@link ./LDKeyPair.md#publicKey} | ||
| */ | ||
| get publicKey() { | ||
| return this.publicKeyBase58; | ||
| } | ||
| /** | ||
| * Returns the Base58 encoded private key. | ||
| * @implements {LDKeyPair#privateKey} | ||
| * @readonly | ||
| * | ||
| * @returns {string} The Base58 encoded private key. | ||
| * @see [privateKey]{@link ./LDKeyPair.md#privateKey} | ||
| */ | ||
| get privateKey() { | ||
| return this.privateKeyBase58; | ||
| } | ||
| /** | ||
| * Generates a KeyPair with an optional deterministic seed. | ||
| * @example | ||
| * > const keyPair = await Ed25519KeyPair.generate(); | ||
| * > keyPair | ||
| * Ed25519KeyPair { ... | ||
| * @param {KeyPairOptions} [options={}] - See LDKeyPair | ||
| * docstring for full list. | ||
| * @param {Uint8Array|Buffer} [options.seed] - | ||
| * a 32-byte array seed for a deterministic key. | ||
| * | ||
| * @returns {Promise<Ed25519KeyPair>} Generates a key pair. | ||
| */ | ||
| static async generate(options = {}) { | ||
| if(env.nodejs && require('semver').gte(process.version, '12.0.0')) { | ||
| const bs58 = require('bs58'); | ||
| const { | ||
| asn1, ed25519: {privateKeyFromAsn1, publicKeyFromAsn1}, | ||
| util: {ByteBuffer} | ||
| } = forge; | ||
| const {promisify} = require('util'); | ||
| const {createPublicKey, generateKeyPair} = require('crypto'); | ||
| const publicKeyEncoding = {format: 'der', type: 'spki'}; | ||
| const privateKeyEncoding = {format: 'der', type: 'pkcs8'}; | ||
| // if no seed provided, generate a random key | ||
| if(!('seed' in options)) { | ||
| const generateKeyPairAsync = promisify(generateKeyPair); | ||
| const {publicKey, privateKey} = await generateKeyPairAsync('ed25519', { | ||
| publicKeyEncoding, privateKeyEncoding | ||
| }); | ||
| const publicKeyBytes = publicKeyFromAsn1( | ||
| asn1.fromDer(new ByteBuffer(publicKey))); | ||
| const {privateKeyBytes} = privateKeyFromAsn1( | ||
| asn1.fromDer(new ByteBuffer(privateKey))); | ||
| return new Ed25519KeyPair({ | ||
| publicKeyBase58: bs58.encode(publicKeyBytes), | ||
| // private key is the 32 byte private key + 32 byte public key | ||
| privateKeyBase58: bs58.encode(Buffer.concat( | ||
| [privateKeyBytes, publicKeyBytes])), | ||
| ...options | ||
| }); | ||
| } | ||
| // create a key from the provided seed | ||
| const {seed} = options; | ||
| let seedBytes; | ||
| if(seed instanceof Uint8Array || Buffer.isBuffer(seed)) { | ||
| seedBytes = Buffer.from(seed); | ||
| } | ||
| if(!(Buffer.isBuffer(seedBytes) && seedBytes.length === 32)) { | ||
| throw new TypeError('`seed` must be a 32 byte Buffer or Uint8Array.'); | ||
| } | ||
| const _privateKey = require('./ed25519PrivateKeyNode12'); | ||
| // create a node private key | ||
| const privateKey = _privateKey.create({seedBytes}); | ||
| // create a node public key from the private key | ||
| const publicKey = createPublicKey(privateKey); | ||
| // export the keys and extract key bytes from the exported DERs | ||
| const publicKeyBytes = publicKeyFromAsn1( | ||
| asn1.fromDer(new ByteBuffer(publicKey.export(publicKeyEncoding)))); | ||
| const {privateKeyBytes} = privateKeyFromAsn1( | ||
| asn1.fromDer(new ByteBuffer(privateKey.export(privateKeyEncoding)))); | ||
| return new Ed25519KeyPair({ | ||
| publicKeyBase58: bs58.encode(publicKeyBytes), | ||
| // private key is the 32 byte private key + 32 byte public key | ||
| privateKeyBase58: bs58.encode(Buffer.concat( | ||
| [privateKeyBytes, publicKeyBytes])), | ||
| ...options | ||
| }); | ||
| } | ||
| if(env.nodejs) { | ||
| // TODO: use native node crypto api once it's available | ||
| const sodium = require('sodium-native'); | ||
| const bs58 = require('bs58'); | ||
| const publicKey = new Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES); | ||
| const privateKey = new Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES); | ||
| if('seed' in options) { | ||
| sodium.crypto_sign_seed_keypair(publicKey, privateKey, options.seed); | ||
| } else { | ||
| sodium.crypto_sign_keypair(publicKey, privateKey); | ||
| } | ||
| return new Ed25519KeyPair({ | ||
| publicKeyBase58: bs58.encode(publicKey), | ||
| privateKeyBase58: bs58.encode(privateKey), | ||
| ...options | ||
| }); | ||
| } | ||
| const generateOptions = {}; | ||
| if('seed' in options) { | ||
| generateOptions.seed = options.seed; | ||
| } | ||
| const {publicKey, privateKey} = ed25519.generateKeyPair(generateOptions); | ||
| return new Ed25519KeyPair({ | ||
| publicKeyBase58: base58.encode(publicKey), | ||
| privateKeyBase58: base58.encode(privateKey), | ||
| ...options | ||
| }); | ||
| } | ||
| /** | ||
| * Creates an Ed25519 Key Pair from an existing private key. | ||
| * @example | ||
| * > const options = { | ||
| * privateKeyBase58: privateKey | ||
| * }; | ||
| * > const key = await Ed25519KeyPair.from(options); | ||
| * > key | ||
| * Ed25519KeyPair { ... | ||
| * @param {Object} options - Contains a private key. | ||
| * @param {Object} [options.privateKey] - A private key object. | ||
| * @param {string} [options.privateKeyBase58] - A Base58 | ||
| * Private key string. | ||
| * | ||
| * @returns {Ed25519KeyPair} An Ed25519 Key Pair. | ||
| */ | ||
| static async from(options) { | ||
| const privateKeyBase58 = options.privateKeyBase58 || | ||
| // legacy privateDidDoc format | ||
| (options.privateKey && options.privateKey.privateKeyBase58); | ||
| const keyPair = new Ed25519KeyPair({ | ||
| privateKeyBase58, | ||
| type: options.type || options.keyType, // Todo: deprecate keyType usage | ||
| ...options | ||
| }); | ||
| return keyPair; | ||
| } | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * Returns a signer object for use with | ||
| * [jsonld-signatures]{@link https://github.com/digitalbazaar/jsonld-signatures}. | ||
| * @example | ||
| * > const signer = keyPair.signer(); | ||
| * > signer | ||
| * { sign: [AsyncFunction: sign] } | ||
| * > signer.sign({data}); | ||
| * | ||
| * @returns {{sign: Function}} A signer for the json-ld block. | ||
| */ | ||
| /* eslint-enable */ | ||
| signer() { | ||
| return ed25519SignerFactory(this); | ||
| } | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * Returns a verifier object for use with | ||
| * [jsonld-signatures]{@link https://github.com/digitalbazaar/jsonld-signatures}. | ||
| * @example | ||
| * > const verifier = keyPair.verifier(); | ||
| * > verifier | ||
| * { verify: [AsyncFunction: verify] } | ||
| * > verifier.verify(key); | ||
| * | ||
| * @returns {{verify: Function}} Used to verify jsonld-signatures. | ||
| */ | ||
| /* eslint-enable */ | ||
| verifier() { | ||
| return ed25519VerifierFactory(this); | ||
| } | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * Adds a public key base to a public key node. | ||
| * @example | ||
| * > keyPair.addEncodedPublicKey({}); | ||
| * { publicKeyBase58: 'GycSSui454dpYRKiFdsQ5uaE8Gy3ac6dSMPcAoQsk8yq' } | ||
| * @param {Object} publicKeyNode - The public key node in a jsonld-signature. | ||
| * @param {string} publicKeyNode.publicKeyBase58 - Base58 Public Key for | ||
| * [jsonld-signatures]{@link https://github.com/digitalbazaar/jsonld-signatures}. | ||
| * | ||
| * @returns {{verify: Function}} A PublicKeyNode in a block. | ||
| */ | ||
| /* eslint-enable */ | ||
| addEncodedPublicKey(publicKeyNode) { | ||
| publicKeyNode.publicKeyBase58 = this.publicKeyBase58; | ||
| return publicKeyNode; | ||
| } | ||
| /** | ||
| * Adds an encrypted private key to the KeyPair. | ||
| * @param {Object} keyNode - A plain object. | ||
| * | ||
| * @return {Object} The keyNode with an encrypted private key attached. | ||
| */ | ||
| async addEncryptedPrivateKey(keyNode) { | ||
| if(this.passphrase !== null) { | ||
| keyNode.privateKeyJwe = await this.encrypt( | ||
| {privateKeyBase58: this.privateKeyBase58}, | ||
| this.passphrase | ||
| ); | ||
| } else { | ||
| // no passphrase, do not encrypt private key | ||
| keyNode.privateKeyBase58 = this.privateKeyBase58; | ||
| } | ||
| return keyNode; | ||
| } | ||
| /** | ||
| * Produces a 32-byte encrypted key. | ||
| * @example | ||
| * > const encryptedContent = await edKeyPair | ||
| * .encrypt(privateKey, 'Test1244!'); | ||
| * @param {string} privateKey - The base58 private key. | ||
| * @param {string} password - The password. | ||
| * | ||
| * @returns {Promise<JWE>} Produces JSON Web encrypted content. | ||
| * @see [JWE]{@link ./index.md#JWE} | ||
| */ | ||
| async encrypt(privateKey, password) { | ||
| const keySize = 32; | ||
| const salt = forge.random.getBytesSync(32); | ||
| const iterations = 4096; | ||
| const key = await LDKeyPair.pbkdf2(password, salt, iterations, keySize); | ||
| const jweHeader = { | ||
| alg: 'PBES2-A128GCMKW', | ||
| enc: 'A128GCMKW', | ||
| jwk: { | ||
| kty: 'PBKDF2', | ||
| s: base64url.encode(salt), | ||
| c: iterations | ||
| } | ||
| }; | ||
| // FIXME: this probably needs to be cleaned up/made more standard | ||
| const iv = forge.random.getBytesSync(12); | ||
| const cipher = forge.cipher.createCipher('AES-GCM', key); | ||
| cipher.start({iv}); | ||
| cipher.update(forge.util.createBuffer(JSON.stringify(privateKey))); | ||
| cipher.finish(); | ||
| const encrypted = cipher.output.getBytes(); | ||
| const tag = cipher.mode.tag.getBytes(); | ||
| const jwe = { | ||
| unprotected: jweHeader, | ||
| iv: base64url.encode(iv), | ||
| ciphertext: base64url.encode(encrypted), | ||
| tag: base64url.encode(tag) | ||
| }; | ||
| return jwe; | ||
| } | ||
| /** | ||
| * Decrypts jwe content to a privateKey. | ||
| * @param {JWE} jwe - Encrypted content from a block. | ||
| * @param {string} password - Password for the key used to sign the content. | ||
| * | ||
| * @returns {Object} A Base58 private key. | ||
| * @see [JWE]{@link ./index.md#JWE} | ||
| */ | ||
| async decrypt(jwe, password) { | ||
| // FIXME: check header, implement according to JWE standard | ||
| const keySize = 32; | ||
| const {c: iterations} = jwe.unprotected.jwk; | ||
| let {s: salt} = jwe.unprotected.jwk; | ||
| salt = base64url.encode(salt); | ||
| const key = await LDKeyPair.pbkdf2(password, salt, iterations, keySize); | ||
| const iv = base64url.encode(jwe.iv); | ||
| const tag = base64url.encode(jwe.tag); | ||
| const decipher = forge.cipher.createDecipher('AES-GCM', key); | ||
| decipher.start({iv, tag}); | ||
| decipher.update(base64url.encode(jwe.ciphertext)); | ||
| const pass = decipher.finish(); | ||
| if(!pass) { | ||
| throw new Error('Invalid password.'); | ||
| } | ||
| const privateKey = JSON.parse(decipher.output.getBytes()); | ||
| return privateKey; | ||
| } | ||
| /** | ||
| * Generates and returns a multiformats encoded | ||
| * ed25519 public key fingerprint (for use with cryptonyms, for example). | ||
| * @see https://github.com/multiformats/multicodec | ||
| * | ||
| * @param {string} publicKeyBase58 - The base58 encoded public key material. | ||
| * | ||
| * @returns {string} The fingerprint. | ||
| */ | ||
| static fingerprintFromPublicKey({publicKeyBase58}) { | ||
| // ed25519 cryptonyms are multicodec encoded values, specifically: | ||
| // (multicodec ed25519-pub 0xed01 + key bytes) | ||
| const pubkeyBytes = util.base58Decode({ | ||
| decode: base58.decode, | ||
| keyMaterial: publicKeyBase58, | ||
| type: 'public' | ||
| }); | ||
| const buffer = new Uint8Array(2 + pubkeyBytes.length); | ||
| buffer[0] = 0xed; | ||
| buffer[1] = 0x01; | ||
| buffer.set(pubkeyBytes, 2); | ||
| // prefix with `z` to indicate multi-base base58btc encoding | ||
| return `z${base58.encode(buffer)}`; | ||
| } | ||
| /** | ||
| * Generates and returns a multiformats encoded | ||
| * ed25519 public key fingerprint (for use with cryptonyms, for example). | ||
| * @see https://github.com/multiformats/multicodec | ||
| * | ||
| * @returns {string} The fingerprint. | ||
| */ | ||
| fingerprint() { | ||
| const {publicKeyBase58} = this; | ||
| return Ed25519KeyPair.fingerprintFromPublicKey({publicKeyBase58}); | ||
| } | ||
| /** | ||
| * Tests whether the fingerprint was | ||
| * generated from a given key pair. | ||
| * @example | ||
| * > edKeyPair.verifyFingerprint('z2S2Q6MkaFJewa'); | ||
| * {valid: true}; | ||
| * @param {string} fingerprint - A Base58 public key. | ||
| * | ||
| * @returns {Object} An object indicating valid is true or false. | ||
| */ | ||
| verifyFingerprint(fingerprint) { | ||
| // fingerprint should have `z` prefix indicating | ||
| // that it's multi-base encoded | ||
| if(!(typeof fingerprint === 'string' && fingerprint[0] === 'z')) { | ||
| return { | ||
| error: new Error('`fingerprint` must be a multibase encoded string.'), | ||
| valid: false | ||
| }; | ||
| } | ||
| let fingerprintBuffer; | ||
| try { | ||
| fingerprintBuffer = util.base58Decode({ | ||
| decode: base58.decode, | ||
| keyMaterial: fingerprint.slice(1), | ||
| type: `fingerprint's` | ||
| }); | ||
| } catch(e) { | ||
| return {error: e, valid: false}; | ||
| } | ||
| let publicKeyBuffer; | ||
| try { | ||
| publicKeyBuffer = util.base58Decode({ | ||
| decode: base58.decode, | ||
| keyMaterial: this.publicKeyBase58, | ||
| type: 'public' | ||
| }); | ||
| } catch(e) { | ||
| return {error: e, valid: false}; | ||
| } | ||
| // validate the first two multicodec bytes 0xed01 | ||
| const valid = fingerprintBuffer.slice(0, 2).toString('hex') === 'ed01' && | ||
| publicKeyBuffer.equals(fingerprintBuffer.slice(2)); | ||
| if(!valid) { | ||
| return { | ||
| error: new Error('The fingerprint does not match the public key.'), | ||
| valid: false | ||
| }; | ||
| } | ||
| return {valid}; | ||
| } | ||
| } | ||
| /** | ||
| * @ignore | ||
| * Returns an object with an async sign function. | ||
| * The sign function is bound to the KeyPair | ||
| * and then returned by the KeyPair's signer method. | ||
| * @param {Ed25519KeyPair} key - An ED25519KeyPair. | ||
| * @example | ||
| * > const mySigner = ed25519SignerFactory(edKeyPair); | ||
| * > await mySigner.sign({data}) | ||
| * | ||
| * @returns {{sign: Function}} An object with an async function sign | ||
| * using the private key passed in. | ||
| */ | ||
| function ed25519SignerFactory(key) { | ||
| if(!key.privateKeyBase58) { | ||
| return { | ||
| async sign() { | ||
| throw new Error('No private key to sign with.'); | ||
| } | ||
| }; | ||
| } | ||
| if(env.nodejs && require('semver').gte(process.version, '12.0.0')) { | ||
| const bs58 = require('bs58'); | ||
| const privateKeyBytes = util.base58Decode({ | ||
| decode: bs58.decode, | ||
| keyMaterial: key.privateKeyBase58, | ||
| type: 'private' | ||
| }); | ||
| const _privateKey = require('./ed25519PrivateKeyNode12'); | ||
| // create a Node private key | ||
| const privateKey = _privateKey.create({privateKeyBytes}); | ||
| const {sign} = require('crypto'); | ||
| return { | ||
| async sign({data}) { | ||
| const signature = sign( | ||
| null, Buffer.from(data.buffer, data.byteOffset, data.length), | ||
| privateKey); | ||
| return signature; | ||
| } | ||
| }; | ||
| } | ||
| if(env.nodejs) { | ||
| const sodium = require('sodium-native'); | ||
| const bs58 = require('bs58'); | ||
| const privateKey = util.base58Decode({ | ||
| decode: bs58.decode, | ||
| keyMaterial: key.privateKeyBase58, | ||
| type: 'private' | ||
| }); | ||
| return { | ||
| async sign({data}) { | ||
| const signature = Buffer.alloc(sodium.crypto_sign_BYTES); | ||
| await sodium.crypto_sign_detached( | ||
| signature, | ||
| Buffer.from(data.buffer, data.byteOffset, data.length), | ||
| privateKey); | ||
| return signature; | ||
| } | ||
| }; | ||
| } | ||
| // browser implementation | ||
| const privateKey = util.base58Decode({ | ||
| decode: base58.decode, | ||
| keyMaterial: key.privateKeyBase58, | ||
| type: 'private' | ||
| }); | ||
| return { | ||
| async sign({data}) { | ||
| return ed25519.sign({message: data, privateKey}); | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * @ignore | ||
| * Returns an object with an async verify function. | ||
| * The verify function is bound to the KeyPair | ||
| * and then returned by the KeyPair's verifier method. | ||
| * @param {Ed25519KeyPair} key - An Ed25519KeyPair. | ||
| * @example | ||
| * > const myVerifier = ed25519Verifier(edKeyPair); | ||
| * > await myVerifier.verify({data, signature}); | ||
| * | ||
| * @returns {{verify: Function}} An async verifier specific | ||
| * to the key passed in. | ||
| */ | ||
| function ed25519VerifierFactory(key) { | ||
| if(env.nodejs && require('semver').gte(process.version, '12.0.0')) { | ||
| const bs58 = require('bs58'); | ||
| const publicKeyBytes = util.base58Decode({ | ||
| decode: bs58.decode, | ||
| keyMaterial: key.publicKeyBase58, | ||
| type: 'public' | ||
| }); | ||
| const _publicKey = require('./ed25519PublicKeyNode12'); | ||
| // create a Node public key | ||
| const publicKey = _publicKey.create({publicKeyBytes}); | ||
| const {verify} = require('crypto'); | ||
| return { | ||
| async verify({data, signature}) { | ||
| return verify( | ||
| null, Buffer.from(data.buffer, data.byteOffset, data.length), | ||
| publicKey, signature); | ||
| } | ||
| }; | ||
| } | ||
| if(env.nodejs) { | ||
| const sodium = require('sodium-native'); | ||
| const bs58 = require('bs58'); | ||
| const publicKey = util.base58Decode({ | ||
| decode: bs58.decode, | ||
| keyMaterial: key.publicKeyBase58, | ||
| type: 'public' | ||
| }); | ||
| return { | ||
| async verify({data, signature}) { | ||
| return sodium.crypto_sign_verify_detached( | ||
| Buffer.from(signature.buffer, signature.byteOffset, signature.length), | ||
| Buffer.from(data.buffer, data.byteOffset, data.length), | ||
| publicKey); | ||
| } | ||
| }; | ||
| } | ||
| // browser implementation | ||
| const publicKey = util.base58Decode({ | ||
| decode: base58.decode, | ||
| keyMaterial: key.publicKeyBase58, | ||
| type: 'public' | ||
| }); | ||
| return { | ||
| async verify({data, signature}) { | ||
| return ed25519.verify({message: data, signature, publicKey}); | ||
| } | ||
| }; | ||
| } | ||
| module.exports = Ed25519KeyPair; |
| /*! | ||
| * Copyright (c) 2019 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const {createPrivateKey} = require('crypto'); | ||
| const {privateKeyDerEncode} = require('./util'); | ||
| exports.create = ({privateKeyBytes, seedBytes}) => createPrivateKey({ | ||
| key: privateKeyDerEncode({privateKeyBytes, seedBytes}), | ||
| format: 'der', | ||
| type: 'pkcs8' | ||
| }); |
| /*! | ||
| * Copyright (c) 2019 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const {createPublicKey} = require('crypto'); | ||
| const {publicKeyDerEncode} = require('./util'); | ||
| exports.create = ({publicKeyBytes}) => createPublicKey({ | ||
| key: publicKeyDerEncode({publicKeyBytes}), | ||
| format: 'der', | ||
| type: 'spki' | ||
| }); |
-14
| /*! | ||
| * Copyright (c) 2018-2019 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const nodejs = ( | ||
| typeof process !== 'undefined' && process.versions && process.versions.node); | ||
| const browser = !nodejs && | ||
| (typeof window !== 'undefined' || typeof self !== 'undefined'); | ||
| module.exports = { | ||
| nodejs, | ||
| browser | ||
| }; |
| /*! | ||
| * Copyright (c) 2018-2019 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const env = require('./env'); | ||
| const forge = require('node-forge'); | ||
| const { | ||
| md: {sha256}, | ||
| pki: {getPublicKeyFingerprint, publicKeyFromPem}, | ||
| util: {binary: {base58, raw}} | ||
| } = forge; | ||
| const LDKeyPair = require('./LDKeyPair'); | ||
| /** | ||
| * @constant | ||
| * @type {number} | ||
| * @default | ||
| */ | ||
| const DEFAULT_RSA_KEY_BITS = 2048; | ||
| /** | ||
| * @constant | ||
| * @type {number} | ||
| * @default | ||
| */ | ||
| const DEFAULT_RSA_EXPONENT = 0x10001; | ||
| class RSAKeyPair extends LDKeyPair { | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * An implementation of | ||
| * [RSA encryption]{@link https://simple.wikipedia.org/wiki/RSA_algorithm} | ||
| * for | ||
| * [jsonld-signatures]{@link https://github.com/digitalbazaar/jsonld-signatures}. | ||
| * @example | ||
| * > const options = { | ||
| * privateKeyPem: 'testPrivateKey', | ||
| * publicKeyPem: 'testPublicKey' | ||
| * }; | ||
| * > const RSAKey = new RSAKeyPair(options); | ||
| * @param {KeyPairOptions} options - Keys must be in RSA format other | ||
| * options must follow [KeyPairOptions]{@link ./index.md#KeyPairOptions}. | ||
| * @param {string} options.publicKeyPem - Public Key for Signatures. | ||
| * @param {string} options.privateKeyPem - Your Confidential key for signing. | ||
| */ | ||
| /* eslint-enable */ | ||
| constructor(options = {}) { | ||
| super(options); | ||
| this.type = 'RsaVerificationKey2018'; | ||
| this.privateKeyPem = options.privateKeyPem; | ||
| this.publicKeyPem = options.publicKeyPem; | ||
| this.validateKeyParams(); // validate keyBits and exponent | ||
| } | ||
| /** | ||
| * Returns the public key. | ||
| * @implements {LDKeyPair#publicKey} | ||
| * @readonly | ||
| * | ||
| * @returns {string} The public key. | ||
| * @see [publicKey]{@link ./LDKeyPair.md#publicKey} | ||
| */ | ||
| get publicKey() { | ||
| return this.publicKeyPem; | ||
| } | ||
| /** | ||
| * Returns the private key. | ||
| * @implements {LDKeyPair#privateKey} | ||
| * @readonly | ||
| * | ||
| * @returns {string} The private key. | ||
| * @see [privateKey]{@link ./LDKeyPair.md#privateKey} | ||
| */ | ||
| get privateKey() { | ||
| return this.privateKeyPem; | ||
| } | ||
| /** | ||
| * Generates an RSA KeyPair using the RSA Defaults. | ||
| * @example | ||
| * > const keyPair = await RSAKeyPair.generate(); | ||
| * > keyPair | ||
| * RSAKeyPair { ... | ||
| * @param {KeyPairOptions} [options={}] - See LDKeyPair | ||
| * docstring for full list. | ||
| * | ||
| * @returns {Promise<RSAKeyPair>} A Default encrypted RSA KeyPair. | ||
| * @see [KeyPairOptions]{@link ./index.md#KeyPairOptions} | ||
| */ | ||
| static async generate(options = {}) { | ||
| // forge will use a native implementation in nodejs >= 10.12.0 | ||
| // and a purejs implementation in browser and nodejs < 10.12.0 | ||
| return new Promise((resolve, reject) => { | ||
| forge.pki.rsa.generateKeyPair({ | ||
| bits: DEFAULT_RSA_KEY_BITS, | ||
| e: DEFAULT_RSA_EXPONENT, | ||
| workers: -1 | ||
| }, (err, keyPair) => { | ||
| if(err) { | ||
| return reject(err); | ||
| } | ||
| resolve(new RSAKeyPair({ | ||
| publicKeyPem: forge.pki.publicKeyToPem(keyPair.publicKey), | ||
| privateKeyPem: forge.pki.privateKeyToPem(keyPair.privateKey), | ||
| ...options | ||
| })); | ||
| }); | ||
| }); | ||
| } | ||
| /** | ||
| * Creates a RSA Key Pair from an existing private key. | ||
| * @example | ||
| * > const options = { | ||
| * privateKeyPem: 'testkeypem' | ||
| * }; | ||
| * > const key = await RSAKeyPair.from(options); | ||
| * @param {Object} options - Contains a private key. | ||
| * @param {Object} [options.privateKey] - A private key. | ||
| * @param {string} [options.privateKeyPem] - An RSA Private key. | ||
| * | ||
| * @returns {RSAKeyPair} An RSA Key Pair. | ||
| */ | ||
| static async from(options) { | ||
| const privateKeyPem = options.privateKeyPem || | ||
| // legacy privateDidDoc format | ||
| (options.privateKeyPem && options.privateKey.privateKeyPem); | ||
| const keys = new RSAKeyPair({ | ||
| publicKey: options.publicKeyPem, | ||
| privateKeyPem, | ||
| type: options.type || options.keyType, // todo: deprecate keyType usage | ||
| ...options | ||
| }); | ||
| return keys; | ||
| } | ||
| /** | ||
| * Validates this key. | ||
| * @example | ||
| * > rsaKeyPair.validateKeyParams(); | ||
| * undefined | ||
| * | ||
| * @returns {undefined} If it does not throw then the key is valid. | ||
| * @throws Invalid RSA keyBit length | ||
| * @throws Invalid RSA exponent | ||
| */ | ||
| validateKeyParams() { | ||
| if(this.publicKeyPem) { | ||
| const publicKey = forge.pki.publicKeyFromPem(this.publicKeyPem); | ||
| const keyBits = publicKey.n.bitLength(); | ||
| if(keyBits !== DEFAULT_RSA_KEY_BITS) { | ||
| throw new Error(`Invalid RSA keyBit length ${JSON.stringify(keyBits)}` + | ||
| ` required value is ${DEFAULT_RSA_KEY_BITS}`); | ||
| } | ||
| if(publicKey.e.toString(10) !== '65537') { | ||
| throw new Error( | ||
| `Invalid RSA exponent ${JSON.stringify(publicKey.e.toString(10))}` + | ||
| ' required value is 65537}'); | ||
| } | ||
| } | ||
| if(this.privateKeyPem) { | ||
| const privateKey = forge.pki.privateKeyFromPem(this.privateKeyPem); | ||
| const keyBits = privateKey.n.bitLength(); | ||
| if(keyBits !== DEFAULT_RSA_KEY_BITS) { | ||
| throw new Error(`Invalid RSA keyBit length ${JSON.stringify(keyBits)}` + | ||
| ` required value is ${DEFAULT_RSA_KEY_BITS}`); | ||
| } | ||
| if(privateKey.e.toString(10) !== '65537') { | ||
| throw new Error( | ||
| `Invalid RSA exponent ${JSON.stringify(privateKey.e.toString(10))}` + | ||
| ' required value is 65537}'); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Adds this KeyPair's publicKeyPem to a public node. | ||
| * @example | ||
| * > rsaKeyPair.addEncodedPublicKey({id: 'testnode'}); | ||
| * { publicKeyPem: 'testPublicKey' } | ||
| * @param {KeyPairOptions} publicKeyNode - A Node with out a publicKeyPem set. | ||
| * | ||
| * @returns {KeyPairOptions} A public node with a publicKeyPem set. | ||
| * @see [KeyPairOptions]{@link ./index.md#KeyPairOptions} | ||
| */ | ||
| addEncodedPublicKey(publicKeyNode) { | ||
| publicKeyNode.publicKeyPem = this.publicKeyPem; | ||
| return publicKeyNode; | ||
| } | ||
| /** | ||
| * Adds this KeyPair's privateKeyPem to a public node. | ||
| * @example | ||
| * > rsaKeyPair.addEncryptedPrivateKey({id: 'testnode'}); | ||
| * { privateKeyPem: 'testPrivateKey' } | ||
| * @param {KeyPairOptions} keyNode - A Node with out a publicKeyPem set. | ||
| * | ||
| * @returns {KeyPairOptions} A public node with a privateKeyPem set. | ||
| * @see [KeyPairOptions]{@link ./index.md#KeyPairOptions} | ||
| */ | ||
| async addEncryptedPrivateKey(keyNode) { | ||
| if(this.passphrase !== null) { | ||
| keyNode.privateKeyPem = forge.pki.encryptRsaPrivateKey( | ||
| forge.pki.privateKeyFromPem(this.privateKeyPem), | ||
| this.passphrase, | ||
| {algorithm: 'aes256'} | ||
| ); | ||
| } else { | ||
| // no passphrase, do not encrypt private key | ||
| keyNode.privateKeyPem = this.privateKeyPem; | ||
| } | ||
| return keyNode; | ||
| } | ||
| /** | ||
| * Generates and returns a multiformats | ||
| * encoded RSA public key fingerprint (for use with cryptonyms, for example). | ||
| * @example | ||
| * > rsaKeyPair.fingerprint(); | ||
| * 3423dfdsf3432sdfdsds | ||
| * | ||
| * @returns {string} An RSA fingerprint. | ||
| */ | ||
| fingerprint() { | ||
| const buffer = forge.util.createBuffer(); | ||
| // use SubjectPublicKeyInfo fingerprint | ||
| const fingerprintBuffer = forge.pki.getPublicKeyFingerprint( | ||
| forge.pki.publicKeyFromPem(this.publicKeyPem), | ||
| {md: sha256.create()}); | ||
| // RSA cryptonyms are multiformats encoded values, specifically they are: | ||
| // (multicodec RSA SPKI-based public key 0x5d + sha2-256 0x12 + | ||
| // 32 byte value 0x20) | ||
| buffer.putBytes(forge.util.hexToBytes('5d1220')); | ||
| buffer.putBytes(fingerprintBuffer.bytes()); | ||
| // prefix with `z` to indicate multi-base base58btc encoding | ||
| return `z${base58.encode(buffer)}`; | ||
| } | ||
| /* | ||
| * Tests whether the fingerprint | ||
| * was generated from a given key pair. | ||
| * @example | ||
| * > rsaKeyPair.verifyFingerprint('zdsfdsfsdfdsfsd34234'); | ||
| * {valid: true} | ||
| * @param {string} fingerprint - An RSA fingerprint for a key. | ||
| * | ||
| * @returns {boolean} True if the fingerprint is verified. | ||
| */ | ||
| verifyFingerprint(fingerprint) { | ||
| // fingerprint should have `z` prefix indicating | ||
| // that it's multi-base encoded | ||
| if(!(typeof fingerprint === 'string' && fingerprint[0] === 'z')) { | ||
| return { | ||
| error: new Error('`fingerprint` must be a multibase encoded string.'), | ||
| valid: false | ||
| }; | ||
| } | ||
| // base58.decode returns Buffer(nodejs) or Uint8Array | ||
| const fingerprintBuffer = base58.decode(fingerprint.slice(1)); | ||
| // keyFingerprintBuffer is a forge ByteStringBuffer | ||
| const keyFingerprintBuffer = getPublicKeyFingerprint( | ||
| publicKeyFromPem(this.publicKeyPem), {md: sha256.create()}); | ||
| // validate the first three multicodec bytes 0x5d1220 | ||
| const valid = fingerprintBuffer.slice(0, 3).toString('hex') === '5d1220' && | ||
| keyFingerprintBuffer.toHex() === | ||
| fingerprintBuffer.slice(3).toString('hex'); | ||
| if(!valid) { | ||
| return { | ||
| error: new Error('The fingerprint does not match the public key.'), | ||
| valid: false | ||
| }; | ||
| } | ||
| return {valid}; | ||
| } | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * Returns a signer object with an async sign function for use by | ||
| * [jsonld-signatures]{@link https://github.com/digitalbazaar/jsonld-signatures} | ||
| * to sign content in a signature. | ||
| * @example | ||
| * > const signer = rsaKeyPair.signer(); | ||
| * > signer.sign({data}); | ||
| * | ||
| * @returns {{sign: Function}} An RSA Signer Function for a single key. | ||
| * for a single Private Key. | ||
| */ | ||
| /* eslint-enable */ | ||
| signer() { | ||
| return rsaSignerFactory(this); | ||
| } | ||
| /* eslint-disable max-len */ | ||
| /** | ||
| * Returns a verifier object with an async | ||
| * function verify for use with | ||
| * [jsonld-signatures]{@link https://github.com/digitalbazaar/jsonld-signatures}. | ||
| * @example | ||
| * > const verifier = rsaKeyPair.verifier(); | ||
| * > const valid = await verifier.verify({data, signature}); | ||
| * | ||
| * @returns {{verify: Function}} An RSA Verifier Function for a single key. | ||
| */ | ||
| /* eslint-enable */ | ||
| verifier() { | ||
| return rsaVerifierFactory(this); | ||
| } | ||
| } | ||
| /** | ||
| * @ignore | ||
| * Returns an object with an async sign function. | ||
| * The sign function is bound to the KeyPair | ||
| * and then returned by the KeyPair's signer method. | ||
| * @example | ||
| * > const factory = rsaSignerFactory(rsaKeyPair); | ||
| * > const bytes = await factory.sign({data}); | ||
| * @param {RSAKeyPair} key - They key this factory will verify for. | ||
| * | ||
| * @returns {{sign: Function}} An RSA Verifier Function for a single key. | ||
| */ | ||
| function rsaSignerFactory(key) { | ||
| if(!key.privateKeyPem) { | ||
| return { | ||
| async sign() { | ||
| throw new Error('No private key to sign with.'); | ||
| } | ||
| }; | ||
| } | ||
| // Note: Per rfc7518, the digest algorithm for PS256 is SHA-256, | ||
| // https://tools.ietf.org/html/rfc7518 | ||
| // sign data using RSASSA-PSS where PSS uses a SHA-256 hash, | ||
| // a SHA-256 based masking function MGF1, and a 32 byte salt to match | ||
| // the hash size | ||
| if(env.nodejs) { | ||
| // node.js 8+ | ||
| const crypto = require('crypto'); | ||
| if('RSA_PKCS1_PSS_PADDING' in crypto.constants) { | ||
| return { | ||
| async sign({data}) { | ||
| const signer = crypto.createSign('RSA-SHA256'); | ||
| signer.update(Buffer.from(data.buffer, data.byteOffset, data.length)); | ||
| const buffer = signer.sign({ | ||
| key: key.privateKeyPem, | ||
| padding: crypto.constants.RSA_PKCS1_PSS_PADDING, | ||
| saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST | ||
| }); | ||
| return new Uint8Array( | ||
| buffer.buffer, buffer.byteOffset, buffer.length); | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| // browser or other environment (including node 6.x) | ||
| const privateKey = forge.pki.privateKeyFromPem(key.privateKeyPem); | ||
| return { | ||
| async sign({data}) { | ||
| const pss = createPss(); | ||
| const md = sha256.create(); | ||
| md.update(raw.encode(data), 'binary'); | ||
| const binaryString = privateKey.sign(md, pss); | ||
| return raw.decode(binaryString); | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * @ignore | ||
| * Returns an object with an async verify function. | ||
| * The verify function is bound to the KeyPair | ||
| * and then returned by the KeyPair's verifier method. | ||
| * @example | ||
| * > const verifier = rsaVerifierFactory(rsaKeyPair); | ||
| * > verifier.verify({data, signature}); | ||
| * false | ||
| * @param {RSAKeyPair} key - An RSAKeyPair. | ||
| * | ||
| * @returns {Function} An RSA Verifier for the key pair passed in. | ||
| */ | ||
| function rsaVerifierFactory(key) { | ||
| if(env.nodejs) { | ||
| // node.js 8+ | ||
| const crypto = require('crypto'); | ||
| if('RSA_PKCS1_PSS_PADDING' in crypto.constants) { | ||
| return { | ||
| async verify({data, signature}) { | ||
| const verifier = crypto.createVerify('RSA-SHA256'); | ||
| verifier.update( | ||
| Buffer.from(data.buffer, data.byteOffset, data.length)); | ||
| return verifier.verify({ | ||
| key: key.publicKeyPem, | ||
| padding: crypto.constants.RSA_PKCS1_PSS_PADDING, | ||
| saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST | ||
| }, Buffer.from( | ||
| signature.buffer, signature.byteOffset, signature.length)); | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| // browser or other environment (including node 6.x) | ||
| const publicKey = publicKeyFromPem(key.publicKeyPem); | ||
| return { | ||
| async verify({data, signature}) { | ||
| const pss = createPss(); | ||
| const md = sha256.create(); | ||
| md.update(raw.encode(data), 'binary'); | ||
| try { | ||
| return publicKey.verify( | ||
| md.digest().bytes(), | ||
| raw.encode(signature), | ||
| pss); | ||
| } catch(e) { | ||
| // simply return false, do return information about malformed signature | ||
| return false; | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * @ignore | ||
| * creates an RSA PSS used in signatures. | ||
| * @example | ||
| * > const pss = createPss(); | ||
| * | ||
| * @returns {PSS} A PSS object. | ||
| * @see [PSS]{@link ./index.md#PSS} | ||
| */ | ||
| function createPss() { | ||
| const md = sha256.create(); | ||
| return forge.pss.create({ | ||
| md, | ||
| mgf: forge.mgf.mgf1.create(sha256.create()), | ||
| saltLength: md.digestLength | ||
| }); | ||
| } | ||
| module.exports = RSAKeyPair; |
-129
| /* | ||
| * Copyright (c) 2018-2019 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| 'use strict'; | ||
| const {asn1, oids, util: {ByteBuffer}} = require('node-forge'); | ||
| /** | ||
| * Wraps Base58 decoding operations in | ||
| * order to provide consistent error messages. | ||
| * @ignore | ||
| * @example | ||
| * > const pubkeyBytes = _base58Decode({ | ||
| * decode: base58.decode, | ||
| * keyMaterial: this.publicKeyBase58, | ||
| * type: 'public' | ||
| * }); | ||
| * @param {Object} options - The decoder options. | ||
| * @param {Function} options.decode - The decode function to use. | ||
| * @param {string} options.keyMaterial - The Base58 encoded | ||
| * key material to decode. | ||
| * @param {string} options.type - A description of the | ||
| * key material that will be included | ||
| * in an error message (e.g. 'public', 'private'). | ||
| * | ||
| * @returns {Object} - The decoded bytes. The data structure for the bytes is | ||
| * determined by the provided decode function. | ||
| */ | ||
| exports.base58Decode = ({decode, keyMaterial, type}) => { | ||
| let bytes; | ||
| try { | ||
| bytes = decode(keyMaterial); | ||
| } catch(e) { | ||
| // do nothing | ||
| // the bs58 implementation throws, forge returns undefined | ||
| // this helper throws when no result is produced | ||
| } | ||
| if(bytes === undefined) { | ||
| throw new TypeError(`The ${type} key material must be Base58 encoded.`); | ||
| } | ||
| return bytes; | ||
| }; | ||
| exports.privateKeyDerEncode = ({privateKeyBytes, seedBytes}) => { | ||
| if(!(privateKeyBytes || seedBytes)) { | ||
| throw new TypeError('`privateKeyBytes` or `seedBytes` is required.'); | ||
| } | ||
| if(!privateKeyBytes && !(Buffer.isBuffer(seedBytes) && | ||
| seedBytes.length === 32)) { | ||
| throw new TypeError('`seedBytes` must be a 32 byte Buffer.'); | ||
| } | ||
| if(!seedBytes && !(Buffer.isBuffer(privateKeyBytes) && | ||
| privateKeyBytes.length === 64)) { | ||
| throw new TypeError('`privateKeyBytes` must be a 64 byte Buffer.'); | ||
| } | ||
| let p; | ||
| if(seedBytes) { | ||
| p = seedBytes; | ||
| } else { | ||
| // extract the first 32 bytes of the 64 byte private key representation | ||
| p = Buffer.from(privateKeyBytes.buffer, privateKeyBytes.byteOffset, 32); | ||
| } | ||
| const keyBuffer = new ByteBuffer(p); | ||
| const asn1Key = asn1.create( | ||
| asn1.UNIVERSAL, | ||
| asn1.Type.OCTETSTRING, | ||
| false, | ||
| keyBuffer.getBytes() | ||
| ); | ||
| const a = asn1.create( | ||
| asn1.Class.UNIVERSAL, | ||
| asn1.Type.SEQUENCE, | ||
| true, [ | ||
| asn1.create( | ||
| asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, | ||
| asn1.integerToDer(0).getBytes()), | ||
| // privateKeyAlgorithm | ||
| asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ | ||
| asn1.create( | ||
| asn1.Class.UNIVERSAL, | ||
| asn1.Type.OID, | ||
| false, | ||
| asn1.oidToDer(oids.EdDSA25519).getBytes() | ||
| ), | ||
| ]), | ||
| // private key | ||
| asn1.create( | ||
| asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, | ||
| asn1.toDer(asn1Key).getBytes()), | ||
| ] | ||
| ); | ||
| const privateKeyDer = asn1.toDer(a); | ||
| return Buffer.from(privateKeyDer.getBytes(), 'binary'); | ||
| }; | ||
| exports.publicKeyDerEncode = ({publicKeyBytes}) => { | ||
| if(!(Buffer.isBuffer(publicKeyBytes) && publicKeyBytes.length === 32)) { | ||
| throw new TypeError('`publicKeyBytes` must be a 32 byte Buffer.'); | ||
| } | ||
| // add a zero byte to the front of the publicKeyBytes, this results in | ||
| // the bitstring being 256 bits vs. 170 bits (without padding) | ||
| const zeroBuffer = Buffer.from(new Uint8Array([0])); | ||
| const keyBuffer = new ByteBuffer(Buffer.concat([zeroBuffer, publicKeyBytes])); | ||
| const a = asn1.create( | ||
| asn1.Class.UNIVERSAL, | ||
| asn1.Type.SEQUENCE, | ||
| true, [ | ||
| asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ | ||
| asn1.create( | ||
| asn1.Class.UNIVERSAL, | ||
| asn1.Type.OID, | ||
| false, | ||
| asn1.oidToDer(oids.EdDSA25519).getBytes() | ||
| ), | ||
| ]), | ||
| // public key | ||
| asn1.create( | ||
| asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, | ||
| keyBuffer.getBytes()), | ||
| ] | ||
| ); | ||
| const publicKeyDer = asn1.toDer(a); | ||
| return Buffer.from(publicKeyDer.getBytes(), 'binary'); | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1
-80%23
-30.3%232
28.89%1
-80%26565
-54.99%8
-33.33%272
-79.91%1
Infinity%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed