Compare commits

..

9 Commits

Author SHA1 Message Date
Jacob Heun
2d647cd98b chore: release version v0.16.3 2019-10-25 19:05:42 +02:00
Jacob Heun
a8896de3a2 chore: update contributors 2019-10-25 19:05:42 +02:00
Maciej Krüger
e71fc150c0 feat: backport enc/dec (#160)
* feat: add (rsa)pubKey.encrypt and (rsa)privKey.decrypt

nodeJS only for now

* feat: browser enc/dec

* fix: browser rsa enc/dec

* refactor: cleanup

* fix: lint

* fix: use direct buffers instead of converting to hex

* fix: padding error

* test: add interop test

* feat: use forge to convert jwk2forge

* fix: jwk var naming

* fix: missing cbwrap
2019-10-25 19:01:57 +02:00
dignifiedquire
967594c9a3 chore: release version v0.16.2 2019-09-25 11:50:14 +02:00
dignifiedquire
03893a3795 chore: update contributors 2019-09-25 11:50:14 +02:00
dignifiedquire
50afa4efbc chore(deps): update ursa-optional 2019-09-25 11:44:47 +02:00
Friedel Ziegelmayer
1c54a9b196 Merge pull request #158 from libp2p/v0.16.x-upgrade-node-forge
V0.16.x upgrade node forge
2019-09-25 11:39:07 +02:00
achingbrain
6f2c347895 chore: update aegir so tests run on browsers agin 2019-09-23 10:36:22 +01:00
achingbrain
867982ab92 chore: upgrade node-forge dep 2019-09-23 10:31:35 +01:00
12 changed files with 159 additions and 15 deletions

View File

@@ -1,3 +1,18 @@
<a name="0.16.3"></a>
## [0.16.3](https://github.com/libp2p/js-libp2p-crypto/compare/v0.16.2...v0.16.3) (2019-10-25)
### Features
* backport enc/dec ([#160](https://github.com/libp2p/js-libp2p-crypto/issues/160)) ([e71fc15](https://github.com/libp2p/js-libp2p-crypto/commit/e71fc15))
<a name="0.16.2"></a>
## [0.16.2](https://github.com/libp2p/js-libp2p-crypto/compare/v0.16.1...v0.16.2) (2019-09-25)
<a name="0.16.1"></a>
## [0.16.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.16.0...v0.16.1) (2019-02-26)

View File

@@ -1,6 +1,6 @@
{
"name": "libp2p-crypto",
"version": "0.16.1",
"version": "0.16.3",
"description": "Crypto primitives for libp2p",
"main": "src/index.js",
"leadMaintainer": "Friedel Ziegelmayer <dignifiedquire@gmail.com>",
@@ -45,17 +45,17 @@
"keypair": "^1.0.1",
"libp2p-crypto-secp256k1": "~0.3.0",
"multihashing-async": "~0.5.1",
"node-forge": "~0.7.6",
"node-forge": "~0.9.1",
"pem-jwk": "^2.0.0",
"protons": "^1.0.1",
"rsa-pem-to-jwk": "^1.1.3",
"tweetnacl": "^1.0.0",
"ursa-optional": "~0.9.10"
"ursa-optional": "~0.10.0"
},
"devDependencies": {
"aegir": "^18.0.3",
"aegir": "^20.0.0",
"benchmark": "^2.1.4",
"bundlesize": "~0.17.0",
"bundlesize": "^0.18.0",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
"dirty-chai": "^2.0.1"
@@ -89,6 +89,8 @@
"Vasco Santos <vasco.santos@ua.pt>",
"Victor Bjelkholm <victorbjelkholm@gmail.com>",
"Yusef Napora <yusef@napora.org>",
"achingbrain <alex@achingbrain.net>",
"dignifiedquire <dignifiedquire@users.noreply.github.com>",
"greenkeeper[bot] <greenkeeper[bot]@users.noreply.github.com>",
"nikuda <nikuda@gmail.com>"
]

View File

@@ -28,7 +28,7 @@ exports.generateEphemeralKeyPair = require('./ephemeral-keys')
// Generates a keypair of the given type and bitsize
exports.generateKeyPair = (type, bits, cb) => {
let key = supportedKeys[type.toLowerCase()]
const key = supportedKeys[type.toLowerCase()]
if (!key) {
return cb(new Error('invalid or unsupported key type'))
@@ -40,7 +40,7 @@ exports.generateKeyPair = (type, bits, cb) => {
// Generates a keypair of the given type and bitsize
// seed is a 32 byte uint8array
exports.generateKeyPairFromSeed = (type, seed, bits, cb) => {
let key = supportedKeys[type.toLowerCase()]
const key = supportedKeys[type.toLowerCase()]
if (!key) {
return cb(new Error('invalid or unsupported key type'))
}

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

@@ -0,0 +1,42 @@
'use strict'
const forge = {
util: require('node-forge/lib/util'),
pki: require('node-forge/lib/pki'),
jsbn: require('node-forge/lib/jsbn')
}
function base64urlToBigInteger (str) {
var bytes = forge.util.decode64(
(str + '==='.slice((str.length + 3) % 4))
.replace(/-/g, '+')
.replace(/_/g, '/'))
return new forge.jsbn.BigInteger(forge.util.bytesToHex(bytes), 16)
}
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 jwk2privPem (key) {
return forge.pki.privateKeyToPem(jwk2priv(key))
}
function jwk2pub (key) {
return forge.pki.setRsaPublicKey(...convert(key, ['n', 'e']))
}
function jwk2pubPem (key) {
return forge.pki.publicKeyToPem(jwk2pub(key))
}
module.exports = {
jwk2pub,
jwk2pubPem,
jwk2priv,
jwk2privPem
}

View File

@@ -47,7 +47,7 @@ module.exports = (cipherType, hash, secret, callback) => {
return callback(err)
}
let result = []
const result = []
let j = 0
whilst(

View File

@@ -113,3 +113,32 @@ function derivePublicFromPrivate (jwKey) {
['verify']
)
}
/*
RSA encryption/decryption for the browser with webcrypto workarround
"bloody dark magic. webcrypto's why."
Explanation:
- Convert JWK to nodeForge
- Convert msg buffer to nodeForge buffer: ByteBuffer is a "binary-string backed buffer", so let's make our buffer a binary string
- Convert resulting nodeForge buffer to buffer: it returns a binary string, turn that into a uint8array(buffer)
*/
const { jwk2pub, jwk2priv } = require('./jwk2pem')
function convertKey (key, pub, msg, handle) {
const fkey = pub ? jwk2pub(key) : jwk2priv(key)
const fmsg = Buffer.from(msg).toString('binary')
const fomsg = handle(fmsg, fkey)
return Buffer.from(fomsg, 'binary')
}
exports.encrypt = function (key, msg) {
return convertKey(key, true, msg, (msg, key) => key.encrypt(msg))
}
exports.decrypt = function (key, msg) {
return convertKey(key, false, msg, (msg, key) => key.decrypt(msg))
}

View File

@@ -32,8 +32,8 @@ class RsaPublicKey {
})
}
encrypt (bytes) {
return this._key.encrypt(bytes, 'RSAES-PKCS1-V1_5')
encrypt (bytes, cb) {
return cbWrap(() => crypto.encrypt(this._key, bytes), cb)
}
equals (key) {
@@ -46,6 +46,17 @@ class RsaPublicKey {
}
}
function cbWrap (f, cb) {
let res
try {
res = f()
} catch (err) {
cb(err)
}
return cb(null, res)
}
class RsaPrivateKey {
// key - Object of the jwk format
// publicKey - Buffer of the spki format
@@ -71,8 +82,8 @@ class RsaPrivateKey {
return new RsaPublicKey(this._publicKey)
}
decrypt (msg, callback) {
crypto.decrypt(this._key, msg, callback)
decrypt (bytes, cb) {
cbWrap(() => crypto.decrypt(this._key, bytes), cb)
}
marshal () {

View File

@@ -97,3 +97,13 @@ exports.hashAndVerify = function (key, sig, msg, callback) {
callback(null, result)
})
}
const padding = crypto.constants.RSA_PKCS1_PADDING
exports.encrypt = function (key, bytes) {
return crypto.publicEncrypt({ key: jwkToPem(key), padding }, bytes)
}
exports.decrypt = function (key, bytes) {
return crypto.privateDecrypt({ key: jwkToPem(key), padding }, bytes)
}

View File

@@ -6,7 +6,7 @@ const BN = require('asn1.js').bignum
// 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')
const s = bn.toArrayLike(Buffer, 'be', len).toString('base64')
return s
.replace(/(=*)$/, '') // Remove any trailing '='s

View File

@@ -19,7 +19,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)
}

View File

@@ -128,6 +128,41 @@ describe('RSA', function () {
})
})
it('encrypt and decrypt', (done) => {
const data = Buffer.from('hello world')
key.public.encrypt(data, (err, enc) => {
if (err) { return done(err) }
key.decrypt(enc, (err, dec) => {
if (err) { return done(err) }
expect(dec).to.be.eql(data)
done()
})
})
})
it('encrypt decrypt browser/node interop', (done) => {
crypto.keys.unmarshalPrivateKey(Buffer.from('CAASqAkwggSkAgEAAoIBAQCk0O+6oNRxhcdZe2GxEDrFBkDV4TZFZnp2ly/dL1cGMBql/8oXPZgei6h7+P5zzfDq2YCfwbjbf0IVY1AshRl6B5VGE1WS+9p1y1OZxJf5os6V1ENnTi6FTcyuBl4BN8dmIKOif0hqgqflaT5OhfYZDXfbJyVQj4vb2+Stu2Xpph3nwqAnTw/7GC/7jrt2Cq6Tu1PoZi36wSwEPYW3eQ1HAYxZjTYYDXl2iyHygnTcbkGRwAQ7vjk+mW7u60zyoolCm9f6Y7c/orJ33DDUocbaGJLlHcfd8bioBwaZy/2m7q43X8pQs0Q1/iwUt0HHZj1YARmHKbh0zR31ciFiV37dAgMBAAECggEADtJBNKnA4QKURj47r0YT2uLwkqtBi6UnDyISalQXAdXyl4n0nPlrhBewC5H9I+HZr+zmTbeIjaiYgz7el1pSy7AB4v7bG7AtWZlyx6mvtwHGjR+8/f3AXjl8Vgv5iSeAdXUq8fJ7SyS7v3wi38HZOzCEXj9bci6ud5ODMYJgLE4gZD0+i1+/V9cpuYfGpS/gLTLEMQLiw/9o8NSZ7sAnxg0UlYhotqaQY23hvXPBOe+0oa95zl2n6XTxCafa3dQl/B6CD1tUq9dhbQew4bxqMq/mhRO9pREEqZ083Uh+u4PTc1BeHgIQaS864pHPb+AY1F7KDvPtHhdojnghp8d70QKBgQDeRYFxo6sd04ohY86Z/i9icVYIyCvfXAKnaMKeGUjK7ou6sDJwFX8W97+CzXpZ/vffsk/l5GGhC50KqrITxHAy/h5IjyDODfps7NMIp0Dm9sO4PWibbw3OOVBRc8w3b3i7I8MrUUA1nLHE1T1HA1rKOTz5jYhE0fi9XKiT1ciKOQKBgQC903w+n9y7M7eaMW7Z5/13kZ7PS3HlM681eaPrk8J4J+c6miFF40/8HOsmarS38v0fgTeKkriPz5A7aLzRHhSiOnp350JNM6c3sLwPEs2qx/CRuWWx1rMERatfDdUH6mvlK6QHu0QgSfQR27EO6a6XvVSJXbvFmimjmtIaz/IpxQKBgQDWJ9HYVAGC81abZTaiWK3/A4QJYhQjWNuVwPICsgnYvI4Uib+PDqcs0ffLZ38DRw48kek5bxpBuJbOuDhro1EXUJCNCJpq7jzixituovd9kTRyR3iKii2bDM2+LPwOTXDdnk9lZRugjCEbrPkleq33Ob7uEtfAty4aBTTHe6uEwQKBgQCB+2q8RyMSXNuADhFlzOFXGrOwJm0bEUUMTPrduRQUyt4e1qOqA3klnXe3mqGcxBpnlEe/76/JacvNom6Ikxx16a0qpYRU8OWz0KU1fR6vrrEgV98241k5t6sdL4+MGA1Bo5xyXtzLb1hdUh3vpDwVU2OrnC+To3iXus/b5EBiMQKBgEI1OaBcFiyjgLGEyFKoZbtzH1mdatTExfrAQqCjOVjQByoMpGhHTXwEaosvyYu63Pa8AJPT7juSGaiKYEJFcXO9BiNyVfmQiqSHJcYeuh+fmO9IlHRHgy5xaIIC00AHS2vC/gXwmXAdPis6BZqDJeiCuOLWJ94QXn8JBT8IgGAI', 'base64'), (err, id) => {
if (err) { return done(err) }
const msg = Buffer.from('hello')
// browser
id.decrypt(Buffer.from('YRFUDx8UjbWSfDS84cDA4WowaaOmd1qFNAv5QutodCKYb9uPtU/tDiAvJzOGu5DCJRo2J0l/35P2weiB4/C2Cb1aZgXKMx/QQC+2jSJiymhqcZaYerjTvkCFwkjCaqthoVo/YXxsaFZ1q7bdTZUDH1TaJR7hWfSyzyPcA8c0w43MIsw16pY8ZaPSclvnCwhoTg1JGjMk6te3we7+wR8QU7VrPhs54mZWxrpu3NQ8xZ6xQqIedsEiNhBUccrCSzYghgsP0Ae/8iKyGyl3U6IegsJNn8jcocvzOJrmU03rgIFPjvuBdaqB38xDSTjbA123KadB28jNoSZh18q/yH3ZIg==', 'base64'), (err, dec1) => {
if (err) { return done(err) }
expect(dec1).to.be.eql(msg)
// node
id.decrypt(Buffer.from('e6yxssqXsWc27ozDy0PGKtMkCS28KwFyES2Ijz89yiz+w6bSFkNOhHPKplpPzgQEuNoUGdbseKlJFyRYHjIT8FQFBHZM8UgSkgoimbY5on4xSxXs7E5/+twjqKdB7oNveTaTf7JCwaeUYnKSjbiYFEawtMiQE91F8sTT7TmSzOZ48tUhnddAAZ3Ac/O3Z9MSAKOCDipi+JdZtXRT8KimGt36/7hjjosYmPuHR1Xy/yMTL6SMbXtBM3yAuEgbQgP+q/7kHMHji3/JvTpYdIUU+LVtkMusXNasRA+UWG2zAht18vqjFMsm9JTiihZw9jRHD4vxAhf75M992tnC+0ZuQg==', 'base64'), (err, dec2) => {
if (err) { return done(err) }
expect(dec2).to.be.eql(msg)
done()
})
})
})
})
it('fails to verify for different data', (done) => {
const data = Buffer.from('hello world')
key.sign(data, (err, sig) => {

View File

@@ -24,7 +24,7 @@ describe('Util', () => {
})
it('toBase64 zero padding', (done) => {
let bnpad = new BN('ff', 16)
const bnpad = new BN('ff', 16)
expect(util.toBase64(bnpad, 2)).to.eql('AP8')
done()
})