Launch Week Day 5: Introducing Reachability for PHP.Learn More
Socket
Book a DemoSign in
Socket

crypto-ld

Package Overview
Dependencies
Maintainers
5
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

crypto-ld - npm Package Compare versions

Comparing version
3.8.0
to
4.0.0
+83
lib/CryptoLD.js
/*!
* 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.
*/
/*!
* 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
};
{
"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)_
[![Build Status](https://travis-ci.org/digitalbazaar/crypto-ld.png?branch=master)](https://travis-ci.org/digitalbazaar/crypto-ld)
[![Node.js CI](https://github.com/digitalbazaar/crypto-ld/workflows/Node.js%20CI/badge.svg)](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'
});
/*!
* 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;
/*
* 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');
};