mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-07-23 10:51:57 +00:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2d647cd98b | ||
|
a8896de3a2 | ||
|
e71fc150c0 | ||
|
967594c9a3 | ||
|
03893a3795 | ||
|
50afa4efbc | ||
|
1c54a9b196 | ||
|
6f2c347895 | ||
|
867982ab92 |
@@ -9,7 +9,6 @@ stages:
|
||||
|
||||
node_js:
|
||||
- '10'
|
||||
- '12'
|
||||
|
||||
os:
|
||||
- linux
|
||||
@@ -23,7 +22,7 @@ jobs:
|
||||
include:
|
||||
- stage: check
|
||||
script:
|
||||
- npx aegir build --bundlesize
|
||||
- npx aegir commitlint --travis
|
||||
- npx aegir dep-check
|
||||
- npm run lint
|
||||
|
||||
|
59
CHANGELOG.md
59
CHANGELOG.md
@@ -1,64 +1,15 @@
|
||||
<a name="0.17.2"></a>
|
||||
## [0.17.2](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.1...v0.17.2) (2020-01-17)
|
||||
<a name="0.16.3"></a>
|
||||
## [0.16.3](https://github.com/libp2p/js-libp2p-crypto/compare/v0.16.2...v0.16.3) (2019-10-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add typescript types + linting/tests ([#161](https://github.com/libp2p/js-libp2p-crypto/issues/161)) ([e01977c](https://github.com/libp2p/js-libp2p-crypto/commit/e01977c))
|
||||
* backport enc/dec ([#160](https://github.com/libp2p/js-libp2p-crypto/issues/160)) ([e71fc15](https://github.com/libp2p/js-libp2p-crypto/commit/e71fc15))
|
||||
|
||||
|
||||
|
||||
<a name="0.17.1"></a>
|
||||
## [0.17.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.0...v0.17.1) (2019-10-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* better error for missing web crypto ([a5e0560](https://github.com/libp2p/js-libp2p-crypto/commit/a5e0560))
|
||||
* browser rsa enc/dec ([b8e2414](https://github.com/libp2p/js-libp2p-crypto/commit/b8e2414))
|
||||
* jwk var naming ([8b8d0c1](https://github.com/libp2p/js-libp2p-crypto/commit/8b8d0c1))
|
||||
* lint ([2c294b5](https://github.com/libp2p/js-libp2p-crypto/commit/2c294b5))
|
||||
* padding error ([2c1bac5](https://github.com/libp2p/js-libp2p-crypto/commit/2c1bac5))
|
||||
* use direct buffers instead of converting to hex ([027a5a9](https://github.com/libp2p/js-libp2p-crypto/commit/027a5a9))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add (rsa)pubKey.encrypt and (rsa)privKey.decrypt ([34c5f5c](https://github.com/libp2p/js-libp2p-crypto/commit/34c5f5c))
|
||||
* browser enc/dec ([9f747a1](https://github.com/libp2p/js-libp2p-crypto/commit/9f747a1))
|
||||
* use forge to convert jwk2forge ([b998f63](https://github.com/libp2p/js-libp2p-crypto/commit/b998f63))
|
||||
|
||||
|
||||
|
||||
<a name="0.17.0"></a>
|
||||
# [0.17.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.16.1...v0.17.0) (2019-07-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update to ursa-optiona@0.10 ([26b6217](https://github.com/libp2p/js-libp2p-crypto/commit/26b6217))
|
||||
* fix links in README ([#148](https://github.com/libp2p/js-libp2p-crypto/issues/148)) ([5cd0e8c](https://github.com/libp2p/js-libp2p-crypto/commit/5cd0e8c))
|
||||
* put optional args last for key export ([#154](https://github.com/libp2p/js-libp2p-crypto/issues/154)) ([d675670](https://github.com/libp2p/js-libp2p-crypto/commit/d675670))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* refactor to use async/await ([#131](https://github.com/libp2p/js-libp2p-crypto/issues/131)) ([ad71072](https://github.com/libp2p/js-libp2p-crypto/commit/ad71072))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* key export arguments are now swapped so that the optional format is last
|
||||
* API refactored to use async/await
|
||||
|
||||
feat: WIP use async await
|
||||
fix: passing tests
|
||||
chore: update travis node.js versions
|
||||
fix: skip ursa optional tests on windows
|
||||
fix: benchmarks
|
||||
docs: update docs
|
||||
fix: remove broken and intested private key decrypt
|
||||
chore: update deps
|
||||
<a name="0.16.2"></a>
|
||||
## [0.16.2](https://github.com/libp2p/js-libp2p-crypto/compare/v0.16.1...v0.16.2) (2019-09-25)
|
||||
|
||||
|
||||
|
||||
|
215
README.md
215
README.md
@@ -3,11 +3,13 @@
|
||||
[](http://protocol.ai)
|
||||
[](http://libp2p.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
[](https://discuss.libp2p.io)
|
||||
[](https://codecov.io/gh/libp2p/js-libp2p-crypto)
|
||||
[](https://travis-ci.com/libp2p/js-libp2p-crypto)
|
||||
[](https://coveralls.io/github/libp2p/js-libp2p-crypto?branch=master)
|
||||
[](https://travis-ci.org/libp2p/js-libp2p-crypto)
|
||||
[](https://circleci.com/gh/libp2p/js-libp2p-crypto)
|
||||
[](https://david-dm.org/libp2p/js-libp2p-crypto)
|
||||
[](https://github.com/feross/standard)
|
||||

|
||||

|
||||
|
||||
> Crypto primitives for libp2p in JavaScript
|
||||
|
||||
@@ -15,35 +17,31 @@ This repo contains the JavaScript implementation of the crypto primitives needed
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Jacob Heun](https://github.com/jacobheun/)
|
||||
[Friedel Ziegelmayer](https://github.com/dignifiedquire/)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [js-libp2p-crypto](#js-libp2p-crypto)
|
||||
- [Lead Maintainer](#Lead-Maintainer)
|
||||
- [Table of Contents](#Table-of-Contents)
|
||||
- [Install](#Install)
|
||||
- [API](#API)
|
||||
- [`crypto.aes`](#cryptoaes)
|
||||
- [`crypto.aes.create(key, iv)`](#cryptoaescreatekey-iv)
|
||||
- [`decrypt(data)`](#decryptdata)
|
||||
- [`encrypt(data)`](#encryptdata)
|
||||
- [`crypto.hmac`](#cryptohmac)
|
||||
- [`crypto.hmac.create(hash, secret)`](#cryptohmaccreatehash-secret)
|
||||
- [`digest(data)`](#digestdata)
|
||||
- [`crypto.keys`](#cryptokeys)
|
||||
- [`crypto.keys.generateKeyPair(type, bits)`](#cryptokeysgenerateKeyPairtype-bits)
|
||||
- [`crypto.keys.generateEphemeralKeyPair(curve)`](#cryptokeysgenerateEphemeralKeyPaircurve)
|
||||
- [`crypto.keys.keyStretcher(cipherType, hashType, secret)`](#cryptokeyskeyStretchercipherType-hashType-secret)
|
||||
- [`crypto.keys.marshalPublicKey(key, [type])`](#cryptokeysmarshalPublicKeykey-type)
|
||||
- [`crypto.keys.unmarshalPublicKey(buf)`](#cryptokeysunmarshalPublicKeybuf)
|
||||
- [`crypto.keys.marshalPrivateKey(key, [type])`](#cryptokeysmarshalPrivateKeykey-type)
|
||||
- [`crypto.keys.unmarshalPrivateKey(buf)`](#cryptokeysunmarshalPrivateKeybuf)
|
||||
- [`crypto.keys.import(pem, password)`](#cryptokeysimportpem-password)
|
||||
- [`crypto.randomBytes(number)`](#cryptorandomBytesnumber)
|
||||
- [`crypto.pbkdf2(password, salt, iterations, keySize, hash)`](#cryptopbkdf2password-salt-iterations-keySize-hash)
|
||||
- [Contribute](#Contribute)
|
||||
- [License](#License)
|
||||
- [Install](#install)
|
||||
- [API](#api)
|
||||
- [`crypto.hmac`](#hmac)
|
||||
- [`create(hash, secret, callback)`](#createhash-secret-callback)
|
||||
- [`digest(data, callback)`](#digestdata-callback)
|
||||
- [`crypto.aes`](#aes)
|
||||
- [`create(key, iv, callback)`](#createkey-iv-callback)
|
||||
- [`encrypt(data, callback)`](#encryptdata-callback)
|
||||
- [`decrypt(data, callback)`](#decryptdata-callback)
|
||||
- [`keys`](#keys)
|
||||
- [`generateKeyPair(type, bits, callback)`](#generatekeypairtype-bits-callback)
|
||||
- [`generateEphemeralKeyPair(curve, callback)`](#generateephemeralkeypaircurve-callback)
|
||||
- [`keyStretcher(cipherType, hashType, secret, callback)`](#keystretcherciphertype-hashtype-secret-callback)
|
||||
- [`marshalPublicKey(key[, type], callback)`](#marshalpublickeykey-type-callback)
|
||||
- [`unmarshalPublicKey(buf)`](#unmarshalpublickeybuf)
|
||||
- [`marshalPrivateKey(key[, type])`](#marshalprivatekeykey-type)
|
||||
- [`unmarshalPrivateKey(buf, callback)`](#unmarshalprivatekeybuf-callback)
|
||||
- [`import(pem, password, callback)`](#importpem-password-callback)
|
||||
- [`webcrypto`](#webcrypto)
|
||||
- [Contribute](#contribute)
|
||||
- [License](#license)
|
||||
|
||||
## Install
|
||||
|
||||
@@ -51,150 +49,138 @@ This repo contains the JavaScript implementation of the crypto primitives needed
|
||||
npm install --save libp2p-crypto
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const crypto = require('libp2p-crypto')
|
||||
|
||||
// Now available to you:
|
||||
//
|
||||
// crypto.aes
|
||||
// crypto.hmac
|
||||
// crypto.keys
|
||||
// etc.
|
||||
//
|
||||
// See full API details below...
|
||||
```
|
||||
|
||||
### Web Crypto API
|
||||
|
||||
The `libp2p-crypto` library depends on the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) in the browser. Web Crypto is available in all modern browsers, however browsers restrict its usage to [Secure Contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
|
||||
|
||||
**This means you will not be able to use some `libp2p-crypto` functions in the browser when the page is served over HTTP.** To enable the Web Crypto API and allow `libp2p-crypto` to work fully, please serve your page over HTTPS.
|
||||
|
||||
## API
|
||||
|
||||
### `crypto.aes`
|
||||
|
||||
Exposes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
|
||||
Expoes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
|
||||
|
||||
This uses `CTR` mode.
|
||||
|
||||
#### `crypto.aes.create(key, iv)`
|
||||
#### `crypto.aes.create(key, iv, callback)`
|
||||
|
||||
- `key: Buffer` The key, if length `16` then `AES 128` is used. For length `32`, `AES 256` is used.
|
||||
- `iv: Buffer` Must have length `16`.
|
||||
- `callback: Function`
|
||||
|
||||
Returns `Promise<{decrypt<Function>, encrypt<Function>}>`
|
||||
|
||||
##### `decrypt(data)`
|
||||
##### `decrypt(data, callback)`
|
||||
|
||||
- `data: Buffer`
|
||||
- `callback: Function`
|
||||
|
||||
Returns `Promise<Buffer>`
|
||||
|
||||
##### `encrypt(data)`
|
||||
##### `encrypt(data, callback)`
|
||||
|
||||
- `data: Buffer`
|
||||
|
||||
Returns `Promise<Buffer>`
|
||||
- `callback: Function`
|
||||
|
||||
```js
|
||||
const crypto = require('libp2p-crypto')
|
||||
var crypto = require('libp2p-crypto')
|
||||
|
||||
// Setting up Key and IV
|
||||
|
||||
// A 16 bytes array, 128 Bits, AES-128 is chosen
|
||||
const key128 = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
||||
var key128 = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
||||
|
||||
// A 16 bytes array, 128 Bits,
|
||||
const IV = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
||||
var IV = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
||||
|
||||
async function main () {
|
||||
const decryptedMessage = 'Hello, world!'
|
||||
let decryptedMessage = 'Hello, world!'
|
||||
let encryptedMessage
|
||||
|
||||
// Encrypting
|
||||
const cipher = await crypto.aes.create(key128, IV)
|
||||
const encryptedBuffer = await cipher.encrypt(Buffer.from(decryptedMessage))
|
||||
console.log(encryptedBuffer)
|
||||
// prints: <Buffer 42 f1 67 d9 2e 42 d0 32 9e b1 f8 3c>
|
||||
await crypto.aes.create(key128, IV, (err, cipher) => {
|
||||
if (!err) {
|
||||
cipher.encrypt(Buffer.from(decryptedMessage), (err, encryptedBuffer) => {
|
||||
if (!err) {
|
||||
console.log(encryptedBuffer)
|
||||
// prints: <Buffer 42 f1 67 d9 2e 42 d0 32 9e b1 f8 3c>
|
||||
encryptedMessage = encryptedBuffer
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Decrypting
|
||||
const decipher = await crypto.aes.create(key128, IV)
|
||||
const decryptedBuffer = await cipher.decrypt(encryptedBuffer)
|
||||
await crypto.aes.create(key128, IV, (err, cipher) => {
|
||||
if (!err) {
|
||||
cipher.decrypt(encryptedMessage, (err, decryptedBuffer) => {
|
||||
if (!err) {
|
||||
console.log(decryptedBuffer)
|
||||
// prints: <Buffer 42 f1 67 d9 2e 42 d0 32 9e b1 f8 3c>
|
||||
|
||||
console.log(decryptedBuffer)
|
||||
// prints: <Buffer 42 f1 67 d9 2e 42 d0 32 9e b1 f8 3c>
|
||||
|
||||
console.log(decryptedBuffer.toString('utf-8'))
|
||||
// prints: Hello, world!
|
||||
console.log(decryptedBuffer.toString('utf-8'))
|
||||
// prints: Hello, world!
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
```
|
||||
|
||||
### `crypto.hmac`
|
||||
|
||||
Exposes an interface to the Keyed-Hash Message Authentication Code (HMAC) as defined in U.S. Federal Information Processing Standards Publication 198. An HMAC is a cryptographic hash that uses a key to sign a message. The receiver verifies the hash by recomputing it using the same key.
|
||||
|
||||
#### `crypto.hmac.create(hash, secret)`
|
||||
#### `crypto.hmac.create(hash, secret, callback)`
|
||||
|
||||
- `hash: String`
|
||||
- `secret: Buffer`
|
||||
- `callback: Function`
|
||||
|
||||
Returns `Promise<{digest<Function>}>`
|
||||
|
||||
##### `digest(data)`
|
||||
##### `digest(data, callback)`
|
||||
|
||||
- `data: Buffer`
|
||||
|
||||
Returns `Promise<Buffer>`
|
||||
- `callback: Function`
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
const crypto = require('libp2p-crypto')
|
||||
var crypto = require('libp2p-crypto')
|
||||
|
||||
async function main () {
|
||||
const hash = 'SHA1' // 'SHA256' || 'SHA512'
|
||||
const hmac = await crypto.hmac.create(hash, Buffer.from('secret'))
|
||||
const sig = await hmac.digest(Buffer.from('hello world'))
|
||||
console.log(sig)
|
||||
}
|
||||
let hash = 'SHA1' // 'SHA256' || 'SHA512'
|
||||
|
||||
main()
|
||||
crypto.hmac.create(hash, Buffer.from('secret'), (err, hmac) => {
|
||||
if (!err) {
|
||||
hmac.digest(Buffer.from('hello world'), (err, sig) => {
|
||||
if (!err) {
|
||||
console.log(sig)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `crypto.keys`
|
||||
|
||||
**Supported Key Types**
|
||||
|
||||
The [`generateKeyPair`](#generatekeypairtype-bits), [`marshalPublicKey`](#marshalpublickeykey-type), and [`marshalPrivateKey`](#marshalprivatekeykey-type) functions accept a string `type` argument.
|
||||
The [`generateKeyPair`](#generatekeypairtype-bits-callback), [`marshalPublicKey`](#marshalpublickeykey-type-callback), and [`marshalPrivateKey`](#marshalprivatekeykey-type) functions accept a string `type` argument.
|
||||
|
||||
Currently the `'RSA'` and `'ed25519'` types are supported, although ed25519 keys support only signing and verification of messages. For encryption / decryption support, RSA keys should be used.
|
||||
|
||||
Installing the [libp2p-crypto-secp256k1](https://github.com/libp2p/js-libp2p-crypto-secp256k1) module adds support for the `'secp256k1'` type, which supports ECDSA signatures using the secp256k1 elliptic curve popularized by Bitcoin. This module is not installed by default, and should be explicitly depended on if your project requires secp256k1 support.
|
||||
|
||||
### `crypto.keys.generateKeyPair(type, bits)`
|
||||
### `crypto.keys.generateKeyPair(type, bits, callback)`
|
||||
|
||||
- `type: String`, see [Supported Key Types](#supported-key-types) above.
|
||||
- `bits: Number` Minimum of 1024
|
||||
|
||||
Returns `Promise<{privateKey<Buffer>, publicKey<Buffer>}>`
|
||||
- `callback: Function`
|
||||
|
||||
Generates a keypair of the given type and bitsize.
|
||||
|
||||
### `crypto.keys.generateEphemeralKeyPair(curve)`
|
||||
### `crypto.keys.generateEphemeralKeyPair(curve, callback)`
|
||||
|
||||
- `curve: String`, one of `'P-256'`, `'P-384'`, `'P-521'` is currently supported
|
||||
|
||||
Returns `Promise`
|
||||
- `callback: Function`
|
||||
|
||||
Generates an ephemeral public key and returns a function that will compute the shared secret key.
|
||||
|
||||
Focuses only on ECDH now, but can be made more general in the future.
|
||||
|
||||
Resolves to an object of the form:
|
||||
Calls back with an object of the form
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -203,17 +189,16 @@ Resolves to an object of the form:
|
||||
}
|
||||
```
|
||||
|
||||
### `crypto.keys.keyStretcher(cipherType, hashType, secret)`
|
||||
### `crypto.keys.keyStretcher(cipherType, hashType, secret, callback)`
|
||||
|
||||
- `cipherType: String`, one of `'AES-128'`, `'AES-256'`, `'Blowfish'`
|
||||
- `hashType: String`, one of `'SHA1'`, `SHA256`, `SHA512`
|
||||
- `secret: Buffer`
|
||||
|
||||
Returns `Promise`
|
||||
- `callback: Function`
|
||||
|
||||
Generates a set of keys for each party by stretching the shared key.
|
||||
|
||||
Resolves to an object of the form:
|
||||
Calls back with an object of the form:
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -230,12 +215,10 @@ Resolves to an object of the form:
|
||||
}
|
||||
```
|
||||
|
||||
### `crypto.keys.marshalPublicKey(key, [type])`
|
||||
### `crypto.keys.marshalPublicKey(key[, type], callback)`
|
||||
|
||||
- `key: keys.rsa.RsaPublicKey | keys.ed25519.Ed25519PublicKey | require('libp2p-crypto-secp256k1').Secp256k1PublicKey`
|
||||
- `type: String`, see [Supported Key Types](#supported-key-types) above. Defaults to 'rsa'.
|
||||
|
||||
Returns `Buffer`
|
||||
- `type: String`, see [Supported Key Types](#supported-key-types) above.
|
||||
|
||||
Converts a public key object into a protobuf serialized public key.
|
||||
|
||||
@@ -243,33 +226,27 @@ Converts a public key object into a protobuf serialized public key.
|
||||
|
||||
- `buf: Buffer`
|
||||
|
||||
Returns `RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey`
|
||||
Converts a protobuf serialized public key into its representative object.
|
||||
|
||||
Converts a protobuf serialized public key into its representative object.
|
||||
|
||||
### `crypto.keys.marshalPrivateKey(key, [type])`
|
||||
### `crypto.keys.marshalPrivateKey(key[, type])`
|
||||
|
||||
- `key: keys.rsa.RsaPrivateKey | keys.ed25519.Ed25519PrivateKey | require('libp2p-crypto-secp256k1').Secp256k1PrivateKey`
|
||||
- `type: String`, see [Supported Key Types](#supported-key-types) above.
|
||||
|
||||
Returns `Buffer`
|
||||
|
||||
Converts a private key object into a protobuf serialized private key.
|
||||
|
||||
### `crypto.keys.unmarshalPrivateKey(buf)`
|
||||
### `crypto.keys.unmarshalPrivateKey(buf, callback)`
|
||||
|
||||
- `buf: Buffer`
|
||||
|
||||
Returns `Promise<RsaPrivateKey|Ed25519PrivateKey|Secp256k1PrivateKey>`
|
||||
- `callback: Function`
|
||||
|
||||
Converts a protobuf serialized private key into its representative object.
|
||||
|
||||
### `crypto.keys.import(pem, password)`
|
||||
### `crypto.keys.import(pem, password, callback)`
|
||||
|
||||
- `pem: string`
|
||||
- `password: string`
|
||||
|
||||
Returns `Promise<RsaPrivateKey>`
|
||||
- `callback: Function`
|
||||
|
||||
Converts a PEM password protected private key into its representative object.
|
||||
|
||||
@@ -277,8 +254,6 @@ Converts a PEM password protected private key into its representative object.
|
||||
|
||||
- `number: Number`
|
||||
|
||||
Returns `Buffer`
|
||||
|
||||
Generates a Buffer with length `number` populated by random bytes.
|
||||
|
||||
### `crypto.pbkdf2(password, salt, iterations, keySize, hash)`
|
||||
|
@@ -10,11 +10,16 @@ const secrets = []
|
||||
const curves = ['P-256', 'P-384', 'P-521']
|
||||
|
||||
curves.forEach((curve) => {
|
||||
suite.add(`ephemeral key with secrect ${curve}`, async (d) => {
|
||||
const res = await crypto.keys.generateEphemeralKeyPair('P-256')
|
||||
const secret = await res.genSharedKey(res.key)
|
||||
secrets.push(secret)
|
||||
d.resolve()
|
||||
suite.add(`ephemeral key with secrect ${curve}`, (d) => {
|
||||
crypto.keys.generateEphemeralKeyPair('P-256', (err, res) => {
|
||||
if (err) { throw err }
|
||||
res.genSharedKey(res.key, (err, secret) => {
|
||||
if (err) { throw err }
|
||||
secrets.push(secret)
|
||||
|
||||
d.resolve()
|
||||
})
|
||||
})
|
||||
}, { defer: true })
|
||||
})
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
'use strict'
|
||||
|
||||
const Benchmark = require('benchmark')
|
||||
const async = require('async')
|
||||
|
||||
const crypto = require('../src')
|
||||
|
||||
@@ -12,9 +13,11 @@ const keys = []
|
||||
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
|
||||
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
|
||||
;(async () => {
|
||||
const res = await crypto.keys.generateEphemeralKeyPair('P-256')
|
||||
const secret = await res.genSharedKey(res.key)
|
||||
async.waterfall([
|
||||
(cb) => crypto.keys.generateEphemeralKeyPair('P-256', cb),
|
||||
(res, cb) => res.genSharedKey(res.key, cb)
|
||||
], (err, secret) => {
|
||||
if (err) { throw err }
|
||||
|
||||
ciphers.forEach((cipher) => hashes.forEach((hash) => {
|
||||
setup(cipher, hash, secret)
|
||||
@@ -23,12 +26,15 @@ const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
suite
|
||||
.on('cycle', (event) => console.log(String(event.target)))
|
||||
.run({ async: true })
|
||||
})()
|
||||
})
|
||||
|
||||
function setup (cipher, hash, secret) {
|
||||
suite.add(`keyStretcher ${cipher} ${hash}`, async (d) => {
|
||||
const k = await crypto.keys.keyStretcher(cipher, hash, secret)
|
||||
keys.push(k)
|
||||
d.resolve()
|
||||
suite.add(`keyStretcher ${cipher} ${hash}`, (d) => {
|
||||
crypto.keys.keyStretcher(cipher, hash, secret, (err, k) => {
|
||||
if (err) { throw err }
|
||||
|
||||
keys.push(k)
|
||||
d.resolve()
|
||||
})
|
||||
}, { defer: true })
|
||||
}
|
||||
|
@@ -10,24 +10,30 @@ const keys = []
|
||||
const bits = [1024, 2048, 4096]
|
||||
|
||||
bits.forEach((bit) => {
|
||||
suite.add(`generateKeyPair ${bit}bits`, async (d) => {
|
||||
const key = await crypto.keys.generateKeyPair('RSA', bit)
|
||||
keys.push(key)
|
||||
d.resolve()
|
||||
suite.add(`generateKeyPair ${bit}bits`, (d) => {
|
||||
crypto.keys.generateKeyPair('RSA', bit, (err, key) => {
|
||||
if (err) { throw err }
|
||||
keys.push(key)
|
||||
d.resolve()
|
||||
})
|
||||
}, {
|
||||
defer: true
|
||||
})
|
||||
})
|
||||
|
||||
suite.add('sign and verify', async (d) => {
|
||||
suite.add('sign and verify', (d) => {
|
||||
const key = keys[0]
|
||||
const text = key.genSecret()
|
||||
|
||||
const sig = await key.sign(text)
|
||||
const res = await key.public.verify(text, sig)
|
||||
key.sign(text, (err, sig) => {
|
||||
if (err) { throw err }
|
||||
|
||||
if (res !== true) { throw new Error('failed to verify') }
|
||||
d.resolve()
|
||||
key.public.verify(text, sig, (err, res) => {
|
||||
if (err) { throw err }
|
||||
if (res !== true) { throw new Error('failed to verify') }
|
||||
d.resolve()
|
||||
})
|
||||
})
|
||||
}, {
|
||||
defer: true
|
||||
})
|
||||
|
40
package.json
40
package.json
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "libp2p-crypto",
|
||||
"version": "0.17.2",
|
||||
"version": "0.16.3",
|
||||
"description": "Crypto primitives for libp2p",
|
||||
"main": "src/index.js",
|
||||
"types": "src/index.d.ts",
|
||||
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
||||
"leadMaintainer": "Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||
"browser": {
|
||||
"./src/hmac/index.js": "./src/hmac/index-browser.js",
|
||||
"./src/keys/ecdh.js": "./src/keys/ecdh-browser.js",
|
||||
@@ -26,8 +25,7 @@
|
||||
"release-minor": "aegir release --type minor",
|
||||
"release-major": "aegir release --type major",
|
||||
"coverage": "aegir coverage --ignore src/keys/keys.proto.js",
|
||||
"size": "aegir build --bundlesize",
|
||||
"test:types": "npx tsc"
|
||||
"size": "bundlesize -f dist/index.min.js -s 139kB"
|
||||
},
|
||||
"keywords": [
|
||||
"IPFS",
|
||||
@@ -38,35 +36,29 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asmcrypto.js": "^2.3.2",
|
||||
"asn1.js": "^5.2.0",
|
||||
"bn.js": "^5.0.0",
|
||||
"asn1.js": "^5.0.1",
|
||||
"async": "^2.6.1",
|
||||
"bn.js": "^4.11.8",
|
||||
"browserify-aes": "^1.2.0",
|
||||
"bs58": "^4.0.1",
|
||||
"err-code": "^1.1.2",
|
||||
"iso-random-stream": "^1.1.0",
|
||||
"keypair": "^1.0.1",
|
||||
"libp2p-crypto-secp256k1": "~0.4.0",
|
||||
"multihashing-async": "~0.8.0",
|
||||
"libp2p-crypto-secp256k1": "~0.3.0",
|
||||
"multihashing-async": "~0.5.1",
|
||||
"node-forge": "~0.9.1",
|
||||
"pem-jwk": "^2.0.0",
|
||||
"protons": "^1.0.1",
|
||||
"rsa-pem-to-jwk": "^1.1.3",
|
||||
"tweetnacl": "^1.0.1",
|
||||
"ursa-optional": "~0.10.1"
|
||||
"tweetnacl": "^1.0.0",
|
||||
"ursa-optional": "~0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"@types/chai": "^4.2.7",
|
||||
"@types/chai-string": "^1.4.2",
|
||||
"@types/dirty-chai": "^2.0.2",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/sinon": "^7.5.1",
|
||||
"aegir": "^20.4.1",
|
||||
"aegir": "^20.0.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"bundlesize": "^0.18.0",
|
||||
"chai": "^4.2.0",
|
||||
"chai-string": "^1.5.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"sinon": "^7.5.0"
|
||||
"dirty-chai": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0",
|
||||
@@ -81,11 +73,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/libp2p/js-libp2p-crypto",
|
||||
"contributors": [
|
||||
"Alan Shaw <alan.shaw@protocol.ai>",
|
||||
"Alan Shaw <alan@tableflip.io>",
|
||||
"Alberto Elias <hi@albertoelias.me>",
|
||||
"Arve Knudsen <arve.knudsen@gmail.com>",
|
||||
"Carson Farmer <carson.farmer@gmail.com>",
|
||||
"David Dias <daviddias.p@gmail.com>",
|
||||
"Dmitriy Ryajov <dryajov@gmail.com>",
|
||||
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||
@@ -101,8 +89,8 @@
|
||||
"Vasco Santos <vasco.santos@ua.pt>",
|
||||
"Victor Bjelkholm <victorbjelkholm@gmail.com>",
|
||||
"Yusef Napora <yusef@napora.org>",
|
||||
"achingbrain <alex@achingbrain.net>",
|
||||
"dignifiedquire <dignifiedquire@users.noreply.github.com>",
|
||||
"dirkmc <dirkmdev@gmail.com>",
|
||||
"greenkeeper[bot] <greenkeeper[bot]@users.noreply.github.com>",
|
||||
"nikuda <nikuda@gmail.com>"
|
||||
]
|
||||
|
@@ -1,17 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
|
||||
const CIPHER_MODES = {
|
||||
16: 'aes-128-ctr',
|
||||
32: 'aes-256-ctr'
|
||||
}
|
||||
|
||||
module.exports = function (key) {
|
||||
const mode = CIPHER_MODES[key.length]
|
||||
if (!mode) {
|
||||
const modes = Object.entries(CIPHER_MODES).map(([k, v]) => `${k} (${v})`).join(' / ')
|
||||
throw errcode(new Error(`Invalid key length ${key.length} bytes. Must be ${modes}`), 'ERR_INVALID_KEY_LENGTH')
|
||||
}
|
||||
return mode
|
||||
}
|
@@ -1,11 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
const asm = require('asmcrypto.js')
|
||||
const validateCipherMode = require('./cipher-mode')
|
||||
const nextTick = require('async/nextTick')
|
||||
|
||||
exports.create = async function (key, iv) { // eslint-disable-line require-await
|
||||
// Throws an error if mode is invalid
|
||||
validateCipherMode(key)
|
||||
exports.create = function (key, iv, callback) {
|
||||
const done = (err, res) => nextTick(() => callback(err, res))
|
||||
|
||||
if (key.length !== 16 && key.length !== 32) {
|
||||
return done(new Error('Invalid key length'))
|
||||
}
|
||||
|
||||
const enc = new asm.AES_CTR.Encrypt({
|
||||
key: key,
|
||||
@@ -17,18 +20,36 @@ exports.create = async function (key, iv) { // eslint-disable-line require-await
|
||||
})
|
||||
|
||||
const res = {
|
||||
async encrypt (data) { // eslint-disable-line require-await
|
||||
return Buffer.from(
|
||||
enc.process(data).result
|
||||
)
|
||||
encrypt (data, cb) {
|
||||
const done = (err, res) => nextTick(() => cb(err, res))
|
||||
|
||||
let res
|
||||
try {
|
||||
res = Buffer.from(
|
||||
enc.process(data).result
|
||||
)
|
||||
} catch (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
done(null, res)
|
||||
},
|
||||
|
||||
async decrypt (data) { // eslint-disable-line require-await
|
||||
return Buffer.from(
|
||||
dec.process(data).result
|
||||
)
|
||||
decrypt (data, cb) {
|
||||
const done = (err, res) => nextTick(() => cb(err, res))
|
||||
|
||||
let res
|
||||
try {
|
||||
res = Buffer.from(
|
||||
dec.process(data).result
|
||||
)
|
||||
} catch (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
done(null, res)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
done(null, res)
|
||||
}
|
||||
|
@@ -1,22 +1,30 @@
|
||||
'use strict'
|
||||
|
||||
const ciphers = require('./ciphers')
|
||||
const cipherMode = require('./cipher-mode')
|
||||
|
||||
exports.create = async function (key, iv) { // eslint-disable-line require-await
|
||||
const mode = cipherMode(key)
|
||||
const CIPHER_MODES = {
|
||||
16: 'aes-128-ctr',
|
||||
32: 'aes-256-ctr'
|
||||
}
|
||||
|
||||
exports.create = function (key, iv, callback) {
|
||||
const mode = CIPHER_MODES[key.length]
|
||||
if (!mode) {
|
||||
return callback(new Error('Invalid key length'))
|
||||
}
|
||||
|
||||
const cipher = ciphers.createCipheriv(mode, key, iv)
|
||||
const decipher = ciphers.createDecipheriv(mode, key, iv)
|
||||
|
||||
const res = {
|
||||
async encrypt (data) { // eslint-disable-line require-await
|
||||
return cipher.update(data)
|
||||
encrypt (data, cb) {
|
||||
cb(null, cipher.update(data))
|
||||
},
|
||||
|
||||
async decrypt (data) { // eslint-disable-line require-await
|
||||
return decipher.update(data)
|
||||
decrypt (data, cb) {
|
||||
cb(null, decipher.update(data))
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
callback(null, res)
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
const webcrypto = require('../webcrypto')
|
||||
const nodeify = require('../nodeify')
|
||||
|
||||
const crypto = require('../webcrypto')
|
||||
const lengths = require('./lengths')
|
||||
|
||||
const hashTypes = {
|
||||
@@ -9,14 +11,15 @@ const hashTypes = {
|
||||
SHA512: 'SHA-512'
|
||||
}
|
||||
|
||||
const sign = async (key, data) => {
|
||||
return Buffer.from(await webcrypto.get().subtle.sign({ name: 'HMAC' }, key, data))
|
||||
const sign = (key, data, cb) => {
|
||||
nodeify(crypto.subtle.sign({ name: 'HMAC' }, key, data)
|
||||
.then((raw) => Buffer.from(raw)), cb)
|
||||
}
|
||||
|
||||
exports.create = async function (hashType, secret) {
|
||||
exports.create = function (hashType, secret, callback) {
|
||||
const hash = hashTypes[hashType]
|
||||
|
||||
const key = await webcrypto.get().subtle.importKey(
|
||||
nodeify(crypto.subtle.importKey(
|
||||
'raw',
|
||||
secret,
|
||||
{
|
||||
@@ -25,12 +28,12 @@ exports.create = async function (hashType, secret) {
|
||||
},
|
||||
false,
|
||||
['sign']
|
||||
)
|
||||
|
||||
return {
|
||||
async digest (data) { // eslint-disable-line require-await
|
||||
return sign(key, data)
|
||||
},
|
||||
length: lengths[hashType]
|
||||
}
|
||||
).then((key) => {
|
||||
return {
|
||||
digest (data, cb) {
|
||||
sign(key, data, cb)
|
||||
},
|
||||
length: lengths[hashType]
|
||||
}
|
||||
}), callback)
|
||||
}
|
||||
|
@@ -2,16 +2,21 @@
|
||||
|
||||
const crypto = require('crypto')
|
||||
const lengths = require('./lengths')
|
||||
const nextTick = require('async/nextTick')
|
||||
|
||||
exports.create = async function (hash, secret) { // eslint-disable-line require-await
|
||||
exports.create = function (hash, secret, callback) {
|
||||
const res = {
|
||||
async digest (data) { // eslint-disable-line require-await
|
||||
digest (data, cb) {
|
||||
const hmac = crypto.createHmac(hash.toLowerCase(), secret)
|
||||
|
||||
hmac.update(data)
|
||||
return hmac.digest()
|
||||
|
||||
nextTick(() => {
|
||||
cb(null, hmac.digest())
|
||||
})
|
||||
},
|
||||
length: lengths[hash]
|
||||
}
|
||||
|
||||
return res
|
||||
callback(null, res)
|
||||
}
|
||||
|
345
src/index.d.ts
vendored
345
src/index.d.ts
vendored
@@ -1,345 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
/**
|
||||
* Supported key types.
|
||||
*/
|
||||
export type KeyType = "Ed25519" | "RSA" | "secp256k1";
|
||||
|
||||
/**
|
||||
* Maps an IPFS hash name to its node-forge equivalent.
|
||||
* See https://github.com/multiformats/multihash/blob/master/hashtable.csv
|
||||
*/
|
||||
export type HashType = "SHA1" | "SHA256" | "SHA512";
|
||||
|
||||
/**
|
||||
* Supported curve types.
|
||||
*/
|
||||
export type CurveType = "P-256" | "P-384" | "P-521";
|
||||
|
||||
/**
|
||||
* Supported cipher types.
|
||||
*/
|
||||
export type CipherType = "AES-128" | "AES-256" | "Blowfish";
|
||||
|
||||
/**
|
||||
* Exposes an interface to AES encryption (formerly Rijndael),
|
||||
* as defined in U.S. Federal Information Processing Standards Publication 197.
|
||||
* This uses CTR mode.
|
||||
*/
|
||||
export namespace aes {
|
||||
/**
|
||||
* AES Cipher in CTR mode.
|
||||
*/
|
||||
interface Cipher {
|
||||
encrypt(data: Buffer): Promise<Buffer>;
|
||||
decrypt(data: Buffer): Promise<Buffer>;
|
||||
}
|
||||
/**
|
||||
* Create a new AES Cipher.
|
||||
* @param key The key, if length 16 then AES 128 is used. For length 32, AES 256 is used.
|
||||
* @param iv Must have length 16.
|
||||
*/
|
||||
function create(key: Buffer, iv: Buffer): Promise<Cipher>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes an interface to the Keyed-Hash Message Authentication Code (HMAC)
|
||||
* as defined in U.S. Federal Information Processing Standards Publication 198.
|
||||
* An HMAC is a cryptographic hash that uses a key to sign a message.
|
||||
* The receiver verifies the hash by recomputing it using the same key.
|
||||
*/
|
||||
export namespace hmac {
|
||||
/**
|
||||
* HMAC Digest.
|
||||
*/
|
||||
interface Digest {
|
||||
digest(data: Buffer): Promise<Buffer>;
|
||||
length: 20 | 32 | 64 | number;
|
||||
}
|
||||
/**
|
||||
* Create a new HMAC Digest.
|
||||
*/
|
||||
function create(
|
||||
hash: "SHA1" | "SHA256" | "SHA512" | string,
|
||||
secret: Buffer
|
||||
): Promise<Digest>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic public key interface.
|
||||
*/
|
||||
export interface PublicKey {
|
||||
readonly bytes: Buffer;
|
||||
verify(data: Buffer, sig: Buffer): Promise<boolean>;
|
||||
marshal(): Buffer;
|
||||
equals(key: PublicKey): boolean;
|
||||
hash(): Promise<Buffer>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic private key interface.
|
||||
*/
|
||||
export interface PrivateKey {
|
||||
readonly public: PublicKey;
|
||||
readonly bytes: Buffer;
|
||||
sign(data: Buffer): Promise<Buffer>;
|
||||
marshal(): Buffer;
|
||||
equals(key: PrivateKey): boolean;
|
||||
hash(): Promise<Buffer>;
|
||||
/**
|
||||
* Gets the ID of the key.
|
||||
*
|
||||
* The key id is the base58 encoding of the SHA-256 multihash of its public key.
|
||||
* The public key is a protobuf encoding containing a type and the DER encoding
|
||||
* of the PKCS SubjectPublicKeyInfo.
|
||||
*/
|
||||
id(): Promise<string>;
|
||||
}
|
||||
|
||||
export interface Keystretcher {
|
||||
(res: Buffer): Keystretcher;
|
||||
iv: Buffer;
|
||||
cipherKey: Buffer;
|
||||
macKey: Buffer;
|
||||
}
|
||||
|
||||
export interface StretchPair {
|
||||
k1: Keystretcher;
|
||||
k2: Keystretcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes an interface to various cryptographic key generation routines.
|
||||
* Currently the 'RSA' and 'ed25519' types are supported, although ed25519 keys
|
||||
* support only signing and verification of messages. For encryption / decryption
|
||||
* support, RSA keys should be used.
|
||||
* Installing the libp2p-crypto-secp256k1 module adds support for the 'secp256k1'
|
||||
* type, which supports ECDSA signatures using the secp256k1 elliptic curve
|
||||
* popularized by Bitcoin. This module is not installed by default, and should be
|
||||
* explicitly depended on if your project requires secp256k1 support.
|
||||
*/
|
||||
export namespace keys {
|
||||
export {};
|
||||
export namespace supportedKeys {
|
||||
namespace rsa {
|
||||
class RsaPublicKey implements PublicKey {
|
||||
constructor(key: Buffer);
|
||||
readonly bytes: Buffer;
|
||||
verify(data: Buffer, sig: Buffer): Promise<boolean>;
|
||||
marshal(): Buffer;
|
||||
encrypt(bytes: Buffer): Buffer;
|
||||
equals(key: PublicKey): boolean;
|
||||
hash(): Promise<Buffer>;
|
||||
}
|
||||
|
||||
// Type alias for export method
|
||||
export type KeyInfo = any;
|
||||
|
||||
class RsaPrivateKey implements PrivateKey {
|
||||
constructor(key: any, publicKey: Buffer);
|
||||
readonly public: RsaPublicKey;
|
||||
readonly bytes: Buffer;
|
||||
genSecret(): Buffer;
|
||||
sign(data: Buffer): Promise<Buffer>;
|
||||
decrypt(bytes: Buffer): Buffer;
|
||||
marshal(): Buffer;
|
||||
equals(key: PrivateKey): boolean;
|
||||
hash(): Promise<Buffer>;
|
||||
id(): Promise<string>;
|
||||
/**
|
||||
* Exports the key into a password protected PEM format
|
||||
*
|
||||
* @param password The password to read the encrypted PEM
|
||||
* @param format Defaults to 'pkcs-8'.
|
||||
*/
|
||||
export(password: string, format?: "pkcs-8" | string): KeyInfo;
|
||||
}
|
||||
function unmarshalRsaPublicKey(buf: Buffer): RsaPublicKey;
|
||||
function unmarshalRsaPrivateKey(buf: Buffer): Promise<RsaPrivateKey>;
|
||||
function generateKeyPair(bits: number): Promise<RsaPrivateKey>;
|
||||
function fromJwk(jwk: Buffer): Promise<RsaPrivateKey>;
|
||||
}
|
||||
|
||||
namespace ed25519 {
|
||||
class Ed25519PublicKey implements PublicKey {
|
||||
constructor(key: Buffer);
|
||||
readonly bytes: Buffer;
|
||||
verify(data: Buffer, sig: Buffer): Promise<boolean>;
|
||||
marshal(): Buffer;
|
||||
encrypt(bytes: Buffer): Buffer;
|
||||
equals(key: PublicKey): boolean;
|
||||
hash(): Promise<Buffer>;
|
||||
}
|
||||
|
||||
class Ed25519PrivateKey implements PrivateKey {
|
||||
constructor(key: Buffer, publicKey: Buffer);
|
||||
readonly public: Ed25519PublicKey;
|
||||
readonly bytes: Buffer;
|
||||
sign(data: Buffer): Promise<Buffer>;
|
||||
marshal(): Buffer;
|
||||
equals(key: PrivateKey): boolean;
|
||||
hash(): Promise<Buffer>;
|
||||
id(): Promise<string>;
|
||||
}
|
||||
|
||||
function unmarshalEd25519PrivateKey(
|
||||
buf: Buffer
|
||||
): Promise<Ed25519PrivateKey>;
|
||||
function unmarshalEd25519PublicKey(buf: Buffer): Ed25519PublicKey;
|
||||
function generateKeyPair(): Promise<Ed25519PrivateKey>;
|
||||
function generateKeyPairFromSeed(
|
||||
seed: Buffer
|
||||
): Promise<Ed25519PrivateKey>;
|
||||
}
|
||||
|
||||
namespace secp256k1 {
|
||||
class Secp256k1PublicKey implements PublicKey {
|
||||
constructor(key: Buffer);
|
||||
readonly bytes: Buffer;
|
||||
verify(data: Buffer, sig: Buffer): Promise<boolean>;
|
||||
marshal(): Buffer;
|
||||
encrypt(bytes: Buffer): Buffer;
|
||||
equals(key: PublicKey): boolean;
|
||||
hash(): Promise<Buffer>;
|
||||
}
|
||||
|
||||
class Secp256k1PrivateKey implements PrivateKey {
|
||||
constructor(key: Uint8Array | Buffer, publicKey: Uint8Array | Buffer);
|
||||
readonly public: Secp256k1PublicKey;
|
||||
readonly bytes: Buffer;
|
||||
sign(data: Buffer): Promise<Buffer>;
|
||||
marshal(): Buffer;
|
||||
equals(key: PrivateKey): boolean;
|
||||
hash(): Promise<Buffer>;
|
||||
id(): Promise<string>;
|
||||
}
|
||||
|
||||
function unmarshalSecp256k1PrivateKey(
|
||||
bytes: Buffer
|
||||
): Promise<Secp256k1PrivateKey>;
|
||||
function unmarshalSecp256k1PublicKey(bytes: Buffer): Secp256k1PublicKey;
|
||||
function generateKeyPair(): Promise<Secp256k1PrivateKey>;
|
||||
}
|
||||
}
|
||||
|
||||
export const keysPBM: any;
|
||||
|
||||
/**
|
||||
* Generates a keypair of the given type and bitsize.
|
||||
* @param type One of the supported key types.
|
||||
* @param bits Number of bits. Minimum of 1024.
|
||||
*/
|
||||
export function generateKeyPair(
|
||||
type: KeyType | string,
|
||||
bits: number
|
||||
): Promise<PrivateKey>;
|
||||
export function generateKeyPair(
|
||||
type: "Ed25519",
|
||||
bits: number
|
||||
): Promise<keys.supportedKeys.ed25519.Ed25519PrivateKey>;
|
||||
export function generateKeyPair(
|
||||
type: "RSA",
|
||||
bits: number
|
||||
): Promise<keys.supportedKeys.rsa.RsaPrivateKey>;
|
||||
export function generateKeyPair(
|
||||
type: "secp256k1",
|
||||
bits: number
|
||||
): Promise<keys.supportedKeys.secp256k1.Secp256k1PrivateKey>;
|
||||
|
||||
/**
|
||||
* Generates a keypair of the given type and bitsize.
|
||||
* @param type One of the supported key types. Currently only 'Ed25519' is supported.
|
||||
* @param seed A 32 byte uint8array.
|
||||
* @param bits Number of bits. Minimum of 1024.
|
||||
*/
|
||||
export function generateKeyPairFromSeed(
|
||||
type: KeyType | string,
|
||||
seed: Uint8Array,
|
||||
bits: number
|
||||
): Promise<PrivateKey>;
|
||||
export function generateKeyPairFromSeed(
|
||||
type: "Ed25519",
|
||||
seed: Uint8Array,
|
||||
bits: number
|
||||
): Promise<keys.supportedKeys.ed25519.Ed25519PrivateKey>;
|
||||
|
||||
/**
|
||||
* Generates an ephemeral public key and returns a function that will compute the shared secret key.
|
||||
* Focuses only on ECDH now, but can be made more general in the future.
|
||||
* @param curve The curve to use. One of 'P-256', 'P-384', 'P-521' is currently supported.
|
||||
*/
|
||||
export function generateEphemeralKeyPair(
|
||||
curve: CurveType | string
|
||||
): Promise<{
|
||||
key: Buffer;
|
||||
genSharedKey: (theirPub: Buffer, forcePrivate?: any) => Promise<Buffer>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Generates a set of keys for each party by stretching the shared key.
|
||||
* @param cipherType The cipher type to use. One of 'AES-128', 'AES-256', or 'Blowfish'
|
||||
* @param hashType The hash type to use. One of 'SHA1', 'SHA2256', or 'SHA2512'.
|
||||
* @param secret The shared key secret.
|
||||
*/
|
||||
export function keyStretcher(
|
||||
cipherType: CipherType | string,
|
||||
hashType: HashType | string,
|
||||
secret: Buffer | string
|
||||
): Promise<StretchPair>;
|
||||
|
||||
/**
|
||||
* Converts a protobuf serialized public key into its representative object.
|
||||
* @param buf The protobuf serialized public key.
|
||||
*/
|
||||
export function unmarshalPublicKey(buf: Buffer): PublicKey;
|
||||
|
||||
/**
|
||||
* Converts a public key object into a protobuf serialized public key.
|
||||
* @param key An RSA, Ed25519, or Secp256k1 public key object.
|
||||
* @param type One of the supported key types.
|
||||
*/
|
||||
export function marshalPublicKey(key: PublicKey, type?: KeyType | string): Buffer;
|
||||
|
||||
/**
|
||||
* Converts a protobuf serialized private key into its representative object.
|
||||
* @param buf The protobuf serialized private key.
|
||||
*/
|
||||
export function unmarshalPrivateKey(buf: Buffer): Promise<PrivateKey>;
|
||||
|
||||
/**
|
||||
* Converts a private key object into a protobuf serialized private key.
|
||||
* @param key An RSA, Ed25519, or Secp256k1 private key object.
|
||||
* @param type One of the supported key types.
|
||||
*/
|
||||
export function marshalPrivateKey(key: PrivateKey, type?: KeyType | string): Buffer;
|
||||
|
||||
/**
|
||||
* Converts a PEM password protected private key into its representative object.
|
||||
* @param pem Password protected private key in PEM format.
|
||||
* @param password The password used to protect the key.
|
||||
*/
|
||||
function _import(pem: string, password: string): Promise<supportedKeys.rsa.RsaPrivateKey>;
|
||||
export { _import as import };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Buffer populated by random bytes.
|
||||
* @param The size of the random bytes Buffer.
|
||||
*/
|
||||
export function randomBytes(number: number): Buffer;
|
||||
|
||||
/**
|
||||
* Computes the Password-Based Key Derivation Function 2.
|
||||
* @param password The password.
|
||||
* @param salt The salt.
|
||||
* @param iterations Number of iterations to use.
|
||||
* @param keySize The size of the output key in bytes.
|
||||
* @param hash The hash name ('sha1', 'sha2-512, ...)
|
||||
*/
|
||||
export function pbkdf2(
|
||||
password: string | Buffer,
|
||||
salt: string | Buffer,
|
||||
iterations: number,
|
||||
keySize: number,
|
||||
hash: string
|
||||
): Buffer;
|
@@ -4,6 +4,8 @@ const hmac = require('./hmac')
|
||||
const aes = require('./aes')
|
||||
const keys = require('./keys')
|
||||
|
||||
exports = module.exports
|
||||
|
||||
exports.aes = aes
|
||||
exports.hmac = hmac
|
||||
exports.keys = keys
|
||||
|
@@ -1,10 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
const webcrypto = require('../webcrypto')
|
||||
const nodeify = require('../nodeify')
|
||||
const BN = require('asn1.js').bignum
|
||||
const { toBase64, toBn } = require('../util')
|
||||
const validateCurveType = require('./validate-curve-type')
|
||||
|
||||
const util = require('../util')
|
||||
const toBase64 = util.toBase64
|
||||
const toBn = util.toBn
|
||||
|
||||
const bits = {
|
||||
'P-256': 256,
|
||||
@@ -12,67 +14,72 @@ const bits = {
|
||||
'P-521': 521
|
||||
}
|
||||
|
||||
exports.generateEphmeralKeyPair = async function (curve) {
|
||||
validateCurveType(Object.keys(bits), curve)
|
||||
const pair = await webcrypto.get().subtle.generateKey(
|
||||
exports.generateEphmeralKeyPair = function (curve, callback) {
|
||||
nodeify(webcrypto.subtle.generateKey(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
true,
|
||||
['deriveBits']
|
||||
)
|
||||
).then((pair) => {
|
||||
// forcePrivate is used for testing only
|
||||
const genSharedKey = (theirPub, forcePrivate, cb) => {
|
||||
if (typeof forcePrivate === 'function') {
|
||||
cb = forcePrivate
|
||||
forcePrivate = undefined
|
||||
}
|
||||
|
||||
// forcePrivate is used for testing only
|
||||
const genSharedKey = async (theirPub, forcePrivate) => {
|
||||
let privateKey
|
||||
let privateKey
|
||||
|
||||
if (forcePrivate) {
|
||||
privateKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPrivateKey(curve, forcePrivate),
|
||||
if (forcePrivate) {
|
||||
privateKey = webcrypto.subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPrivateKey(curve, forcePrivate),
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
false,
|
||||
['deriveBits']
|
||||
)
|
||||
} else {
|
||||
privateKey = Promise.resolve(pair.privateKey)
|
||||
}
|
||||
|
||||
const keys = Promise.all([
|
||||
webcrypto.subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPublicKey(curve, theirPub),
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
false,
|
||||
[]
|
||||
),
|
||||
privateKey
|
||||
])
|
||||
|
||||
nodeify(keys.then((keys) => webcrypto.subtle.deriveBits(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
namedCurve: curve,
|
||||
public: keys[0]
|
||||
},
|
||||
false,
|
||||
['deriveBits']
|
||||
)
|
||||
} else {
|
||||
privateKey = pair.privateKey
|
||||
keys[1],
|
||||
bits[curve]
|
||||
)).then((bits) => Buffer.from(bits)), cb)
|
||||
}
|
||||
|
||||
const keys = [
|
||||
await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPublicKey(curve, theirPub),
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
false,
|
||||
[]
|
||||
),
|
||||
privateKey
|
||||
]
|
||||
|
||||
return Buffer.from(await webcrypto.get().subtle.deriveBits(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve,
|
||||
public: keys[0]
|
||||
},
|
||||
keys[1],
|
||||
bits[curve]
|
||||
))
|
||||
}
|
||||
|
||||
const publicKey = await webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
|
||||
|
||||
return {
|
||||
key: marshalPublicKey(publicKey),
|
||||
genSharedKey
|
||||
}
|
||||
return webcrypto.subtle.exportKey('jwk', pair.publicKey)
|
||||
.then((publicKey) => {
|
||||
return {
|
||||
key: marshalPublicKey(publicKey),
|
||||
genSharedKey
|
||||
}
|
||||
})
|
||||
}), callback)
|
||||
}
|
||||
|
||||
const curveLengths = {
|
||||
@@ -99,7 +106,7 @@ function unmarshalPublicKey (curve, key) {
|
||||
const byteLen = curveLengths[curve]
|
||||
|
||||
if (!key.slice(0, 1).equals(Buffer.from([4]))) {
|
||||
throw errcode(new Error('Cannot unmarshal public key - invalid key format'), 'ERR_INVALID_KEY_FORMAT')
|
||||
throw new Error('Invalid key format')
|
||||
}
|
||||
const x = new BN(key.slice(1, byteLen + 1))
|
||||
const y = new BN(key.slice(1 + byteLen))
|
||||
|
@@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
const validateCurveType = require('./validate-curve-type')
|
||||
const nextTick = require('async/nextTick')
|
||||
|
||||
const curves = {
|
||||
'P-256': 'prime256v1',
|
||||
@@ -9,20 +9,33 @@ const curves = {
|
||||
'P-521': 'secp521r1'
|
||||
}
|
||||
|
||||
exports.generateEphmeralKeyPair = async function (curve) { // eslint-disable-line require-await
|
||||
validateCurveType(Object.keys(curves), curve)
|
||||
|
||||
exports.generateEphmeralKeyPair = function (curve, callback) {
|
||||
if (!curves[curve]) {
|
||||
return callback(new Error(`Unkown curve: ${curve}`))
|
||||
}
|
||||
const ecdh = crypto.createECDH(curves[curve])
|
||||
ecdh.generateKeys()
|
||||
|
||||
return {
|
||||
nextTick(() => callback(null, {
|
||||
key: ecdh.getPublicKey(),
|
||||
async genSharedKey (theirPub, forcePrivate) { // eslint-disable-line require-await
|
||||
genSharedKey (theirPub, forcePrivate, cb) {
|
||||
if (typeof forcePrivate === 'function') {
|
||||
cb = forcePrivate
|
||||
forcePrivate = null
|
||||
}
|
||||
|
||||
if (forcePrivate) {
|
||||
ecdh.setPrivateKey(forcePrivate.private)
|
||||
}
|
||||
|
||||
return ecdh.computeSecret(theirPub)
|
||||
let secret
|
||||
try {
|
||||
secret = ecdh.computeSecret(theirPub)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
nextTick(() => cb(null, secret))
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@
|
||||
const multihashing = require('multihashing-async')
|
||||
const protobuf = require('protons')
|
||||
const bs58 = require('bs58')
|
||||
const errcode = require('err-code')
|
||||
|
||||
const crypto = require('./ed25519')
|
||||
const pbm = protobuf(require('./keys.proto'))
|
||||
@@ -13,8 +12,9 @@ class Ed25519PublicKey {
|
||||
this._key = ensureKey(key, crypto.publicKeyLength)
|
||||
}
|
||||
|
||||
async verify (data, sig) { // eslint-disable-line require-await
|
||||
return crypto.hashAndVerify(this._key, sig, data)
|
||||
verify (data, sig, callback) {
|
||||
ensure(callback)
|
||||
crypto.hashAndVerify(this._key, sig, data, callback)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
@@ -32,8 +32,9 @@ class Ed25519PublicKey {
|
||||
return this.bytes.equals(key.bytes)
|
||||
}
|
||||
|
||||
async hash () { // eslint-disable-line require-await
|
||||
return multihashing(this.bytes, 'sha2-256')
|
||||
hash (callback) {
|
||||
ensure(callback)
|
||||
multihashing(this.bytes, 'sha2-256', callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +46,16 @@ class Ed25519PrivateKey {
|
||||
this._publicKey = ensureKey(publicKey, crypto.publicKeyLength)
|
||||
}
|
||||
|
||||
async sign (message) { // eslint-disable-line require-await
|
||||
return crypto.hashAndSign(this._key, message)
|
||||
sign (message, callback) {
|
||||
ensure(callback)
|
||||
crypto.hashAndSign(this._key, message, callback)
|
||||
}
|
||||
|
||||
get public () {
|
||||
if (!this._publicKey) {
|
||||
throw new Error('public key not provided')
|
||||
}
|
||||
|
||||
return new Ed25519PublicKey(this._publicKey)
|
||||
}
|
||||
|
||||
@@ -68,8 +74,9 @@ class Ed25519PrivateKey {
|
||||
return this.bytes.equals(key.bytes)
|
||||
}
|
||||
|
||||
async hash () { // eslint-disable-line require-await
|
||||
return multihashing(this.bytes, 'sha2-256')
|
||||
hash (callback) {
|
||||
ensure(callback)
|
||||
multihashing(this.bytes, 'sha2-256', callback)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,19 +86,28 @@ class Ed25519PrivateKey {
|
||||
* The public key is a protobuf encoding containing a type and the DER encoding
|
||||
* of the PKCS SubjectPublicKeyInfo.
|
||||
*
|
||||
* @returns {Promise<String>}
|
||||
* @param {function(Error, id)} callback
|
||||
* @returns {undefined}
|
||||
*/
|
||||
async id () {
|
||||
const hash = await this.public.hash()
|
||||
return bs58.encode(hash)
|
||||
id (callback) {
|
||||
this.public.hash((err, hash) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null, bs58.encode(hash))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function unmarshalEd25519PrivateKey (bytes) {
|
||||
bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength)
|
||||
function unmarshalEd25519PrivateKey (bytes, callback) {
|
||||
try {
|
||||
bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength)
|
||||
} catch (err) {
|
||||
return callback(err)
|
||||
}
|
||||
const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength)
|
||||
const publicKeyBytes = bytes.slice(crypto.privateKeyLength, bytes.length)
|
||||
return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
|
||||
callback(null, new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes))
|
||||
}
|
||||
|
||||
function unmarshalEd25519PublicKey (bytes) {
|
||||
@@ -99,14 +115,52 @@ function unmarshalEd25519PublicKey (bytes) {
|
||||
return new Ed25519PublicKey(bytes)
|
||||
}
|
||||
|
||||
async function generateKeyPair () {
|
||||
const { secretKey, publicKey } = await crypto.generateKey()
|
||||
return new Ed25519PrivateKey(secretKey, publicKey)
|
||||
function generateKeyPair (_bits, cb) {
|
||||
if (cb === undefined && typeof _bits === 'function') {
|
||||
cb = _bits
|
||||
}
|
||||
|
||||
crypto.generateKey((err, keys) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
let privkey
|
||||
try {
|
||||
privkey = new Ed25519PrivateKey(keys.secretKey, keys.publicKey)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
return
|
||||
}
|
||||
|
||||
cb(null, privkey)
|
||||
})
|
||||
}
|
||||
|
||||
async function generateKeyPairFromSeed (seed) {
|
||||
const { secretKey, publicKey } = await crypto.generateKeyFromSeed(seed)
|
||||
return new Ed25519PrivateKey(secretKey, publicKey)
|
||||
function generateKeyPairFromSeed (seed, _bits, cb) {
|
||||
if (cb === undefined && typeof _bits === 'function') {
|
||||
cb = _bits
|
||||
}
|
||||
|
||||
crypto.generateKeyFromSeed(seed, (err, keys) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
let privkey
|
||||
try {
|
||||
privkey = new Ed25519PrivateKey(keys.secretKey, keys.publicKey)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
return
|
||||
}
|
||||
|
||||
cb(null, privkey)
|
||||
})
|
||||
}
|
||||
|
||||
function ensure (cb) {
|
||||
if (typeof cb !== 'function') {
|
||||
throw new Error('callback is required')
|
||||
}
|
||||
}
|
||||
|
||||
function ensureKey (key, length) {
|
||||
@@ -114,7 +168,7 @@ function ensureKey (key, length) {
|
||||
key = new Uint8Array(key)
|
||||
}
|
||||
if (!(key instanceof Uint8Array) || key.length !== length) {
|
||||
throw errcode(new Error('Key must be a Uint8Array or Buffer of length ' + length), 'ERR_INVALID_KEY_TYPE')
|
||||
throw new Error('Key must be a Uint8Array or Buffer of length ' + length)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
@@ -1,23 +1,51 @@
|
||||
'use strict'
|
||||
|
||||
const nacl = require('tweetnacl')
|
||||
const nextTick = require('async/nextTick')
|
||||
|
||||
exports.publicKeyLength = nacl.sign.publicKeyLength
|
||||
exports.privateKeyLength = nacl.sign.secretKeyLength
|
||||
|
||||
exports.generateKey = async function () { // eslint-disable-line require-await
|
||||
return nacl.sign.keyPair()
|
||||
exports.generateKey = function (callback) {
|
||||
nextTick(() => {
|
||||
let result
|
||||
try {
|
||||
result = nacl.sign.keyPair()
|
||||
} catch (err) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
// seed should be a 32 byte uint8array
|
||||
exports.generateKeyFromSeed = async function (seed) { // eslint-disable-line require-await
|
||||
return nacl.sign.keyPair.fromSeed(seed)
|
||||
exports.generateKeyFromSeed = function (seed, callback) {
|
||||
nextTick(() => {
|
||||
let result
|
||||
try {
|
||||
result = nacl.sign.keyPair.fromSeed(seed)
|
||||
} catch (err) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
exports.hashAndSign = async function (key, msg) { // eslint-disable-line require-await
|
||||
return Buffer.from(nacl.sign.detached(msg, key))
|
||||
exports.hashAndSign = function (key, msg, callback) {
|
||||
nextTick(() => {
|
||||
callback(null, Buffer.from(nacl.sign.detached(msg, key)))
|
||||
})
|
||||
}
|
||||
|
||||
exports.hashAndVerify = async function (key, sig, msg) { // eslint-disable-line require-await
|
||||
return nacl.sign.detached.verify(msg, sig, key)
|
||||
exports.hashAndVerify = function (key, sig, msg, callback) {
|
||||
nextTick(() => {
|
||||
let result
|
||||
try {
|
||||
result = nacl.sign.detached.verify(msg, sig, key)
|
||||
} catch (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
callback(null, result)
|
||||
})
|
||||
}
|
||||
|
@@ -6,4 +6,6 @@ const ecdh = require('./ecdh')
|
||||
// the shared secret key.
|
||||
//
|
||||
// Focuses only on ECDH now, but can be made more general in the future.
|
||||
module.exports = async (curve) => ecdh.generateEphmeralKeyPair(curve) // eslint-disable-line require-await
|
||||
module.exports = (curve, callback) => {
|
||||
ecdh.generateEphmeralKeyPair(curve, callback)
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ require('node-forge/lib/asn1')
|
||||
require('node-forge/lib/rsa')
|
||||
require('node-forge/lib/pbe')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
const errcode = require('err-code')
|
||||
|
||||
exports = module.exports
|
||||
|
||||
@@ -19,36 +18,36 @@ const supportedKeys = {
|
||||
exports.supportedKeys = supportedKeys
|
||||
exports.keysPBM = keysPBM
|
||||
|
||||
const ErrMissingSecp256K1 = {
|
||||
message: 'secp256k1 support requires libp2p-crypto-secp256k1 package',
|
||||
code: 'ERR_MISSING_PACKAGE'
|
||||
}
|
||||
|
||||
function typeToKey (type) {
|
||||
const key = supportedKeys[type.toLowerCase()]
|
||||
if (!key) {
|
||||
const supported = Object.keys(supportedKeys).join(' / ')
|
||||
throw errcode(new Error(`invalid or unsupported key type ${type}. Must be ${supported}`), 'ERR_UNSUPPORTED_KEY_TYPE')
|
||||
}
|
||||
return key
|
||||
function isValidKeyType (keyType) {
|
||||
const key = supportedKeys[keyType.toLowerCase()]
|
||||
return key !== undefined
|
||||
}
|
||||
|
||||
exports.keyStretcher = require('./key-stretcher')
|
||||
exports.generateEphemeralKeyPair = require('./ephemeral-keys')
|
||||
|
||||
// Generates a keypair of the given type and bitsize
|
||||
exports.generateKeyPair = async (type, bits) => { // eslint-disable-line require-await
|
||||
return typeToKey(type).generateKeyPair(bits)
|
||||
exports.generateKeyPair = (type, bits, cb) => {
|
||||
const key = supportedKeys[type.toLowerCase()]
|
||||
|
||||
if (!key) {
|
||||
return cb(new Error('invalid or unsupported key type'))
|
||||
}
|
||||
|
||||
key.generateKeyPair(bits, cb)
|
||||
}
|
||||
|
||||
// Generates a keypair of the given type and bitsize
|
||||
// seed is a 32 byte uint8array
|
||||
exports.generateKeyPairFromSeed = async (type, seed, bits) => { // eslint-disable-line require-await
|
||||
const key = typeToKey(type)
|
||||
if (type.toLowerCase() !== 'ed25519') {
|
||||
throw errcode(new Error('Seed key derivation is unimplemented for RSA or secp256k1'), 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE')
|
||||
exports.generateKeyPairFromSeed = (type, seed, bits, cb) => {
|
||||
const key = supportedKeys[type.toLowerCase()]
|
||||
if (!key) {
|
||||
return cb(new Error('invalid or unsupported key type'))
|
||||
}
|
||||
return key.generateKeyPairFromSeed(seed, bits)
|
||||
if (type.toLowerCase() !== 'ed25519') {
|
||||
return cb(new Error('Seed key derivation is unimplemented for RSA or secp256k1'))
|
||||
}
|
||||
key.generateKeyPairFromSeed(seed, bits, cb)
|
||||
}
|
||||
|
||||
// Converts a protobuf serialized public key into its
|
||||
@@ -66,55 +65,71 @@ exports.unmarshalPublicKey = (buf) => {
|
||||
if (supportedKeys.secp256k1) {
|
||||
return supportedKeys.secp256k1.unmarshalSecp256k1PublicKey(data)
|
||||
} else {
|
||||
throw errcode(new Error(ErrMissingSecp256K1.message), ErrMissingSecp256K1.code)
|
||||
throw new Error('secp256k1 support requires libp2p-crypto-secp256k1 package')
|
||||
}
|
||||
default:
|
||||
typeToKey(decoded.Type) // throws because type is not supported
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a public key object into a protobuf serialized public key
|
||||
exports.marshalPublicKey = (key, type) => {
|
||||
type = (type || 'rsa').toLowerCase()
|
||||
typeToKey(type) // check type
|
||||
if (!isValidKeyType(type)) {
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
|
||||
return key.bytes
|
||||
}
|
||||
|
||||
// Converts a protobuf serialized private key into its
|
||||
// representative object
|
||||
exports.unmarshalPrivateKey = async (buf) => { // eslint-disable-line require-await
|
||||
const decoded = keysPBM.PrivateKey.decode(buf)
|
||||
exports.unmarshalPrivateKey = (buf, callback) => {
|
||||
let decoded
|
||||
try {
|
||||
decoded = keysPBM.PrivateKey.decode(buf)
|
||||
} catch (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
const data = decoded.Data
|
||||
|
||||
switch (decoded.Type) {
|
||||
case keysPBM.KeyType.RSA:
|
||||
return supportedKeys.rsa.unmarshalRsaPrivateKey(data)
|
||||
return supportedKeys.rsa.unmarshalRsaPrivateKey(data, callback)
|
||||
case keysPBM.KeyType.Ed25519:
|
||||
return supportedKeys.ed25519.unmarshalEd25519PrivateKey(data)
|
||||
return supportedKeys.ed25519.unmarshalEd25519PrivateKey(data, callback)
|
||||
case keysPBM.KeyType.Secp256k1:
|
||||
if (supportedKeys.secp256k1) {
|
||||
return supportedKeys.secp256k1.unmarshalSecp256k1PrivateKey(data)
|
||||
return supportedKeys.secp256k1.unmarshalSecp256k1PrivateKey(data, callback)
|
||||
} else {
|
||||
throw errcode(new Error(ErrMissingSecp256K1.message), ErrMissingSecp256K1.code)
|
||||
return callback(new Error('secp256k1 support requires libp2p-crypto-secp256k1 package'))
|
||||
}
|
||||
default:
|
||||
typeToKey(decoded.Type) // throws because type is not supported
|
||||
callback(new Error('invalid or unsupported key type'))
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a private key object into a protobuf serialized private key
|
||||
exports.marshalPrivateKey = (key, type) => {
|
||||
type = (type || 'rsa').toLowerCase()
|
||||
typeToKey(type) // check type
|
||||
if (!isValidKeyType(type)) {
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
|
||||
return key.bytes
|
||||
}
|
||||
|
||||
exports.import = async (pem, password) => { // eslint-disable-line require-await
|
||||
const key = forge.pki.decryptRsaPrivateKey(pem, password)
|
||||
if (key === null) {
|
||||
throw errcode(new Error('Cannot read the key, most likely the password is wrong or not a RSA key'), 'ERR_CANNOT_DECRYPT_PEM')
|
||||
exports.import = (pem, password, callback) => {
|
||||
try {
|
||||
const key = forge.pki.decryptRsaPrivateKey(pem, password)
|
||||
if (key === null) {
|
||||
throw new Error('Cannot read the key, most likely the password is wrong or not a RSA key')
|
||||
}
|
||||
let der = forge.asn1.toDer(forge.pki.privateKeyToAsn1(key))
|
||||
der = Buffer.from(der.getBytes(), 'binary')
|
||||
return supportedKeys.rsa.unmarshalRsaPrivateKey(der, callback)
|
||||
} catch (err) {
|
||||
callback(err)
|
||||
}
|
||||
let der = forge.asn1.toDer(forge.pki.privateKeyToAsn1(key))
|
||||
der = Buffer.from(der.getBytes(), 'binary')
|
||||
return supportedKeys.rsa.unmarshalRsaPrivateKey(der)
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
const whilst = require('async/whilst')
|
||||
const hmac = require('../hmac')
|
||||
|
||||
const cipherMap = {
|
||||
@@ -20,16 +20,15 @@ const cipherMap = {
|
||||
|
||||
// Generates a set of keys for each party by stretching the shared key.
|
||||
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
|
||||
module.exports = async (cipherType, hash, secret) => {
|
||||
module.exports = (cipherType, hash, secret, callback) => {
|
||||
const cipher = cipherMap[cipherType]
|
||||
|
||||
if (!cipher) {
|
||||
const allowed = Object.keys(cipherMap).join(' / ')
|
||||
throw errcode(new Error(`unknown cipher type '${cipherType}'. Must be ${allowed}`), 'ERR_INVALID_CIPHER_TYPE')
|
||||
return callback(new Error('unkown cipherType passed'))
|
||||
}
|
||||
|
||||
if (!hash) {
|
||||
throw errcode(new Error('missing hash type'), 'ERR_MISSING_HASH_TYPE')
|
||||
return callback(new Error('unkown hashType passed'))
|
||||
}
|
||||
|
||||
const cipherKeySize = cipher.keySize
|
||||
@@ -38,38 +37,72 @@ module.exports = async (cipherType, hash, secret) => {
|
||||
const seed = Buffer.from('key expansion')
|
||||
const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize)
|
||||
|
||||
const m = await hmac.create(hash, secret)
|
||||
let a = await m.digest(seed)
|
||||
|
||||
const result = []
|
||||
let j = 0
|
||||
|
||||
while (j < resultLength) {
|
||||
const b = await m.digest(Buffer.concat([a, seed]))
|
||||
let todo = b.length
|
||||
|
||||
if (j + todo > resultLength) {
|
||||
todo = resultLength - j
|
||||
hmac.create(hash, secret, (err, m) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
result.push(b)
|
||||
j += todo
|
||||
a = await m.digest(a)
|
||||
}
|
||||
m.digest(seed, (err, a) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
const half = resultLength / 2
|
||||
const resultBuffer = Buffer.concat(result)
|
||||
const r1 = resultBuffer.slice(0, half)
|
||||
const r2 = resultBuffer.slice(half, resultLength)
|
||||
const result = []
|
||||
let j = 0
|
||||
|
||||
const createKey = (res) => ({
|
||||
iv: res.slice(0, ivSize),
|
||||
cipherKey: res.slice(ivSize, ivSize + cipherKeySize),
|
||||
macKey: res.slice(ivSize + cipherKeySize)
|
||||
whilst(
|
||||
() => j < resultLength,
|
||||
stretch,
|
||||
finish
|
||||
)
|
||||
|
||||
function stretch (cb) {
|
||||
m.digest(Buffer.concat([a, seed]), (err, b) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
let todo = b.length
|
||||
|
||||
if (j + todo > resultLength) {
|
||||
todo = resultLength - j
|
||||
}
|
||||
|
||||
result.push(b)
|
||||
|
||||
j += todo
|
||||
|
||||
m.digest(a, (err, _a) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
a = _a
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function finish (err) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
const half = resultLength / 2
|
||||
const resultBuffer = Buffer.concat(result)
|
||||
const r1 = resultBuffer.slice(0, half)
|
||||
const r2 = resultBuffer.slice(half, resultLength)
|
||||
|
||||
const createKey = (res) => ({
|
||||
iv: res.slice(0, ivSize),
|
||||
cipherKey: res.slice(ivSize, ivSize + cipherKeySize),
|
||||
macKey: res.slice(ivSize + cipherKeySize)
|
||||
})
|
||||
|
||||
callback(null, {
|
||||
k1: createKey(r1),
|
||||
k2: createKey(r2)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
k1: createKey(r1),
|
||||
k2: createKey(r2)
|
||||
}
|
||||
}
|
||||
|
@@ -12,4 +12,4 @@ message PublicKey {
|
||||
message PrivateKey {
|
||||
required KeyType Type = 1;
|
||||
required bytes Data = 2;
|
||||
}`
|
||||
}`
|
@@ -1,12 +1,13 @@
|
||||
'use strict'
|
||||
|
||||
const nodeify = require('../nodeify')
|
||||
const webcrypto = require('../webcrypto')
|
||||
const randomBytes = require('../random-bytes')
|
||||
|
||||
exports.utils = require('./rsa-utils')
|
||||
|
||||
exports.generateKey = async function (bits) {
|
||||
const pair = await webcrypto.get().subtle.generateKey(
|
||||
exports.generateKey = function (bits, callback) {
|
||||
nodeify(webcrypto.subtle.generateKey(
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: bits,
|
||||
@@ -16,18 +17,16 @@ exports.generateKey = async function (bits) {
|
||||
true,
|
||||
['sign', 'verify']
|
||||
)
|
||||
|
||||
const keys = await exportKey(pair)
|
||||
|
||||
return {
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
}
|
||||
.then(exportKey)
|
||||
.then((keys) => ({
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
})), callback)
|
||||
}
|
||||
|
||||
// Takes a jwk key
|
||||
exports.unmarshalPrivateKey = async function (key) {
|
||||
const privateKey = await webcrypto.get().subtle.importKey(
|
||||
exports.unmarshalPrivateKey = function (key, callback) {
|
||||
const privateKey = webcrypto.subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
@@ -38,26 +37,22 @@ exports.unmarshalPrivateKey = async function (key) {
|
||||
['sign']
|
||||
)
|
||||
|
||||
const pair = [
|
||||
nodeify(Promise.all([
|
||||
privateKey,
|
||||
await derivePublicFromPrivate(key)
|
||||
]
|
||||
|
||||
const keys = await exportKey({
|
||||
privateKey: pair[0],
|
||||
publicKey: pair[1]
|
||||
})
|
||||
|
||||
return {
|
||||
derivePublicFromPrivate(key)
|
||||
]).then((keys) => exportKey({
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
}
|
||||
})).then((keys) => ({
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
})), callback)
|
||||
}
|
||||
|
||||
exports.getRandomValues = randomBytes
|
||||
|
||||
exports.hashAndSign = async function (key, msg) {
|
||||
const privateKey = await webcrypto.get().subtle.importKey(
|
||||
exports.hashAndSign = function (key, msg, callback) {
|
||||
nodeify(webcrypto.subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
@@ -66,19 +61,17 @@ exports.hashAndSign = async function (key, msg) {
|
||||
},
|
||||
false,
|
||||
['sign']
|
||||
)
|
||||
|
||||
const sig = await webcrypto.get().subtle.sign(
|
||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||
privateKey,
|
||||
Uint8Array.from(msg)
|
||||
)
|
||||
|
||||
return Buffer.from(sig)
|
||||
).then((privateKey) => {
|
||||
return webcrypto.subtle.sign(
|
||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||
privateKey,
|
||||
Uint8Array.from(msg)
|
||||
)
|
||||
}).then((sig) => Buffer.from(sig)), callback)
|
||||
}
|
||||
|
||||
exports.hashAndVerify = async function (key, sig, msg) {
|
||||
const publicKey = await webcrypto.get().subtle.importKey(
|
||||
exports.hashAndVerify = function (key, sig, msg, callback) {
|
||||
nodeify(webcrypto.subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
@@ -87,25 +80,25 @@ exports.hashAndVerify = async function (key, sig, msg) {
|
||||
},
|
||||
false,
|
||||
['verify']
|
||||
)
|
||||
|
||||
return webcrypto.get().subtle.verify(
|
||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||
publicKey,
|
||||
sig,
|
||||
msg
|
||||
)
|
||||
).then((publicKey) => {
|
||||
return webcrypto.subtle.verify(
|
||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||
publicKey,
|
||||
sig,
|
||||
msg
|
||||
)
|
||||
}), callback)
|
||||
}
|
||||
|
||||
function exportKey (pair) {
|
||||
return Promise.all([
|
||||
webcrypto.get().subtle.exportKey('jwk', pair.privateKey),
|
||||
webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
|
||||
webcrypto.subtle.exportKey('jwk', pair.privateKey),
|
||||
webcrypto.subtle.exportKey('jwk', pair.publicKey)
|
||||
])
|
||||
}
|
||||
|
||||
function derivePublicFromPrivate (jwKey) {
|
||||
return webcrypto.get().subtle.importKey(
|
||||
return webcrypto.subtle.importKey(
|
||||
'jwk',
|
||||
{
|
||||
kty: jwKey.kty,
|
||||
|
@@ -3,7 +3,7 @@
|
||||
const multihashing = require('multihashing-async')
|
||||
const protobuf = require('protons')
|
||||
const bs58 = require('bs58')
|
||||
const errcode = require('err-code')
|
||||
const nextTick = require('async/nextTick')
|
||||
|
||||
const crypto = require('./rsa')
|
||||
const pbm = protobuf(require('./keys.proto'))
|
||||
@@ -16,8 +16,9 @@ class RsaPublicKey {
|
||||
this._key = key
|
||||
}
|
||||
|
||||
async verify (data, sig) { // eslint-disable-line require-await
|
||||
return crypto.hashAndVerify(this._key, sig, data)
|
||||
verify (data, sig, callback) {
|
||||
ensure(callback)
|
||||
crypto.hashAndVerify(this._key, sig, data, callback)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
@@ -31,19 +32,31 @@ class RsaPublicKey {
|
||||
})
|
||||
}
|
||||
|
||||
encrypt (bytes) {
|
||||
return crypto.encrypt(this._key, bytes)
|
||||
encrypt (bytes, cb) {
|
||||
return cbWrap(() => crypto.encrypt(this._key, bytes), cb)
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return this.bytes.equals(key.bytes)
|
||||
}
|
||||
|
||||
async hash () { // eslint-disable-line require-await
|
||||
return multihashing(this.bytes, 'sha2-256')
|
||||
hash (callback) {
|
||||
ensure(callback)
|
||||
multihashing(this.bytes, 'sha2-256', callback)
|
||||
}
|
||||
}
|
||||
|
||||
function cbWrap (f, cb) {
|
||||
let res
|
||||
try {
|
||||
res = f()
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
|
||||
return cb(null, res)
|
||||
}
|
||||
|
||||
class RsaPrivateKey {
|
||||
// key - Object of the jwk format
|
||||
// publicKey - Buffer of the spki format
|
||||
@@ -56,20 +69,21 @@ class RsaPrivateKey {
|
||||
return crypto.getRandomValues(16)
|
||||
}
|
||||
|
||||
async sign (message) { // eslint-disable-line require-await
|
||||
return crypto.hashAndSign(this._key, message)
|
||||
sign (message, callback) {
|
||||
ensure(callback)
|
||||
crypto.hashAndSign(this._key, message, callback)
|
||||
}
|
||||
|
||||
get public () {
|
||||
if (!this._publicKey) {
|
||||
throw errcode(new Error('public key not provided'), 'ERR_PUBKEY_NOT_PROVIDED')
|
||||
throw new Error('public key not provided')
|
||||
}
|
||||
|
||||
return new RsaPublicKey(this._publicKey)
|
||||
}
|
||||
|
||||
decrypt (bytes) {
|
||||
return crypto.decrypt(this._key, bytes)
|
||||
decrypt (bytes, cb) {
|
||||
cbWrap(() => crypto.decrypt(this._key, bytes), cb)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
@@ -87,8 +101,9 @@ class RsaPrivateKey {
|
||||
return this.bytes.equals(key.bytes)
|
||||
}
|
||||
|
||||
async hash () { // eslint-disable-line require-await
|
||||
return multihashing(this.bytes, 'sha2-256')
|
||||
hash (callback) {
|
||||
ensure(callback)
|
||||
multihashing(this.bytes, 'sha2-256', callback)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,61 +113,104 @@ class RsaPrivateKey {
|
||||
* The public key is a protobuf encoding containing a type and the DER encoding
|
||||
* of the PKCS SubjectPublicKeyInfo.
|
||||
*
|
||||
* @returns {Promise<String>}
|
||||
* @param {function(Error, id)} callback
|
||||
* @returns {undefined}
|
||||
*/
|
||||
async id () {
|
||||
const hash = await this.public.hash()
|
||||
return bs58.encode(hash)
|
||||
id (callback) {
|
||||
this.public.hash((err, hash) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null, bs58.encode(hash))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the key into a password protected PEM format
|
||||
*
|
||||
* @param {string} password - The password to read the encrypted PEM
|
||||
* @param {string} [format] - Defaults to 'pkcs-8'.
|
||||
* @param {string} password - The password to read the encrypted PEM
|
||||
* @param {function(Error, KeyInfo)} callback
|
||||
* @returns {undefined}
|
||||
*/
|
||||
async export (password, format = 'pkcs-8') { // eslint-disable-line require-await
|
||||
let pem = null
|
||||
|
||||
const buffer = new forge.util.ByteBuffer(this.marshal())
|
||||
const asn1 = forge.asn1.fromDer(buffer)
|
||||
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
|
||||
|
||||
if (format === 'pkcs-8') {
|
||||
const options = {
|
||||
algorithm: 'aes256',
|
||||
count: 10000,
|
||||
saltSize: 128 / 8,
|
||||
prfAlgorithm: 'sha512'
|
||||
}
|
||||
pem = forge.pki.encryptRsaPrivateKey(privateKey, password, options)
|
||||
} else {
|
||||
throw errcode(new Error(`Unknown export format '${format}'. Must be pkcs-8`), 'ERR_INVALID_EXPORT_FORMAT')
|
||||
export (format, password, callback) {
|
||||
if (typeof password === 'function') {
|
||||
callback = password
|
||||
password = format
|
||||
format = 'pkcs-8'
|
||||
}
|
||||
|
||||
return pem
|
||||
ensure(callback)
|
||||
|
||||
nextTick(() => {
|
||||
let err = null
|
||||
let pem = null
|
||||
try {
|
||||
const buffer = new forge.util.ByteBuffer(this.marshal())
|
||||
const asn1 = forge.asn1.fromDer(buffer)
|
||||
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
|
||||
if (format === 'pkcs-8') {
|
||||
const options = {
|
||||
algorithm: 'aes256',
|
||||
count: 10000,
|
||||
saltSize: 128 / 8,
|
||||
prfAlgorithm: 'sha512'
|
||||
}
|
||||
pem = forge.pki.encryptRsaPrivateKey(privateKey, password, options)
|
||||
} else {
|
||||
err = new Error(`Unknown export format '${format}'`)
|
||||
}
|
||||
} catch (_err) {
|
||||
err = _err
|
||||
}
|
||||
|
||||
callback(err, pem)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function unmarshalRsaPrivateKey (bytes) {
|
||||
function unmarshalRsaPrivateKey (bytes, callback) {
|
||||
const jwk = crypto.utils.pkcs1ToJwk(bytes)
|
||||
const keys = await crypto.unmarshalPrivateKey(jwk)
|
||||
return new RsaPrivateKey(keys.privateKey, keys.publicKey)
|
||||
|
||||
crypto.unmarshalPrivateKey(jwk, (err, keys) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
callback(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
|
||||
})
|
||||
}
|
||||
|
||||
function unmarshalRsaPublicKey (bytes) {
|
||||
const jwk = crypto.utils.pkixToJwk(bytes)
|
||||
|
||||
return new RsaPublicKey(jwk)
|
||||
}
|
||||
|
||||
async function fromJwk (jwk) {
|
||||
const keys = await crypto.unmarshalPrivateKey(jwk)
|
||||
return new RsaPrivateKey(keys.privateKey, keys.publicKey)
|
||||
function fromJwk (jwk, callback) {
|
||||
crypto.unmarshalPrivateKey(jwk, (err, keys) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
callback(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
|
||||
})
|
||||
}
|
||||
|
||||
async function generateKeyPair (bits) {
|
||||
const keys = await crypto.generateKey(bits)
|
||||
return new RsaPrivateKey(keys.privateKey, keys.publicKey)
|
||||
function generateKeyPair (bits, callback) {
|
||||
crypto.generateKey(bits, (err, keys) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
callback(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
|
||||
})
|
||||
}
|
||||
|
||||
function ensure (callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error('callback is required')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@@ -1,12 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
const errcode = require('err-code')
|
||||
const randomBytes = require('../random-bytes')
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {PrivateKey}
|
||||
*/
|
||||
const nextTick = require('async/nextTick')
|
||||
|
||||
let keypair
|
||||
try {
|
||||
if (process.env.LP2P_FORCE_CRYPTO_LIB === 'keypair') {
|
||||
@@ -33,43 +30,72 @@ const jwkToPem = require('pem-jwk').jwk2pem
|
||||
|
||||
exports.utils = require('./rsa-utils')
|
||||
|
||||
exports.generateKey = async function (bits) { // eslint-disable-line require-await
|
||||
const key = keypair({ bits })
|
||||
return {
|
||||
privateKey: pemToJwk(key.private),
|
||||
publicKey: pemToJwk(key.public)
|
||||
}
|
||||
exports.generateKey = function (bits, callback) {
|
||||
nextTick(() => {
|
||||
let result
|
||||
try {
|
||||
const key = keypair({ bits: bits })
|
||||
result = {
|
||||
privateKey: pemToJwk(key.private),
|
||||
publicKey: pemToJwk(key.public)
|
||||
}
|
||||
} catch (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
callback(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
// Takes a jwk key
|
||||
exports.unmarshalPrivateKey = async function (key) { // eslint-disable-line require-await
|
||||
if (!key) {
|
||||
throw errcode(new Error('Missing key parameter'), 'ERR_MISSING_KEY')
|
||||
}
|
||||
return {
|
||||
privateKey: key,
|
||||
publicKey: {
|
||||
kty: key.kty,
|
||||
n: key.n,
|
||||
e: key.e
|
||||
exports.unmarshalPrivateKey = function (key, callback) {
|
||||
nextTick(() => {
|
||||
if (!key) {
|
||||
return callback(new Error('Key is invalid'))
|
||||
}
|
||||
}
|
||||
callback(null, {
|
||||
privateKey: key,
|
||||
publicKey: {
|
||||
kty: key.kty,
|
||||
n: key.n,
|
||||
e: key.e
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.getRandomValues = randomBytes
|
||||
|
||||
exports.hashAndSign = async function (key, msg) { // eslint-disable-line require-await
|
||||
const sign = crypto.createSign('RSA-SHA256')
|
||||
sign.update(msg)
|
||||
const pem = jwkToPem(key)
|
||||
return sign.sign(pem)
|
||||
exports.hashAndSign = function (key, msg, callback) {
|
||||
nextTick(() => {
|
||||
let result
|
||||
try {
|
||||
const sign = crypto.createSign('RSA-SHA256')
|
||||
sign.update(msg)
|
||||
const pem = jwkToPem(key)
|
||||
result = sign.sign(pem)
|
||||
} catch (err) {
|
||||
return callback(new Error('Key or message is invalid!: ' + err.message))
|
||||
}
|
||||
|
||||
callback(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
exports.hashAndVerify = async function (key, sig, msg) { // eslint-disable-line require-await
|
||||
const verify = crypto.createVerify('RSA-SHA256')
|
||||
verify.update(msg)
|
||||
const pem = jwkToPem(key)
|
||||
return verify.verify(pem, sig)
|
||||
exports.hashAndVerify = function (key, sig, msg, callback) {
|
||||
nextTick(() => {
|
||||
let result
|
||||
try {
|
||||
const verify = crypto.createVerify('RSA-SHA256')
|
||||
verify.update(msg)
|
||||
const pem = jwkToPem(key)
|
||||
result = verify.verify(pem, sig)
|
||||
} catch (err) {
|
||||
return callback(new Error('Key or message is invalid!:' + err.message))
|
||||
}
|
||||
|
||||
callback(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
const padding = crypto.constants.RSA_PKCS1_PADDING
|
||||
|
@@ -1,10 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
|
||||
module.exports = function (curveTypes, type) {
|
||||
if (!curveTypes.includes(type)) {
|
||||
const names = curveTypes.join(' / ')
|
||||
throw errcode(new Error(`Unknown curve: ${type}. Must be ${names}`), 'ERR_INVALID_CURVE')
|
||||
}
|
||||
}
|
11
src/nodeify.js
Normal file
11
src/nodeify.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
// Based on npmjs.com/nodeify but without additional `nextTick` calls
|
||||
// to keep the overhead low
|
||||
module.exports = function nodeify (promise, cb) {
|
||||
return promise.then((res) => {
|
||||
cb(null, res)
|
||||
}, (err) => {
|
||||
cb(err)
|
||||
})
|
||||
}
|
@@ -2,7 +2,6 @@
|
||||
|
||||
const forgePbkdf2 = require('node-forge/lib/pbkdf2')
|
||||
const forgeUtil = require('node-forge/lib/util')
|
||||
const errcode = require('err-code')
|
||||
|
||||
/**
|
||||
* Maps an IPFS hash name to its node-forge equivalent.
|
||||
@@ -30,8 +29,7 @@ const hashName = {
|
||||
function pbkdf2 (password, salt, iterations, keySize, hash) {
|
||||
const hasher = hashName[hash]
|
||||
if (!hasher) {
|
||||
const types = Object.keys(hashName).join(' / ')
|
||||
throw errcode(new Error(`Hash '${hash}' is unknown or not supported. Must be ${types}`), 'ERR_UNSUPPORTED_HASH_TYPE')
|
||||
throw new Error(`Hash '${hash}' is unknown or not supported`)
|
||||
}
|
||||
const dek = forgePbkdf2(
|
||||
password,
|
||||
|
@@ -1,10 +1,9 @@
|
||||
'use strict'
|
||||
const randomBytes = require('iso-random-stream/src/random')
|
||||
const errcode = require('err-code')
|
||||
|
||||
module.exports = function (length) {
|
||||
if (isNaN(length) || length <= 0) {
|
||||
throw errcode(new Error('random bytes length must be a Number bigger than 0'), 'ERR_INVALID_LENGTH')
|
||||
module.exports = function (number) {
|
||||
if (!number || typeof number !== 'number') {
|
||||
throw new Error('first argument must be a Number bigger than 0')
|
||||
}
|
||||
return randomBytes(length)
|
||||
return randomBytes(number)
|
||||
}
|
||||
|
@@ -1,24 +1,5 @@
|
||||
/* eslint-env browser */
|
||||
/* global self */
|
||||
|
||||
'use strict'
|
||||
|
||||
// Check native crypto exists and is enabled (In insecure context `self.crypto`
|
||||
// exists but `self.crypto.subtle` does not).
|
||||
exports.get = (win = self) => {
|
||||
const nativeCrypto = win.crypto || win.msCrypto
|
||||
|
||||
if (!nativeCrypto || !nativeCrypto.subtle) {
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
'Missing Web Crypto API. ' +
|
||||
'The most likely cause of this error is that this page is being accessed ' +
|
||||
'from an insecure context (i.e. not HTTPS). For more information and ' +
|
||||
'possible resolutions see ' +
|
||||
'https://github.com/libp2p/js-libp2p-crypto/blob/master/README.md#web-crypto-api'
|
||||
),
|
||||
{ code: 'ERR_MISSING_WEB_CRYPTO' }
|
||||
)
|
||||
}
|
||||
|
||||
return nativeCrypto
|
||||
}
|
||||
module.exports = self.crypto || self.msCrypto
|
||||
|
@@ -1,5 +1,4 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
@@ -7,7 +6,7 @@ const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const { expectErrCode } = require('../util')
|
||||
const series = require('async/series')
|
||||
|
||||
const crypto = require('../../src')
|
||||
const fixtures = require('./../fixtures/aes')
|
||||
@@ -18,47 +17,55 @@ const bytes = {
|
||||
32: 'AES-256'
|
||||
}
|
||||
|
||||
/** @typedef {import("libp2p-crypto").aes.Cipher} Cipher */
|
||||
|
||||
describe('AES-CTR', () => {
|
||||
Object.keys(bytes).forEach((byte) => {
|
||||
it(`${bytes[byte]} - encrypt and decrypt`, async () => {
|
||||
it(`${bytes[byte]} - encrypt and decrypt`, (done) => {
|
||||
const key = Buffer.alloc(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = Buffer.alloc(16)
|
||||
iv.fill(1)
|
||||
|
||||
const cipher = await crypto.aes.create(key, iv)
|
||||
crypto.aes.create(key, iv, (err, cipher) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
await encryptAndDecrypt(cipher)
|
||||
await encryptAndDecrypt(cipher)
|
||||
await encryptAndDecrypt(cipher)
|
||||
await encryptAndDecrypt(cipher)
|
||||
await encryptAndDecrypt(cipher)
|
||||
series([
|
||||
encryptAndDecrypt(cipher),
|
||||
encryptAndDecrypt(cipher),
|
||||
encryptAndDecrypt(cipher),
|
||||
encryptAndDecrypt(cipher),
|
||||
encryptAndDecrypt(cipher)
|
||||
], done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Object.keys(bytes).forEach((byte) => {
|
||||
it(`${bytes[byte]} - fixed - encrypt and decrypt`, async () => {
|
||||
it(`${bytes[byte]} - fixed - encrypt and decrypt`, (done) => {
|
||||
const key = Buffer.alloc(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = Buffer.alloc(16)
|
||||
iv.fill(1)
|
||||
|
||||
const cipher = await crypto.aes.create(key, iv)
|
||||
crypto.aes.create(key, iv, (err, cipher) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
for (let i = 0; i < fixtures[byte].inputs.length; i++) {
|
||||
const rawIn = fixtures[byte].inputs[i]
|
||||
const input = Buffer.from(rawIn)
|
||||
const output = Buffer.from(fixtures[byte].outputs[i])
|
||||
const encrypted = await cipher.encrypt(input)
|
||||
expect(encrypted).to.have.length(output.length)
|
||||
expect(encrypted).to.eql(output)
|
||||
const decrypted = await cipher.decrypt(encrypted)
|
||||
expect(decrypted).to.eql(input)
|
||||
}
|
||||
series(fixtures[byte].inputs.map((rawIn, i) => (cb) => {
|
||||
const input = Buffer.from(rawIn)
|
||||
const output = Buffer.from(fixtures[byte].outputs[i])
|
||||
cipher.encrypt(input, (err, res) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(res).to.have.length(output.length)
|
||||
expect(res).to.eql(output)
|
||||
cipher.decrypt(res, (err, res) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(res).to.eql(input)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}), done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -67,45 +74,46 @@ describe('AES-CTR', () => {
|
||||
return
|
||||
}
|
||||
|
||||
it(`${bytes[byte]} - go interop - encrypt and decrypt`, async () => {
|
||||
it(`${bytes[byte]} - go interop - encrypt and decrypt`, (done) => {
|
||||
const key = Buffer.alloc(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = Buffer.alloc(16)
|
||||
iv.fill(1)
|
||||
|
||||
const cipher = await crypto.aes.create(key, iv)
|
||||
crypto.aes.create(key, iv, (err, cipher) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
for (let i = 0; i < goFixtures[byte].inputs.length; i++) {
|
||||
const rawIn = goFixtures[byte].inputs[i]
|
||||
const input = Buffer.from(rawIn)
|
||||
const output = Buffer.from(goFixtures[byte].outputs[i])
|
||||
const encrypted = await cipher.encrypt(input)
|
||||
expect(encrypted).to.have.length(output.length)
|
||||
expect(encrypted).to.eql(output)
|
||||
const decrypted = await cipher.decrypt(encrypted)
|
||||
expect(decrypted).to.eql(input)
|
||||
}
|
||||
series(goFixtures[byte].inputs.map((rawIn, i) => (cb) => {
|
||||
const input = Buffer.from(rawIn)
|
||||
const output = Buffer.from(goFixtures[byte].outputs[i])
|
||||
cipher.encrypt(input, (err, res) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(res).to.have.length(output.length)
|
||||
expect(res).to.be.eql(output)
|
||||
cipher.decrypt(res, (err, res) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(res).to.be.eql(input)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}), done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('checks key length', () => {
|
||||
const key = Buffer.alloc(5)
|
||||
const iv = Buffer.alloc(16)
|
||||
return expectErrCode(crypto.aes.create(key, iv), 'ERR_INVALID_KEY_LENGTH')
|
||||
})
|
||||
})
|
||||
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {function(Cipher): void}
|
||||
*/
|
||||
async function encryptAndDecrypt (cipher) {
|
||||
function encryptAndDecrypt (cipher) {
|
||||
const data = Buffer.alloc(100)
|
||||
data.fill(Math.ceil(Math.random() * 100))
|
||||
|
||||
const encrypted = await cipher.encrypt(data)
|
||||
const decrypted = await cipher.decrypt(encrypted)
|
||||
|
||||
expect(decrypted).to.be.eql(data)
|
||||
return (cb) => {
|
||||
cipher.encrypt(data, (err, res) => {
|
||||
expect(err).to.not.exist()
|
||||
cipher.decrypt(res, (err, res) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(res).to.be.eql(data)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,61 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const crypto = require('../')
|
||||
const webcrypto = require('../src/webcrypto')
|
||||
|
||||
async function expectMissingWebCrypto (fn) {
|
||||
try {
|
||||
await fn()
|
||||
} catch (err) {
|
||||
expect(err.code).to.equal('ERR_MISSING_WEB_CRYPTO')
|
||||
return
|
||||
}
|
||||
throw new Error('Expected missing web crypto error')
|
||||
}
|
||||
|
||||
describe('Missing web crypto', () => {
|
||||
let webcryptoGet
|
||||
let rsaPrivateKey
|
||||
|
||||
before(async () => {
|
||||
rsaPrivateKey = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
})
|
||||
|
||||
before(() => {
|
||||
webcryptoGet = webcrypto.get
|
||||
webcrypto.get = () => webcryptoGet({})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
webcrypto.get = webcryptoGet
|
||||
})
|
||||
|
||||
it('should error for hmac create when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.hmac.create('SHA256', Buffer.from('secret')))
|
||||
})
|
||||
|
||||
it('should error for generate ephemeral key pair when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.generateEphemeralKeyPair('P-256'))
|
||||
})
|
||||
|
||||
it('should error for generate rsa key pair when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.generateKeyPair('rsa', 256))
|
||||
})
|
||||
|
||||
it('should error for unmarshal RSA private key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.unmarshalPrivateKey(crypto.keys.marshalPrivateKey(rsaPrivateKey)))
|
||||
})
|
||||
|
||||
it('should error for sign RSA private key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => rsaPrivateKey.sign(Buffer.from('test')))
|
||||
})
|
||||
|
||||
it('should error for verify RSA public key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => rsaPrivateKey.public.verify(Buffer.from('test'), Buffer.from('test')))
|
||||
})
|
||||
})
|
@@ -8,19 +8,18 @@ const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const crypto = require('../src')
|
||||
const fixtures = require('./fixtures/go-key-rsa')
|
||||
const { expectErrCode } = require('./util')
|
||||
|
||||
/** @typedef {import("libp2p-crypto").PrivateKey} PrivateKey */
|
||||
|
||||
describe('libp2p-crypto', function () {
|
||||
this.timeout(20 * 1000)
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {PrivateKey}
|
||||
*/
|
||||
let key
|
||||
before(async () => {
|
||||
key = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
before((done) => {
|
||||
crypto.keys.generateKeyPair('RSA', 512, (err, _key) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
key = _key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('marshalPublicKey and unmarshalPublicKey', () => {
|
||||
@@ -34,50 +33,71 @@ describe('libp2p-crypto', function () {
|
||||
}).to.throw()
|
||||
})
|
||||
|
||||
it('marshalPrivateKey and unmarshalPrivateKey', async () => {
|
||||
it('marshalPrivateKey and unmarshalPrivateKey', (done) => {
|
||||
expect(() => {
|
||||
crypto.keys.marshalPrivateKey(key, 'invalid-key-type')
|
||||
}).to.throw()
|
||||
|
||||
const key2 = await crypto.keys.unmarshalPrivateKey(crypto.keys.marshalPrivateKey(key))
|
||||
crypto.keys.unmarshalPrivateKey(crypto.keys.marshalPrivateKey(key), (err, key2) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(key2.equals(key)).to.be.eql(true)
|
||||
expect(key2.public.equals(key.public)).to.be.eql(true)
|
||||
})
|
||||
|
||||
it('generateKeyPair', () => {
|
||||
return expectErrCode(crypto.keys.generateKeyPair('invalid-key-type', 512), 'ERR_UNSUPPORTED_KEY_TYPE')
|
||||
})
|
||||
|
||||
it('generateKeyPairFromSeed', () => {
|
||||
var seed = crypto.randomBytes(32)
|
||||
return expectErrCode(crypto.keys.generateKeyPairFromSeed('invalid-key-type', seed, 512), 'ERR_UNSUPPORTED_KEY_TYPE')
|
||||
expect(key2.equals(key)).to.be.eql(true)
|
||||
expect(key2.public.equals(key.public)).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// marshalled keys seem to be slightly different
|
||||
// unsure as to if this is just a difference in encoding
|
||||
// or a bug
|
||||
describe('go interop', () => {
|
||||
it('unmarshals private key', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.private.key)
|
||||
const hash = fixtures.private.hash
|
||||
expect(fixtures.private.key).to.eql(key.bytes)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.eql(hash)
|
||||
it('unmarshals private key', (done) => {
|
||||
crypto.keys.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
const hash = fixtures.private.hash
|
||||
expect(fixtures.private.key).to.eql(key.bytes)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.eql(hash)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('unmarshals public key', async () => {
|
||||
it('unmarshals public key', (done) => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.public.key)
|
||||
const hash = fixtures.public.hash
|
||||
|
||||
expect(crypto.keys.marshalPublicKey(key)).to.eql(fixtures.public.key)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.eql(hash)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.eql(hash)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('unmarshal -> marshal, private key', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.private.key)
|
||||
const marshalled = crypto.keys.marshalPrivateKey(key)
|
||||
expect(marshalled).to.eql(fixtures.private.key)
|
||||
it('unmarshal -> marshal, private key', (done) => {
|
||||
crypto.keys.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
const marshalled = crypto.keys.marshalPrivateKey(key)
|
||||
expect(marshalled).to.eql(fixtures.private.key)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('unmarshal -> marshal, public key', () => {
|
||||
@@ -109,15 +129,14 @@ describe('libp2p-crypto', function () {
|
||||
})
|
||||
|
||||
it('throws on invalid hash name', () => {
|
||||
const fn = () => crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'shaX-xxx')
|
||||
expect(fn).to.throw().with.property('code', 'ERR_UNSUPPORTED_HASH_TYPE')
|
||||
expect(() => crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'shaX-xxx')).to.throw()
|
||||
})
|
||||
})
|
||||
|
||||
describe('randomBytes', () => {
|
||||
it('throws with invalid number passed', () => {
|
||||
it('throws with no number passed', () => {
|
||||
expect(() => {
|
||||
crypto.randomBytes(-1)
|
||||
crypto.randomBytes()
|
||||
}).to.throw()
|
||||
})
|
||||
|
||||
|
@@ -1,6 +1,11 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
|
||||
const util = require('util')
|
||||
const garbage = [Buffer.from('00010203040506070809', 'hex'), {}, null, false, undefined, true, 1, 0, Buffer.from(''), 'aGVsbG93b3JsZA==', 'helloworld', '']
|
||||
|
||||
@@ -18,15 +23,24 @@ function doTests (fncName, fnc, num, skipBuffersAndStrings) {
|
||||
for (let i = 0; i < num; i++) {
|
||||
args.push(garbage)
|
||||
}
|
||||
it(fncName + '(' + args.map(garbage => util.inspect(garbage)).join(', ') + ')', async () => {
|
||||
try {
|
||||
await fnc.apply(null, args)
|
||||
} catch (err) {
|
||||
return // expected
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
it(fncName + '(' + args.map(garbage => util.inspect(garbage)).join(', ') + ')', cb => {
|
||||
args.push((err, res) => {
|
||||
expect(err).to.exist()
|
||||
expect(res).to.not.exist()
|
||||
cb()
|
||||
})
|
||||
|
||||
fnc.apply(null, args)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { doTests }
|
||||
module.exports = (obj, fncs, num) => {
|
||||
describe('returns error via cb instead of crashing', () => {
|
||||
fncs.forEach(fnc => {
|
||||
doTests(fnc, obj[fnc], num)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.doTests = doTests
|
||||
|
@@ -13,10 +13,16 @@ const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
|
||||
describe('HMAC', () => {
|
||||
hashes.forEach((hash) => {
|
||||
it(`${hash} - sign and verify`, async () => {
|
||||
const hmac = await crypto.hmac.create(hash, Buffer.from('secret'))
|
||||
const sig = await hmac.digest(Buffer.from('hello world'))
|
||||
expect(sig).to.have.length(hmac.length)
|
||||
it(`${hash} - sign and verify`, (done) => {
|
||||
crypto.hmac.create(hash, Buffer.from('secret'), (err, hmac) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
hmac.digest(Buffer.from('hello world'), (err, sig) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(sig).to.have.length(hmac.length)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -12,76 +12,118 @@ const fixtures = require('../fixtures/go-key-ed25519')
|
||||
|
||||
const testGarbage = require('../helpers/test-garbage-error-handling')
|
||||
|
||||
/** @typedef {import("libp2p-crypto").PrivateKey} PrivateKey */
|
||||
|
||||
describe('ed25519', function () {
|
||||
this.timeout(20 * 1000)
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {PrivateKey}
|
||||
*/
|
||||
let key
|
||||
before(async () => {
|
||||
key = await crypto.keys.generateKeyPair('Ed25519', 512)
|
||||
before((done) => {
|
||||
crypto.keys.generateKeyPair('Ed25519', 512, (err, _key) => {
|
||||
if (err) return done(err)
|
||||
key = _key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('generates a valid key', async () => {
|
||||
it('generates a valid key', (done) => {
|
||||
expect(key).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.have.length(34)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('generates a valid key from seed', async () => {
|
||||
it('generates a valid key from seed', (done) => {
|
||||
var seed = crypto.randomBytes(32)
|
||||
const seededkey = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
|
||||
expect(seededkey).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
|
||||
const digest = await seededkey.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512, (err, seededkey) => {
|
||||
if (err) return done(err)
|
||||
expect(seededkey).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
|
||||
|
||||
seededkey.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.have.length(34)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('generates the same key from the same seed', async () => {
|
||||
const seed = crypto.randomBytes(32)
|
||||
const seededkey1 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
|
||||
const seededkey2 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
|
||||
expect(seededkey1.equals(seededkey2)).to.eql(true)
|
||||
expect(seededkey1.public.equals(seededkey2.public)).to.eql(true)
|
||||
it('generates the same key from the same seed', (done) => {
|
||||
var seed = crypto.randomBytes(32)
|
||||
crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512, (err, seededkey1) => {
|
||||
if (err) return done(err)
|
||||
crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512, (err, seededkey2) => {
|
||||
if (err) return done(err)
|
||||
expect(seededkey1.equals(seededkey2)).to.eql(true)
|
||||
expect(seededkey1.public.equals(seededkey2.public)).to.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('generates different keys for different seeds', async () => {
|
||||
it('generates different keys for different seeds', (done) => {
|
||||
const seed1 = crypto.randomBytes(32)
|
||||
const seededkey1 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed1, 512)
|
||||
const seed2 = crypto.randomBytes(32)
|
||||
const seededkey2 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed2, 512)
|
||||
expect(seededkey1.equals(seededkey2)).to.eql(false)
|
||||
expect(seededkey1.public.equals(seededkey2.public)).to.eql(false)
|
||||
crypto.keys.generateKeyPairFromSeed('Ed25519', seed1, 512, (err, seededkey1) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
const seed2 = crypto.randomBytes(32)
|
||||
crypto.keys.generateKeyPairFromSeed('Ed25519', seed2, 512, (err, seededkey2) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
expect(seededkey1.equals(seededkey2)).to.eql(false)
|
||||
expect(seededkey1.public.equals(seededkey2.public))
|
||||
.to.eql(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('signs', async () => {
|
||||
it('signs', (done) => {
|
||||
const text = crypto.randomBytes(512)
|
||||
const sig = await key.sign(text)
|
||||
const res = await key.public.verify(text, sig)
|
||||
expect(res).to.be.eql(true)
|
||||
|
||||
key.sign(text, (err, sig) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
key.public.verify(text, sig, (err, res) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(res).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('encoding', async () => {
|
||||
it('encoding', (done) => {
|
||||
const keyMarshal = key.marshal()
|
||||
const key2 = await ed25519.unmarshalEd25519PrivateKey(keyMarshal)
|
||||
const keyMarshal2 = key2.marshal()
|
||||
ed25519.unmarshalEd25519PrivateKey(keyMarshal, (err, key2) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
const keyMarshal2 = key2.marshal()
|
||||
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
|
||||
const pk = key.public
|
||||
const pkMarshal = pk.marshal()
|
||||
const pk2 = ed25519.unmarshalEd25519PublicKey(pkMarshal)
|
||||
const pkMarshal2 = pk2.marshal()
|
||||
const pk = key.public
|
||||
const pkMarshal = pk.marshal()
|
||||
const pk2 = ed25519.unmarshalEd25519PublicKey(pkMarshal)
|
||||
const pkMarshal2 = pk2.marshal()
|
||||
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('key id', async () => {
|
||||
const id = await key.id()
|
||||
expect(id).to.exist()
|
||||
expect(id).to.be.a('string')
|
||||
it('key id', (done) => {
|
||||
key.id((err, id) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(id).to.exist()
|
||||
expect(id).to.be.a('string')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
describe('key equals', () => {
|
||||
@@ -99,56 +141,86 @@ describe('ed25519', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('not equals other key', async () => {
|
||||
const key2 = await crypto.keys.generateKeyPair('Ed25519', 512)
|
||||
expect(key.equals(key2)).to.eql(false)
|
||||
expect(key2.equals(key)).to.eql(false)
|
||||
expect(key.public.equals(key2.public)).to.eql(false)
|
||||
expect(key2.public.equals(key.public)).to.eql(false)
|
||||
it('not equals other key', (done) => {
|
||||
crypto.keys.generateKeyPair('Ed25519', 512, (err, key2) => {
|
||||
if (err) return done(err)
|
||||
|
||||
expect(key.equals(key2)).to.eql(false)
|
||||
expect(key2.equals(key)).to.eql(false)
|
||||
expect(key.public.equals(key2.public)).to.eql(false)
|
||||
expect(key2.public.equals(key.public)).to.eql(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('sign and verify', async () => {
|
||||
it('sign and verify', (done) => {
|
||||
const data = Buffer.from('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(data, sig)
|
||||
expect(valid).to.eql(true)
|
||||
key.sign(data, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(data, sig, (err, valid) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(valid).to.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('fails to verify for different data', async () => {
|
||||
it('fails to verify for different data', (done) => {
|
||||
const data = Buffer.from('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(Buffer.from('hello'), sig)
|
||||
expect(valid).to.be.eql(false)
|
||||
key.sign(data, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(Buffer.from('hello'), sig, (err, valid) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(valid).to.be.eql(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('throws error instead of crashing', () => {
|
||||
describe('returns error via cb instead of crashing', () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
testGarbage.doTests('key.verify', key.verify.bind(key), 2, null)
|
||||
testGarbage.doTests('crypto.keys.unmarshalPrivateKey', crypto.keys.unmarshalPrivateKey.bind(crypto.keys), null, null)
|
||||
testGarbage.doTests('key.verify', key.verify.bind(key), 2)
|
||||
testGarbage.doTests('crypto.keys.unmarshalPrivateKey', crypto.keys.unmarshalPrivateKey.bind(crypto.keys))
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {PrivateKey}
|
||||
*/
|
||||
let privateKey
|
||||
|
||||
before(async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey)
|
||||
privateKey = key
|
||||
before((done) => {
|
||||
crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey, (err, key) => {
|
||||
expect(err).to.not.exist()
|
||||
privateKey = key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('verifies with data from go', async () => {
|
||||
it('verifies with data from go', (done) => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
const ok = await key.verify(fixtures.verify.data, fixtures.verify.signature)
|
||||
expect(ok).to.eql(true)
|
||||
|
||||
key.verify(fixtures.verify.data, fixtures.verify.signature, (err, ok) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(ok).to.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('generates the same signature as go', async () => {
|
||||
const sig = await privateKey.sign(fixtures.verify.data)
|
||||
expect(sig).to.eql(fixtures.verify.signature)
|
||||
it('generates the same signature as go', (done) => {
|
||||
privateKey.sign(fixtures.verify.data, (err, sig) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(sig).to.eql(fixtures.verify.signature)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -6,15 +6,12 @@ const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const parallel = require('async/parallel')
|
||||
|
||||
const fixtures = require('../fixtures/go-elliptic-key')
|
||||
const crypto = require('../../src')
|
||||
|
||||
const curves = ['P-256', 'P-384'] // 'P-521' fails in tests :( no clue why
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {Record<string, number>}
|
||||
*/
|
||||
const lengths = {
|
||||
'P-256': 65,
|
||||
'P-384': 97,
|
||||
@@ -28,50 +25,49 @@ const secretLengths = {
|
||||
|
||||
describe('generateEphemeralKeyPair', () => {
|
||||
curves.forEach((curve) => {
|
||||
it(`generate and shared key ${curve}`, async () => {
|
||||
const keys = await Promise.all([
|
||||
crypto.keys.generateEphemeralKeyPair(curve),
|
||||
crypto.keys.generateEphemeralKeyPair(curve)
|
||||
])
|
||||
it(`generate and shared key ${curve}`, (done) => {
|
||||
parallel([
|
||||
(cb) => crypto.keys.generateEphemeralKeyPair(curve, cb),
|
||||
(cb) => crypto.keys.generateEphemeralKeyPair(curve, cb)
|
||||
], (err, keys) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(keys[0].key).to.have.length(lengths[curve])
|
||||
expect(keys[1].key).to.have.length(lengths[curve])
|
||||
|
||||
expect(keys[0].key).to.have.length(lengths[curve])
|
||||
expect(keys[1].key).to.have.length(lengths[curve])
|
||||
|
||||
const shared = await keys[0].genSharedKey(keys[1].key)
|
||||
expect(shared).to.have.length(secretLengths[curve])
|
||||
keys[0].genSharedKey(keys[1].key, (err, shared) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(shared).to.have.length(secretLengths[curve])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
it('generates a shared secret', async () => {
|
||||
it('generates a shared secret', (done) => {
|
||||
const curve = fixtures.curve
|
||||
|
||||
const keys = await Promise.all([
|
||||
crypto.keys.generateEphemeralKeyPair(curve),
|
||||
crypto.keys.generateEphemeralKeyPair(curve)
|
||||
])
|
||||
parallel([
|
||||
(cb) => crypto.keys.generateEphemeralKeyPair(curve, cb),
|
||||
(cb) => crypto.keys.generateEphemeralKeyPair(curve, cb)
|
||||
], (err, res) => {
|
||||
expect(err).to.not.exist()
|
||||
const alice = res[0]
|
||||
const bob = res[1]
|
||||
bob.key = fixtures.bob.public
|
||||
|
||||
const alice = keys[0]
|
||||
const bob = keys[1]
|
||||
bob.key = fixtures.bob.public
|
||||
parallel([
|
||||
(cb) => alice.genSharedKey(bob.key, cb),
|
||||
(cb) => bob.genSharedKey(alice.key, fixtures.bob, cb)
|
||||
], (err, secrets) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
const secrets = await Promise.all([
|
||||
alice.genSharedKey(bob.key),
|
||||
bob.genSharedKey(alice.key, fixtures.bob)
|
||||
])
|
||||
expect(secrets[0]).to.eql(secrets[1])
|
||||
expect(secrets[0]).to.have.length(32)
|
||||
|
||||
expect(secrets[0]).to.eql(secrets[1])
|
||||
expect(secrets[0]).to.have.length(32)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('handles bad curve name', async () => {
|
||||
try {
|
||||
await crypto.keys.generateEphemeralKeyPair('bad name')
|
||||
} catch (err) {
|
||||
expect(err.code).equals('ERR_INVALID_CURVE')
|
||||
return
|
||||
}
|
||||
expect.fail('Did not throw error')
|
||||
})
|
||||
})
|
||||
|
@@ -6,7 +6,6 @@ const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const { expectErrCode } = require('../util')
|
||||
const crypto = require('../../src')
|
||||
const fixtures = require('../fixtures/go-stretch-key')
|
||||
|
||||
@@ -15,51 +14,62 @@ describe('keyStretcher', () => {
|
||||
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
|
||||
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
let res
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {Buffer}
|
||||
*/
|
||||
let secret
|
||||
|
||||
before(async () => {
|
||||
res = await crypto.keys.generateEphemeralKeyPair('P-256')
|
||||
secret = await res.genSharedKey(res.key)
|
||||
})
|
||||
before((done) => {
|
||||
crypto.keys.generateEphemeralKeyPair('P-256', (err, _res) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
res = _res
|
||||
res.genSharedKey(res.key, (err, _secret) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
ciphers.forEach((cipher) => {
|
||||
hashes.forEach((hash) => {
|
||||
it(`${cipher} - ${hash}`, async () => {
|
||||
const keys = await crypto.keys.keyStretcher(cipher, hash, secret)
|
||||
expect(keys.k1).to.exist()
|
||||
expect(keys.k2).to.exist()
|
||||
secret = _secret
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('handles invalid cipher type', () => {
|
||||
return expectErrCode(crypto.keys.keyStretcher('invalid-cipher', 'SHA256', 'secret'), 'ERR_INVALID_CIPHER_TYPE')
|
||||
})
|
||||
ciphers.forEach((cipher) => {
|
||||
hashes.forEach((hash) => {
|
||||
it(`${cipher} - ${hash}`, (done) => {
|
||||
crypto.keys.keyStretcher(cipher, hash, secret, (err, keys) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
it('handles missing hash type', () => {
|
||||
return expectErrCode(crypto.keys.keyStretcher('AES-128', '', 'secret'), 'ERR_MISSING_HASH_TYPE')
|
||||
expect(keys.k1).to.exist()
|
||||
expect(keys.k2).to.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
fixtures.forEach((test) => {
|
||||
it(`${test.cipher} - ${test.hash}`, async () => {
|
||||
it(`${test.cipher} - ${test.hash}`, (done) => {
|
||||
const cipher = test.cipher
|
||||
const hash = test.hash
|
||||
const secret = test.secret
|
||||
const keys = await crypto.keys.keyStretcher(cipher, hash, secret)
|
||||
crypto.keys.keyStretcher(cipher, hash, secret, (err, keys) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(keys.k1.iv).to.be.eql(test.k1.iv)
|
||||
expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
|
||||
expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
|
||||
expect(keys.k1.iv).to.be.eql(test.k1.iv)
|
||||
expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
|
||||
expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
|
||||
|
||||
expect(keys.k2.iv).to.be.eql(test.k2.iv)
|
||||
expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
|
||||
expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
|
||||
expect(keys.k2.iv).to.be.eql(test.k2.iv)
|
||||
expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
|
||||
expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -32,11 +32,23 @@ describe('RSA crypto libs', function () {
|
||||
rsa = crypto.keys.supportedKeys.rsa
|
||||
})
|
||||
|
||||
it('generates a valid key', async () => {
|
||||
const key = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
expect(key).to.be.an.instanceof(rsa.RsaPrivateKey)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
it('generates a valid key', (done) => {
|
||||
crypto.keys.generateKeyPair('RSA', 512, (err, key) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(key).to.be.an.instanceof(rsa.RsaPrivateKey)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.have.length(34)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
|
@@ -7,7 +7,6 @@ const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
chai.use(require('chai-string'))
|
||||
const { expectErrCode } = require('../util')
|
||||
|
||||
const crypto = require('../../src')
|
||||
const rsa = crypto.keys.supportedKeys.rsa
|
||||
@@ -15,52 +14,79 @@ const fixtures = require('../fixtures/go-key-rsa')
|
||||
|
||||
const testGarbage = require('../helpers/test-garbage-error-handling')
|
||||
|
||||
/** @typedef {import('libp2p-crypto').keys.supportedKeys.rsa.RsaPrivateKey} RsaPrivateKey */
|
||||
|
||||
describe('RSA', function () {
|
||||
this.timeout(20 * 1000)
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {RsaPrivateKey}
|
||||
*/
|
||||
let key
|
||||
|
||||
before(async () => {
|
||||
key = await rsa.generateKeyPair(512)
|
||||
before((done) => {
|
||||
crypto.keys.generateKeyPair('RSA', 512, (err, _key) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
key = _key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('generates a valid key', async () => {
|
||||
it('generates a valid key', (done) => {
|
||||
expect(key).to.be.an.instanceof(rsa.RsaPrivateKey)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.have.length(34)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('signs', async () => {
|
||||
it('signs', (done) => {
|
||||
const text = key.genSecret()
|
||||
const sig = await key.sign(text)
|
||||
const res = await key.public.verify(text, sig)
|
||||
expect(res).to.be.eql(true)
|
||||
|
||||
key.sign(text, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(text, sig, (err, res) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(res).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('encoding', async () => {
|
||||
it('encoding', (done) => {
|
||||
const keyMarshal = key.marshal()
|
||||
const key2 = await rsa.unmarshalRsaPrivateKey(keyMarshal)
|
||||
const keyMarshal2 = key2.marshal()
|
||||
rsa.unmarshalRsaPrivateKey(keyMarshal, (err, key2) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
const keyMarshal2 = key2.marshal()
|
||||
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
|
||||
const pk = key.public
|
||||
const pkMarshal = pk.marshal()
|
||||
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
|
||||
const pkMarshal2 = pk2.marshal()
|
||||
const pk = key.public
|
||||
const pkMarshal = pk.marshal()
|
||||
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
|
||||
const pkMarshal2 = pk2.marshal()
|
||||
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('key id', async () => {
|
||||
const id = await key.id()
|
||||
expect(id).to.exist()
|
||||
expect(id).to.be.a('string')
|
||||
it('key id', (done) => {
|
||||
key.id((err, id) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(id).to.exist()
|
||||
expect(id).to.be.a('string')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
describe('key equals', () => {
|
||||
@@ -70,106 +96,149 @@ describe('RSA', function () {
|
||||
expect(key.public.equals(key.public)).to.eql(true)
|
||||
})
|
||||
|
||||
it('not equals other key', async () => {
|
||||
const key2 = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
expect(key.equals(key2)).to.eql(false)
|
||||
expect(key2.equals(key)).to.eql(false)
|
||||
expect(key.public.equals(key2.public)).to.eql(false)
|
||||
expect(key2.public.equals(key.public)).to.eql(false)
|
||||
it('not equals other key', (done) => {
|
||||
crypto.keys.generateKeyPair('RSA', 512, (err, key2) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(key.equals(key2)).to.eql(false)
|
||||
expect(key2.equals(key)).to.eql(false)
|
||||
expect(key.public.equals(key2.public)).to.eql(false)
|
||||
expect(key2.public.equals(key.public)).to.eql(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('sign and verify', async () => {
|
||||
it('sign and verify', (done) => {
|
||||
const data = Buffer.from('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(data, sig)
|
||||
expect(valid).to.be.eql(true)
|
||||
key.sign(data, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(data, sig, (err, valid) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(valid).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('encrypt and decrypt', async () => {
|
||||
it('encrypt and decrypt', (done) => {
|
||||
const data = Buffer.from('hello world')
|
||||
const enc = await key.public.encrypt(data)
|
||||
const dec = await key.decrypt(enc)
|
||||
expect(dec).to.be.eql(data)
|
||||
key.public.encrypt(data, (err, enc) => {
|
||||
if (err) { return done(err) }
|
||||
|
||||
key.decrypt(enc, (err, dec) => {
|
||||
if (err) { return done(err) }
|
||||
expect(dec).to.be.eql(data)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('encrypt decrypt browser/node interop', async () => {
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
const id = await crypto.keys.unmarshalPrivateKey(Buffer.from('CAASqAkwggSkAgEAAoIBAQCk0O+6oNRxhcdZe2GxEDrFBkDV4TZFZnp2ly/dL1cGMBql/8oXPZgei6h7+P5zzfDq2YCfwbjbf0IVY1AshRl6B5VGE1WS+9p1y1OZxJf5os6V1ENnTi6FTcyuBl4BN8dmIKOif0hqgqflaT5OhfYZDXfbJyVQj4vb2+Stu2Xpph3nwqAnTw/7GC/7jrt2Cq6Tu1PoZi36wSwEPYW3eQ1HAYxZjTYYDXl2iyHygnTcbkGRwAQ7vjk+mW7u60zyoolCm9f6Y7c/orJ33DDUocbaGJLlHcfd8bioBwaZy/2m7q43X8pQs0Q1/iwUt0HHZj1YARmHKbh0zR31ciFiV37dAgMBAAECggEADtJBNKnA4QKURj47r0YT2uLwkqtBi6UnDyISalQXAdXyl4n0nPlrhBewC5H9I+HZr+zmTbeIjaiYgz7el1pSy7AB4v7bG7AtWZlyx6mvtwHGjR+8/f3AXjl8Vgv5iSeAdXUq8fJ7SyS7v3wi38HZOzCEXj9bci6ud5ODMYJgLE4gZD0+i1+/V9cpuYfGpS/gLTLEMQLiw/9o8NSZ7sAnxg0UlYhotqaQY23hvXPBOe+0oa95zl2n6XTxCafa3dQl/B6CD1tUq9dhbQew4bxqMq/mhRO9pREEqZ083Uh+u4PTc1BeHgIQaS864pHPb+AY1F7KDvPtHhdojnghp8d70QKBgQDeRYFxo6sd04ohY86Z/i9icVYIyCvfXAKnaMKeGUjK7ou6sDJwFX8W97+CzXpZ/vffsk/l5GGhC50KqrITxHAy/h5IjyDODfps7NMIp0Dm9sO4PWibbw3OOVBRc8w3b3i7I8MrUUA1nLHE1T1HA1rKOTz5jYhE0fi9XKiT1ciKOQKBgQC903w+n9y7M7eaMW7Z5/13kZ7PS3HlM681eaPrk8J4J+c6miFF40/8HOsmarS38v0fgTeKkriPz5A7aLzRHhSiOnp350JNM6c3sLwPEs2qx/CRuWWx1rMERatfDdUH6mvlK6QHu0QgSfQR27EO6a6XvVSJXbvFmimjmtIaz/IpxQKBgQDWJ9HYVAGC81abZTaiWK3/A4QJYhQjWNuVwPICsgnYvI4Uib+PDqcs0ffLZ38DRw48kek5bxpBuJbOuDhro1EXUJCNCJpq7jzixituovd9kTRyR3iKii2bDM2+LPwOTXDdnk9lZRugjCEbrPkleq33Ob7uEtfAty4aBTTHe6uEwQKBgQCB+2q8RyMSXNuADhFlzOFXGrOwJm0bEUUMTPrduRQUyt4e1qOqA3klnXe3mqGcxBpnlEe/76/JacvNom6Ikxx16a0qpYRU8OWz0KU1fR6vrrEgV98241k5t6sdL4+MGA1Bo5xyXtzLb1hdUh3vpDwVU2OrnC+To3iXus/b5EBiMQKBgEI1OaBcFiyjgLGEyFKoZbtzH1mdatTExfrAQqCjOVjQByoMpGhHTXwEaosvyYu63Pa8AJPT7juSGaiKYEJFcXO9BiNyVfmQiqSHJcYeuh+fmO9IlHRHgy5xaIIC00AHS2vC/gXwmXAdPis6BZqDJeiCuOLWJ94QXn8JBT8IgGAI', 'base64'))
|
||||
it('encrypt decrypt browser/node interop', (done) => {
|
||||
crypto.keys.unmarshalPrivateKey(Buffer.from('CAASqAkwggSkAgEAAoIBAQCk0O+6oNRxhcdZe2GxEDrFBkDV4TZFZnp2ly/dL1cGMBql/8oXPZgei6h7+P5zzfDq2YCfwbjbf0IVY1AshRl6B5VGE1WS+9p1y1OZxJf5os6V1ENnTi6FTcyuBl4BN8dmIKOif0hqgqflaT5OhfYZDXfbJyVQj4vb2+Stu2Xpph3nwqAnTw/7GC/7jrt2Cq6Tu1PoZi36wSwEPYW3eQ1HAYxZjTYYDXl2iyHygnTcbkGRwAQ7vjk+mW7u60zyoolCm9f6Y7c/orJ33DDUocbaGJLlHcfd8bioBwaZy/2m7q43X8pQs0Q1/iwUt0HHZj1YARmHKbh0zR31ciFiV37dAgMBAAECggEADtJBNKnA4QKURj47r0YT2uLwkqtBi6UnDyISalQXAdXyl4n0nPlrhBewC5H9I+HZr+zmTbeIjaiYgz7el1pSy7AB4v7bG7AtWZlyx6mvtwHGjR+8/f3AXjl8Vgv5iSeAdXUq8fJ7SyS7v3wi38HZOzCEXj9bci6ud5ODMYJgLE4gZD0+i1+/V9cpuYfGpS/gLTLEMQLiw/9o8NSZ7sAnxg0UlYhotqaQY23hvXPBOe+0oa95zl2n6XTxCafa3dQl/B6CD1tUq9dhbQew4bxqMq/mhRO9pREEqZ083Uh+u4PTc1BeHgIQaS864pHPb+AY1F7KDvPtHhdojnghp8d70QKBgQDeRYFxo6sd04ohY86Z/i9icVYIyCvfXAKnaMKeGUjK7ou6sDJwFX8W97+CzXpZ/vffsk/l5GGhC50KqrITxHAy/h5IjyDODfps7NMIp0Dm9sO4PWibbw3OOVBRc8w3b3i7I8MrUUA1nLHE1T1HA1rKOTz5jYhE0fi9XKiT1ciKOQKBgQC903w+n9y7M7eaMW7Z5/13kZ7PS3HlM681eaPrk8J4J+c6miFF40/8HOsmarS38v0fgTeKkriPz5A7aLzRHhSiOnp350JNM6c3sLwPEs2qx/CRuWWx1rMERatfDdUH6mvlK6QHu0QgSfQR27EO6a6XvVSJXbvFmimjmtIaz/IpxQKBgQDWJ9HYVAGC81abZTaiWK3/A4QJYhQjWNuVwPICsgnYvI4Uib+PDqcs0ffLZ38DRw48kek5bxpBuJbOuDhro1EXUJCNCJpq7jzixituovd9kTRyR3iKii2bDM2+LPwOTXDdnk9lZRugjCEbrPkleq33Ob7uEtfAty4aBTTHe6uEwQKBgQCB+2q8RyMSXNuADhFlzOFXGrOwJm0bEUUMTPrduRQUyt4e1qOqA3klnXe3mqGcxBpnlEe/76/JacvNom6Ikxx16a0qpYRU8OWz0KU1fR6vrrEgV98241k5t6sdL4+MGA1Bo5xyXtzLb1hdUh3vpDwVU2OrnC+To3iXus/b5EBiMQKBgEI1OaBcFiyjgLGEyFKoZbtzH1mdatTExfrAQqCjOVjQByoMpGhHTXwEaosvyYu63Pa8AJPT7juSGaiKYEJFcXO9BiNyVfmQiqSHJcYeuh+fmO9IlHRHgy5xaIIC00AHS2vC/gXwmXAdPis6BZqDJeiCuOLWJ94QXn8JBT8IgGAI', 'base64'), (err, id) => {
|
||||
if (err) { return done(err) }
|
||||
|
||||
const msg = Buffer.from('hello')
|
||||
const msg = Buffer.from('hello')
|
||||
// browser
|
||||
id.decrypt(Buffer.from('YRFUDx8UjbWSfDS84cDA4WowaaOmd1qFNAv5QutodCKYb9uPtU/tDiAvJzOGu5DCJRo2J0l/35P2weiB4/C2Cb1aZgXKMx/QQC+2jSJiymhqcZaYerjTvkCFwkjCaqthoVo/YXxsaFZ1q7bdTZUDH1TaJR7hWfSyzyPcA8c0w43MIsw16pY8ZaPSclvnCwhoTg1JGjMk6te3we7+wR8QU7VrPhs54mZWxrpu3NQ8xZ6xQqIedsEiNhBUccrCSzYghgsP0Ae/8iKyGyl3U6IegsJNn8jcocvzOJrmU03rgIFPjvuBdaqB38xDSTjbA123KadB28jNoSZh18q/yH3ZIg==', 'base64'), (err, dec1) => {
|
||||
if (err) { return done(err) }
|
||||
expect(dec1).to.be.eql(msg)
|
||||
|
||||
// browser
|
||||
const dec1 = id.decrypt(Buffer.from('YRFUDx8UjbWSfDS84cDA4WowaaOmd1qFNAv5QutodCKYb9uPtU/tDiAvJzOGu5DCJRo2J0l/35P2weiB4/C2Cb1aZgXKMx/QQC+2jSJiymhqcZaYerjTvkCFwkjCaqthoVo/YXxsaFZ1q7bdTZUDH1TaJR7hWfSyzyPcA8c0w43MIsw16pY8ZaPSclvnCwhoTg1JGjMk6te3we7+wR8QU7VrPhs54mZWxrpu3NQ8xZ6xQqIedsEiNhBUccrCSzYghgsP0Ae/8iKyGyl3U6IegsJNn8jcocvzOJrmU03rgIFPjvuBdaqB38xDSTjbA123KadB28jNoSZh18q/yH3ZIg==', 'base64'))
|
||||
expect(dec1).to.be.eql(msg)
|
||||
// node
|
||||
const dec2 = id.decrypt(Buffer.from('e6yxssqXsWc27ozDy0PGKtMkCS28KwFyES2Ijz89yiz+w6bSFkNOhHPKplpPzgQEuNoUGdbseKlJFyRYHjIT8FQFBHZM8UgSkgoimbY5on4xSxXs7E5/+twjqKdB7oNveTaTf7JCwaeUYnKSjbiYFEawtMiQE91F8sTT7TmSzOZ48tUhnddAAZ3Ac/O3Z9MSAKOCDipi+JdZtXRT8KimGt36/7hjjosYmPuHR1Xy/yMTL6SMbXtBM3yAuEgbQgP+q/7kHMHji3/JvTpYdIUU+LVtkMusXNasRA+UWG2zAht18vqjFMsm9JTiihZw9jRHD4vxAhf75M992tnC+0ZuQg==', 'base64'))
|
||||
expect(dec2).to.be.eql(msg)
|
||||
// node
|
||||
id.decrypt(Buffer.from('e6yxssqXsWc27ozDy0PGKtMkCS28KwFyES2Ijz89yiz+w6bSFkNOhHPKplpPzgQEuNoUGdbseKlJFyRYHjIT8FQFBHZM8UgSkgoimbY5on4xSxXs7E5/+twjqKdB7oNveTaTf7JCwaeUYnKSjbiYFEawtMiQE91F8sTT7TmSzOZ48tUhnddAAZ3Ac/O3Z9MSAKOCDipi+JdZtXRT8KimGt36/7hjjosYmPuHR1Xy/yMTL6SMbXtBM3yAuEgbQgP+q/7kHMHji3/JvTpYdIUU+LVtkMusXNasRA+UWG2zAht18vqjFMsm9JTiihZw9jRHD4vxAhf75M992tnC+0ZuQg==', 'base64'), (err, dec2) => {
|
||||
if (err) { return done(err) }
|
||||
expect(dec2).to.be.eql(msg)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('fails to verify for different data', async () => {
|
||||
it('fails to verify for different data', (done) => {
|
||||
const data = Buffer.from('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(Buffer.from('hello'), sig)
|
||||
expect(valid).to.be.eql(false)
|
||||
key.sign(data, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(Buffer.from('hello'), sig, (err, valid) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(valid).to.be.eql(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('export and import', () => {
|
||||
it('password protected PKCS #8', async () => {
|
||||
const pem = await key.export('my secret', 'pkcs-8')
|
||||
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
|
||||
const clone = await crypto.keys.import(pem, 'my secret')
|
||||
expect(clone).to.exist()
|
||||
expect(key.equals(clone)).to.eql(true)
|
||||
it('password protected PKCS #8', (done) => {
|
||||
key.export('pkcs-8', 'my secret', (err, pem) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
|
||||
crypto.keys.import(pem, 'my secret', (err, clone) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(clone).to.exist()
|
||||
expect(key.equals(clone)).to.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('defaults to PKCS #8', async () => {
|
||||
const pem = await key.export('another secret')
|
||||
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
|
||||
const clone = await crypto.keys.import(pem, 'another secret')
|
||||
expect(clone).to.exist()
|
||||
expect(key.equals(clone)).to.eql(true)
|
||||
it('defaults to PKCS #8', (done) => {
|
||||
key.export('another secret', (err, pem) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
|
||||
crypto.keys.import(pem, 'another secret', (err, clone) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(clone).to.exist()
|
||||
expect(key.equals(clone)).to.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('needs correct password', async () => {
|
||||
const pem = await key.export('another secret')
|
||||
try {
|
||||
await crypto.keys.import(pem, 'not the secret')
|
||||
} catch (err) {
|
||||
return // expected
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
|
||||
it('handles invalid export type', () => {
|
||||
return expectErrCode(key.export('secret', 'invalid-type'), 'ERR_INVALID_EXPORT_FORMAT')
|
||||
it('needs correct password', (done) => {
|
||||
key.export('another secret', (err, pem) => {
|
||||
expect(err).to.not.exist()
|
||||
crypto.keys.import(pem, 'not the secret', (err, clone) => {
|
||||
expect(err).to.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('throws error instead of crashing', () => {
|
||||
describe('returns error via cb instead of crashing', () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
testGarbage.doTests('key.verify', key.verify.bind(key), 2, true)
|
||||
testGarbage.doTests(
|
||||
'crypto.keys.unmarshalPrivateKey',
|
||||
crypto.keys.unmarshalPrivateKey.bind(crypto.keys),
|
||||
null,
|
||||
null
|
||||
)
|
||||
testGarbage.doTests('crypto.keys.unmarshalPrivateKey', crypto.keys.unmarshalPrivateKey.bind(crypto.keys))
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
it('verifies with data from go', async () => {
|
||||
it('verifies with data from go', (done) => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
const ok = await key.verify(fixtures.verify.data, fixtures.verify.signature)
|
||||
expect(ok).to.equal(true)
|
||||
|
||||
key.verify(fixtures.verify.data, fixtures.verify.signature, (err, ok) => {
|
||||
if (err) throw err
|
||||
expect(err).to.not.exist()
|
||||
expect(ok).to.equal(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('openssl interop', () => {
|
||||
it('can read a private key', async () => {
|
||||
it('can read a private key', (done) => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
@@ -217,14 +286,19 @@ gnjREs10u7zyqBIZH7KYVgyh27WxLr859ap8cKAH6Fb+UOPtZo3sUeeume60aebn
|
||||
4pMwXeXP+LO8NIfRXV8mgrm86g==
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, '')
|
||||
expect(key).to.exist()
|
||||
const id = await key.id()
|
||||
expect(id).to.equal('QmfWu2Xp8DZzCkZZzoPB9rcrq4R4RZid6AWE6kmrUAzuHy')
|
||||
crypto.keys.import(pem, '', (err, key) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(key).to.exist()
|
||||
key.id((err, id) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(id).to.equal('QmfWu2Xp8DZzCkZZzoPB9rcrq4R4RZid6AWE6kmrUAzuHy')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// AssertionError: expected 'this only supports pkcs5PBES2' to not exist
|
||||
it.skip('can read a private encrypted key (v1)', async () => {
|
||||
it.skip('can read a private encrypted key (v1)', (done) => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
@@ -251,11 +325,14 @@ mBdkD5r+ixWF174naw53L8U9wF8kiK7pIE1N9TR4USEeovLwX6Ni/2MMDZedOfof
|
||||
0uxzo5Y=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
crypto.keys.import(pem, 'mypassword', (err, key) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(key).to.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can read a private encrypted key (v2 aes-128-cbc)', async () => {
|
||||
it('can read a private encrypted key (v2 aes-128-cbc)', (done) => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
@@ -283,11 +360,14 @@ ePGbz+UtSb9xczvqpRCOiFLh2MG1dUgWuHazjOtUcVWvilKnkjCMzZ9s1qG0sUDj
|
||||
nPyn
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
crypto.keys.import(pem, 'mypassword', (err, key) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(key).to.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can read a private encrypted key (v2 aes-256-cbc)', async () => {
|
||||
it('can read a private encrypted key (v2 aes-256-cbc)', (done) => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
@@ -315,11 +395,14 @@ mBUuWAZMpz7njBi7h+JDfmSW/GAaMwrVFC2gef5375R0TejAh+COAjItyoeYEvv8
|
||||
DQd8
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
crypto.keys.import(pem, 'mypassword', (err, key) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(key).to.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can read a private encrypted key (v2 des)', async () => {
|
||||
it('can read a private encrypted key (v2 des)', (done) => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
@@ -346,11 +429,14 @@ A+qCKbprxyL8SKI5vug2hE+mfC1leXVRtUYm1DnE+oet99bFd0fN20NwTw0rOeRg
|
||||
EBqQkwAUXR1tNekF8CWLOrfC/wbLRxVRkayb8bQUfdgukLpz0bgw
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
crypto.keys.import(pem, 'mypassword', (err, key) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(key).to.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can read a private encrypted key (v2 des3)', async () => {
|
||||
it('can read a private encrypted key (v2 des3)', (done) => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
@@ -377,8 +463,11 @@ NT2TO3kSzXpQ5M2VjOoHPm2fqxD/js+ThDB3QLi4+C7HqakfiTY1lYzXl9/vayt6
|
||||
DUD29r9pYL9ErB9tYko2rat54EY7k7Ts6S5jf+8G7Zz234We1APhvqaG
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
crypto.keys.import(pem, 'mypassword', (err, key) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(key).to.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -5,37 +5,49 @@ const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const sinon = require('sinon')
|
||||
|
||||
const fixtures = require('../fixtures/secp256k1')
|
||||
const crypto = require('../../src')
|
||||
|
||||
const mockPublicKey = {
|
||||
bytes: fixtures.pbmPublicKey
|
||||
}
|
||||
|
||||
const mockPrivateKey = {
|
||||
bytes: fixtures.pbmPrivateKey,
|
||||
public: mockPublicKey
|
||||
}
|
||||
|
||||
const mockSecp256k1Module = {
|
||||
generateKeyPair (bits, callback) {
|
||||
callback(null, mockPrivateKey)
|
||||
},
|
||||
|
||||
unmarshalSecp256k1PrivateKey (buf, callback) {
|
||||
callback(null, mockPrivateKey)
|
||||
},
|
||||
|
||||
unmarshalSecp256k1PublicKey (buf) {
|
||||
return mockPublicKey
|
||||
}
|
||||
}
|
||||
|
||||
describe('without libp2p-crypto-secp256k1 module present', () => {
|
||||
before(() => {
|
||||
const empty = null
|
||||
sinon.replace(crypto.keys.supportedKeys, 'secp256k1', empty)
|
||||
crypto.keys.supportedKeys.secp256k1 = undefined
|
||||
|
||||
it('fails to generate a secp256k1 key', (done) => {
|
||||
crypto.keys.generateKeyPair('secp256k1', 256, (err, key) => {
|
||||
expect(err).to.exist()
|
||||
expect(key).to.not.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('fails to generate a secp256k1 key', async () => {
|
||||
try {
|
||||
await crypto.keys.generateKeyPair('secp256k1', 256)
|
||||
} catch (err) {
|
||||
return // expected
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
|
||||
it('fails to unmarshal a secp256k1 private key', async () => {
|
||||
try {
|
||||
await crypto.keys.unmarshalPrivateKey(fixtures.pbmPrivateKey)
|
||||
} catch (err) {
|
||||
return // expected
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
it('fails to unmarshal a secp256k1 private key', (done) => {
|
||||
crypto.keys.unmarshalPrivateKey(fixtures.pbmPrivateKey, (err, key) => {
|
||||
expect(err).to.exist()
|
||||
expect(key).to.not.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('fails to unmarshal a secp256k1 public key', () => {
|
||||
@@ -46,36 +58,42 @@ describe('without libp2p-crypto-secp256k1 module present', () => {
|
||||
})
|
||||
|
||||
describe('with libp2p-crypto-secp256k1 module present', () => {
|
||||
it('generates a valid key', async () => {
|
||||
const key = await crypto.keys.generateKeyPair('secp256k1', 256)
|
||||
expect(key).to.exist()
|
||||
let key
|
||||
|
||||
before((done) => {
|
||||
crypto.keys.supportedKeys.secp256k1 = mockSecp256k1Module
|
||||
crypto.keys.generateKeyPair('secp256k1', 256, (err, _key) => {
|
||||
if (err) return done(err)
|
||||
key = _key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('protobuf encoding', async () => {
|
||||
const key = await crypto.keys.generateKeyPair('secp256k1', 256)
|
||||
expect(key).to.exist()
|
||||
after((done) => {
|
||||
delete crypto.keys.secp256k1
|
||||
done()
|
||||
})
|
||||
|
||||
it('generates a valid key', (done) => {
|
||||
expect(key).to.exist()
|
||||
done()
|
||||
})
|
||||
|
||||
it('protobuf encoding', (done) => {
|
||||
const keyMarshal = crypto.keys.marshalPrivateKey(key)
|
||||
const key2 = await crypto.keys.unmarshalPrivateKey(keyMarshal)
|
||||
const keyMarshal2 = crypto.keys.marshalPrivateKey(key2)
|
||||
crypto.keys.unmarshalPrivateKey(keyMarshal, (err, key2) => {
|
||||
if (err) return done(err)
|
||||
const keyMarshal2 = crypto.keys.marshalPrivateKey(key2)
|
||||
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
|
||||
const pk = key.public
|
||||
const pkMarshal = crypto.keys.marshalPublicKey(pk)
|
||||
const pk2 = crypto.keys.unmarshalPublicKey(pkMarshal)
|
||||
const pkMarshal2 = crypto.keys.marshalPublicKey(pk2)
|
||||
const pk = key.public
|
||||
const pkMarshal = crypto.keys.marshalPublicKey(pk)
|
||||
const pk2 = crypto.keys.unmarshalPublicKey(pkMarshal)
|
||||
const pkMarshal2 = crypto.keys.marshalPublicKey(pk2)
|
||||
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
})
|
||||
|
||||
it('unmarshals a secp256k1 private key', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.pbmPrivateKey)
|
||||
expect(key).to.exist()
|
||||
})
|
||||
|
||||
it('unmarshals a secp256k1 public key', () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.pbmPublicKey)
|
||||
expect(key).to.exist()
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -1,27 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
|
||||
const randomBytes = require('../src/random-bytes')
|
||||
|
||||
describe('randomBytes', () => {
|
||||
it('produces random bytes', () => {
|
||||
expect(randomBytes(16)).to.have.length(16)
|
||||
})
|
||||
|
||||
it('throws if length is 0', () => {
|
||||
expect(() => randomBytes(0)).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
|
||||
})
|
||||
|
||||
it('throws if length is < 0', () => {
|
||||
expect(() => randomBytes(-1)).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
|
||||
})
|
||||
|
||||
it('throws if length is not a number', () => {
|
||||
expect(() => randomBytes('hi')).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
|
||||
})
|
||||
})
|
@@ -1,21 +0,0 @@
|
||||
/* eslint-disable valid-jsdoc */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const expect = chai.expect
|
||||
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {function(any, string): void}
|
||||
*/
|
||||
const expectErrCode = async (p, code) => {
|
||||
try {
|
||||
await p
|
||||
} catch (err) {
|
||||
expect(err).to.have.property('code', code)
|
||||
return
|
||||
}
|
||||
expect.fail(`Expected error with code ${code} but no error thrown`)
|
||||
}
|
||||
|
||||
module.exports = { expectErrCode }
|
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"target": "ES5",
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"libp2p-crypto": [
|
||||
"./src",
|
||||
"../../src",
|
||||
"../src"
|
||||
]
|
||||
},
|
||||
"types": ["node", "mocha", "chai"],
|
||||
"noEmit": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"files": ["./src/index.d.ts",],
|
||||
"include": ["./test/**/*.spec.js"]
|
||||
}
|
Reference in New Issue
Block a user