Compare commits

...

36 Commits

Author SHA1 Message Date
Jacob Heun
1b0fac84a8 chore: release version v0.17.5 2020-03-24 14:27:31 +01:00
Jacob Heun
efaafa9c06 chore: update contributors 2020-03-24 14:27:30 +01:00
dependabot-preview[bot]
88b3018c9c chore(deps): bump multibase from 0.6.1 to 0.7.0 (#171)
Bumps [multibase](https://github.com/multiformats/js-multibase) from 0.6.1 to 0.7.0.
- [Release notes](https://github.com/multiformats/js-multibase/releases)
- [Changelog](https://github.com/multiformats/js-multibase/blob/master/CHANGELOG.md)
- [Commits](https://github.com/multiformats/js-multibase/compare/v0.6.1...v0.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-24 14:11:07 +01:00
Jacob Heun
9aacb478c4 chore: release version v0.17.4 2020-03-23 17:06:35 +01:00
Jacob Heun
269d169f7c chore: update contributors 2020-03-23 17:06:35 +01:00
Hugo Dias
c956d1ad2a fix: add buffer, cleanup, reduce size (#170)
* fix: add buffer, cleanup, reduce size

- add buffer related to https://github.com/ipfs/js-ipfs/issues/2924
- remove unnecessary eslint ignore
- remove tweelnacl and use node-forge
- remove browserify-aes  and use node-forge
- use multibase to encode b58
- require only sha256 from multihashing
- reduce bundle size

after all the deps here https://github.com/ipfs/js-ipfs/issues/2924 are merged libp2p-crypto will be able to be bundle with `node: false` 🎉

* fix: reduce bundle size

* fix: use new secp

* fix: bundle size

* chore: update secp

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-03-23 16:55:35 +01:00
Jacob Heun
d3601fa936 chore: release version v0.17.3 2020-02-26 17:21:45 +01:00
Jacob Heun
f01e3812e9 chore: update contributors 2020-02-26 17:21:45 +01:00
Alan Shaw
00477e3bcb perf: remove asn1.js and use node-forge (#166)
* perf: remove asn1.js from rsa

* fix: tweaks

* fix: it works, but I do not know 100% why

* chore: remove asn1.js

* fix: ensure jwk params encoded as uint

* fix: util tests

* fix: zero pad base64urlToBuffer

* fix: more zero pad

* test: add round trip test

* test: base64url to Buffer with padding
2020-02-26 17:16:32 +01:00
dependabot-preview[bot]
0f4c533dfa chore(deps-dev): bump sinon from 8.1.1 to 9.0.0
Bumps [sinon](https://github.com/sinonjs/sinon) from 8.1.1 to 9.0.0.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v8.1.1...v9.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-19 08:27:36 -05:00
dependabot-preview[bot]
d566e7ef3b chore(deps-dev): bump @types/mocha from 5.2.7 to 7.0.1
Bumps [@types/mocha](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mocha) from 5.2.7 to 7.0.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/mocha)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-18 08:31:56 -05:00
dependabot-preview[bot]
78e2ddd2bd chore(deps-dev): bump sinon from 7.5.0 to 8.1.1
Bumps [sinon](https://github.com/sinonjs/sinon) from 7.5.0 to 8.1.1.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v7.5.0...v8.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-18 08:31:41 -05:00
dependabot-preview[bot]
0ad513887a chore(deps): bump err-code from 1.1.2 to 2.0.0 (#165)
Bumps [err-code](https://github.com/IndigoUnited/js-err-code) from 1.1.2 to 2.0.0.
- [Release notes](https://github.com/IndigoUnited/js-err-code/releases)
- [Commits](https://github.com/IndigoUnited/js-err-code/compare/1.1.2...v2.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-18 07:57:07 -05:00
dependabot-preview[bot]
e7468d830d chore(deps-dev): bump aegir from 20.6.1 to 21.0.2 (#167)
Bumps [aegir](https://github.com/ipfs/aegir) from 20.6.1 to 21.0.2.
- [Release notes](https://github.com/ipfs/aegir/releases)
- [Changelog](https://github.com/ipfs/aegir/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ipfs/aegir/compare/v20.6.1...v21.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-18 07:56:49 -05:00
Alan Shaw
cc2094975b perf: remove jwk2privPem and jwk2pubPem (#162)
These 2 unused functions required us to import the whole of the node-forge PKI implementation when we only use some RSA stuffs.

BREAKING CHANGE: removes unused jwk2pem methods `jwk2pubPem` and `jwk2privPem`. These methods are not being used in any js libp2p modules, so only users referencing these directly will be impacted.
2020-02-03 14:28:23 +01:00
Jacob Heun
ad4bf3b357 chore: release version v0.17.2 2020-01-17 12:08:41 +01:00
Jacob Heun
730d762717 chore: update contributors 2020-01-17 12:08:40 +01:00
Carson Farmer
e01977c5a3 feat: add typescript types + linting/tests (#161)
* feat: adds typescript types + linting/tests

Signed-off-by: Carson Farmer <carson.farmer@gmail.com>

* feat: much better types testing

Signed-off-by: Carson Farmer <carson.farmer@gmail.com>

* chore: revert eslintignore

Signed-off-by: Carson Farmer <carson.farmer@gmail.com>

* feat: update types entry

Signed-off-by: Carson Farmer <carson.farmer@gmail.com>

* chore: exclude has no effect here

Signed-off-by: Carson Farmer <carson.farmer@gmail.com>

* feat: more nuanced return types on keypair

Signed-off-by: Carson Farmer <carson.farmer@gmail.com>
2020-01-17 12:04:52 +01:00
Jacob Heun
b5d94ecae7 chore: release version v0.17.1 2019-10-25 13:56:54 +02:00
Jacob Heun
1f9c2ddadb chore: update contributors 2019-10-25 13:56:53 +02:00
Jacob Heun
d6d06a8404 chore: remove commitlint
chore: update deps

chore: add bundlesize to ci
2019-10-25 13:51:00 +02:00
Maciej Krüger
8b8d0c1510 fix: jwk var naming 2019-10-25 13:51:00 +02:00
Maciej Krüger
b998f63aec feat: use forge to convert jwk2forge 2019-10-25 13:51:00 +02:00
Maciej Krüger
adc6eb478c test: add interop test 2019-10-25 13:51:00 +02:00
Maciej Krüger
2c1bac5ce9 fix: padding error 2019-10-25 13:51:00 +02:00
Maciej Krüger
027a5a9332 fix: use direct buffers instead of converting to hex 2019-10-25 13:51:00 +02:00
Maciej Krüger
2c294b56ab fix: lint 2019-10-25 13:51:00 +02:00
Maciej Krüger
487cd076fb refactor: cleanup 2019-10-25 13:51:00 +02:00
Maciej Krüger
b8e2414420 fix: browser rsa enc/dec 2019-10-25 13:51:00 +02:00
Maciej Krüger
9f747a173f feat: browser enc/dec 2019-10-25 13:51:00 +02:00
Maciej Krüger
34c5f5c8f0 feat: add (rsa)pubKey.encrypt and (rsa)privKey.decrypt
nodeJS only for now
2019-10-25 13:51:00 +02:00
Jacob Heun
a008bc2fcb chore: update lead maintainer (#159) 2019-09-26 12:51:16 +02:00
Friedel Ziegelmayer
b68060388f Merge pull request #157 from libp2p/fix/better-err-missing-webcrypto
fix: better error for missing web crypto
2019-08-13 15:28:33 +02:00
Alan Shaw
afe94ded6b docs: problem resolution 2019-07-22 11:21:24 +01:00
Alan Shaw
a5e05603ef fix: better error for missing web crypto
This PR simply detects missing web crypto and throws an error with an appropriate message.

This is a stepping stone that will help users understand the problem until we have time to do a refactor of this module and of all the modules that use it to enable optionally passing your own crypto implementation.

refs https://github.com/libp2p/js-libp2p-crypto/pull/149
refs https://github.com/libp2p/js-libp2p-crypto/pull/150
refs https://github.com/libp2p/js-libp2p-crypto/issues/105
refs https://github.com/ipfs/js-ipfs/issues/2153
refs https://github.com/ipfs/js-ipfs/issues/2017

License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
2019-07-22 11:21:22 +01:00
dirkmc
0b686d363c chore: add error codes (#155)
* chore: add error codes

* chore: create errors with new Error()

* fix: better error testin

* refactor: simplify random bytes error checks
2019-07-22 11:16:02 +01:00
47 changed files with 1092 additions and 326 deletions

3
.aegir.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
bundlesize: { maxSize: '124kB' }
}

View File

@@ -1 +0,0 @@
src/keys/keys.proto.js

View File

@@ -23,7 +23,7 @@ jobs:
include:
- stage: check
script:
- npx aegir commitlint --travis
- npx aegir build --bundlesize
- npx aegir dep-check
- npm run lint

View File

@@ -1,3 +1,66 @@
<a name="0.17.5"></a>
## [0.17.5](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.4...v0.17.5) (2020-03-24)
<a name="0.17.4"></a>
## [0.17.4](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.3...v0.17.4) (2020-03-23)
### Bug Fixes
* add buffer, cleanup, reduce size ([#170](https://github.com/libp2p/js-libp2p-crypto/issues/170)) ([c956d1a](https://github.com/libp2p/js-libp2p-crypto/commit/c956d1a))
<a name="0.17.3"></a>
## [0.17.3](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.2...v0.17.3) (2020-02-26)
### Performance Improvements
* remove asn1.js and use node-forge ([#166](https://github.com/libp2p/js-libp2p-crypto/issues/166)) ([00477e3](https://github.com/libp2p/js-libp2p-crypto/commit/00477e3))
* remove jwk2privPem and jwk2pubPem ([#162](https://github.com/libp2p/js-libp2p-crypto/issues/162)) ([cc20949](https://github.com/libp2p/js-libp2p-crypto/commit/cc20949))
### BREAKING CHANGES
* removes unused jwk2pem methods `jwk2pubPem` and `jwk2privPem`. These methods are not being used in any js libp2p modules, so only users referencing these directly will be impacted.
<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)
### 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))
<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)

View File

@@ -15,7 +15,7 @@ This repo contains the JavaScript implementation of the crypto primitives needed
## Lead Maintainer
[Friedel Ziegelmayer](https://github.com/dignifiedquire/)
[Jacob Heun](https://github.com/jacobheun/)
## Table of Contents
@@ -51,11 +51,32 @@ 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`
Expoes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
Exposes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
This uses `CTR` mode.

View File

@@ -1,9 +1,10 @@
{
"name": "libp2p-crypto",
"version": "0.17.0",
"version": "0.17.5",
"description": "Crypto primitives for libp2p",
"main": "src/index.js",
"leadMaintainer": "Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"types": "src/index.d.ts",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"browser": {
"./src/hmac/index.js": "./src/hmac/index-browser.js",
"./src/keys/ecdh.js": "./src/keys/ecdh-browser.js",
@@ -25,7 +26,8 @@
"release-minor": "aegir release --type minor",
"release-major": "aegir release --type major",
"coverage": "aegir coverage --ignore src/keys/keys.proto.js",
"size": "bundlesize -f dist/index.min.js -s 139kB"
"size": "aegir build --bundlesize",
"test:types": "npx tsc"
},
"keywords": [
"IPFS",
@@ -35,30 +37,30 @@
],
"license": "MIT",
"dependencies": {
"asmcrypto.js": "^2.3.2",
"asn1.js": "^5.0.1",
"bn.js": "^5.0.0",
"browserify-aes": "^1.2.0",
"bs58": "^4.0.1",
"buffer": "^5.5.0",
"err-code": "^2.0.0",
"iso-random-stream": "^1.1.0",
"keypair": "^1.0.1",
"libp2p-crypto-secp256k1": "~0.4.0",
"multihashing-async": "~0.7.0",
"node-forge": "~0.8.5",
"libp2p-crypto-secp256k1": "^0.4.2",
"multibase": "^0.7.0",
"multihashing-async": "^0.8.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.0"
"ursa-optional": "~0.10.1"
},
"devDependencies": {
"aegir": "^19.0.5",
"@types/chai": "^4.2.11",
"@types/chai-string": "^1.4.2",
"@types/dirty-chai": "^2.0.2",
"@types/mocha": "^7.0.1",
"@types/sinon": "^7.5.1",
"aegir": "^21.0.2",
"benchmark": "^2.1.4",
"bundlesize": "~0.18.0",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
"dirty-chai": "^2.0.1",
"sinon": "^7.3.2"
"sinon": "^9.0.0"
},
"engines": {
"node": ">=10.0.0",
@@ -73,27 +75,25 @@
},
"homepage": "https://github.com/libp2p/js-libp2p-crypto",
"contributors": [
"Alan Shaw <alan.shaw@protocol.ai>",
"Alberto Elias <hi@albertoelias.me>",
"Arve Knudsen <arve.knudsen@gmail.com>",
"David Dias <daviddias.p@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"Greenkeeper <support@greenkeeper.io>",
"Maciej Krüger <mkg20001@gmail.com>",
"Jacob Heun <jacobheun@gmail.com>",
"dryajov <dryajov@gmail.com>",
"Alan Shaw <alan.shaw@protocol.ai>",
"Yusef Napora <yusef@napora.org>",
"Richard Littauer <richard.littauer@gmail.com>",
"Arve Knudsen <arve.knudsen@gmail.com>",
"Hugo Dias <hugomrdias@gmail.com>",
"Jack Kleeman <jackkleeman@gmail.com>",
"Jacob Heun <jacobheun@gmail.com>",
"Joao Santos <jrmsantos15@gmail.com>",
"Maciej Krüger <mkg20001@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"Richard Schneider <makaretu@gmail.com>",
"Tom Swindell <t.swindell@rubyx.co.uk>",
"Vasco Santos <vasco.santos@ua.pt>",
"Victor Bjelkholm <victorbjelkholm@gmail.com>",
"Yusef Napora <yusef@napora.org>",
"dignifiedquire <dignifiedquire@users.noreply.github.com>",
"dirkmc <dirkmdev@gmail.com>",
"greenkeeper[bot] <greenkeeper[bot]@users.noreply.github.com>",
"nikuda <nikuda@gmail.com>"
"nikuda <nikuda@gmail.com>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Tom Swindell <t.swindell@rubyx.co.uk>",
"Carson Farmer <carson.farmer@gmail.com>",
"Alberto Elias <hi@albertoelias.me>",
"Joao Santos <jrmsantos15@gmail.com>"
]
}

17
src/aes/cipher-mode.js Normal file
View File

@@ -0,0 +1,17 @@
'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
}

View File

@@ -1,8 +1,27 @@
'use strict'
const crypto = require('browserify-aes')
const { Buffer } = require('buffer')
require('node-forge/lib/aes')
const forge = require('node-forge/lib/forge')
module.exports = {
createCipheriv: crypto.createCipheriv,
createDecipheriv: crypto.createDecipheriv
createCipheriv: (mode, key, iv) => {
const cipher2 = forge.cipher.createCipher('AES-CTR', key.toString('binary'))
cipher2.start({ iv: iv.toString('binary') })
return {
update: (data) => {
cipher2.update(forge.util.createBuffer(data.toString('binary')))
return Buffer.from(cipher2.output.getBytes(), 'binary')
}
}
},
createDecipheriv: (mode, key, iv) => {
const cipher2 = forge.cipher.createDecipher('AES-CTR', key.toString('binary'))
cipher2.start({ iv: iv.toString('binary') })
return {
update: (data) => {
cipher2.update(forge.util.createBuffer(data.toString('binary')))
return Buffer.from(cipher2.output.getBytes(), 'binary')
}
}
}
}

View File

@@ -1,34 +0,0 @@
'use strict'
const asm = require('asmcrypto.js')
exports.create = async function (key, iv) { // eslint-disable-line require-await
if (key.length !== 16 && key.length !== 32) {
throw new Error('Invalid key length')
}
const enc = new asm.AES_CTR.Encrypt({
key: key,
nonce: iv
})
const dec = new asm.AES_CTR.Decrypt({
key: key,
nonce: iv
})
const res = {
async encrypt (data) { // eslint-disable-line require-await
return Buffer.from(
enc.process(data).result
)
},
async decrypt (data) { // eslint-disable-line require-await
return Buffer.from(
dec.process(data).result
)
}
}
return res
}

View File

@@ -1,18 +1,10 @@
'use strict'
const ciphers = require('./ciphers')
const CIPHER_MODES = {
16: 'aes-128-ctr',
32: 'aes-256-ctr'
}
const cipherMode = require('./cipher-mode')
exports.create = async function (key, iv) { // eslint-disable-line require-await
const mode = CIPHER_MODES[key.length]
if (!mode) {
throw new Error('Invalid key length')
}
const mode = cipherMode(key)
const cipher = ciphers.createCipheriv(mode, key, iv)
const decipher = ciphers.createDecipheriv(mode, key, iv)

View File

@@ -1,6 +1,6 @@
'use strict'
const webcrypto = require('../webcrypto.js')
const { Buffer } = require('buffer')
const webcrypto = require('../webcrypto')
const lengths = require('./lengths')
const hashTypes = {
@@ -10,13 +10,13 @@ const hashTypes = {
}
const sign = async (key, data) => {
return Buffer.from(await webcrypto.subtle.sign({ name: 'HMAC' }, key, data))
return Buffer.from(await webcrypto.get().subtle.sign({ name: 'HMAC' }, key, data))
}
exports.create = async function (hashType, secret) {
const hash = hashTypes[hashType]
const key = await webcrypto.subtle.importKey(
const key = await webcrypto.get().subtle.importKey(
'raw',
secret,
{

345
src/index.d.ts vendored Normal file
View File

@@ -0,0 +1,345 @@
/// <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;

View File

@@ -1,8 +1,10 @@
'use strict'
const webcrypto = require('../webcrypto.js')
const BN = require('asn1.js').bignum
const { toBase64, toBn } = require('../util')
const errcode = require('err-code')
const { Buffer } = require('buffer')
const webcrypto = require('../webcrypto')
const { bufferToBase64url, base64urlToBuffer } = require('../util')
const validateCurveType = require('./validate-curve-type')
const bits = {
'P-256': 256,
@@ -11,7 +13,8 @@ const bits = {
}
exports.generateEphmeralKeyPair = async function (curve) {
const pair = await webcrypto.subtle.generateKey(
validateCurveType(Object.keys(bits), curve)
const pair = await webcrypto.get().subtle.generateKey(
{
name: 'ECDH',
namedCurve: curve
@@ -25,7 +28,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
let privateKey
if (forcePrivate) {
privateKey = await webcrypto.subtle.importKey(
privateKey = await webcrypto.get().subtle.importKey(
'jwk',
unmarshalPrivateKey(curve, forcePrivate),
{
@@ -40,7 +43,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
}
const keys = [
await webcrypto.subtle.importKey(
await webcrypto.get().subtle.importKey(
'jwk',
unmarshalPublicKey(curve, theirPub),
{
@@ -53,7 +56,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
privateKey
]
return Buffer.from(await webcrypto.subtle.deriveBits(
return Buffer.from(await webcrypto.get().subtle.deriveBits(
{
name: 'ECDH',
namedCurve: curve,
@@ -64,7 +67,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
))
}
const publicKey = await webcrypto.subtle.exportKey('jwk', pair.publicKey)
const publicKey = await webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
return {
key: marshalPublicKey(publicKey),
@@ -86,8 +89,8 @@ function marshalPublicKey (jwk) {
return Buffer.concat([
Buffer.from([4]), // uncompressed point
toBn(jwk.x).toArrayLike(Buffer, 'be', byteLen),
toBn(jwk.y).toArrayLike(Buffer, 'be', byteLen)
base64urlToBuffer(jwk.x, byteLen),
base64urlToBuffer(jwk.y, byteLen)
], 1 + byteLen * 2)
}
@@ -96,22 +99,19 @@ function unmarshalPublicKey (curve, key) {
const byteLen = curveLengths[curve]
if (!key.slice(0, 1).equals(Buffer.from([4]))) {
throw new Error('Invalid key format')
throw errcode(new Error('Cannot unmarshal public key - invalid key format'), 'ERR_INVALID_KEY_FORMAT')
}
const x = new BN(key.slice(1, byteLen + 1))
const y = new BN(key.slice(1 + byteLen))
return {
kty: 'EC',
crv: curve,
x: toBase64(x, byteLen),
y: toBase64(y, byteLen),
x: bufferToBase64url(key.slice(1, byteLen + 1), byteLen),
y: bufferToBase64url(key.slice(1 + byteLen), byteLen),
ext: true
}
}
function unmarshalPrivateKey (curve, key) {
const result = unmarshalPublicKey(curve, key.public)
result.d = toBase64(new BN(key.private))
return result
}
const unmarshalPrivateKey = (curve, key) => ({
...unmarshalPublicKey(curve, key.public),
d: bufferToBase64url(key.private)
})

View File

@@ -1,6 +1,7 @@
'use strict'
const crypto = require('crypto')
const validateCurveType = require('./validate-curve-type')
const curves = {
'P-256': 'prime256v1',
@@ -9,9 +10,8 @@ const curves = {
}
exports.generateEphmeralKeyPair = async function (curve) { // eslint-disable-line require-await
if (!curves[curve]) {
throw new Error(`Unkown curve: ${curve}`)
}
validateCurveType(Object.keys(curves), curve)
const ecdh = crypto.createECDH(curves[curve])
ecdh.generateKeys()

View File

@@ -1,8 +1,10 @@
'use strict'
const multihashing = require('multihashing-async')
const { Buffer } = require('buffer')
const sha = require('multihashing-async/src/sha')
const protobuf = require('protons')
const bs58 = require('bs58')
const multibase = require('multibase')
const errcode = require('err-code')
const crypto = require('./ed25519')
const pbm = protobuf(require('./keys.proto'))
@@ -32,7 +34,7 @@ class Ed25519PublicKey {
}
async hash () { // eslint-disable-line require-await
return multihashing(this.bytes, 'sha2-256')
return sha.multihashing(this.bytes, 'sha2-256')
}
}
@@ -49,10 +51,6 @@ class Ed25519PrivateKey {
}
get public () {
if (!this._publicKey) {
throw new Error('public key not provided')
}
return new Ed25519PublicKey(this._publicKey)
}
@@ -72,7 +70,7 @@ class Ed25519PrivateKey {
}
async hash () { // eslint-disable-line require-await
return multihashing(this.bytes, 'sha2-256')
return sha.multihashing(this.bytes, 'sha2-256')
}
/**
@@ -86,7 +84,7 @@ class Ed25519PrivateKey {
*/
async id () {
const hash = await this.public.hash()
return bs58.encode(hash)
return multibase.encode('base58btc', hash).toString().slice(1)
}
}
@@ -103,13 +101,13 @@ function unmarshalEd25519PublicKey (bytes) {
}
async function generateKeyPair () {
const { secretKey, publicKey } = await crypto.generateKey()
return new Ed25519PrivateKey(secretKey, publicKey)
const { privateKey, publicKey } = await crypto.generateKey()
return new Ed25519PrivateKey(privateKey, publicKey)
}
async function generateKeyPairFromSeed (seed) {
const { secretKey, publicKey } = await crypto.generateKeyFromSeed(seed)
return new Ed25519PrivateKey(secretKey, publicKey)
const { privateKey, publicKey } = await crypto.generateKeyFromSeed(seed)
return new Ed25519PrivateKey(privateKey, publicKey)
}
function ensureKey (key, length) {
@@ -117,7 +115,7 @@ function ensureKey (key, length) {
key = new Uint8Array(key)
}
if (!(key instanceof Uint8Array) || key.length !== length) {
throw new Error('Key must be a Uint8Array or Buffer of length ' + length)
throw errcode(new Error('Key must be a Uint8Array or Buffer of length ' + length), 'ERR_INVALID_KEY_TYPE')
}
return key
}

View File

@@ -1,23 +1,24 @@
'use strict'
const nacl = require('tweetnacl')
exports.publicKeyLength = nacl.sign.publicKeyLength
exports.privateKeyLength = nacl.sign.secretKeyLength
require('node-forge/lib/ed25519')
const forge = require('node-forge/lib/forge')
exports.publicKeyLength = forge.pki.ed25519.constants.PUBLIC_KEY_BYTE_LENGTH
exports.privateKeyLength = forge.pki.ed25519.constants.PRIVATE_KEY_BYTE_LENGTH
exports.generateKey = async function () { // eslint-disable-line require-await
return nacl.sign.keyPair()
return forge.pki.ed25519.generateKeyPair()
}
// seed should be a 32 byte uint8array
exports.generateKeyFromSeed = async function (seed) { // eslint-disable-line require-await
return nacl.sign.keyPair.fromSeed(seed)
return forge.pki.ed25519.generateKeyPair({ seed })
}
exports.hashAndSign = async function (key, msg) { // eslint-disable-line require-await
return Buffer.from(nacl.sign.detached(msg, key))
return forge.pki.ed25519.sign({ message: msg, privateKey: key })
// return 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)
return forge.pki.ed25519.verify({ signature: sig, message: msg, publicKey: key })
}

View File

@@ -1,11 +1,12 @@
'use strict'
const { Buffer } = require('buffer')
const protobuf = require('protons')
const keysPBM = protobuf(require('./keys.proto'))
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
@@ -18,9 +19,18 @@ const supportedKeys = {
exports.supportedKeys = supportedKeys
exports.keysPBM = keysPBM
function isValidKeyType (keyType) {
const key = supportedKeys[keyType.toLowerCase()]
return key !== undefined
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
}
exports.keyStretcher = require('./key-stretcher')
@@ -28,24 +38,15 @@ exports.generateEphemeralKeyPair = require('./ephemeral-keys')
// Generates a keypair of the given type and bitsize
exports.generateKeyPair = async (type, bits) => { // eslint-disable-line require-await
let key = supportedKeys[type.toLowerCase()]
if (!key) {
throw new Error('invalid or unsupported key type')
}
return key.generateKeyPair(bits)
return typeToKey(type).generateKeyPair(bits)
}
// 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
let key = supportedKeys[type.toLowerCase()]
if (!key) {
throw new Error('invalid or unsupported key type')
}
const key = typeToKey(type)
if (type.toLowerCase() !== 'ed25519') {
throw new Error('Seed key derivation is unimplemented for RSA or secp256k1')
throw errcode(new Error('Seed key derivation is unimplemented for RSA or secp256k1'), 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE')
}
return key.generateKeyPairFromSeed(seed, bits)
}
@@ -65,20 +66,17 @@ exports.unmarshalPublicKey = (buf) => {
if (supportedKeys.secp256k1) {
return supportedKeys.secp256k1.unmarshalSecp256k1PublicKey(data)
} else {
throw new Error('secp256k1 support requires libp2p-crypto-secp256k1 package')
throw errcode(new Error(ErrMissingSecp256K1.message), ErrMissingSecp256K1.code)
}
default:
throw new Error('invalid or unsupported key type')
typeToKey(decoded.Type) // throws because type is not supported
}
}
// Converts a public key object into a protobuf serialized public key
exports.marshalPublicKey = (key, type) => {
type = (type || 'rsa').toLowerCase()
if (!isValidKeyType(type)) {
throw new Error('invalid or unsupported key type')
}
typeToKey(type) // check type
return key.bytes
}
@@ -97,27 +95,24 @@ exports.unmarshalPrivateKey = async (buf) => { // eslint-disable-line require-aw
if (supportedKeys.secp256k1) {
return supportedKeys.secp256k1.unmarshalSecp256k1PrivateKey(data)
} else {
throw new Error('secp256k1 support requires libp2p-crypto-secp256k1 package')
throw errcode(new Error(ErrMissingSecp256K1.message), ErrMissingSecp256K1.code)
}
default:
throw new Error('invalid or unsupported key type')
typeToKey(decoded.Type) // throws because type is not supported
}
}
// Converts a private key object into a protobuf serialized private key
exports.marshalPrivateKey = (key, type) => {
type = (type || 'rsa').toLowerCase()
if (!isValidKeyType(type)) {
throw new Error('invalid or unsupported key type')
}
typeToKey(type) // check 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 new Error('Cannot read the key, most likely the password is wrong or not a RSA key')
throw errcode(new Error('Cannot read the key, most likely the password is wrong or not a RSA key'), 'ERR_CANNOT_DECRYPT_PEM')
}
let der = forge.asn1.toDer(forge.pki.privateKeyToAsn1(key))
der = Buffer.from(der.getBytes(), 'binary')

22
src/keys/jwk2pem.js Normal file
View File

@@ -0,0 +1,22 @@
'use strict'
require('node-forge/lib/rsa')
const forge = require('node-forge/lib/forge')
const { base64urlToBigInteger } = require('../util')
function convert (key, types) {
return types.map(t => base64urlToBigInteger(key[t]))
}
function jwk2priv (key) {
return forge.pki.setRsaPrivateKey(...convert(key, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']))
}
function jwk2pub (key) {
return forge.pki.setRsaPublicKey(...convert(key, ['n', 'e']))
}
module.exports = {
jwk2pub,
jwk2priv
}

View File

@@ -1,5 +1,6 @@
'use strict'
const { Buffer } = require('buffer')
const errcode = require('err-code')
const hmac = require('../hmac')
const cipherMap = {
@@ -23,11 +24,12 @@ module.exports = async (cipherType, hash, secret) => {
const cipher = cipherMap[cipherType]
if (!cipher) {
throw new Error('unkown cipherType passed')
const allowed = Object.keys(cipherMap).join(' / ')
throw errcode(new Error(`unknown cipher type '${cipherType}'. Must be ${allowed}`), 'ERR_INVALID_CIPHER_TYPE')
}
if (!hash) {
throw new Error('unkown hashType passed')
throw errcode(new Error('missing hash type'), 'ERR_MISSING_HASH_TYPE')
}
const cipherKeySize = cipher.keySize
@@ -39,7 +41,7 @@ module.exports = async (cipherType, hash, secret) => {
const m = await hmac.create(hash, secret)
let a = await m.digest(seed)
let result = []
const result = []
let j = 0
while (j < resultLength) {

View File

@@ -1,12 +1,13 @@
'use strict'
const webcrypto = require('../webcrypto.js')
const { Buffer } = require('buffer')
const webcrypto = require('../webcrypto')
const randomBytes = require('../random-bytes')
exports.utils = require('./rsa-utils')
exports.generateKey = async function (bits) {
const pair = await webcrypto.subtle.generateKey(
const pair = await webcrypto.get().subtle.generateKey(
{
name: 'RSASSA-PKCS1-v1_5',
modulusLength: bits,
@@ -27,7 +28,7 @@ exports.generateKey = async function (bits) {
// Takes a jwk key
exports.unmarshalPrivateKey = async function (key) {
const privateKey = await webcrypto.subtle.importKey(
const privateKey = await webcrypto.get().subtle.importKey(
'jwk',
key,
{
@@ -57,7 +58,7 @@ exports.unmarshalPrivateKey = async function (key) {
exports.getRandomValues = randomBytes
exports.hashAndSign = async function (key, msg) {
const privateKey = await webcrypto.subtle.importKey(
const privateKey = await webcrypto.get().subtle.importKey(
'jwk',
key,
{
@@ -68,7 +69,7 @@ exports.hashAndSign = async function (key, msg) {
['sign']
)
const sig = await webcrypto.subtle.sign(
const sig = await webcrypto.get().subtle.sign(
{ name: 'RSASSA-PKCS1-v1_5' },
privateKey,
Uint8Array.from(msg)
@@ -78,7 +79,7 @@ exports.hashAndSign = async function (key, msg) {
}
exports.hashAndVerify = async function (key, sig, msg) {
const publicKey = await webcrypto.subtle.importKey(
const publicKey = await webcrypto.get().subtle.importKey(
'jwk',
key,
{
@@ -89,7 +90,7 @@ exports.hashAndVerify = async function (key, sig, msg) {
['verify']
)
return webcrypto.subtle.verify(
return webcrypto.get().subtle.verify(
{ name: 'RSASSA-PKCS1-v1_5' },
publicKey,
sig,
@@ -99,13 +100,13 @@ exports.hashAndVerify = async function (key, sig, msg) {
function exportKey (pair) {
return Promise.all([
webcrypto.subtle.exportKey('jwk', pair.privateKey),
webcrypto.subtle.exportKey('jwk', pair.publicKey)
webcrypto.get().subtle.exportKey('jwk', pair.privateKey),
webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
])
}
function derivePublicFromPrivate (jwKey) {
return webcrypto.subtle.importKey(
return webcrypto.get().subtle.importKey(
'jwk',
{
kty: jwKey.kty,
@@ -120,3 +121,32 @@ function derivePublicFromPrivate (jwKey) {
['verify']
)
}
/*
RSA encryption/decryption for the browser with webcrypto workarround
"bloody dark magic. webcrypto's why."
Explanation:
- Convert JWK to nodeForge
- Convert msg buffer to nodeForge buffer: ByteBuffer is a "binary-string backed buffer", so let's make our buffer a binary string
- Convert resulting nodeForge buffer to buffer: it returns a binary string, turn that into a uint8array(buffer)
*/
const { jwk2pub, jwk2priv } = require('./jwk2pem')
function convertKey (key, pub, msg, handle) {
const fkey = pub ? jwk2pub(key) : jwk2priv(key)
const fmsg = Buffer.from(msg).toString('binary')
const fomsg = handle(fmsg, fkey)
return Buffer.from(fomsg, 'binary')
}
exports.encrypt = function (key, msg) {
return convertKey(key, true, msg, (msg, key) => key.encrypt(msg))
}
exports.decrypt = function (key, msg) {
return convertKey(key, false, msg, (msg, key) => key.decrypt(msg))
}

View File

@@ -1,13 +1,14 @@
'use strict'
const multihashing = require('multihashing-async')
const sha = require('multihashing-async/src/sha')
const protobuf = require('protons')
const bs58 = require('bs58')
const multibase = require('multibase')
const errcode = require('err-code')
const crypto = require('./rsa')
const pbm = protobuf(require('./keys.proto'))
require('node-forge/lib/sha512')
require('node-forge/lib/pbe')
require('node-forge/lib/ed25519')
const forge = require('node-forge/lib/forge')
class RsaPublicKey {
@@ -31,7 +32,7 @@ class RsaPublicKey {
}
encrypt (bytes) {
return this._key.encrypt(bytes, 'RSAES-PKCS1-V1_5')
return crypto.encrypt(this._key, bytes)
}
equals (key) {
@@ -39,7 +40,7 @@ class RsaPublicKey {
}
async hash () { // eslint-disable-line require-await
return multihashing(this.bytes, 'sha2-256')
return sha.multihashing(this.bytes, 'sha2-256')
}
}
@@ -61,12 +62,16 @@ class RsaPrivateKey {
get public () {
if (!this._publicKey) {
throw new Error('public key not provided')
throw errcode(new Error('public key not provided'), 'ERR_PUBKEY_NOT_PROVIDED')
}
return new RsaPublicKey(this._publicKey)
}
decrypt (bytes) {
return crypto.decrypt(this._key, bytes)
}
marshal () {
return crypto.utils.jwkToPkcs1(this._key)
}
@@ -83,7 +88,7 @@ class RsaPrivateKey {
}
async hash () { // eslint-disable-line require-await
return multihashing(this.bytes, 'sha2-256')
return sha.multihashing(this.bytes, 'sha2-256')
}
/**
@@ -97,7 +102,7 @@ class RsaPrivateKey {
*/
async id () {
const hash = await this.public.hash()
return bs58.encode(hash)
return multibase.encode('base58btc', hash).toString().slice(1)
}
/**
@@ -105,7 +110,6 @@ class RsaPrivateKey {
*
* @param {string} password - The password to read the encrypted PEM
* @param {string} [format] - Defaults to 'pkcs-8'.
* @returns {KeyInfo}
*/
async export (password, format = 'pkcs-8') { // eslint-disable-line require-await
let pem = null
@@ -123,7 +127,7 @@ class RsaPrivateKey {
}
pem = forge.pki.encryptRsaPrivateKey(privateKey, password, options)
} else {
throw new Error(`Unknown export format '${format}'`)
throw errcode(new Error(`Unknown export format '${format}'. Must be pkcs-8`), 'ERR_INVALID_EXPORT_FORMAT')
}
return pem

View File

@@ -1,68 +1,27 @@
'use strict'
const asn1 = require('asn1.js')
const util = require('./../util')
const toBase64 = util.toBase64
const toBn = util.toBn
const RSAPrivateKey = asn1.define('RSAPrivateKey', function () {
this.seq().obj(
this.key('version').int(),
this.key('modulus').int(),
this.key('publicExponent').int(),
this.key('privateExponent').int(),
this.key('prime1').int(),
this.key('prime2').int(),
this.key('exponent1').int(),
this.key('exponent2').int(),
this.key('coefficient').int()
)
})
const AlgorithmIdentifier = asn1.define('AlgorithmIdentifier', function () {
this.seq().obj(
this.key('algorithm').objid({
'1.2.840.113549.1.1.1': 'rsa'
}),
this.key('none').optional().null_(),
this.key('curve').optional().objid(),
this.key('params').optional().seq().obj(
this.key('p').int(),
this.key('q').int(),
this.key('g').int()
)
)
})
const PublicKey = asn1.define('RSAPublicKey', function () {
this.seq().obj(
this.key('algorithm').use(AlgorithmIdentifier),
this.key('subjectPublicKey').bitstr()
)
})
const RSAPublicKey = asn1.define('RSAPublicKey', function () {
this.seq().obj(
this.key('modulus').int(),
this.key('publicExponent').int()
)
})
const { Buffer } = require('buffer')
require('node-forge/lib/asn1')
require('node-forge/lib/rsa')
const forge = require('node-forge/lib/forge')
const { bigIntegerToUintBase64url, base64urlToBigInteger } = require('./../util')
// Convert a PKCS#1 in ASN1 DER format to a JWK key
exports.pkcs1ToJwk = function (bytes) {
const asn1 = RSAPrivateKey.decode(bytes, 'der')
const asn1 = forge.asn1.fromDer(bytes.toString('binary'))
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
// https://tools.ietf.org/html/rfc7518#section-6.3.1
return {
kty: 'RSA',
n: toBase64(asn1.modulus),
e: toBase64(asn1.publicExponent),
d: toBase64(asn1.privateExponent),
p: toBase64(asn1.prime1),
q: toBase64(asn1.prime2),
dp: toBase64(asn1.exponent1),
dq: toBase64(asn1.exponent2),
qi: toBase64(asn1.coefficient),
n: bigIntegerToUintBase64url(privateKey.n),
e: bigIntegerToUintBase64url(privateKey.e),
d: bigIntegerToUintBase64url(privateKey.d),
p: bigIntegerToUintBase64url(privateKey.p),
q: bigIntegerToUintBase64url(privateKey.q),
dp: bigIntegerToUintBase64url(privateKey.dP),
dq: bigIntegerToUintBase64url(privateKey.dQ),
qi: bigIntegerToUintBase64url(privateKey.qInv),
alg: 'RS256',
kid: '2011-04-29'
}
@@ -70,28 +29,29 @@ exports.pkcs1ToJwk = function (bytes) {
// Convert a JWK key into PKCS#1 in ASN1 DER format
exports.jwkToPkcs1 = function (jwk) {
return RSAPrivateKey.encode({
version: 0,
modulus: toBn(jwk.n),
publicExponent: toBn(jwk.e),
privateExponent: toBn(jwk.d),
prime1: toBn(jwk.p),
prime2: toBn(jwk.q),
exponent1: toBn(jwk.dp),
exponent2: toBn(jwk.dq),
coefficient: toBn(jwk.qi)
}, 'der')
const asn1 = forge.pki.privateKeyToAsn1({
n: base64urlToBigInteger(jwk.n),
e: base64urlToBigInteger(jwk.e),
d: base64urlToBigInteger(jwk.d),
p: base64urlToBigInteger(jwk.p),
q: base64urlToBigInteger(jwk.q),
dP: base64urlToBigInteger(jwk.dp),
dQ: base64urlToBigInteger(jwk.dq),
qInv: base64urlToBigInteger(jwk.qi)
})
return Buffer.from(forge.asn1.toDer(asn1).getBytes(), 'binary')
}
// Convert a PKCIX in ASN1 DER format to a JWK key
exports.pkixToJwk = function (bytes) {
const ndata = PublicKey.decode(bytes, 'der')
const asn1 = RSAPublicKey.decode(ndata.subjectPublicKey.data, 'der')
const asn1 = forge.asn1.fromDer(bytes.toString('binary'))
const publicKey = forge.pki.publicKeyFromAsn1(asn1)
return {
kty: 'RSA',
n: toBase64(asn1.modulus),
e: toBase64(asn1.publicExponent),
n: bigIntegerToUintBase64url(publicKey.n),
e: bigIntegerToUintBase64url(publicKey.e),
alg: 'RS256',
kid: '2011-04-29'
}
@@ -99,16 +59,10 @@ exports.pkixToJwk = function (bytes) {
// Convert a JWK key to PKCIX in ASN1 DER format
exports.jwkToPkix = function (jwk) {
return PublicKey.encode({
algorithm: {
algorithm: 'rsa',
none: null
},
subjectPublicKey: {
data: RSAPublicKey.encode({
modulus: toBn(jwk.n),
publicExponent: toBn(jwk.e)
}, 'der')
}
}, 'der')
const asn1 = forge.pki.publicKeyToAsn1({
n: base64urlToBigInteger(jwk.n),
e: base64urlToBigInteger(jwk.e)
})
return Buffer.from(forge.asn1.toDer(asn1).getBytes(), 'binary')
}

View File

@@ -1,8 +1,12 @@
'use strict'
const crypto = require('crypto')
const errcode = require('err-code')
const randomBytes = require('../random-bytes')
// @ts-check
/**
* @type {PrivateKey}
*/
let keypair
try {
if (process.env.LP2P_FORCE_CRYPTO_LIB === 'keypair') {
@@ -40,7 +44,7 @@ exports.generateKey = async function (bits) { // eslint-disable-line require-awa
// Takes a jwk key
exports.unmarshalPrivateKey = async function (key) { // eslint-disable-line require-await
if (!key) {
throw new Error('Key is invalid')
throw errcode(new Error('Missing key parameter'), 'ERR_MISSING_KEY')
}
return {
privateKey: key,
@@ -67,3 +71,13 @@ exports.hashAndVerify = async function (key, sig, msg) { // eslint-disable-line
const pem = jwkToPem(key)
return verify.verify(pem, sig)
}
const padding = crypto.constants.RSA_PKCS1_PADDING
exports.encrypt = function (key, bytes) {
return crypto.publicEncrypt({ key: jwkToPem(key), padding }, bytes)
}
exports.decrypt = function (key, bytes) {
return crypto.privateDecrypt({ key: jwkToPem(key), padding }, bytes)
}

View File

@@ -0,0 +1,10 @@
'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')
}
}

View File

@@ -2,6 +2,7 @@
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.
@@ -29,7 +30,8 @@ const hashName = {
function pbkdf2 (password, salt, iterations, keySize, hash) {
const hasher = hashName[hash]
if (!hasher) {
throw new Error(`Hash '${hash}' is unknown or not supported`)
const types = Object.keys(hashName).join(' / ')
throw errcode(new Error(`Hash '${hash}' is unknown or not supported. Must be ${types}`), 'ERR_UNSUPPORTED_HASH_TYPE')
}
const dek = forgePbkdf2(
password,

View File

@@ -1,9 +1,10 @@
'use strict'
const randomBytes = require('iso-random-stream/src/random')
const errcode = require('err-code')
module.exports = function (number) {
if (!number || typeof number !== 'number') {
throw new Error('first argument must be a Number bigger than 0')
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')
}
return randomBytes(number)
return randomBytes(length)
}

View File

@@ -1,20 +1,55 @@
'use strict'
const BN = require('asn1.js').bignum
const { Buffer } = require('buffer')
require('node-forge/lib/util')
require('node-forge/lib/jsbn')
const forge = require('node-forge/lib/forge')
// Convert a BN.js instance to a base64 encoded string without padding
exports.bigIntegerToUintBase64url = (num, len) => {
// Call `.abs()` to convert to unsigned
let buf = Buffer.from(num.abs().toByteArray()) // toByteArray converts to big endian
// toByteArray() gives us back a signed array, which will include a leading 0
// byte if the most significant bit of the number is 1:
// https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-integer
// Our number will always be positive so we should remove the leading padding.
buf = buf[0] === 0 ? buf.slice(1) : buf
if (len != null) {
if (buf.length > len) throw new Error('byte array longer than desired length')
buf = Buffer.concat([Buffer.alloc(len - buf.length), buf])
}
return exports.bufferToBase64url(buf)
}
// Convert a Buffer to a base64 encoded string without padding
// Adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#appendix-C
exports.toBase64 = function toBase64 (bn, len) {
// if len is defined then the bytes are leading-0 padded to the length
let s = bn.toArrayLike(Buffer, 'be', len).toString('base64')
return s
.replace(/(=*)$/, '') // Remove any trailing '='s
exports.bufferToBase64url = buf => {
return buf
.toString('base64')
.split('=')[0] // Remove any trailing '='s
.replace(/\+/g, '-') // 62nd char of encoding
.replace(/\//g, '_') // 63rd char of encoding
}
// Convert a base64 encoded string to a BN.js instance
exports.toBn = function toBn (str) {
return new BN(Buffer.from(str, 'base64'))
// Convert a base64url encoded string to a BigInteger
exports.base64urlToBigInteger = str => {
const buf = exports.base64urlToBuffer(str)
return new forge.jsbn.BigInteger(buf.toString('hex'), 16)
}
exports.base64urlToBuffer = (str, len) => {
str = (str + '==='.slice((str.length + 3) % 4))
.replace(/-/g, '+')
.replace(/_/g, '/')
let buf = Buffer.from(str, 'base64')
if (len != null) {
if (buf.length > len) throw new Error('byte array longer than desired length')
buf = Buffer.concat([Buffer.alloc(len - buf.length), buf])
}
return buf
}

View File

@@ -1,5 +1,24 @@
/* global self */
/* eslint-env browser */
'use strict'
module.exports = self.crypto || self.msCrypto
// 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
}

View File

@@ -1,11 +1,13 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-disable valid-jsdoc */
/* eslint-env mocha */
'use strict'
const { Buffer } = require('buffer')
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/aes')
@@ -16,6 +18,8 @@ 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 () => {
@@ -84,8 +88,18 @@ describe('AES-CTR', () => {
}
})
})
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) {
const data = Buffer.alloc(100)
data.fill(Math.ceil(Math.random() * 100))

61
test/browser.js Normal file
View File

@@ -0,0 +1,61 @@
/* 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')))
})
})

View File

@@ -8,9 +8,16 @@ 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)
@@ -38,6 +45,15 @@ describe('libp2p-crypto', function () {
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')
})
// marshalled keys seem to be slightly different
// unsure as to if this is just a difference in encoding
// or a bug
@@ -93,14 +109,15 @@ describe('libp2p-crypto', function () {
})
it('throws on invalid hash name', () => {
expect(() => crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'shaX-xxx')).to.throw()
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')
})
})
describe('randomBytes', () => {
it('throws with no number passed', () => {
it('throws with invalid number passed', () => {
expect(() => {
crypto.randomBytes()
crypto.randomBytes(-1)
}).to.throw()
})

View File

@@ -1,4 +1,5 @@
'use strict'
const { Buffer } = require('buffer')
module.exports = {
curve: 'P-256',

View File

@@ -1,4 +1,5 @@
'use strict'
const { Buffer } = require('buffer')
module.exports = {
// These were generated in a gore (https://github.com/motemen/gore) repl session:

View File

@@ -1,5 +1,5 @@
'use strict'
const { Buffer } = require('buffer')
module.exports = {
private: {
hash: Buffer.from([

View File

@@ -1,5 +1,5 @@
'use strict'
const { Buffer } = require('buffer')
module.exports = [{
cipher: 'AES-256',
hash: 'SHA256',

View File

@@ -1,5 +1,6 @@
'use strict'
const { Buffer } = require('buffer')
module.exports = {
// protobuf marshaled key pair generated with libp2p-crypto-secp256k1
// and marshaled with libp2p-crypto.marshalPublicKey / marshalPrivateKey

View File

@@ -1,6 +1,7 @@
/* eslint-env mocha */
'use strict'
const { Buffer } = require('buffer')
const util = require('util')
const garbage = [Buffer.from('00010203040506070809', 'hex'), {}, null, false, undefined, true, 1, 0, Buffer.from(''), 'aGVsbG93b3JsZA==', 'helloworld', '']
@@ -14,7 +15,7 @@ function doTests (fncName, fnc, num, skipBuffersAndStrings) {
// skip this garbage because it's a buffer or a string and we were told do do that
return
}
let args = []
const args = []
for (let i = 0; i < num; i++) {
args.push(garbage)
}
@@ -29,12 +30,4 @@ function doTests (fncName, fnc, num, skipBuffersAndStrings) {
})
}
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
module.exports = { doTests }

View File

@@ -1,7 +1,7 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
const { Buffer } = require('buffer')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect

View File

@@ -1,6 +1,7 @@
/* eslint-env mocha */
'use strict'
const { Buffer } = require('buffer')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
@@ -12,8 +13,14 @@ 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)
@@ -116,13 +123,17 @@ describe('ed25519', function () {
expect(valid).to.be.eql(false)
})
describe('returns error via cb instead of crashing', () => {
describe('throws error instead of crashing', () => {
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
testGarbage.doTests('key.verify', key.verify.bind(key), 2)
testGarbage.doTests('crypto.keys.unmarshalPrivateKey', crypto.keys.unmarshalPrivateKey.bind(crypto.keys))
testGarbage.doTests('key.verify', key.verify.bind(key), 2, null)
testGarbage.doTests('crypto.keys.unmarshalPrivateKey', crypto.keys.unmarshalPrivateKey.bind(crypto.keys), null, null)
})
describe('go interop', () => {
// @ts-check
/**
* @type {PrivateKey}
*/
let privateKey
before(async () => {

View File

@@ -11,6 +11,10 @@ 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,
@@ -60,4 +64,14 @@ describe('generateEphemeralKeyPair', () => {
expect(secrets[0]).to.have.length(32)
})
})
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')
})
})

View File

@@ -6,6 +6,7 @@ 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')
@@ -14,6 +15,10 @@ describe('keyStretcher', () => {
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
const hashes = ['SHA1', 'SHA256', 'SHA512']
let res
// @ts-check
/**
* @type {Buffer}
*/
let secret
before(async () => {
@@ -30,6 +35,14 @@ describe('keyStretcher', () => {
})
})
})
it('handles invalid cipher type', () => {
return expectErrCode(crypto.keys.keyStretcher('invalid-cipher', 'SHA256', 'secret'), 'ERR_INVALID_CIPHER_TYPE')
})
it('handles missing hash type', () => {
return expectErrCode(crypto.keys.keyStretcher('AES-128', '', 'secret'), 'ERR_MISSING_HASH_TYPE')
})
})
describe('go interop', () => {

View File

@@ -2,11 +2,13 @@
/* eslint-env mocha */
'use strict'
const { Buffer } = require('buffer')
const chai = require('chai')
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
@@ -14,12 +16,18 @@ 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 crypto.keys.generateKeyPair('RSA', 512)
key = await rsa.generateKeyPair(512)
})
it('generates a valid key', async () => {
@@ -79,6 +87,30 @@ describe('RSA', function () {
expect(valid).to.be.eql(true)
})
it('encrypt and decrypt', async () => {
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)
})
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'))
const msg = Buffer.from('hello')
// 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)
})
it('fails to verify for different data', async () => {
const data = Buffer.from('hello world')
const sig = await key.sign(data)
@@ -112,12 +144,21 @@ describe('RSA', function () {
}
throw new Error('Expected error to be thrown')
})
it('handles invalid export type', () => {
return expectErrCode(key.export('secret', 'invalid-type'), 'ERR_INVALID_EXPORT_FORMAT')
})
})
describe('returns error via cb instead of crashing', () => {
describe('throws error 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))
testGarbage.doTests(
'crypto.keys.unmarshalPrivateKey',
crypto.keys.unmarshalPrivateKey.bind(crypto.keys),
null,
null
)
})
describe('go interop', () => {

View File

@@ -12,7 +12,8 @@ const crypto = require('../../src')
describe('without libp2p-crypto-secp256k1 module present', () => {
before(() => {
sinon.replace(crypto.keys.supportedKeys, 'secp256k1', null)
const empty = null
sinon.replace(crypto.keys.supportedKeys, 'secp256k1', empty)
})
after(() => {

27
test/random-bytes.spec.js Normal file
View File

@@ -0,0 +1,27 @@
/* 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')
})
})

View File

@@ -3,29 +3,37 @@
'use strict'
const chai = require('chai')
const { Buffer } = require('buffer')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
require('node-forge/lib/jsbn')
const forge = require('node-forge/lib/forge')
const util = require('../src/util')
const BN = require('bn.js')
describe('Util', () => {
let bn
before((done) => {
bn = new BN('dead', 16)
done()
before(() => {
bn = new forge.jsbn.BigInteger('dead', 16)
})
it('toBase64', (done) => {
expect(util.toBase64(bn)).to.eql('3q0')
done()
it('should convert BigInteger to a uint base64url encoded string', () => {
expect(util.bigIntegerToUintBase64url(bn)).to.eql('3q0')
})
it('toBase64 zero padding', (done) => {
let bnpad = new BN('ff', 16)
expect(util.toBase64(bnpad, 2)).to.eql('AP8')
done()
it('should convert BigInteger to a uint base64url encoded string with padding', () => {
const bnpad = new forge.jsbn.BigInteger('ff', 16)
expect(util.bigIntegerToUintBase64url(bnpad, 2)).to.eql('AP8')
})
it('should convert base64url encoded string to BigInteger', () => {
const num = util.base64urlToBigInteger('3q0')
expect(num.equals(bn)).to.be.true()
})
it('should convert base64url encoded string to Buffer with padding', () => {
const buf = util.base64urlToBuffer('AP8', 2)
expect(Buffer.from([0, 255])).to.eql(buf)
})
})

21
test/util/index.js Normal file
View File

@@ -0,0 +1,21 @@
/* 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 }

30
tsconfig.json Normal file
View File

@@ -0,0 +1,30 @@
{
"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"]
}