From a17a5536fc195aefdbd70fb9de7868973712c8b8 Mon Sep 17 00:00:00 2001 From: kevlened Date: Sat, 3 Feb 2018 13:11:39 -0800 Subject: [PATCH] Allow React Native use without ejecting --- README.md | 29 +++--------------------- package-lock.json | 13 +++++++++++ package.json | 6 ++--- src/react-native.js | 54 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 66 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index d89d2c0..ad0ca73 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ webcrypto library for Node, React Native and IE11+ ## What? -There's a great Node polyfill for the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API), but [it's not isomorphic yet](https://github.com/anvilresearch/webcrypto/issues/57). This fills the gap until it is. +There's a great Node polyfill for the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API), but [it's not isomorphic](https://github.com/anvilresearch/webcrypto/issues/57). IE11 and versions of Safari < 11 use an older version of the spec, so the browser implementation includes a [webcrypto-shim](https://github.com/vibornoff/webcrypto-shim) to iron out the differences. You'll still need to provide your own Promise polyfill. @@ -42,24 +42,6 @@ crypto.subtle.digest( * Node 4+ * React Native -### Other Environments - -If you need to support other environments (like older browsers), use the [Microsoft Research library](https://github.com/kevlened/msrCrypto). - -```javascript -const crypto = require('msrcrypto') - -/** - * IMPORTANT: On platforms without crypto, the - * js-only implementation needs another source - * of entropy for operations that require - * random numbers (creating keys, encrypting, - * wrapping keys) This should NOT be Math.random() - */ - -crypto.initPrng(randomArrayOf48Bytes) -``` - ### React Native React Native support is implemented using [the Microsoft Research library](https://github.com/kevlened/msrCrypto). The React Native environment only supports `Math.random()`, so [react-native-securerandom](https://github.com/rh389/react-native-securerandom) is used to provide proper entropy. This is handled automatically, except for `crypto.getRandomValues()`, which requires you wait: @@ -67,16 +49,11 @@ React Native support is implemented using [the Microsoft Research library](https ```javascript const crypto = require('isomorphic-webcrypto') -crypto.generateKey() // safe (all other methods are safe) -crypto.getRandomValues() // insecure!!! - // Only needed for crypto.getRandomValues crypto.ensureSecure(err => { if (err) throw err - crypto.getRandomValues() // safe - + const safeValues = crypto.getRandomValues(); // Only wait once, future calls are secure - // No need to wrap every getRandomValues call }) ``` @@ -92,4 +69,4 @@ You should use [the webcrypto-shim](https://github.com/vibornoff/webcrypto-shim) ## License -MIT \ No newline at end of file +MIT diff --git a/package-lock.json b/package-lock.json index 4951e90..bad0cb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,11 @@ "b64-lite": "1.2.0" } }, + "base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" + }, "base64url": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", @@ -135,6 +140,14 @@ "asn1": "0.2.3" } }, + "react-native-securerandom": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-0.1.1.tgz", + "integrity": "sha1-8TBiOkEsM4sK+t7bwgTFy7i/IHA=", + "requires": { + "base64-js": "1.2.1" + } + }, "str2buf": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/str2buf/-/str2buf-1.2.0.tgz", diff --git a/package.json b/package.json index 4367af8..8012161 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "isomorphic-webcrypto", - "version": "1.3.1", + "version": "1.3.0", "description": "webcrypto library for Node, React Native and IE11+", "main": "./src/main.js", "module": "./src/index.mjs", @@ -37,10 +37,8 @@ "b64u-lite": "^1.0.1", "mitt": "^1.1.3", "msrcrypto": "^1.4.0", + "react-native-securerandom": "^0.1.1", "str2buf": "^1.2.0", "webcrypto-shim": "^0.1.2" - }, - "peerDependencies": { - "react-native-securerandom": "^0.1.1" } } diff --git a/src/react-native.js b/src/react-native.js index 89e774c..3f26f69 100644 --- a/src/react-native.js +++ b/src/react-native.js @@ -1,4 +1,32 @@ -const generateSecureRandom = require('react-native-securerandom').generateSecureRandom +let generateSecureRandom; +if (require.getModules) { + const NativeModules = require('react-native').NativeModules; + const RNSecureRandom = NativeModules.RNSecureRandom; + if (RNSecureRandom && RNSecureRandom.generateSecureRandomAsBase64) { + generateSecureRandom = require('react-native-securerandom').generateSecureRandom; + } +} + +if (!generateSecureRandom) { + console.log(` + isomorphic-webcrypto cannot ensure the security of some operations. + Please eject and run: + + npm install react-native-securerandom --save + react-native link + + If you'd like not to eject, upvote this feature request: + https://expo.canny.io/feature-requests/p/crypto-api + `); + generateSecureRandom = function(length) { + const uint8Array = new Uint8Array(length); + while (length && length--) { + uint8Array[length] = Math.floor(Math.random() * 256); + } + return Promise.resolve(uint8Array); + } +} + const EventEmitter = require('mitt') const b64u = require('b64u-lite') const str2buf = require('str2buf') @@ -29,11 +57,25 @@ crypto.ensureSecure = function(cb) { secureWatch.on('secureRandomError', () => cb(secureRandomError)) } -function standardAlgoName(algo) { +function standardizeAlgoName(algo) { const upper = algo.toUpperCase(); return upper === 'RSASSA-PKCS1-V1_5' ? 'RSASSA-PKCS1-v1_5' : upper; } +const originalGetRandomValues = crypto.getRandomValues; +crypto.getRandomValues = function getRandomValues() { + if (!secured) { + throw new Error(` + You must wait until the library is secure to call this method: + crypto.ensureSecure(err => { + if (err) throw err; + const safeValues = crypto.getRandomValues(); + }); + `); + } + return originalGetRandomValues.apply(crypto, arguments); +} + // wrap all methods to ensure they're secure const methods = [ 'decrypt', @@ -72,12 +114,12 @@ crypto.subtle.generateKey = function() { .then(res => { if (res.publicKey) { res.publicKey.usages = ['verify']; - res.publicKey.algorithm.name = standardAlgoName(res.publicKey.algorithm.name); + res.publicKey.algorithm.name = standardizeAlgoName(res.publicKey.algorithm.name); res.privateKey.usages = ['sign']; - res.privateKey.algorithm.name = standardAlgoName(res.privateKey.algorithm.name); + res.privateKey.algorithm.name = standardizeAlgoName(res.privateKey.algorithm.name); } else { res.usages = ['sign', 'verify']; - res.algorithm.name = standardAlgoName(res.algorithm.name); + res.algorithm.name = standardizeAlgoName(res.algorithm.name); } return res; }); @@ -89,7 +131,7 @@ crypto.subtle.importKey = function() { const key = arguments[1]; return originalImportKey.apply(this, arguments) .then(res => { - res.algorithm.name = standardAlgoName(res.algorithm.name); + res.algorithm.name = standardizeAlgoName(res.algorithm.name); switch(res.type) { case 'secret': res.usages = ['sign', 'verify'];