diff --git a/IntegrationTests/CryptoTest.js b/IntegrationTests/CryptoTest.js new file mode 100644 index 000000000000..6651e9494343 --- /dev/null +++ b/IntegrationTests/CryptoTest.js @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +declare var crypto: {getRandomValues: (data: Uint8Array) => Uint8Array}; + +const React = require('react'); +const ReactNative = require('react-native'); +const {View} = ReactNative; +const {TestModule} = ReactNative.NativeModules; + +class CryptoTest extends React.Component<{}> { + componentDidMount() { + const data = new Uint8Array(8); + const returnValue = crypto.getRandomValues(data); + + const returnsArray = data === returnValue; + const populatesData = data.find(value => value !== 0) !== undefined; + + TestModule.markTestPassed(returnsArray && populatesData); + } + + render(): React.Node { + return ; + } +} + +CryptoTest.displayName = 'CryptoTest'; + +module.exports = CryptoTest; diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js index 421b4f6abe2b..e47018c30262 100644 --- a/IntegrationTests/IntegrationTestsApp.js +++ b/IntegrationTests/IntegrationTestsApp.js @@ -31,6 +31,7 @@ const TESTS = [ require('./SimpleSnapshotTest'), require('./ImageCachePolicyTest'), require('./ImageSnapshotTest'), + require('./CryptoTest'), require('./PromiseTest'), require('./WebViewTest'), require('./SyncMethodTest'), diff --git a/RNTester/RNTesterIntegrationTests/RNTesterIntegrationTests.m b/RNTester/RNTesterIntegrationTests/RNTesterIntegrationTests.m index 1549898e4377..2f00509afa60 100644 --- a/RNTester/RNTesterIntegrationTests/RNTesterIntegrationTests.m +++ b/RNTester/RNTesterIntegrationTests/RNTesterIntegrationTests.m @@ -70,6 +70,7 @@ - (void)testTheTester_ExpectError //RCT_TEST(LayoutEventsTest) // Disabled due to flakiness: #8686784 RCT_TEST(SimpleSnapshotTest) RCT_TEST(SyncMethodTest) +RCT_TEST(CryptoTest) RCT_TEST(PromiseTest) RCT_TEST_ONLY_WITH_PACKAGER(WebSocketTest) RCT_TEST(AccessibilityManagerTest) diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index ac97a4d41e64..dbcf30c769be 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -199,6 +199,10 @@ 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; }; 14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */; }; 14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */; }; + 15698CC82125A6B300EE2C0D /* Crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = 15698CC62125A6B200EE2C0D /* Crypto.h */; }; + 15698CC92125A6B300EE2C0D /* Crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = 15698CC62125A6B200EE2C0D /* Crypto.h */; }; + 15698CCA2125A6B300EE2C0D /* Crypto.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 15698CC72125A6B300EE2C0D /* Crypto.cpp */; }; + 15698CCB2125A6B300EE2C0D /* Crypto.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 15698CC72125A6B300EE2C0D /* Crypto.cpp */; }; 191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */; }; 191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */; }; 199B8A6F1F44DB16005DEF67 /* RCTVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 199B8A6E1F44DB16005DEF67 /* RCTVersion.h */; }; @@ -2068,6 +2072,8 @@ 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerfMonitor.m; sourceTree = ""; }; 14F7A0EE1BDA714B003C6C10 /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTFPSGraph.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = ""; }; + 15698CC62125A6B200EE2C0D /* Crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Crypto.h; sourceTree = ""; }; + 15698CC72125A6B300EE2C0D /* Crypto.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Crypto.cpp; sourceTree = ""; }; 191E3EBC1C29D9AF00C180A6 /* RCTRefreshControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRefreshControlManager.h; sourceTree = ""; }; 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControlManager.m; sourceTree = ""; }; 191E3EBF1C29DC3800C180A6 /* RCTRefreshControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRefreshControl.h; sourceTree = ""; }; @@ -2759,6 +2765,8 @@ 3D4A621D1DDD3985001F41B4 /* jschelpers */ = { isa = PBXGroup; children = ( + 15698CC72125A6B300EE2C0D /* Crypto.cpp */, + 15698CC62125A6B200EE2C0D /* Crypto.h */, 19DED2281E77E29200F089BB /* systemJSCWrapper.cpp */, 3D7A27DC1DE32541002E3F95 /* JavaScriptCore.h */, 3D92B1071E0369AD0018521A /* JSCHelpers.cpp */, @@ -3376,6 +3384,7 @@ 19F61C041E8495FF00571D81 /* JSCHelpers.h in Headers */, 19F61C051E8495FF00571D81 /* noncopyable.h in Headers */, 19F61C061E8495FF00571D81 /* Unicode.h in Headers */, + 15698CC92125A6B300EE2C0D /* Crypto.h in Headers */, 19F61C071E8495FF00571D81 /* Value.h in Headers */, 3D3030251DF8295E00D6DDAE /* JavaScriptCore.h in Headers */, 3D3030261DF8295E00D6DDAE /* JSCWrapper.h in Headers */, @@ -3423,6 +3432,7 @@ 13EBC6821E28733C00880AC5 /* Value.h in Headers */, 13EBC6811E28733C00880AC5 /* Unicode.h in Headers */, 13EBC6801E28733C00880AC5 /* noncopyable.h in Headers */, + 15698CC82125A6B300EE2C0D /* Crypto.h in Headers */, 13EBC67E1E28726000880AC5 /* JSCHelpers.h in Headers */, 3D3CD93D1DE5FC1400167DC4 /* JavaScriptCore.h in Headers */, 3D3CD93E1DE5FC1400167DC4 /* JSCWrapper.h in Headers */, @@ -4387,6 +4397,7 @@ 13EBC6731E2870DE00880AC5 /* Value.cpp in Sources */, 13EBC67D1E28725900880AC5 /* JSCHelpers.cpp in Sources */, 135A9C021E7B0F4800587AEB /* systemJSCWrapper.cpp in Sources */, + 15698CCA2125A6B300EE2C0D /* Crypto.cpp in Sources */, 13EBC67B1E28723000880AC5 /* Unicode.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4399,6 +4410,7 @@ 13EBC6781E2870E400880AC5 /* JSCWrapper.cpp in Sources */, 13EBC6771E2870E400880AC5 /* JSCHelpers.cpp in Sources */, 135A9C011E7B0F4700587AEB /* systemJSCWrapper.cpp in Sources */, + 15698CCB2125A6B300EE2C0D /* Crypto.cpp in Sources */, 13EBC67A1E2870E400880AC5 /* Value.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/JSCryptoTest.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/JSCryptoTest.java new file mode 100644 index 000000000000..9f02cbed1dd1 --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/JSCryptoTest.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.tests; + +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.modules.appstate.AppStateModule; +import com.facebook.react.modules.core.ReactChoreographer; +import com.facebook.react.modules.deviceinfo.DeviceInfoModule; +import com.facebook.react.testing.FakeWebSocketModule; +import com.facebook.react.testing.ReactIntegrationTestCase; +import com.facebook.react.testing.ReactTestHelper; +import com.facebook.react.testing.StringRecordingModule; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.views.view.ReactViewManager; +import java.util.Arrays; +import java.util.List; + +/** + * Test crypto-based functionality of JS VM + */ +public class JSCryptoTest extends ReactIntegrationTestCase { + + private interface TestJSCryptoModule extends JavaScriptModule { + void getRandomValues(); + } + + StringRecordingModule mStringRecordingModule; + + private CatalystInstance mInstance; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + List viewManagers = Arrays.asList( + new ReactViewManager()); + final UIManagerModule mUIManager = new UIManagerModule( + getContext(), + viewManagers, + 0); + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + ReactChoreographer.initialize(); + mUIManager.onHostResume(); + } + }); + waitForIdleSync(); + + mStringRecordingModule = new StringRecordingModule(); + mInstance = ReactTestHelper.catalystInstanceBuilder(this) + .addNativeModule(mStringRecordingModule) + .addNativeModule(mUIManager) + .addNativeModule(new DeviceInfoModule(getContext())) + .addNativeModule(new AppStateModule(getContext())) + .addNativeModule(new FakeWebSocketModule()) + .build(); + } + + public void testGetRandomValues() { + TestJSCryptoModule testModule = mInstance.getJSModule(TestJSCryptoModule.class); + waitForBridgeAndUIIdle(); + + testModule.getRandomValues(); + waitForBridgeAndUIIdle(); + + String[] answers = mStringRecordingModule.getCalls().toArray(new String[0]); + assertEquals("true", answers[0]); + } +} diff --git a/ReactAndroid/src/androidTest/js/TestBundle.js b/ReactAndroid/src/androidTest/js/TestBundle.js index 59da7e6695b8..e85fc4c7f023 100644 --- a/ReactAndroid/src/androidTest/js/TestBundle.js +++ b/ReactAndroid/src/androidTest/js/TestBundle.js @@ -16,6 +16,7 @@ console.disableYellowBox = true; require('ProgressBarTestModule'); require('ViewRenderingTestModule'); require('TestJavaToJSArgumentsModule'); +require('TestJSCryptoModule'); require('TestJSLocaleModule'); require('TestJSToJavaParametersModule'); require('TestJavaToJSReturnValuesModule'); diff --git a/ReactAndroid/src/androidTest/js/TestJSCryptoModule.js b/ReactAndroid/src/androidTest/js/TestJSCryptoModule.js new file mode 100644 index 000000000000..8b88a22dc9ba --- /dev/null +++ b/ReactAndroid/src/androidTest/js/TestJSCryptoModule.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +/* global crypto */ + +var BatchedBridge = require('BatchedBridge'); +var Recording = require('NativeModules').Recording; + +var TestJSCryptoModule = { + getRandomValues: function() { + const data = new Uint8Array(8); + const returnValue = crypto.getRandomValues(data); + + const returnsArray = data === returnValue; + const populatesData = data.find(value => value !== 0) !== undefined; + + Recording.record(returnsArray && populatesData ? 'true' : 'false'); + }, +}; + +BatchedBridge.registerCallableModule('TestJSCryptoModule', TestJSCryptoModule); + +module.exports = TestJSCryptoModule; diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index 65ee3bdd8025..4b201735e3dd 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -143,6 +143,11 @@ JSCExecutor::JSCExecutor( "nativeModuleProxy", exceptionWrapMethod<&JSCExecutor::getNativeModule>()); } + + { + SystraceSection s("nativeModuleCrypto object"); + installGlobalCrypto(m_context); + } } JSCExecutor::~JSCExecutor() { diff --git a/ReactCommon/jschelpers/Android.mk b/ReactCommon/jschelpers/Android.mk index 765b0ac7833a..5936a4cfb883 100644 --- a/ReactCommon/jschelpers/Android.mk +++ b/ReactCommon/jschelpers/Android.mk @@ -5,6 +5,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := jschelpers LOCAL_SRC_FILES := \ + Crypto.cpp \ JSCHelpers.cpp \ Unicode.cpp \ Value.cpp \ diff --git a/ReactCommon/jschelpers/BUCK b/ReactCommon/jschelpers/BUCK index 7739b8f7dc54..0320eae30124 100644 --- a/ReactCommon/jschelpers/BUCK +++ b/ReactCommon/jschelpers/BUCK @@ -1,6 +1,7 @@ load("//ReactNative:DEFS.bzl", "ANDROID", "ANDROID_JSC_INTERNAL_DEPS", "APPLE", "APPLE_JSC_INTERNAL_DEPS", "react_native_xplat_target", "rn_xplat_cxx_library") EXPORTED_HEADERS = [ + "Crypto.h", "JavaScriptCore.h", "JSCHelpers.h", "JSCWrapper.h", diff --git a/ReactCommon/jschelpers/Crypto.cpp b/ReactCommon/jschelpers/Crypto.cpp new file mode 100644 index 000000000000..d9cb633034f3 --- /dev/null +++ b/ReactCommon/jschelpers/Crypto.cpp @@ -0,0 +1,221 @@ +// Copyright (c) 2018-present, Facebook, Inc. + +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#include "Crypto.h" + +#include "JSCHelpers.h" + +// FIXME: When we upgrade JSC for Android this will always be supported +#ifdef __APPLE__ +#define JSC_HAS_TYPED_ARRAY_SUPPORT +#endif + +#ifdef __APPLE__ +#import +#else +#include +#endif + +namespace facebook { +namespace react { + +#ifdef JSC_HAS_TYPED_ARRAY_SUPPORT + +static void populateRandomData (JSContextRef ctx, uint8_t *ptr, size_t length, JSValueRef *exception) { +#ifdef __APPLE__ + auto result = SecRandomCopyBytes(kSecRandomDefault, length, ptr); + + if (result != errSecSuccess) { + JSValueRef errorMsgValue = JSC_JSValueMakeString(ctx, JSC_JSStringCreateWithUTF8CString(ctx, "Failed to retreive random values")); + JSValueRef args[] = {errorMsgValue}; + + JSObjectRef errorObj = JSC_JSObjectMakeError(ctx, 1, args, exception); + if (*exception != nullptr) return ; + + JSValueRef errorRef = JSC_JSValueToObject(ctx, errorObj, exception); + if (*exception != nullptr) return ; + + *exception = errorRef; + return ; + } +#else + auto fd = fopen("/dev/urandom", "r"); + + if (fd == nullptr) { + JSValueRef errorMsgValue = JSC_JSValueMakeString(ctx, JSC_JSStringCreateWithUTF8CString(ctx, "Failed to open /dev/urandom")); + JSValueRef args[] = {errorMsgValue}; + + JSObjectRef errorObj = JSC_JSObjectMakeError(ctx, 1, args, exception); + if (*exception != nullptr) return ; + + JSValueRef errorRef = JSC_JSValueToObject(ctx, errorObj, exception); + if (*exception != nullptr) return ; + + *exception = errorRef; + return ; + } + + auto result = fread(ptr, 1, length, fd); + + fclose(fd); + + if (result != length) { + JSValueRef errorMsgValue = JSC_JSValueMakeString(ctx, JSC_JSStringCreateWithUTF8CString(ctx, "Failed to retreive enough random values")); + JSValueRef args[] = {errorMsgValue}; + + JSObjectRef errorObj = JSC_JSObjectMakeError(ctx, 1, args, exception); + if (*exception != nullptr) return ; + + JSValueRef errorRef = JSC_JSValueToObject(ctx, errorObj, exception); + if (*exception != nullptr) return ; + + *exception = errorRef; + return ; + } +#endif +} + +JSValueRef getRandomValues(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + auto type = JSC_JSValueGetTypedArrayType(ctx, arguments[0], exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + auto typedArray = JSC_JSValueToObject(ctx, arguments[0], exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + /* 1. If array is not of an integer type (i.e., Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array or UInt8ClampedArray), throw a TypeMismatchError and terminate the algorithm. */ + if (type == kJSTypedArrayTypeNone) { + JSValueRef errorMsgValue = JSC_JSValueMakeString(ctx, JSC_JSStringCreateWithUTF8CString(ctx, "TypeMismatchError")); + JSValueRef args[] = {errorMsgValue}; + + JSObjectRef errorObj = JSC_JSObjectMakeError(ctx, 1, args, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + JSValueRef errorRef = JSC_JSValueToObject(ctx, errorObj, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + *exception = errorRef; + return JSC_JSValueMakeUndefined(ctx); + } + + auto length = JSC_JSObjectGetTypedArrayLength(ctx, typedArray, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + /* 2. If the byteLength of array is greater than 65536, throw a QuotaExceededError and terminate the algorithm. */ + if (length > 65536) { + JSValueRef errorMsgValue = JSC_JSValueMakeString(ctx, JSC_JSStringCreateWithUTF8CString(ctx, "QuotaExceededError")); + JSValueRef args[] = {errorMsgValue}; + + JSObjectRef errorObj = JSC_JSObjectMakeError(ctx, 1, args, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + JSValueRef errorRef = JSC_JSValueToObject(ctx, errorObj, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + *exception = errorRef; + return JSC_JSValueMakeUndefined(ctx); + } + + uint8_t *ptr = static_cast(JSC_JSObjectGetTypedArrayBytesPtr(ctx, typedArray, exception)); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + auto offset = JSC_JSObjectGetTypedArrayByteOffset(ctx, typedArray, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + /* 3. Overwrite all elements of array with cryptographically random values of the appropriate type. */ + populateRandomData(ctx, ptr + offset, length, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + /* 4. Return array. */ + return arguments[0]; +} + +#else + +static JSValueRef getPropertyNamed(JSContextRef ctx, JSObjectRef object, const char *name, JSValueRef *exception) { + auto jsPropertyName = JSC_JSStringCreateWithUTF8CString(ctx, name); + auto value = JSC_JSObjectGetProperty(ctx, object, jsPropertyName, exception); + JSC_JSStringRelease(ctx, jsPropertyName); + return value; +} + +JSValueRef getRandomValues(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + auto typedArray = JSC_JSValueToObject(ctx, arguments[0], exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + JSValueRef args[3]; + + args[0] = getPropertyNamed(ctx, typedArray, "buffer", exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + args[1] = getPropertyNamed(ctx, typedArray, "byteOffset", exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + args[2] = getPropertyNamed(ctx, typedArray, "byteLength", exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + auto byteLength = JSC_JSValueToNumber(ctx, args[2], exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + /* 2. If the byteLength of array is greater than 65536, throw a QuotaExceededError and terminate the algorithm. */ + if (byteLength > 65536) { + JSValueRef errorMsgValue = JSC_JSValueMakeString(ctx, JSC_JSStringCreateWithUTF8CString(ctx, "QuotaExceededError")); + JSValueRef args[] = {errorMsgValue}; + + JSObjectRef errorObj = JSC_JSObjectMakeError(ctx, 1, args, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + JSValueRef errorRef = JSC_JSValueToObject(ctx, errorObj, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + *exception = errorRef; + return JSC_JSValueMakeUndefined(ctx); + } + + JSObjectRef global = JSC_JSContextGetGlobalObject(ctx); + JSValueRef constructorValue = getPropertyNamed(ctx, global, "Uint8Array", exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + JSObjectRef constructor = JSC_JSValueToObject(ctx, constructorValue, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + JSObjectRef view = JSC_JSObjectCallAsConstructor(ctx, constructor, 3, args, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + auto fd = fopen("/dev/urandom", "r"); + + if (fd == nullptr) { + JSValueRef errorMsgValue = JSC_JSValueMakeString(ctx, JSC_JSStringCreateWithUTF8CString(ctx, "Failed to open /dev/urandom")); + JSValueRef args[] = {errorMsgValue}; + + JSObjectRef errorObj = JSC_JSObjectMakeError(ctx, 1, args, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + JSValueRef errorRef = JSC_JSValueToObject(ctx, errorObj, exception); + if (*exception != nullptr) return JSC_JSValueMakeUndefined(ctx); + + *exception = errorRef; + return JSC_JSValueMakeUndefined(ctx); + } + + /* 3. Overwrite all elements of array with cryptographically random values of the appropriate type. */ + for (auto idx = 0; idx < byteLength; idx++) { + auto randomByte = JSC_JSValueMakeNumber(ctx, fgetc(fd)); + JSC_JSObjectSetPropertyAtIndex(ctx, view, idx, randomByte, exception); + + if (*exception != nullptr) { + fclose(fd); + return JSC_JSValueMakeUndefined(ctx); + } + } + + fclose(fd); + + /* 4. Return array. */ + return arguments[0]; +} + +#endif + +} } diff --git a/ReactCommon/jschelpers/Crypto.h b/ReactCommon/jschelpers/Crypto.h new file mode 100644 index 000000000000..e906aa6b90ff --- /dev/null +++ b/ReactCommon/jschelpers/Crypto.h @@ -0,0 +1,14 @@ +// Copyright (c) 2018-present, Facebook, Inc. + +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include "JSCHelpers.h" + +namespace facebook { +namespace react { +__attribute__((visibility("default"))) JSValueRef getRandomValues(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception); +} +} diff --git a/ReactCommon/jschelpers/JSCHelpers.cpp b/ReactCommon/jschelpers/JSCHelpers.cpp index 3c28eb81ac6b..2ee0bdec9ca0 100644 --- a/ReactCommon/jschelpers/JSCHelpers.cpp +++ b/ReactCommon/jschelpers/JSCHelpers.cpp @@ -15,6 +15,7 @@ #include #endif +#include "Crypto.h" #include "JavaScriptCore.h" #include "Value.h" #include @@ -204,6 +205,17 @@ void installGlobalProxy( Object::getGlobalObject(ctx).setProperty(name, Value(ctx, proxyObj)); } +void installGlobalCrypto(JSGlobalContextRef ctx) { + JSObjectRef cryptoObject = JSC_JSObjectMake(ctx, NULL, NULL); + + JSStringRef getRandomValuesName = JSC_JSStringCreateWithUTF8CString(ctx, "getRandomValues"); + JSObjectRef getRandomValuesFunction = JSC_JSObjectMakeFunctionWithCallback(ctx, getRandomValuesName, getRandomValues); + JSC_JSObjectSetProperty(ctx, cryptoObject, getRandomValuesName, getRandomValuesFunction, kJSPropertyAttributeReadOnly & kJSPropertyAttributeDontDelete, NULL); + JSC_JSStringRelease(ctx, getRandomValuesName); + + Object::getGlobalObject(ctx).setProperty("crypto", Value(ctx, cryptoObject)); +} + void removeGlobal(JSGlobalContextRef ctx, const char* name) { Object::getGlobalObject(ctx).setProperty(name, Value::makeUndefined(ctx)); } diff --git a/ReactCommon/jschelpers/JSCHelpers.h b/ReactCommon/jschelpers/JSCHelpers.h index 9bf2e5ee9021..f4017caf3795 100644 --- a/ReactCommon/jschelpers/JSCHelpers.h +++ b/ReactCommon/jschelpers/JSCHelpers.h @@ -85,6 +85,8 @@ void installGlobalProxy( const char* name, JSObjectGetPropertyCallback callback); +void installGlobalCrypto(JSGlobalContextRef ctx); + void removeGlobal(JSGlobalContextRef ctx, const char* name); JSValueRef evaluateScript( diff --git a/ReactCommon/jschelpers/JSCWrapper.h b/ReactCommon/jschelpers/JSCWrapper.h index 471c23948b4a..e776c7204884 100644 --- a/ReactCommon/jschelpers/JSCWrapper.h +++ b/ReactCommon/jschelpers/JSCWrapper.h @@ -128,6 +128,7 @@ struct JSCWrapper { // JSValue JSC_WRAPPER_METHOD(JSValueCreateJSONString); JSC_WRAPPER_METHOD(JSValueGetType); + JSC_WRAPPER_METHOD(JSValueGetTypedArrayType); JSC_WRAPPER_METHOD(JSValueMakeFromJSONString); JSC_WRAPPER_METHOD(JSValueMakeBoolean); JSC_WRAPPER_METHOD(JSValueMakeNull); @@ -142,6 +143,11 @@ struct JSCWrapper { JSC_WRAPPER_METHOD(JSValueUnprotect); JSC_WRAPPER_METHOD(JSValueIsNull); + // JSTypedArray + JSC_WRAPPER_METHOD(JSObjectGetTypedArrayLength); + JSC_WRAPPER_METHOD(JSObjectGetTypedArrayBytesPtr); + JSC_WRAPPER_METHOD(JSObjectGetTypedArrayByteOffset); + // Sampling profiler JSC_WRAPPER_METHOD(JSSamplingProfilerEnabled); JSC_WRAPPER_METHOD(JSPokeSamplingProfiler); diff --git a/ReactCommon/jschelpers/JavaScriptCore.h b/ReactCommon/jschelpers/JavaScriptCore.h index 4c61e8aeb3a0..24fc3239f018 100644 --- a/ReactCommon/jschelpers/JavaScriptCore.h +++ b/ReactCommon/jschelpers/JavaScriptCore.h @@ -110,6 +110,7 @@ jsc_poison(JSStringCopyCFString JSStringCreateWithCharacters JSStringCreateWithC // JSValueRef #define JSC_JSValueCreateJSONString(...) __jsc_wrapper(JSValueCreateJSONString, __VA_ARGS__) #define JSC_JSValueGetType(...) __jsc_wrapper(JSValueGetType, __VA_ARGS__) +#define JSC_JSValueGetTypedArrayType(...) __jsc_wrapper(JSValueGetTypedArrayType, __VA_ARGS__) #define JSC_JSValueMakeFromJSONString(...) __jsc_wrapper(JSValueMakeFromJSONString, __VA_ARGS__) #define JSC_JSValueMakeBoolean(...) __jsc_wrapper(JSValueMakeBoolean, __VA_ARGS__) #define JSC_JSValueMakeNull(...) __jsc_wrapper(JSValueMakeNull, __VA_ARGS__) @@ -175,6 +176,10 @@ jsc_poison(JSObjectCopyPropertyNames JSPropertyNameAccumulatorAddName JSPropertyNameArrayRelease JSPropertyNameArrayRetain) // JSTypedArray +#define JSC_JSObjectGetTypedArrayLength(...) __jsc_wrapper(JSObjectGetTypedArrayLength, __VA_ARGS__) +#define JSC_JSObjectGetTypedArrayBytesPtr(...) __jsc_wrapper(JSObjectGetTypedArrayBytesPtr, __VA_ARGS__) +#define JSC_JSObjectGetTypedArrayByteOffset(...) __jsc_wrapper(JSObjectGetTypedArrayByteOffset, __VA_ARGS__) + jsc_poison(JSObjectMakeArrayBufferWithBytesNoCopy JSObjectMakeTypedArray JSObjectMakeTypedArrayWithArrayBuffer JSObjectMakeTypedArrayWithArrayBufferAndOffset diff --git a/ReactCommon/jschelpers/systemJSCWrapper.cpp b/ReactCommon/jschelpers/systemJSCWrapper.cpp index eecb036f547c..c8e6e315a90e 100644 --- a/ReactCommon/jschelpers/systemJSCWrapper.cpp +++ b/ReactCommon/jschelpers/systemJSCWrapper.cpp @@ -108,6 +108,7 @@ const JSCWrapper* systemJSCWrapper() { .JSValueCreateJSONString = JSValueCreateJSONString, .JSValueGetType = JSValueGetType, + .JSValueGetTypedArrayType = JSValueGetTypedArrayType, .JSValueMakeFromJSONString = JSValueMakeFromJSONString, .JSValueMakeBoolean = JSValueMakeBoolean, .JSValueMakeNull = JSValueMakeNull, @@ -122,6 +123,10 @@ const JSCWrapper* systemJSCWrapper() { .JSValueUnprotect = JSValueUnprotect, .JSValueIsNull = JSValueIsNull, + .JSObjectGetTypedArrayLength = JSObjectGetTypedArrayLength, + .JSObjectGetTypedArrayBytesPtr = JSObjectGetTypedArrayBytesPtr, + .JSObjectGetTypedArrayByteOffset = JSObjectGetTypedArrayByteOffset, + .JSSamplingProfilerEnabled = JSSamplingProfilerEnabled, .JSPokeSamplingProfiler = (decltype(&JSPokeSamplingProfiler))