mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-03-15 15:10:59 +00:00
feat: use webcrypto in favor of node-forge
BREAKING CHANGE: generateKeyPair is now async
This commit is contained in:
parent
73a5258876
commit
08c5df5e79
@ -8,6 +8,9 @@ module.exports = {
|
|||||||
alias: {
|
alias: {
|
||||||
'node-forge': path.resolve(__dirname, 'vendor/forge.bundle.js')
|
'node-forge': path.resolve(__dirname, 'vendor/forge.bundle.js')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
externals: {
|
||||||
|
ursa: '{}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ needed for libp2p. This is based on this [go implementation](https://github.com/
|
|||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Example](#example)
|
- [Example](#example)
|
||||||
- [API](#api)
|
- [API](#api)
|
||||||
- [`generateKeyPair(type, bits)`](#generatekeypairtype-bits)
|
- [`generateKeyPair(type, bits, cb)`](#generatekeypairtype-bits-cb)
|
||||||
- [`generateEphemeralKeyPair(curve)`](#generateephemeralkeypaircurve)
|
- [`generateEphemeralKeyPair(curve)`](#generateephemeralkeypaircurve)
|
||||||
- [`keyStretcher(cipherType, hashType, secret)`](#keystretcherciphertype-hashtype-secret)
|
- [`keyStretcher(cipherType, hashType, secret)`](#keystretcherciphertype-hashtype-secret)
|
||||||
- [`marshalPublicKey(key[, type])`](#marshalpublickeykey-type)
|
- [`marshalPublicKey(key[, type])`](#marshalpublickeykey-type)
|
||||||
@ -44,15 +44,17 @@ npm install --save libp2p-crypto
|
|||||||
```js
|
```js
|
||||||
const crypto = require('libp2p-crypto')
|
const crypto = require('libp2p-crypto')
|
||||||
|
|
||||||
var keyPair = crypto.generateKeyPair('RSA', 2048)
|
crypto.generateKeyPair('RSA', 2048, (err, key) => {
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### `generateKeyPair(type, bits)`
|
### `generateKeyPair(type, bits, cb)`
|
||||||
|
|
||||||
- `type: String`, only `'RSA'` is currently supported
|
- `type: String`, only `'RSA'` is currently supported
|
||||||
- `bits: Number`
|
- `bits: Number`
|
||||||
|
- `cb: Function`
|
||||||
|
|
||||||
Generates a keypair of the given type and bitsize.
|
Generates a keypair of the given type and bitsize.
|
||||||
|
|
||||||
|
33
benchmarks/ephemeral-keys.js
Normal file
33
benchmarks/ephemeral-keys.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const Benchmark = require('benchmark')
|
||||||
|
const crypto = require('../src')
|
||||||
|
|
||||||
|
const suite = new Benchmark.Suite('ephemeral-keys')
|
||||||
|
|
||||||
|
const secrets = []
|
||||||
|
const curves = ['P-256', 'P-384', 'P-521']
|
||||||
|
|
||||||
|
curves.forEach((curve) => {
|
||||||
|
suite.add(`ephemeral key with secrect ${curve}`, (d) => {
|
||||||
|
crypto.generateEphemeralKeyPair('P-256', (err, res) => {
|
||||||
|
if (err) throw err
|
||||||
|
res.genSharedKey(res.key, (err, secret) => {
|
||||||
|
if (err) throw err
|
||||||
|
secrets.push(secret)
|
||||||
|
|
||||||
|
d.resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
defer: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
suite
|
||||||
|
.on('cycle', (event) => {
|
||||||
|
console.log(String(event.target))
|
||||||
|
})
|
||||||
|
.run({
|
||||||
|
'async': true
|
||||||
|
})
|
43
benchmarks/key-stretcher.js
Normal file
43
benchmarks/key-stretcher.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const Benchmark = require('benchmark')
|
||||||
|
const crypto = require('../src')
|
||||||
|
|
||||||
|
const suite = new Benchmark.Suite('key-stretcher')
|
||||||
|
|
||||||
|
const keys = []
|
||||||
|
|
||||||
|
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
|
||||||
|
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||||
|
|
||||||
|
crypto.generateEphemeralKeyPair('P-256', (err, res) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
res.genSharedKey(res.key, (err, secret) => {
|
||||||
|
if (err) throw err
|
||||||
|
ciphers.forEach((cipher) => {
|
||||||
|
hashes.forEach((hash) => {
|
||||||
|
suite.add(`keyStretcher ${cipher} ${hash}`, (d) => {
|
||||||
|
crypto.keyStretcher(cipher, hash, secret, (err, k) => {
|
||||||
|
if (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.push(k)
|
||||||
|
d.resolve()
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
defer: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
suite
|
||||||
|
.on('cycle', (event) => {
|
||||||
|
console.log(String(event.target))
|
||||||
|
})
|
||||||
|
.run({
|
||||||
|
'async': true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
47
benchmarks/rsa.js
Normal file
47
benchmarks/rsa.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const Benchmark = require('benchmark')
|
||||||
|
const crypto = require('../src')
|
||||||
|
|
||||||
|
const suite = new Benchmark.Suite('rsa')
|
||||||
|
|
||||||
|
const keys = []
|
||||||
|
|
||||||
|
const bits = [1024, 2048, 4096]
|
||||||
|
|
||||||
|
bits.forEach((bit) => {
|
||||||
|
suite.add(`generateKeyPair ${bit}bits`, (d) => {
|
||||||
|
crypto.generateKeyPair('RSA', bit, (err, key) => {
|
||||||
|
if (err) throw err
|
||||||
|
keys.push(key)
|
||||||
|
d.resolve()
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
defer: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.add('sign and verify', (d) => {
|
||||||
|
const key = keys[0]
|
||||||
|
const text = key.genSecret()
|
||||||
|
|
||||||
|
key.sign(text, (err, sig) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
key.public.verify(text, sig, (err, res) => {
|
||||||
|
if (err) throw err
|
||||||
|
if (res !== true) throw new Error('failed to verify')
|
||||||
|
d.resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
defer: true
|
||||||
|
})
|
||||||
|
|
||||||
|
suite
|
||||||
|
.on('cycle', (event) => {
|
||||||
|
console.log(String(event.target))
|
||||||
|
})
|
||||||
|
.run({
|
||||||
|
'async': true
|
||||||
|
})
|
34
package.json
34
package.json
@ -2,17 +2,26 @@
|
|||||||
"name": "libp2p-crypto",
|
"name": "libp2p-crypto",
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"description": "Crypto primitives for libp2p",
|
"description": "Crypto primitives for libp2p",
|
||||||
"main": "lib/index.js",
|
"main": "src/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
|
"browser": {
|
||||||
|
"node-webcrypto-ossl": false,
|
||||||
|
"./src/crypto/webcrypto.js": "./src/crypto/webcrypto-browser.js",
|
||||||
|
"./lib/crypto/webcrypto.js": "./lib/crypto/webcrypto-browser.js",
|
||||||
|
"./src/crypto/hmac.js": "./src/crypto/hmac-browser.js",
|
||||||
|
"./lib/crypto/hmac.js": "./lib/crypto/hmac-browser.js",
|
||||||
|
"./src/crypto/aes.js": "./src/crypto/aes-browser.js",
|
||||||
|
"./lib/crypto/aes.js": "./lib/crypto/aes-browser.js"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "aegir-lint",
|
"lint": "aegir-lint",
|
||||||
"build": "aegir-build",
|
"build": "aegir-build",
|
||||||
"test": "aegir-test",
|
"test": "PHANTOM=off aegir-test",
|
||||||
"test:node": "aegir-test --env node",
|
"test:node": "aegir-test --env node",
|
||||||
"test:browser": "aegir-test --env browser",
|
"test:browser": "PHANTOM=off aegir-test --env browser",
|
||||||
"release": "aegir-release",
|
"release": "PHANTOM=off aegir-release",
|
||||||
"release-minor": "aegir-release --type minor",
|
"release-minor": "PHANTOM=off aegir-release --type minor",
|
||||||
"release-major": "aegir-release --type major",
|
"release-major": "PHANTOM=off aegir-release --type major",
|
||||||
"coverage": "aegir-coverage",
|
"coverage": "aegir-coverage",
|
||||||
"coverage-publish": "aegir-coverage publish"
|
"coverage-publish": "aegir-coverage publish"
|
||||||
},
|
},
|
||||||
@ -25,13 +34,18 @@
|
|||||||
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
|
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"elliptic": "^6.3.2",
|
"asn1.js": "^4.8.1",
|
||||||
|
"async": "^2.0.1",
|
||||||
|
"bn.js": "^4.11.6",
|
||||||
"multihashing": "^0.2.1",
|
"multihashing": "^0.2.1",
|
||||||
"node-forge": "^0.6.39",
|
"node-webcrypto-ossl": "^1.0.7",
|
||||||
"protocol-buffers": "^3.1.6"
|
"nodeify": "^1.0.0",
|
||||||
|
"protocol-buffers": "^3.1.6",
|
||||||
|
"webcrypto-shim": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aegir": "^8.0.0",
|
"aegir": "^8.0.0",
|
||||||
|
"benchmark": "^2.1.1",
|
||||||
"chai": "^3.5.0",
|
"chai": "^3.5.0",
|
||||||
"pre-commit": "^1.1.3"
|
"pre-commit": "^1.1.3"
|
||||||
},
|
},
|
||||||
@ -56,4 +70,4 @@
|
|||||||
"Richard Littauer <richard.littauer@gmail.com>",
|
"Richard Littauer <richard.littauer@gmail.com>",
|
||||||
"greenkeeperio-bot <support@greenkeeper.io>"
|
"greenkeeperio-bot <support@greenkeeper.io>"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
7
src/crypto.js
Normal file
7
src/crypto.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
exports.webcrypto = require('./crypto/webcrypto')()
|
||||||
|
exports.hmac = require('./crypto/hmac')
|
||||||
|
exports.ecdh = require('./crypto/ecdh')
|
||||||
|
exports.aes = require('./crypto/aes')
|
||||||
|
exports.rsa = require('./crypto/rsa')
|
@ -1,13 +0,0 @@
|
|||||||
enum KeyType {
|
|
||||||
RSA = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PublicKey {
|
|
||||||
required KeyType Type = 1;
|
|
||||||
required bytes Data = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PrivateKey {
|
|
||||||
required KeyType Type = 1;
|
|
||||||
required bytes Data = 2;
|
|
||||||
}
|
|
17
src/crypto.proto.js
Normal file
17
src/crypto.proto.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = new Buffer(`
|
||||||
|
enum KeyType {
|
||||||
|
RSA = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PublicKey {
|
||||||
|
required KeyType Type = 1;
|
||||||
|
required bytes Data = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PrivateKey {
|
||||||
|
required KeyType Type = 1;
|
||||||
|
required bytes Data = 2;
|
||||||
|
}
|
||||||
|
`)
|
52
src/crypto/aes-browser.js
Normal file
52
src/crypto/aes-browser.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const nodeify = require('nodeify')
|
||||||
|
|
||||||
|
const crypto = require('./webcrypto')()
|
||||||
|
|
||||||
|
exports.create = function (key, iv, callback) {
|
||||||
|
nodeify(crypto.subtle.importKey(
|
||||||
|
'raw',
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
name: 'AES-CTR'
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
['encrypt', 'decrypt']
|
||||||
|
).then((key) => {
|
||||||
|
const counter = copy(iv)
|
||||||
|
|
||||||
|
return {
|
||||||
|
encrypt (data, cb) {
|
||||||
|
nodeify(crypto.subtle.encrypt(
|
||||||
|
{
|
||||||
|
name: 'AES-CTR',
|
||||||
|
counter: counter,
|
||||||
|
length: 128
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
data
|
||||||
|
).then((raw) => Buffer.from(raw)), cb)
|
||||||
|
},
|
||||||
|
|
||||||
|
decrypt (data, cb) {
|
||||||
|
nodeify(crypto.subtle.decrypt(
|
||||||
|
{
|
||||||
|
name: 'AES-CTR',
|
||||||
|
counter: counter,
|
||||||
|
length: 128
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
data
|
||||||
|
).then((raw) => Buffer.from(raw)), cb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy (buf) {
|
||||||
|
const fresh = new Buffer(buf.length)
|
||||||
|
buf.copy(fresh)
|
||||||
|
|
||||||
|
return fresh
|
||||||
|
}
|
30
src/crypto/aes.js
Normal file
30
src/crypto/aes.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
const ciphers = {
|
||||||
|
16: 'aes-128-ctr',
|
||||||
|
32: 'aes-256-ctr'
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.create = function (key, iv, callback) {
|
||||||
|
const name = ciphers[key.length]
|
||||||
|
if (!name) {
|
||||||
|
return callback(new Error('Invalid key length'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipher = crypto.createCipheriv(name, key, iv)
|
||||||
|
const decipher = crypto.createDecipheriv(name, key, iv)
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
encrypt (data, cb) {
|
||||||
|
cb(null, cipher.update(data))
|
||||||
|
},
|
||||||
|
|
||||||
|
decrypt (data, cb) {
|
||||||
|
cb(null, decipher.update(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, res)
|
||||||
|
}
|
58
src/crypto/ecdh.js
Normal file
58
src/crypto/ecdh.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const crypto = require('./webcrypto')()
|
||||||
|
const nodeify = require('nodeify')
|
||||||
|
|
||||||
|
exports.generateEphmeralKeyPair = function (curve, callback) {
|
||||||
|
nodeify(crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: 'ECDH',
|
||||||
|
namedCurve: curve
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
['deriveBits']
|
||||||
|
).then((pair) => {
|
||||||
|
// forcePrivate is used for testing only
|
||||||
|
const genSharedKey = (theirPub, forcePrivate, cb) => {
|
||||||
|
if (typeof forcePrivate === 'function') {
|
||||||
|
cb = forcePrivate
|
||||||
|
forcePrivate = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateKey = forcePrivate || pair.privateKey
|
||||||
|
nodeify(crypto.subtle.importKey(
|
||||||
|
'spki',
|
||||||
|
theirPub,
|
||||||
|
{
|
||||||
|
name: 'ECDH',
|
||||||
|
namedCurve: curve
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
[]
|
||||||
|
).then((publicKey) => {
|
||||||
|
return crypto.subtle.deriveBits(
|
||||||
|
{
|
||||||
|
name: 'ECDH',
|
||||||
|
namedCurve: curve,
|
||||||
|
public: publicKey
|
||||||
|
},
|
||||||
|
privateKey,
|
||||||
|
256
|
||||||
|
)
|
||||||
|
}).then((bits) => {
|
||||||
|
// return p.derive(pub.getPublic()).toBuffer('be')
|
||||||
|
return Buffer.from(bits)
|
||||||
|
}), cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.subtle.exportKey(
|
||||||
|
'spki',
|
||||||
|
pair.publicKey
|
||||||
|
).then((publicKey) => {
|
||||||
|
return {
|
||||||
|
key: Buffer.from(publicKey),
|
||||||
|
genSharedKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}), callback)
|
||||||
|
}
|
38
src/crypto/hmac-browser.js
Normal file
38
src/crypto/hmac-browser.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const nodeify = require('nodeify')
|
||||||
|
|
||||||
|
const crypto = require('./webcrypto')()
|
||||||
|
const lengths = require('./hmac-lengths')
|
||||||
|
|
||||||
|
const hashTypes = {
|
||||||
|
SHA1: 'SHA-1',
|
||||||
|
SHA256: 'SHA-256',
|
||||||
|
SHA512: 'SHA-512'
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.create = function (hashType, secret, callback) {
|
||||||
|
const hash = hashTypes[hashType]
|
||||||
|
|
||||||
|
nodeify(crypto.subtle.importKey(
|
||||||
|
'raw',
|
||||||
|
secret,
|
||||||
|
{
|
||||||
|
name: 'HMAC',
|
||||||
|
hash: {name: hash}
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
['sign']
|
||||||
|
).then((key) => {
|
||||||
|
return {
|
||||||
|
digest (data, cb) {
|
||||||
|
nodeify(crypto.subtle.sign(
|
||||||
|
{name: 'HMAC'},
|
||||||
|
key,
|
||||||
|
data
|
||||||
|
).then((raw) => Buffer.from(raw)), cb)
|
||||||
|
},
|
||||||
|
length: lengths[hashType]
|
||||||
|
}
|
||||||
|
}), callback)
|
||||||
|
}
|
7
src/crypto/hmac-lengths.js
Normal file
7
src/crypto/hmac-lengths.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
SHA1: 20,
|
||||||
|
SHA256: 32,
|
||||||
|
SHA512: 64
|
||||||
|
}
|
24
src/crypto/hmac.js
Normal file
24
src/crypto/hmac.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
const lengths = require('./hmac-lengths')
|
||||||
|
|
||||||
|
exports.create = function (hash, secret, callback) {
|
||||||
|
const res = {
|
||||||
|
digest (data, cb) {
|
||||||
|
const hmac = genFresh()
|
||||||
|
hmac.update(data)
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
cb(null, hmac.digest())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
length: lengths[hash]
|
||||||
|
}
|
||||||
|
|
||||||
|
function genFresh () {
|
||||||
|
return crypto.createHmac(hash.toLowerCase(), secret)
|
||||||
|
}
|
||||||
|
callback(null, res)
|
||||||
|
}
|
205
src/crypto/rsa.js
Normal file
205
src/crypto/rsa.js
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const multihashing = require('multihashing')
|
||||||
|
const nodeify = require('nodeify')
|
||||||
|
const BN = require('bn.js')
|
||||||
|
const asn1 = require('asn1.js')
|
||||||
|
|
||||||
|
const crypto = require('./webcrypto')()
|
||||||
|
|
||||||
|
const sha2256 = multihashing.createHash('sha2-256')
|
||||||
|
|
||||||
|
exports.generateKey = function (bits, callback) {
|
||||||
|
nodeify(crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: 'RSASSA-PKCS1-v1_5',
|
||||||
|
modulusLength: bits,
|
||||||
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
||||||
|
hash: {name: 'SHA-256'}
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
['sign', 'verify']
|
||||||
|
)
|
||||||
|
.then(exportKey)
|
||||||
|
.then((keys) => {
|
||||||
|
return {
|
||||||
|
privateKey: keys[0],
|
||||||
|
publicKey: Buffer.from(keys[1])
|
||||||
|
}
|
||||||
|
}), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a jwk key
|
||||||
|
exports.unmarshalPrivateKey = function (key, callback) {
|
||||||
|
const privateKey = crypto.subtle.importKey(
|
||||||
|
'jwk',
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
name: 'RSASSA-PKCS1-v1_5',
|
||||||
|
hash: {name: 'SHA-256'}
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
['sign']
|
||||||
|
)
|
||||||
|
|
||||||
|
nodeify(Promise.all([
|
||||||
|
privateKey,
|
||||||
|
derivePublicFromPrivate(privateKey)
|
||||||
|
]).then((keys) => {
|
||||||
|
return exportKey({
|
||||||
|
privateKey: keys[0],
|
||||||
|
publicKey: keys[1]
|
||||||
|
})
|
||||||
|
}).then((keys) => {
|
||||||
|
return {
|
||||||
|
privateKey: keys[0],
|
||||||
|
publicKey: Buffer.from(keys[1])
|
||||||
|
}
|
||||||
|
}), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getRandomValues = function (arr) {
|
||||||
|
return Buffer.from(crypto.getRandomValues(arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.hashAndSign = function (key, msg, callback) {
|
||||||
|
sha2256(msg, (err, digest) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeify(crypto.subtle.importKey(
|
||||||
|
'jwk',
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
name: 'RSASSA-PKCS1-v1_5',
|
||||||
|
hash: {name: 'SHA-256'}
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
['sign']
|
||||||
|
).then((privateKey) => {
|
||||||
|
return crypto.subtle.sign(
|
||||||
|
{name: 'RSASSA-PKCS1-v1_5'},
|
||||||
|
privateKey,
|
||||||
|
Uint8Array.from(digest)
|
||||||
|
)
|
||||||
|
}).then((sig) => Buffer.from(sig)), callback)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.hashAndVerify = function (key, sig, msg, callback) {
|
||||||
|
sha2256(msg, (err, digest) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeify(crypto.subtle.importKey(
|
||||||
|
'spki',
|
||||||
|
Uint8Array.from(key),
|
||||||
|
{
|
||||||
|
name: 'RSASSA-PKCS1-v1_5',
|
||||||
|
hash: {name: 'SHA-256'}
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
['verify']
|
||||||
|
).then((publicKey) => {
|
||||||
|
return crypto.subtle.verify(
|
||||||
|
{name: 'RSASSA-PKCS1-v1_5'},
|
||||||
|
publicKey,
|
||||||
|
Uint8Array.from(sig),
|
||||||
|
Uint8Array.from(digest)
|
||||||
|
)
|
||||||
|
}), callback)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportKey (pair) {
|
||||||
|
return Promise.all([
|
||||||
|
crypto.subtle.exportKey('jwk', pair.privateKey),
|
||||||
|
crypto.subtle.exportKey('spki', pair.publicKey)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function derivePublicFromPrivate (privatePromise) {
|
||||||
|
return privatePromise.then((privateKey) => {
|
||||||
|
return crypto.subtle.exportKey('jwk', privateKey)
|
||||||
|
}).then((jwKey) => crypto.subtle.importKey(
|
||||||
|
'jwk',
|
||||||
|
{
|
||||||
|
kty: jwKey.kty,
|
||||||
|
n: jwKey.n,
|
||||||
|
e: jwKey.e,
|
||||||
|
alg: jwKey.alg,
|
||||||
|
kid: jwKey.kid
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RSASSA-PKCS1-v1_5',
|
||||||
|
hash: {name: 'SHA-256'}
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
['verify']
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert a PKCS#1 in ASN1 DER format to a JWK key
|
||||||
|
exports.pkcs1ToJwk = function (bytes) {
|
||||||
|
const asn1 = RSAPrivateKey.decode(bytes, 'der')
|
||||||
|
|
||||||
|
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),
|
||||||
|
alg: 'RS256',
|
||||||
|
kid: '2011-04-29'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a BN.js instance to a base64 encoded string without padding
|
||||||
|
// Adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#appendix-C
|
||||||
|
function toBase64 (bn) {
|
||||||
|
let s = bn.toBuffer('be').toString('base64')
|
||||||
|
|
||||||
|
return s
|
||||||
|
.replace(/(=*)$/, '') // 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
|
||||||
|
function toBn (str) {
|
||||||
|
return new BN(Buffer.from(str, 'base64'))
|
||||||
|
}
|
13
src/crypto/webcrypto-browser.js
Normal file
13
src/crypto/webcrypto-browser.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = function getWebCrypto () {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
require('webcrypto-shim')
|
||||||
|
|
||||||
|
if (window.crypto) {
|
||||||
|
return window.crypto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Please use an environment with crypto support')
|
||||||
|
}
|
7
src/crypto/webcrypto.js
Normal file
7
src/crypto/webcrypto.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = function getWebCrypto () {
|
||||||
|
const WebCrypto = require('node-webcrypto-ossl')
|
||||||
|
const webCrypto = new WebCrypto()
|
||||||
|
return webCrypto
|
||||||
|
}
|
@ -1,36 +1,11 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const EC = require('elliptic').ec
|
const crypto = require('./crypto')
|
||||||
|
|
||||||
const curveMap = {
|
|
||||||
'P-256': 'p256',
|
|
||||||
'P-384': 'p384',
|
|
||||||
'P-521': 'p521'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates an ephemeral public key and returns a function that will compute
|
// Generates an ephemeral public key and returns a function that will compute
|
||||||
// the shared secret key.
|
// the shared secret key.
|
||||||
//
|
//
|
||||||
// Focuses only on ECDH now, but can be made more general in the future.
|
// Focuses only on ECDH now, but can be made more general in the future.
|
||||||
module.exports = (curveName) => {
|
module.exports = (curve, callback) => {
|
||||||
const curve = curveMap[curveName]
|
crypto.ecdh.generateEphmeralKeyPair(curve, callback)
|
||||||
if (!curve) {
|
|
||||||
throw new Error('unsupported curve passed')
|
|
||||||
}
|
|
||||||
|
|
||||||
const ec = new EC(curve)
|
|
||||||
|
|
||||||
const priv = ec.genKeyPair()
|
|
||||||
|
|
||||||
// forcePrivate is used for testing only
|
|
||||||
const genSharedKey = (theirPub, forcePrivate) => {
|
|
||||||
const pub = ec.keyFromPublic(theirPub, 'hex')
|
|
||||||
const p = forcePrivate || priv
|
|
||||||
return p.derive(pub.getPublic()).toBuffer('be')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: new Buffer(priv.getPublic('hex'), 'hex'),
|
|
||||||
genSharedKey
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
34
src/index.js
34
src/index.js
@ -1,24 +1,26 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const protobuf = require('protocol-buffers')
|
const protobuf = require('protocol-buffers')
|
||||||
const fs = require('fs')
|
const pbm = protobuf(require('./crypto.proto'))
|
||||||
const path = require('path')
|
const c = require('./crypto')
|
||||||
const pbm = protobuf(fs.readFileSync(path.join(__dirname, './crypto.proto')))
|
|
||||||
|
exports.hmac = c.hmac
|
||||||
|
exports.aes = c.aes
|
||||||
|
exports.rsa = c.rsa
|
||||||
|
exports.webcrypto = c.webcrypto
|
||||||
|
|
||||||
exports.utils = require('./utils')
|
|
||||||
const keys = exports.keys = require('./keys')
|
const keys = exports.keys = require('./keys')
|
||||||
|
|
||||||
exports.keyStretcher = require('./key-stretcher')
|
exports.keyStretcher = require('./key-stretcher')
|
||||||
exports.generateEphemeralKeyPair = require('./ephemeral-keys')
|
exports.generateEphemeralKeyPair = require('./ephemeral-keys')
|
||||||
|
|
||||||
// Generates a keypair of the given type and bitsize
|
// Generates a keypair of the given type and bitsize
|
||||||
exports.generateKeyPair = (type, bits) => {
|
exports.generateKeyPair = (type, bits, cb) => {
|
||||||
let key = keys[type.toLowerCase()]
|
let key = keys[type.toLowerCase()]
|
||||||
if (!key) {
|
if (!key) {
|
||||||
throw new Error('invalid or unsupported key type')
|
return cb(new Error('invalid or unsupported key type'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return key.generateKeyPair(bits)
|
key.generateKeyPair(bits, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a protobuf serialized public key into its
|
// Converts a protobuf serialized public key into its
|
||||||
@ -43,22 +45,19 @@ exports.marshalPublicKey = (key, type) => {
|
|||||||
throw new Error('invalid or unsupported key type')
|
throw new Error('invalid or unsupported key type')
|
||||||
}
|
}
|
||||||
|
|
||||||
return pbm.PublicKey.encode({
|
return key.bytes
|
||||||
Type: pbm.KeyType.RSA,
|
|
||||||
Data: key.marshal()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a protobuf serialized private key into its
|
// Converts a protobuf serialized private key into its
|
||||||
// representative object
|
// representative object
|
||||||
exports.unmarshalPrivateKey = (buf) => {
|
exports.unmarshalPrivateKey = (buf, callback) => {
|
||||||
const decoded = pbm.PrivateKey.decode(buf)
|
const decoded = pbm.PrivateKey.decode(buf)
|
||||||
|
|
||||||
switch (decoded.Type) {
|
switch (decoded.Type) {
|
||||||
case pbm.KeyType.RSA:
|
case pbm.KeyType.RSA:
|
||||||
return keys.rsa.unmarshalRsaPrivateKey(decoded.Data)
|
return keys.rsa.unmarshalRsaPrivateKey(decoded.Data, callback)
|
||||||
default:
|
default:
|
||||||
throw new Error('invalid or unsupported key type')
|
callback(new Error('invalid or unsupported key type'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +70,5 @@ exports.marshalPrivateKey = (key, type) => {
|
|||||||
throw new Error('invalid or unsupported key type')
|
throw new Error('invalid or unsupported key type')
|
||||||
}
|
}
|
||||||
|
|
||||||
return pbm.PrivateKey.encode({
|
return key.bytes
|
||||||
Type: pbm.KeyType.RSA,
|
|
||||||
Data: key.marshal()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const forge = require('node-forge')
|
const crypto = require('./crypto')
|
||||||
const createBuffer = forge.util.createBuffer
|
const whilst = require('async/whilst')
|
||||||
|
|
||||||
const cipherMap = {
|
const cipherMap = {
|
||||||
'AES-128': {
|
'AES-128': {
|
||||||
@ -18,78 +18,91 @@ const cipherMap = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashMap = {
|
|
||||||
SHA1: 'sha1',
|
|
||||||
SHA256: 'sha256',
|
|
||||||
// workaround for https://github.com/digitalbazaar/forge/issues/401
|
|
||||||
SHA512: forge.md.sha512.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates a set of keys for each party by stretching the shared key.
|
// Generates a set of keys for each party by stretching the shared key.
|
||||||
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
|
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
|
||||||
module.exports = (cipherType, hashType, secret) => {
|
module.exports = (cipherType, hash, secret, callback) => {
|
||||||
const cipher = cipherMap[cipherType]
|
const cipher = cipherMap[cipherType]
|
||||||
const hash = hashMap[hashType]
|
|
||||||
|
|
||||||
if (!cipher) {
|
if (!cipher) {
|
||||||
throw new Error('unkown cipherType passed')
|
return callback(new Error('unkown cipherType passed'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
throw new Error('unkown hashType passed')
|
return callback(new Error('unkown hashType passed'))
|
||||||
}
|
|
||||||
|
|
||||||
if (Buffer.isBuffer(secret)) {
|
|
||||||
secret = createBuffer(secret.toString('binary'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipherKeySize = cipher.keySize
|
const cipherKeySize = cipher.keySize
|
||||||
const ivSize = cipher.ivSize
|
const ivSize = cipher.ivSize
|
||||||
const hmacKeySize = 20
|
const hmacKeySize = 20
|
||||||
const seed = 'key expansion'
|
const seed = Buffer.from('key expansion')
|
||||||
const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize)
|
const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize)
|
||||||
|
|
||||||
const m = forge.hmac.create()
|
crypto.hmac.create(hash, secret, (err, m) => {
|
||||||
m.start(hash, secret)
|
if (err) {
|
||||||
m.update(seed)
|
return callback(err)
|
||||||
|
|
||||||
let a = m.digest().bytes()
|
|
||||||
const result = createBuffer()
|
|
||||||
|
|
||||||
let j = 0
|
|
||||||
for (; j < resultLength;) {
|
|
||||||
m.start(hash, secret)
|
|
||||||
m.update(a)
|
|
||||||
m.update(seed)
|
|
||||||
|
|
||||||
const b = createBuffer(m.digest(), 'raw')
|
|
||||||
let todo = b.length()
|
|
||||||
|
|
||||||
if (j + todo > resultLength) {
|
|
||||||
todo = resultLength - j
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.putBytes(b.getBytes(todo))
|
m.digest(seed, (err, a) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err)
|
||||||
|
}
|
||||||
|
|
||||||
j += todo
|
let result = []
|
||||||
|
let j = 0
|
||||||
|
|
||||||
m.start(hash, secret)
|
whilst(
|
||||||
m.update(a)
|
() => j < resultLength,
|
||||||
a = m.digest().bytes()
|
stretch,
|
||||||
}
|
finish
|
||||||
|
)
|
||||||
|
|
||||||
const half = resultLength / 2
|
function stretch (cb) {
|
||||||
const r1 = createBuffer(result.getBytes(half))
|
m.digest(Buffer.concat([a, seed]), (err, b) => {
|
||||||
const r2 = createBuffer(result.getBytes())
|
if (err) {
|
||||||
|
return cb(err)
|
||||||
|
}
|
||||||
|
|
||||||
const createKey = (res) => ({
|
let todo = b.length
|
||||||
iv: new Buffer(res.getBytes(ivSize), 'binary'),
|
|
||||||
cipherKey: new Buffer(res.getBytes(cipherKeySize), 'binary'),
|
if (j + todo > resultLength) {
|
||||||
macKey: new Buffer(res.getBytes(), 'binary')
|
todo = resultLength - j
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(b)
|
||||||
|
|
||||||
|
j += todo
|
||||||
|
|
||||||
|
m.digest(a, (err, _a) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err)
|
||||||
|
}
|
||||||
|
a = _a
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const half = resultLength / 2
|
||||||
|
const resultBuffer = Buffer.concat(result)
|
||||||
|
const r1 = resultBuffer.slice(0, half)
|
||||||
|
const r2 = resultBuffer.slice(half, resultLength)
|
||||||
|
|
||||||
|
const createKey = (res) => ({
|
||||||
|
iv: res.slice(0, ivSize),
|
||||||
|
cipherKey: res.slice(ivSize, ivSize + cipherKeySize),
|
||||||
|
macKey: res.slice(ivSize + cipherKeySize)
|
||||||
|
})
|
||||||
|
|
||||||
|
callback(null, {
|
||||||
|
k1: createKey(r1),
|
||||||
|
k2: createKey(r2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
|
||||||
k1: createKey(r1),
|
|
||||||
k2: createKey(r2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
110
src/keys/rsa.js
110
src/keys/rsa.js
@ -1,35 +1,23 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const forge = require('node-forge')
|
const multihashing = require('multihashing')
|
||||||
const protobuf = require('protocol-buffers')
|
const protobuf = require('protocol-buffers')
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const utils = require('../utils')
|
const crypto = require('../crypto').rsa
|
||||||
|
const pbm = protobuf(require('../crypto.proto'))
|
||||||
const pki = forge.pki
|
|
||||||
const rsa = pki.rsa
|
|
||||||
|
|
||||||
const pbm = protobuf(fs.readFileSync(path.join(__dirname, '../crypto.proto')))
|
|
||||||
|
|
||||||
class RsaPublicKey {
|
class RsaPublicKey {
|
||||||
constructor (k) {
|
constructor (key) {
|
||||||
this._key = k
|
this._key = key
|
||||||
}
|
}
|
||||||
|
|
||||||
verify (data, sig) {
|
verify (data, sig, callback) {
|
||||||
const md = forge.md.sha256.create()
|
ensure(callback)
|
||||||
if (Buffer.isBuffer(data)) {
|
crypto.hashAndVerify(this._key, sig, data, callback)
|
||||||
md.update(data.toString('binary'), 'binary')
|
|
||||||
} else {
|
|
||||||
md.update(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._key.verify(md.digest().bytes(), sig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
marshal () {
|
marshal () {
|
||||||
return new Buffer(forge.asn1.toDer(pki.publicKeyToAsn1(this._key)).bytes(), 'binary')
|
return this._key
|
||||||
}
|
}
|
||||||
|
|
||||||
get bytes () {
|
get bytes () {
|
||||||
@ -47,34 +35,27 @@ class RsaPublicKey {
|
|||||||
return this.bytes.equals(key.bytes)
|
return this.bytes.equals(key.bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash () {
|
hash (callback) {
|
||||||
return utils.keyHash(this.bytes)
|
ensure(callback)
|
||||||
|
multihashing(this.bytes, 'sha2-256', callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RsaPrivateKey {
|
class RsaPrivateKey {
|
||||||
constructor (privKey, pubKey) {
|
// key - Object of the jwk format
|
||||||
this._privateKey = privKey
|
// publicKey - Buffer of the spki format
|
||||||
if (pubKey) {
|
constructor (key, publicKey) {
|
||||||
this._publicKey = pubKey
|
this._key = key
|
||||||
} else {
|
this._publicKey = publicKey
|
||||||
this._publicKey = forge.pki.setRsaPublicKey(privKey.n, privKey.e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
genSecret () {
|
genSecret () {
|
||||||
return forge.random.getBytesSync(16)
|
return crypto.getRandomValues(new Uint8Array(16))
|
||||||
}
|
}
|
||||||
|
|
||||||
sign (message) {
|
sign (message, callback) {
|
||||||
const md = forge.md.sha256.create()
|
ensure(callback)
|
||||||
if (Buffer.isBuffer(message)) {
|
crypto.hashAndSign(this._key, message, callback)
|
||||||
md.update(message.toString('binary'), 'binary')
|
|
||||||
} else {
|
|
||||||
md.update(message)
|
|
||||||
}
|
|
||||||
const raw = this._privateKey.sign(md, 'RSASSA-PKCS1-V1_5')
|
|
||||||
return new Buffer(raw, 'binary')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get public () {
|
get public () {
|
||||||
@ -85,12 +66,12 @@ class RsaPrivateKey {
|
|||||||
return new RsaPublicKey(this._publicKey)
|
return new RsaPublicKey(this._publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypt (bytes) {
|
decrypt (msg, callback) {
|
||||||
return this._privateKey.decrypt(bytes, 'RSAES-PKCS1-V1_5')
|
crypto.decrypt(this._key, msg, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
marshal () {
|
marshal () {
|
||||||
return new Buffer(forge.asn1.toDer(pki.privateKeyToAsn1(this._privateKey)).bytes(), 'binary')
|
return crypto.jwkToPkcs1(this._key)
|
||||||
}
|
}
|
||||||
|
|
||||||
get bytes () {
|
get bytes () {
|
||||||
@ -104,32 +85,41 @@ class RsaPrivateKey {
|
|||||||
return this.bytes.equals(key.bytes)
|
return this.bytes.equals(key.bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash () {
|
hash (callback) {
|
||||||
return utils.keyHash(this.bytes)
|
ensure(callback)
|
||||||
|
multihashing(this.bytes, 'sha2-256', callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmarshalRsaPrivateKey (bytes) {
|
function unmarshalRsaPrivateKey (bytes, callback) {
|
||||||
if (Buffer.isBuffer(bytes)) {
|
const jwk = crypto.pkcs1ToJwk(bytes)
|
||||||
bytes = forge.util.createBuffer(bytes.toString('binary'))
|
crypto.unmarshalPrivateKey(jwk, (err, keys) => {
|
||||||
}
|
if (err) {
|
||||||
const key = pki.privateKeyFromAsn1(forge.asn1.fromDer(bytes))
|
return callback(err)
|
||||||
|
}
|
||||||
|
|
||||||
return new RsaPrivateKey(key)
|
callback(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmarshalRsaPublicKey (bytes) {
|
function unmarshalRsaPublicKey (bytes) {
|
||||||
if (Buffer.isBuffer(bytes)) {
|
return new RsaPublicKey(bytes)
|
||||||
bytes = forge.util.createBuffer(bytes.toString('binary'))
|
|
||||||
}
|
|
||||||
const key = pki.publicKeyFromAsn1(forge.asn1.fromDer(bytes))
|
|
||||||
|
|
||||||
return new RsaPublicKey(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateKeyPair (bits) {
|
function generateKeyPair (bits, cb) {
|
||||||
const p = rsa.generateKeyPair({bits})
|
crypto.generateKey(bits, (err, keys) => {
|
||||||
return new RsaPrivateKey(p.privateKey, p.publicKey)
|
if (err) {
|
||||||
|
return cb(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure (cb) {
|
||||||
|
if (typeof cb !== 'function') {
|
||||||
|
throw new Error('callback is required')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const multihashing = require('multihashing')
|
|
||||||
|
|
||||||
// Hashes a key
|
|
||||||
exports.keyHash = (bytes) => {
|
|
||||||
return multihashing(bytes, 'sha2-256')
|
|
||||||
}
|
|
153
stats.md
Normal file
153
stats.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# Stats
|
||||||
|
|
||||||
|
## Size
|
||||||
|
|
||||||
|
| | non-minified | minified |
|
||||||
|
|-------|--------------|----------|
|
||||||
|
|before | `1.7M` | `949K` |
|
||||||
|
|after | `461K` | `291K` |
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
### RSA
|
||||||
|
|
||||||
|
#### Before
|
||||||
|
|
||||||
|
##### Node `6.6.0`
|
||||||
|
|
||||||
|
```
|
||||||
|
generateKeyPair 1024bits x 3.51 ops/sec ±29.45% (22 runs sampled)
|
||||||
|
generateKeyPair 2048bits x 0.17 ops/sec ±145.40% (5 runs sampled)
|
||||||
|
generateKeyPair 4096bits x 0.02 ops/sec ±96.53% (5 runs sampled)
|
||||||
|
sign and verify x 95.98 ops/sec ±1.51% (71 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Browser (Chrome `53.0.2785.116`)
|
||||||
|
|
||||||
|
```
|
||||||
|
generateKeyPair 1024bits x 3.56 ops/sec ±27.16% (23 runs sampled)
|
||||||
|
generateKeyPair 2048bits x 0.49 ops/sec ±69.32% (8 runs sampled)
|
||||||
|
generateKeyPair 4096bits x 0.03 ops/sec ±77.11% (5 runs sampled)
|
||||||
|
sign and verify x 109 ops/sec ±2.00% (53 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### After
|
||||||
|
|
||||||
|
##### Node `6.6.0`
|
||||||
|
|
||||||
|
```
|
||||||
|
generateKeyPair 1024bits x 44.42 ops/sec ±10.21% (43 runs sampled)
|
||||||
|
generateKeyPair 2048bits x 7.46 ops/sec ±22.60% (27 runs sampled)
|
||||||
|
generateKeyPair 4096bits x 1.64 ops/sec ±30.16% (13 runs sampled)
|
||||||
|
sign and verify x 900 ops/sec ±4.03% (68 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Browser (Chrome `53.0.2785.116`)
|
||||||
|
|
||||||
|
```
|
||||||
|
generateKeyPair 1024bits x 5.89 ops/sec ±18.94% (19 runs sampled)
|
||||||
|
generateKeyPair 2048bits x 1.32 ops/sec ±36.84% (10 runs sampled)
|
||||||
|
generateKeyPair 4096bits x 0.20 ops/sec ±62.49% (5 runs sampled)
|
||||||
|
sign and verify x 608 ops/sec ±6.75% (56 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Stretcher
|
||||||
|
|
||||||
|
|
||||||
|
#### Before
|
||||||
|
|
||||||
|
##### Node `6.6.0`
|
||||||
|
|
||||||
|
```
|
||||||
|
keyStretcher AES-128 SHA1 x 3,863 ops/sec ±3.80% (70 runs sampled)
|
||||||
|
keyStretcher AES-128 SHA256 x 3,862 ops/sec ±5.33% (64 runs sampled)
|
||||||
|
keyStretcher AES-128 SHA512 x 3,369 ops/sec ±1.73% (73 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA1 x 3,008 ops/sec ±4.81% (67 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA256 x 2,900 ops/sec ±7.01% (64 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA512 x 2,553 ops/sec ±4.45% (73 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA1 x 28,045 ops/sec ±7.32% (61 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA256 x 18,860 ops/sec ±5.36% (67 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA512 x 12,142 ops/sec ±12.44% (72 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Browser (Chrome `53.0.2785.116`)
|
||||||
|
|
||||||
|
```
|
||||||
|
keyStretcher AES-128 SHA1 x 4,168 ops/sec ±4.08% (49 runs sampled)
|
||||||
|
keyStretcher AES-128 SHA256 x 4,239 ops/sec ±6.36% (48 runs sampled)
|
||||||
|
keyStretcher AES-128 SHA512 x 3,600 ops/sec ±5.15% (51 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA1 x 3,009 ops/sec ±6.82% (48 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA256 x 3,086 ops/sec ±9.56% (19 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA512 x 2,470 ops/sec ±2.22% (54 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA1 x 7,143 ops/sec ±15.17% (9 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA256 x 17,846 ops/sec ±4.74% (46 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA512 x 7,726 ops/sec ±1.81% (50 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### After
|
||||||
|
|
||||||
|
##### Node `6.6.0`
|
||||||
|
|
||||||
|
```
|
||||||
|
keyStretcher AES-128 SHA1 x 6,680 ops/sec ±3.62% (65 runs sampled)
|
||||||
|
keyStretcher AES-128 SHA256 x 8,124 ops/sec ±4.37% (66 runs sampled)
|
||||||
|
keyStretcher AES-128 SHA512 x 11,683 ops/sec ±4.56% (66 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA1 x 5,531 ops/sec ±4.69% (68 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA256 x 6,725 ops/sec ±4.87% (66 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA512 x 9,042 ops/sec ±3.87% (64 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA1 x 40,757 ops/sec ±5.38% (60 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA256 x 41,845 ops/sec ±4.89% (64 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA512 x 42,345 ops/sec ±4.86% (63 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Browser (Chrome `53.0.2785.116`)
|
||||||
|
|
||||||
|
```
|
||||||
|
keyStretcher AES-128 SHA1 x 479 ops/sec ±2.12% (54 runs sampled)
|
||||||
|
keyStretcher AES-128 SHA256 x 668 ops/sec ±2.02% (53 runs sampled)
|
||||||
|
keyStretcher AES-128 SHA512 x 1,112 ops/sec ±1.61% (54 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA1 x 460 ops/sec ±1.37% (54 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA256 x 596 ops/sec ±1.56% (54 runs sampled)
|
||||||
|
keyStretcher AES-256 SHA512 x 808 ops/sec ±3.27% (52 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA1 x 3,015 ops/sec ±3.51% (52 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA256 x 2,755 ops/sec ±3.82% (53 runs sampled)
|
||||||
|
keyStretcher Blowfish SHA512 x 2,955 ops/sec ±5.35% (51 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ephemeral Keys
|
||||||
|
|
||||||
|
#### Before
|
||||||
|
|
||||||
|
##### Node `6.6.0`
|
||||||
|
|
||||||
|
```
|
||||||
|
ephemeral key with secrect P-256 x 89.93 ops/sec ±39.45% (72 runs sampled)
|
||||||
|
ephemeral key with secrect P-384 x 110 ops/sec ±1.28% (71 runs sampled)
|
||||||
|
ephemeral key with secrect P-521 x 112 ops/sec ±1.70% (72 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Browser (Chrome `53.0.2785.116`)
|
||||||
|
|
||||||
|
```
|
||||||
|
ephemeral key with secrect P-256 x 6.27 ops/sec ±15.89% (35 runs sampled)
|
||||||
|
ephemeral key with secrect P-384 x 6.84 ops/sec ±1.21% (35 runs sampled)
|
||||||
|
ephemeral key with secrect P-521 x 6.60 ops/sec ±1.84% (34 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### After
|
||||||
|
|
||||||
|
##### Node `6.6.0`
|
||||||
|
|
||||||
|
```
|
||||||
|
ephemeral key with secrect P-256 x 555 ops/sec ±1.61% (75 runs sampled)
|
||||||
|
ephemeral key with secrect P-384 x 547 ops/sec ±4.40% (68 runs sampled)
|
||||||
|
ephemeral key with secrect P-521 x 583 ops/sec ±4.84% (72 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Browser (Chrome `53.0.2785.116`)
|
||||||
|
|
||||||
|
```
|
||||||
|
ephemeral key with secrect P-256 x 796 ops/sec ±2.36% (53 runs sampled)
|
||||||
|
ephemeral key with secrect P-384 x 788 ops/sec ±2.66% (53 runs sampled)
|
||||||
|
ephemeral key with secrect P-521 x 808 ops/sec ±1.83% (54 runs sampled)
|
||||||
|
```
|
37
test/aes.spec.js
Normal file
37
test/aes.spec.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* eslint max-nested-callbacks: ["error", 8] */
|
||||||
|
/* eslint-env mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const expect = require('chai').expect
|
||||||
|
const crypto = require('../src')
|
||||||
|
|
||||||
|
const bytes = {
|
||||||
|
16: 'AES-128',
|
||||||
|
32: 'AES-256'
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AES-CTR', () => {
|
||||||
|
Object.keys(bytes).forEach((byte) => {
|
||||||
|
it(`${bytes[byte]} - encrypt and decrypt`, (done) => {
|
||||||
|
const key = new Buffer(parseInt(byte, 10))
|
||||||
|
key.fill(5)
|
||||||
|
|
||||||
|
const iv = new Buffer(16)
|
||||||
|
iv.fill(1)
|
||||||
|
|
||||||
|
crypto.aes.create(key, iv, (err, cipher) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
|
|
||||||
|
cipher.encrypt(new Buffer('hello'), (err, res) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
|
|
||||||
|
cipher.decrypt(res, (err, res) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
|
expect(res).to.be.eql(new Buffer('hello'))
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,44 +1,95 @@
|
|||||||
|
/* eslint max-nested-callbacks: ["error", 8] */
|
||||||
/* eslint-env mocha */
|
/* eslint-env mocha */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const expect = require('chai').expect
|
const expect = require('chai').expect
|
||||||
const EC = require('elliptic').ec
|
|
||||||
|
|
||||||
const crypto = require('../src')
|
const crypto = require('../src')
|
||||||
const fixtures = require('./fixtures/go-elliptic-key')
|
const fixtures = require('./fixtures/go-elliptic-key')
|
||||||
|
|
||||||
|
const curves = ['P-256', 'P-384', 'P-521']
|
||||||
|
// const lengths = {
|
||||||
|
// 'P-256': 32,
|
||||||
|
// 'P-384': 48,
|
||||||
|
// 'P-521': 65
|
||||||
|
// }
|
||||||
|
|
||||||
describe('generateEphemeralKeyPair', () => {
|
describe('generateEphemeralKeyPair', () => {
|
||||||
it('returns a function that generates a shared secret', () => {
|
curves.forEach((curve) => {
|
||||||
const res = crypto.generateEphemeralKeyPair('P-256')
|
it(`generate and shared key ${curve}`, (done) => {
|
||||||
const ourPublic = '044374add0df35706db7dade25f3959fc051d2ef5166f8a6a0aa632d0ab41cdb4d30e1a064e121ac56155235a6b8d4c5d8fe35e019f507f4e2ff1445e229d7af43'
|
crypto.generateEphemeralKeyPair(curve, (err, ours) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
expect(
|
crypto.generateEphemeralKeyPair(curve, (err, theirs) => {
|
||||||
res.genSharedKey(ourPublic)
|
if (err) {
|
||||||
).to.have.length(32)
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
expect(
|
ours.genSharedKey(theirs.key, (err, shared) => {
|
||||||
res.key
|
if (err) {
|
||||||
).to.exist
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(shared).to.exist
|
||||||
|
// expect(shared).to.have.length(lengths[curve])
|
||||||
|
expect(ours.key).to.exist
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('go interop', () => {
|
describe.skip('go interop', () => {
|
||||||
it('generates a shared secret', () => {
|
it('generates a shared secret', (done) => {
|
||||||
const curve = fixtures.curve
|
const curve = fixtures.curve
|
||||||
const ec = new EC(fixtures.curveJs)
|
console.log('start', curve)
|
||||||
const bobPrivate = ec.keyFromPrivate(fixtures.bob.private, 'binary')
|
// crypto.webcrypto.subtle.importKey(
|
||||||
|
// 'pkcs8',
|
||||||
|
// Uint8Array.from(fixtures.bob.private),
|
||||||
|
// {
|
||||||
|
// name: 'ECDH',
|
||||||
|
// namedCurve: curve
|
||||||
|
// },
|
||||||
|
// false,
|
||||||
|
// ['deriveBits']
|
||||||
|
// ).then((bobPrivate) => {
|
||||||
|
// console.log('imported bobs key')
|
||||||
|
// checkKeys(bobPrivate)
|
||||||
|
// }).catch((err) => {
|
||||||
|
// done(err)
|
||||||
|
// })
|
||||||
|
checkKeys()
|
||||||
|
function checkKeys (bobPrivate) {
|
||||||
|
crypto.generateEphemeralKeyPair(curve, (err, alice) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
console.log('genreated ephem pair')
|
||||||
|
const bob = {
|
||||||
|
key: fixtures.bob.public,
|
||||||
|
// this is using bobs private key from go ipfs
|
||||||
|
// instead of alices
|
||||||
|
genSharedKey: (key, cb) => alice.genSharedKey(key, bobPrivate, cb)
|
||||||
|
}
|
||||||
|
|
||||||
const alice = crypto.generateEphemeralKeyPair(curve)
|
alice.genSharedKey(bob.key, (err, s1) => {
|
||||||
const bob = {
|
if (err) {
|
||||||
key: fixtures.bob.public,
|
return done(err)
|
||||||
// this is using bobs private key from go ipfs
|
}
|
||||||
// instead of alices
|
console.log('genshared alice')
|
||||||
genSharedKey: (key) => alice.genSharedKey(key, bobPrivate)
|
bob.genSharedKey(alice.key, (err, s2) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
console.log('genshared bob')
|
||||||
|
expect(s1.equals(s2)).to.be.eql(true)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const s1 = alice.genSharedKey(bob.key)
|
|
||||||
const s2 = bob.genSharedKey(alice.key)
|
|
||||||
|
|
||||||
expect(s1.equals(s2)).to.be.eql(true)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
5
test/fixtures/go-elliptic-key.js
vendored
5
test/fixtures/go-elliptic-key.js
vendored
@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
curve: 'P-256',
|
curve: 'P-256',
|
||||||
curveJs: 'p256',
|
|
||||||
bob: {
|
bob: {
|
||||||
private: [
|
private: [
|
||||||
231, 236, 69, 16, 13, 92, 76, 83, 75, 40, 32, 71, 235, 187, 29, 214, 98, 231, 42, 5, 80, 89, 58, 175, 8, 95, 86, 50, 44, 214, 4, 172
|
181, 217, 162, 151, 225, 36, 53, 253, 107, 66, 27, 27, 232, 72, 0, 0, 103, 167, 84, 62, 203, 91, 97, 137, 131, 193, 230, 126, 98, 242, 216, 170
|
||||||
],
|
],
|
||||||
public: new Buffer([
|
public: new Buffer([
|
||||||
4, 160, 169, 215, 85, 152, 11, 209, 69, 105, 17, 51, 49, 83, 214, 171, 157, 73, 165, 85, 28, 196, 161, 234, 87, 149, 139, 76, 123, 37, 174, 194, 67, 167, 18, 34, 164, 35, 171, 164, 238, 141, 199, 206, 86, 130, 183, 88, 63, 121, 110, 150, 229, 10, 213, 176, 181, 1, 98, 20, 246, 85, 212, 200, 229
|
4, 53, 59, 128, 56, 162, 250, 72, 141, 206, 117, 232, 57, 96, 39, 39, 247, 7, 27, 57, 251, 232, 120, 186, 21, 239, 176, 139, 195, 129, 125, 85, 11, 188, 191, 32, 227, 0, 6, 163, 101, 68, 208, 1, 43, 131, 124, 112, 102, 91, 104, 79, 16, 119, 152, 208, 4, 147, 155, 83, 20, 146, 104, 55, 90
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
test/hmac.spec.js
Normal file
24
test/hmac.spec.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* eslint max-nested-callbacks: ["error", 8] */
|
||||||
|
/* eslint-env mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const expect = require('chai').expect
|
||||||
|
const crypto = require('../src')
|
||||||
|
|
||||||
|
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||||
|
|
||||||
|
describe('HMAC', () => {
|
||||||
|
hashes.forEach((hash) => {
|
||||||
|
it(`${hash} - sign and verify`, (done) => {
|
||||||
|
crypto.hmac.create(hash, new Buffer('secret'), (err, hmac) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
|
|
||||||
|
hmac.digest(new Buffer('hello world'), (err, sig) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
|
expect(sig).to.have.length(hmac.length)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint max-nested-callbacks: ["error", 8] */
|
||||||
/* eslint-env mocha */
|
/* eslint-env mocha */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
@ -8,8 +9,12 @@ const fixtures = require('./fixtures/go-key-rsa')
|
|||||||
|
|
||||||
describe('libp2p-crypto', () => {
|
describe('libp2p-crypto', () => {
|
||||||
let key
|
let key
|
||||||
before(() => {
|
before((done) => {
|
||||||
key = crypto.generateKeyPair('RSA', 2048)
|
crypto.generateKeyPair('RSA', 2048, (err, _key) => {
|
||||||
|
if (err) return done(err)
|
||||||
|
key = _key
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('marshalPublicKey and unmarshalPublicKey', () => {
|
it('marshalPublicKey and unmarshalPublicKey', () => {
|
||||||
@ -18,43 +23,67 @@ describe('libp2p-crypto', () => {
|
|||||||
expect(key2.equals(key.public)).to.be.eql(true)
|
expect(key2.equals(key.public)).to.be.eql(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('marshalPrivateKey and unmarshalPrivateKey', () => {
|
it('marshalPrivateKey and unmarshalPrivateKey', (done) => {
|
||||||
const key2 = crypto.unmarshalPrivateKey(crypto.marshalPrivateKey(key))
|
crypto.unmarshalPrivateKey(crypto.marshalPrivateKey(key), (err, key2) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
expect(key2.equals(key)).to.be.eql(true)
|
expect(key2.equals(key)).to.be.eql(true)
|
||||||
|
expect(key2.public.equals(key.public)).to.be.eql(true)
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// marshalled keys seem to be slightly different
|
||||||
|
// unsure as to if this is just a difference in encoding
|
||||||
|
// or a bug
|
||||||
describe('go interop', () => {
|
describe('go interop', () => {
|
||||||
it('unmarshals private key', () => {
|
it('unmarshals private key', (done) => {
|
||||||
const key = crypto.unmarshalPrivateKey(fixtures.private.key)
|
crypto.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
|
||||||
const hash = fixtures.private.hash
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
const hash = fixtures.private.hash
|
||||||
|
expect(fixtures.private.key).to.be.eql(key.bytes)
|
||||||
|
|
||||||
expect(
|
key.hash((err, digest) => {
|
||||||
key.hash()
|
if (err) {
|
||||||
).to.be.eql(
|
return done(err)
|
||||||
hash
|
}
|
||||||
)
|
|
||||||
|
expect(digest).to.be.eql(hash)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('unmarshals public key', () => {
|
it('unmarshals public key', (done) => {
|
||||||
const key = crypto.unmarshalPublicKey(fixtures.public.key)
|
const key = crypto.unmarshalPublicKey(fixtures.public.key)
|
||||||
const hash = fixtures.public.hash
|
const hash = fixtures.public.hash
|
||||||
|
|
||||||
expect(
|
expect(crypto.marshalPublicKey(key)).to.be.eql(fixtures.public.key)
|
||||||
key.hash()
|
|
||||||
).to.be.eql(
|
key.hash((err, digest) => {
|
||||||
hash
|
if (err) {
|
||||||
)
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(digest).to.be.eql(hash)
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('unmarshal -> marshal, private key', () => {
|
it('unmarshal -> marshal, private key', (done) => {
|
||||||
const key = crypto.unmarshalPrivateKey(fixtures.private.key)
|
crypto.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
|
||||||
const marshalled = crypto.marshalPrivateKey(key)
|
if (err) {
|
||||||
expect(
|
return done(err)
|
||||||
fixtures.private.key.equals(marshalled)
|
}
|
||||||
).to.be.eql(
|
|
||||||
true
|
const marshalled = crypto.marshalPrivateKey(key)
|
||||||
)
|
expect(marshalled).to.be.eql(fixtures.private.key)
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('unmarshal -> marshal, public key', () => {
|
it('unmarshal -> marshal, public key', () => {
|
||||||
|
@ -11,15 +11,38 @@ describe('keyStretcher', () => {
|
|||||||
describe('generate', () => {
|
describe('generate', () => {
|
||||||
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
|
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
|
||||||
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||||
const res = crypto.generateEphemeralKeyPair('P-256')
|
let res
|
||||||
const secret = res.genSharedKey(res.key)
|
let secret
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
crypto.generateEphemeralKeyPair('P-256', (err, _res) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
res = _res
|
||||||
|
res.genSharedKey(res.key, (err, _secret) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = _secret
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
ciphers.forEach((cipher) => {
|
ciphers.forEach((cipher) => {
|
||||||
hashes.forEach((hash) => {
|
hashes.forEach((hash) => {
|
||||||
it(`${cipher} - ${hash}`, () => {
|
it(`${cipher} - ${hash}`, (done) => {
|
||||||
const keys = crypto.keyStretcher(cipher, hash, secret)
|
crypto.keyStretcher(cipher, hash, secret, (err, keys) => {
|
||||||
expect(keys.k1).to.exist
|
if (err) {
|
||||||
expect(keys.k2).to.exist
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(keys.k1).to.exist
|
||||||
|
expect(keys.k2).to.exist
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -27,19 +50,24 @@ describe('keyStretcher', () => {
|
|||||||
|
|
||||||
describe('go interop', () => {
|
describe('go interop', () => {
|
||||||
fixtures.forEach((test) => {
|
fixtures.forEach((test) => {
|
||||||
it(`${test.cipher} - ${test.hash}`, () => {
|
it(`${test.cipher} - ${test.hash}`, (done) => {
|
||||||
const cipher = test.cipher
|
const cipher = test.cipher
|
||||||
const hash = test.hash
|
const hash = test.hash
|
||||||
const secret = test.secret
|
const secret = test.secret
|
||||||
const keys = crypto.keyStretcher(cipher, hash, secret)
|
crypto.keyStretcher(cipher, hash, secret, (err, keys) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
expect(keys.k1.iv).to.be.eql(test.k1.iv)
|
expect(keys.k1.iv).to.be.eql(test.k1.iv)
|
||||||
expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
|
expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
|
||||||
expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
|
expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
|
||||||
|
|
||||||
expect(keys.k2.iv).to.be.eql(test.k2.iv)
|
expect(keys.k2.iv).to.be.eql(test.k2.iv)
|
||||||
expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
|
expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
|
||||||
expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
|
expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
172
test/rsa.spec.js
172
test/rsa.spec.js
@ -8,57 +8,76 @@ const rsa = crypto.keys.rsa
|
|||||||
|
|
||||||
describe('RSA', () => {
|
describe('RSA', () => {
|
||||||
let key
|
let key
|
||||||
before(() => {
|
before((done) => {
|
||||||
key = crypto.generateKeyPair('RSA', 2048)
|
crypto.generateKeyPair('RSA', 2048, (err, _key) => {
|
||||||
|
if (err) return done(err)
|
||||||
|
key = _key
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('generates a valid key', () => {
|
it('generates a valid key', (done) => {
|
||||||
expect(
|
expect(
|
||||||
key
|
key
|
||||||
).to.be.an.instanceof(
|
).to.be.an.instanceof(
|
||||||
rsa.RsaPrivateKey
|
rsa.RsaPrivateKey
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
key.hash((err, digest) => {
|
||||||
key.hash()
|
if (err) {
|
||||||
).to.have.length(
|
return done(err)
|
||||||
34
|
}
|
||||||
)
|
|
||||||
|
expect(digest).to.have.length(34)
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('signs', () => {
|
it('signs', (done) => {
|
||||||
const pk = key.public
|
|
||||||
const text = key.genSecret()
|
const text = key.genSecret()
|
||||||
const sig = key.sign(text)
|
|
||||||
|
|
||||||
expect(
|
key.sign(text, (err, sig) => {
|
||||||
pk.verify(text, sig)
|
if (err) {
|
||||||
).to.be.eql(
|
return done(err)
|
||||||
true
|
}
|
||||||
)
|
|
||||||
|
key.public.verify(text, sig, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(res).to.be.eql(true)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('encoding', () => {
|
it('encoding', (done) => {
|
||||||
const keyMarshal = key.marshal()
|
const keyMarshal = key.marshal()
|
||||||
const key2 = rsa.unmarshalRsaPrivateKey(keyMarshal)
|
rsa.unmarshalRsaPrivateKey(keyMarshal, (err, key2) => {
|
||||||
const keyMarshal2 = key2.marshal()
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
const keyMarshal2 = key2.marshal()
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
keyMarshal
|
keyMarshal
|
||||||
).to.be.eql(
|
).to.be.eql(
|
||||||
keyMarshal2
|
keyMarshal2
|
||||||
)
|
)
|
||||||
|
|
||||||
const pk = key.public
|
const pk = key.public
|
||||||
const pkMarshal = pk.marshal()
|
const pkMarshal = pk.marshal()
|
||||||
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
|
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
|
||||||
const pkMarshal2 = pk2.marshal()
|
const pkMarshal2 = pk2.marshal()
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
pkMarshal
|
pkMarshal
|
||||||
).to.be.eql(
|
).to.be.eql(
|
||||||
pkMarshal2
|
pkMarshal2
|
||||||
)
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('key equals', () => {
|
describe('key equals', () => {
|
||||||
@ -76,54 +95,69 @@ describe('RSA', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('not equals other key', () => {
|
it('not equals other key', (done) => {
|
||||||
const key2 = crypto.generateKeyPair('RSA', 2048)
|
crypto.generateKeyPair('RSA', 2048, (err, key2) => {
|
||||||
|
if (err) return done(err)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
key.equals(key2)
|
key.equals(key2)
|
||||||
).to.be.eql(
|
).to.be.eql(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
key2.equals(key)
|
key2.equals(key)
|
||||||
).to.be.eql(
|
).to.be.eql(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
key.public.equals(key2.public)
|
key.public.equals(key2.public)
|
||||||
).to.be.eql(
|
).to.be.eql(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
key2.public.equals(key.public)
|
key2.public.equals(key.public)
|
||||||
).to.be.eql(
|
).to.be.eql(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sign and verify', () => {
|
it('sign and verify', (done) => {
|
||||||
const data = new Buffer('hello world')
|
const data = new Buffer('hello world')
|
||||||
const sig = key.sign(data)
|
key.sign(data, (err, sig) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
expect(
|
key.public.verify(data, sig, (err, valid) => {
|
||||||
key.public.verify(data, sig)
|
if (err) {
|
||||||
).to.be.eql(
|
return done(err)
|
||||||
true
|
}
|
||||||
)
|
expect(valid).to.be.eql(true)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does fails to verify for different data', () => {
|
it('fails to verify for different data', (done) => {
|
||||||
const data = new Buffer('hello world')
|
const data = new Buffer('hello world')
|
||||||
const sig = key.sign(data)
|
key.sign(data, (err, sig) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err)
|
||||||
|
}
|
||||||
|
|
||||||
expect(
|
key.public.verify(new Buffer('hello'), sig, (err, valid) => {
|
||||||
key.public.verify(new Buffer('hello'), sig)
|
if (err) {
|
||||||
).to.be.eql(
|
return done(err)
|
||||||
false
|
}
|
||||||
)
|
expect(valid).to.be.eql(false)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
1486
vendor/prime.worker.js
vendored
Normal file
1486
vendor/prime.worker.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user