mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-07-23 04:41:57 +00:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1b0fac84a8 | ||
|
efaafa9c06 | ||
|
88b3018c9c | ||
|
9aacb478c4 | ||
|
269d169f7c | ||
|
c956d1ad2a | ||
|
d3601fa936 | ||
|
f01e3812e9 | ||
|
00477e3bcb | ||
|
0f4c533dfa | ||
|
d566e7ef3b | ||
|
78e2ddd2bd | ||
|
0ad513887a | ||
|
e7468d830d | ||
|
cc2094975b | ||
|
ad4bf3b357 | ||
|
730d762717 | ||
|
e01977c5a3 | ||
|
b5d94ecae7 | ||
|
1f9c2ddadb | ||
|
d6d06a8404 | ||
|
8b8d0c1510 | ||
|
b998f63aec | ||
|
adc6eb478c | ||
|
2c1bac5ce9 | ||
|
027a5a9332 | ||
|
2c294b56ab | ||
|
487cd076fb | ||
|
b8e2414420 | ||
|
9f747a173f | ||
|
34c5f5c8f0 | ||
|
a008bc2fcb | ||
|
b68060388f | ||
|
afe94ded6b | ||
|
a5e05603ef | ||
|
0b686d363c |
3
.aegir.js
Normal file
3
.aegir.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
bundlesize: { maxSize: '124kB' }
|
||||
}
|
@@ -1 +0,0 @@
|
||||
src/keys/keys.proto.js
|
@@ -23,7 +23,7 @@ jobs:
|
||||
include:
|
||||
- stage: check
|
||||
script:
|
||||
- npx aegir commitlint --travis
|
||||
- npx aegir build --bundlesize
|
||||
- npx aegir dep-check
|
||||
- npm run lint
|
||||
|
||||
|
63
CHANGELOG.md
63
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
25
README.md
25
README.md
@@ -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.
|
||||
|
||||
|
64
package.json
64
package.json
@@ -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
17
src/aes/cipher-mode.js
Normal 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
|
||||
}
|
@@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
|
||||
|
@@ -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
345
src/index.d.ts
vendored
Normal 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;
|
@@ -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)
|
||||
})
|
||||
|
@@ -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()
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 })
|
||||
}
|
||||
|
@@ -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
22
src/keys/jwk2pem.js
Normal 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
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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')
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
10
src/keys/validate-curve-type.js
Normal file
10
src/keys/validate-curve-type.js
Normal 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')
|
||||
}
|
||||
}
|
@@ -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,
|
||||
|
@@ -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)
|
||||
}
|
||||
|
57
src/util.js
57
src/util.js
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
61
test/browser.js
Normal 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')))
|
||||
})
|
||||
})
|
@@ -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()
|
||||
})
|
||||
|
||||
|
1
test/fixtures/go-elliptic-key.js
vendored
1
test/fixtures/go-elliptic-key.js
vendored
@@ -1,4 +1,5 @@
|
||||
'use strict'
|
||||
const { Buffer } = require('buffer')
|
||||
|
||||
module.exports = {
|
||||
curve: 'P-256',
|
||||
|
1
test/fixtures/go-key-ed25519.js
vendored
1
test/fixtures/go-key-ed25519.js
vendored
@@ -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:
|
||||
|
2
test/fixtures/go-key-rsa.js
vendored
2
test/fixtures/go-key-rsa.js
vendored
@@ -1,5 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
module.exports = {
|
||||
private: {
|
||||
hash: Buffer.from([
|
||||
|
2
test/fixtures/go-stretch-key.js
vendored
2
test/fixtures/go-stretch-key.js
vendored
@@ -1,5 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
module.exports = [{
|
||||
cipher: 'AES-256',
|
||||
hash: 'SHA256',
|
||||
|
1
test/fixtures/secp256k1.js
vendored
1
test/fixtures/secp256k1.js
vendored
@@ -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
|
||||
|
@@ -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 }
|
||||
|
@@ -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
|
||||
|
@@ -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 () => {
|
||||
|
@@ -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')
|
||||
})
|
||||
})
|
||||
|
@@ -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', () => {
|
||||
|
@@ -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', () => {
|
||||
|
@@ -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
27
test/random-bytes.spec.js
Normal 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')
|
||||
})
|
||||
})
|
@@ -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
21
test/util/index.js
Normal 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
30
tsconfig.json
Normal 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"]
|
||||
}
|
Reference in New Issue
Block a user