Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

Commit

Permalink
fix: crypto in insecure browser context
Browse files Browse the repository at this point in the history
This PR adds `crypto-browserify` to the dependencies and replaces `crypto` with `crypto-browserify` when bundled in the browser.

In files that require webcrypto we check to see if it's available. If it is not we require the Node.js implementation (which has `crypto` replaced with `crypto-browserify`) and if it is available then we use the webcrypto version (so we get fast crypto).

Shipping `crypto-browserify` adds to the bundle size:

Current gzipped size: 142,824 bytes
New gzipped size: 214,499 bytes

Difference: **+71,675 bytes**

It's not an insignificant addition so we need to decide whether this is worth it.

If not accepted, we need to add checks when libp2p-crypto methods are called and callback with an appropriate error message. JS IPFS will continue to have issues opened with confusion around this otherwise! See ipfs/js-ipfs#963 ipfs/js-ipfs#964 ipfs/js-ipfs#2153

resolves #105

License: MIT
Signed-off-by: Alan Shaw <[email protected]>
  • Loading branch information
Alan Shaw committed Jul 4, 2019
1 parent 0ffe318 commit 92c78f4
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 225 deletions.
12 changes: 4 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
"main": "src/index.js",
"leadMaintainer": "Friedel Ziegelmayer <[email protected]>",
"browser": {
"./src/hmac/index.js": "./src/hmac/index-browser.js",
"./src/keys/ecdh.js": "./src/keys/ecdh-browser.js",
"./src/aes/ciphers.js": "./src/aes/ciphers-browser.js",
"./src/keys/rsa.js": "./src/keys/rsa-browser.js"
"crypto": "crypto-browserify",
"./src/keys/keypair.js": "./src/keys/keypair-browser.js"
},
"files": [
"src",
Expand All @@ -35,25 +33,23 @@
],
"license": "MIT",
"dependencies": {
"asmcrypto.js": "^2.3.2",
"asn1.js": "^5.0.1",
"async": "^2.6.2",
"bn.js": "^4.11.8",
"browserify-aes": "^1.2.0",
"bs58": "^4.0.1",
"crypto-browserify": "^3.12.0",
"iso-random-stream": "^1.1.0",
"keypair": "^1.0.1",
"libp2p-crypto-secp256k1": "~0.3.0",
"multihashing-async": "~0.6.0",
"node-forge": "~0.7.6",
"pem-jwk": "^2.0.0",
"protons": "^1.0.1",
"rsa-pem-to-jwk": "^1.1.3",
"tweetnacl": "^1.0.1",
"ursa-optional": "~0.9.10"
},
"devDependencies": {
"aegir": "^18.2.2",
"bn.js": "^4.11.8",
"benchmark": "^2.1.4",
"bundlesize": "~0.17.1",
"chai": "^4.2.0",
Expand Down
8 changes: 0 additions & 8 deletions src/aes/ciphers-browser.js

This file was deleted.

55 changes: 0 additions & 55 deletions src/aes/index-browser.js

This file was deleted.

22 changes: 22 additions & 0 deletions src/hmac/index-crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'

const crypto = require('crypto')
const lengths = require('./lengths')
const nextTick = require('async/nextTick')

exports.create = function (hash, secret, callback) {
const res = {
digest (data, cb) {
const hmac = crypto.createHmac(hash.toLowerCase(), secret)

hmac.update(data)

nextTick(() => {
cb(null, hmac.digest())
})
},
length: lengths[hash]
}

callback(null, res)
}
File renamed without changes.
21 changes: 2 additions & 19 deletions src/hmac/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
'use strict'

const crypto = require('crypto')
const lengths = require('./lengths')
const nextTick = require('async/nextTick')
const webcrypto = require('../webcrypto')

exports.create = function (hash, secret, callback) {
const res = {
digest (data, cb) {
const hmac = crypto.createHmac(hash.toLowerCase(), secret)

hmac.update(data)

nextTick(() => {
cb(null, hmac.digest())
})
},
length: lengths[hash]
}

callback(null, res)
}
module.exports = webcrypto ? require('./index-webcrypto') : require('./index-crypto')
41 changes: 41 additions & 0 deletions src/keys/ecdh-crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict'

const crypto = require('crypto')
const nextTick = require('async/nextTick')

const curves = {
'P-256': 'prime256v1',
'P-384': 'secp384r1',
'P-521': 'secp521r1'
}

exports.generateEphmeralKeyPair = function (curve, callback) {
if (!curves[curve]) {
return callback(new Error(`Unkown curve: ${curve}`))
}
const ecdh = crypto.createECDH(curves[curve])
ecdh.generateKeys()

nextTick(() => callback(null, {
key: ecdh.getPublicKey(),
genSharedKey (theirPub, forcePrivate, cb) {
if (typeof forcePrivate === 'function') {
cb = forcePrivate
forcePrivate = null
}

if (forcePrivate) {
ecdh.setPrivateKey(forcePrivate.private)
}

let secret
try {
secret = ecdh.computeSecret(theirPub)
} catch (err) {
return cb(err)
}

nextTick(() => cb(null, secret))
}
}))
}
File renamed without changes.
40 changes: 2 additions & 38 deletions src/keys/ecdh.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,5 @@
'use strict'

const crypto = require('crypto')
const nextTick = require('async/nextTick')
const webcrypto = require('../webcrypto')

const curves = {
'P-256': 'prime256v1',
'P-384': 'secp384r1',
'P-521': 'secp521r1'
}

exports.generateEphmeralKeyPair = function (curve, callback) {
if (!curves[curve]) {
return callback(new Error(`Unkown curve: ${curve}`))
}
const ecdh = crypto.createECDH(curves[curve])
ecdh.generateKeys()

nextTick(() => callback(null, {
key: ecdh.getPublicKey(),
genSharedKey (theirPub, forcePrivate, cb) {
if (typeof forcePrivate === 'function') {
cb = forcePrivate
forcePrivate = null
}

if (forcePrivate) {
ecdh.setPrivateKey(forcePrivate.private)
}

let secret
try {
secret = ecdh.computeSecret(theirPub)
} catch (err) {
return cb(err)
}

nextTick(() => cb(null, secret))
}
}))
}
module.exports = webcrypto ? require('./ecdh-webcrypto') : require('./ecdh-crypto')
1 change: 1 addition & 0 deletions src/keys/keypair-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('keypair')
24 changes: 24 additions & 0 deletions src/keys/keypair.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
let keypair

try {
if (process.env.LP2P_FORCE_CRYPTO_LIB === 'keypair') {
throw new Error('Force keypair usage')
}

const ursa = require('ursa-optional') // throws if not compiled
keypair = ({ bits }) => {
const key = ursa.generatePrivateKey(bits)
return {
private: key.toPrivatePem(),
public: key.toPublicPem()
}
}
} catch (e) {
if (process.env.LP2P_FORCE_CRYPTO_LIB === 'ursa') {
throw e
}

keypair = require('keypair')
}

module.exports = keypair
78 changes: 78 additions & 0 deletions src/keys/rsa-crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use strict'

const crypto = require('crypto')
const randomBytes = require('../random-bytes')
const nextTick = require('async/nextTick')
const keypair = require('./keypair')
const pemToJwk = require('pem-jwk').pem2jwk
const jwkToPem = require('pem-jwk').jwk2pem

exports.utils = require('./rsa-utils')

exports.generateKey = function (bits, callback) {
nextTick(() => {
let result
try {
const key = keypair({ bits: bits })
result = {
privateKey: pemToJwk(key.private),
publicKey: pemToJwk(key.public)
}
} catch (err) {
return callback(err)
}

callback(null, result)
})
}

// Takes a jwk key
exports.unmarshalPrivateKey = function (key, callback) {
nextTick(() => {
if (!key) {
return callback(new Error('Key is invalid'))
}
callback(null, {
privateKey: key,
publicKey: {
kty: key.kty,
n: key.n,
e: key.e
}
})
})
}

exports.getRandomValues = randomBytes

exports.hashAndSign = function (key, msg, callback) {
nextTick(() => {
let result
try {
const sign = crypto.createSign('RSA-SHA256')
sign.update(msg)
const pem = jwkToPem(key)
result = sign.sign(pem)
} catch (err) {
return callback(new Error('Key or message is invalid!: ' + err.message))
}

callback(null, result)
})
}

exports.hashAndVerify = function (key, sig, msg, callback) {
nextTick(() => {
let result
try {
const verify = crypto.createVerify('RSA-SHA256')
verify.update(msg)
const pem = jwkToPem(key)
result = verify.verify(pem, sig)
} catch (err) {
return callback(new Error('Key or message is invalid!:' + err.message))
}

callback(null, result)
})
}
File renamed without changes.
Loading

0 comments on commit 92c78f4

Please sign in to comment.