1 | let generateSecureRandom;
|
2 | if (require.getModules) {
|
3 | const NativeModules = require('react-native').NativeModules;
|
4 | const RNSecureRandom = NativeModules.RNSecureRandom;
|
5 | const NativeUnimoduleProxy = NativeModules.NativeUnimoduleProxy;
|
6 | if (RNSecureRandom && RNSecureRandom.generateSecureRandomAsBase64) {
|
7 | generateSecureRandom = require('react-native-securerandom').generateSecureRandom;
|
8 | } else if (NativeUnimoduleProxy && NativeUnimoduleProxy.exportedMethods.ExpoRandom) {
|
9 | generateSecureRandom = require('expo-random').getRandomBytesAsync;
|
10 | }
|
11 | }
|
12 |
|
13 | if (!generateSecureRandom) {
|
14 | console.log(`
|
15 | isomorphic-webcrypto cannot ensure the security of some operations!
|
16 | Install and configure react-native-securerandom or expo-random
|
17 | If managed by Expo, run 'expo install expo-random'
|
18 | `);
|
19 | generateSecureRandom = function(length) {
|
20 | const uint8Array = new Uint8Array(length);
|
21 | while (length && length--) {
|
22 | uint8Array[length] = Math.floor(Math.random() * 256);
|
23 | }
|
24 | return Promise.resolve(uint8Array);
|
25 | }
|
26 | }
|
27 |
|
28 | const str2buf = require('str2buf');
|
29 | const b64u = require('b64u-lite');
|
30 | const b64 = require('b64-lite');
|
31 |
|
32 | if(global.window.navigator === undefined)
|
33 | global.window.navigator = {};
|
34 |
|
35 | global.window.navigator.userAgent = '';
|
36 | global.atob = typeof atob === 'undefined' ? b64.atob : atob;
|
37 | global.btoa = typeof btoa === 'undefined' ? b64.btoa : btoa;
|
38 | global.msrCryptoPermanentForceSync = true;
|
39 |
|
40 | const crypto = require('msrcrypto');
|
41 |
|
42 | let isSecured = false;
|
43 | const secured = new Promise((resolve, reject) => {
|
44 | if (!crypto.initPrng) return resolve(false);
|
45 | return generateSecureRandom(48)
|
46 | .then(byteArray => {
|
47 | crypto.initPrng(Array.from(byteArray))
|
48 | isSecured = true;
|
49 | resolve(true);
|
50 | })
|
51 | .catch(err => reject(err));
|
52 | })
|
53 | .then(() => {
|
54 | if (!global.window.crypto) {
|
55 | global.window.crypto = crypto;
|
56 | }
|
57 |
|
58 | global.asmCrypto = require('asmcrypto.js');
|
59 | const liner = require('./webcrypto-liner');
|
60 |
|
61 | const originalImportKey = crypto.subtle.importKey;
|
62 | crypto.subtle.importKey = function importKey() {
|
63 | const importType = arguments[0];
|
64 | const key = arguments[1];
|
65 | const algorithm = arguments[2];
|
66 | if (algorithm.name.toUpperCase() === 'PBKDF2') {
|
67 | let importKey, ref;
|
68 | if (liner.crypto.subtle.getProvider) {
|
69 | ref = liner.crypto.subtle.getProvider('PBKDF2');
|
70 | importKey = ref.onImportKey;
|
71 | } else {
|
72 | ref = liner.crypto.subtle;
|
73 | importKey = ref.importKey;
|
74 | }
|
75 | if (importType === 'raw') arguments[1] = new ArrayBuffer(arguments[1]);
|
76 | return importKey.apply(ref, arguments);
|
77 | }
|
78 |
|
79 | return originalImportKey.apply(this, arguments)
|
80 | .then(res => {
|
81 | res.algorithm.name = standardizeAlgoName(res.algorithm.name);
|
82 | switch(res.type) {
|
83 | case 'secret':
|
84 | res.usages = res.algorithm.name === 'HMAC' ? ['sign', 'verify'] : ['encrypt', 'decrypt'];
|
85 | break;
|
86 | case 'private':
|
87 | res.usages = ['sign'];
|
88 | break;
|
89 | case 'public':
|
90 | res.usages = ['verify'];
|
91 | break;
|
92 | }
|
93 | if (importType === 'jwk' && key.kty === 'RSA') {
|
94 | res.algorithm.modulusLength = b64u.toBinaryString(key.n).length * 8;
|
95 | res.algorithm.publicExponent = str2buf.toUint8Array(b64u.toBinaryString(key.e));
|
96 | }
|
97 | return res;
|
98 | });
|
99 | }
|
100 |
|
101 | const originalDeriveBits = crypto.subtle.deriveBits;
|
102 | crypto.subtle.deriveBits = function deriveBits() {
|
103 | const algorithm = arguments[0];
|
104 | if (algorithm.name.toUpperCase() === 'PBKDF2') {
|
105 | let deriveBits, ref;
|
106 | if (liner.crypto.subtle.getProvider) {
|
107 | ref = liner.crypto.subtle.getProvider('PBKDF2');
|
108 | deriveBits = ref.onDeriveBits;
|
109 | } else {
|
110 | ref = liner.crypto.subtle;
|
111 | deriveBits = ref.deriveBits;
|
112 | }
|
113 | return deriveBits.apply(ref, arguments);
|
114 | }
|
115 |
|
116 | return originalDeriveBits.apply(this, arguments);
|
117 | }
|
118 | })
|
119 | .catch(e => {
|
120 | console.log('Unable to secure:', e.message);
|
121 | throw e;
|
122 | });
|
123 |
|
124 | crypto.ensureSecure = () => secured;
|
125 |
|
126 | function standardizeAlgoName(algo) {
|
127 | const upper = algo.toUpperCase();
|
128 | return upper === 'RSASSA-PKCS1-V1_5' ? 'RSASSA-PKCS1-v1_5' : upper;
|
129 | }
|
130 |
|
131 | function ensureUint8Array(buffer) {
|
132 | if (typeof buffer === 'string' || buffer instanceof String)
|
133 | return str2buf.toUint8Array(buffer);
|
134 | if (!buffer) return;
|
135 | if (buffer instanceof ArrayBuffer) return new Uint8Array(buffer);
|
136 | if (buffer instanceof Uint8Array) return buffer;
|
137 | return buffer;
|
138 | }
|
139 |
|
140 | const originalGetRandomValues = crypto.getRandomValues;
|
141 | crypto.getRandomValues = function getRandomValues() {
|
142 | if (!isSecured) {
|
143 | throw new Error(`
|
144 | You must wait until the library is secure to call this method:
|
145 |
|
146 | await crypto.ensureSecure();
|
147 | const safeValues = crypto.getRandomValues();
|
148 | `);
|
149 | }
|
150 | return originalGetRandomValues.apply(crypto, arguments);
|
151 | }
|
152 |
|
153 |
|
154 | const methods = [
|
155 | 'decrypt',
|
156 | 'digest',
|
157 | 'deriveKey',
|
158 | 'encrypt',
|
159 | 'exportKey',
|
160 | 'generateKey',
|
161 | 'importKey',
|
162 | 'sign',
|
163 | 'unwrapKey',
|
164 | 'verify',
|
165 | 'wrapKey'
|
166 | ]
|
167 | methods.map(key => {
|
168 | const original = crypto.subtle[key]
|
169 | const proxy = function() {
|
170 | const args = Array.from(arguments)
|
171 | const before = crypto.subtle[key];
|
172 | return crypto.ensureSecure()
|
173 | .then(() => {
|
174 | const after = crypto.subtle[key];
|
175 | if (before === after) {
|
176 | return original.apply(crypto.subtle, args)
|
177 | } else {
|
178 | return crypto.subtle[key].apply(crypto.subtle, args)
|
179 | }
|
180 | });
|
181 | }
|
182 | crypto.subtle[key] = proxy;
|
183 | crypto.subtle[key].name = key;
|
184 | })
|
185 |
|
186 | const originalGenerateKey = crypto.subtle.generateKey;
|
187 | crypto.subtle.generateKey = function generateKey() {
|
188 | const algo = arguments[0];
|
189 | if (algo) {
|
190 | if (algo.name) algo.name = algo.name.toLowerCase();
|
191 | if (algo.hash && algo.hash.name) algo.hash.name = algo.hash.name.toLowerCase();
|
192 | }
|
193 | return originalGenerateKey.apply(this, arguments)
|
194 | .then(res => {
|
195 | if (res.publicKey) {
|
196 | res.publicKey.usages = ['verify'];
|
197 | res.publicKey.algorithm.name = standardizeAlgoName(res.publicKey.algorithm.name);
|
198 | res.privateKey.usages = ['sign'];
|
199 | res.privateKey.algorithm.name = standardizeAlgoName(res.privateKey.algorithm.name);
|
200 | } else {
|
201 | res.algorithm.name = standardizeAlgoName(res.algorithm.name);
|
202 | res.usages = res.algorithm.name === 'HMAC' ? ['sign', 'verify'] : ['encrypt', 'decrypt'];
|
203 | }
|
204 | return res;
|
205 | });
|
206 | }
|
207 |
|
208 | const originalExportKey = crypto.subtle.exportKey;
|
209 | crypto.subtle.exportKey = function exportKey() {
|
210 | const key = arguments[1];
|
211 | return originalExportKey.apply(this, arguments)
|
212 | .then(res => {
|
213 | if (res.kty === 'RSA' || res.kty === 'EC') {
|
214 | if (res.d) {
|
215 | res.key_ops = ['sign'];
|
216 | } else {
|
217 | res.key_ops = ['verify'];
|
218 | }
|
219 | }
|
220 | switch(res.alg) {
|
221 | case 'EC-256':
|
222 | case 'EC-384':
|
223 | case 'EC-521':
|
224 | delete res.alg;
|
225 | }
|
226 | return res;
|
227 | });
|
228 | }
|
229 |
|
230 | const originalDigest = crypto.subtle.digest;
|
231 | crypto.subtle.digest = function digest() {
|
232 | arguments[1] = ensureUint8Array(arguments[1]);
|
233 | return originalDigest.apply(this, arguments);
|
234 | }
|
235 |
|
236 | module.exports = crypto
|