react-native-security-suite 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -2
- package/README.md +20 -1
- package/android/build.gradle +86 -43
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +1 -3
- package/android/src/main/AndroidManifestDeprecated.xml +3 -0
- package/android/src/main/java/com/{reactnativesecuritysuite → securitysuite}/SecuritySuiteModule.java +8 -1
- package/android/src/main/java/com/{reactnativesecuritysuite → securitysuite}/SecuritySuitePackage.java +1 -1
- package/android/src/main/java/com/securitysuite/Sslpinning.java +302 -0
- package/android/src/main/java/com/{reactnativesecuritysuite → securitysuite}/StorageEncryption.java +1 -2
- package/ios/ReactNativeSslPinning-Bridging-Header.h +2 -0
- package/ios/{SecuritySuite.m → SecuritySuite.mm} +2 -0
- package/ios/SecuritySuite.swift +55 -1
- package/ios/SecuritySuite.xcodeproj/project.pbxproj +20 -10
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -3
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcuserdata/mohammadnavabi.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/SecuritySuite.xcodeproj/xcuserdata/mohammadnavabi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +6 -0
- package/ios/SslPinning.swift +195 -0
- package/lib/commonjs/helpers.js +0 -3
- package/lib/commonjs/helpers.js.map +1 -1
- package/lib/commonjs/index.js +46 -38
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/helpers.js +0 -1
- package/lib/module/helpers.js.map +1 -1
- package/lib/module/index.js +43 -14
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/helpers.d.ts +1 -0
- package/lib/typescript/helpers.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +4 -1
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +45 -35
- package/react-native-security-suite.podspec +18 -2
- package/src/index.tsx +66 -16
- package/android/.gradle/7.4/checksums/checksums.lock +0 -0
- package/android/.gradle/7.4/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/7.4/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/7.4/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/7.4/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/7.4/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/7.4/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/7.4/fileChanges/last-build.bin +0 -0
- package/android/.gradle/7.4/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/7.4/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/7.4/fileHashes/resourceHashesCache.bin +0 -0
- package/android/.gradle/7.4/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/.idea/compiler.xml +0 -6
- package/android/.idea/gradle.xml +0 -17
- package/android/.idea/jarRepositories.xml +0 -40
- package/android/.idea/misc.xml +0 -10
- package/android/.idea/vcs.xml +0 -6
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -5
- package/android/gradlew +0 -234
- package/android/gradlew.bat +0 -89
- package/android/local.properties +0 -8
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcuserdata/Navabi.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- /package/ios/SecuritySuite.xcodeproj/xcuserdata/{Navabi.xcuserdatad → mohammadnavabi.xcuserdatad}/xcschemes/xcschememanagement.plist +0 -0
package/LICENSE
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
4
|
-
|
|
3
|
+
Copyright (c) 2023 Mohammad Navabi
|
|
5
4
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
5
|
of this software and associated documentation files (the "Software"), to deal
|
|
7
6
|
in the Software without restriction, including without limitation the rights
|
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# react-native-security-suite
|
|
2
2
|
|
|
3
3
|
Security solutions for Android and iOS
|
|
4
|
+
SSL Pinning
|
|
4
5
|
A native implementation encryption/decryption
|
|
5
6
|
Root/Jailbreak detection
|
|
6
7
|
|
|
@@ -25,9 +26,27 @@ import {
|
|
|
25
26
|
encrypt,
|
|
26
27
|
decrypt,
|
|
27
28
|
deviceHasSecurityRisk,
|
|
29
|
+
fetch,
|
|
28
30
|
} from 'react-native-security-suite';
|
|
29
31
|
|
|
30
|
-
//
|
|
32
|
+
// SSL Pinning
|
|
33
|
+
const response = await fetch('URL', {
|
|
34
|
+
body: {},
|
|
35
|
+
headers: {},
|
|
36
|
+
certificates: [
|
|
37
|
+
/* certs */
|
|
38
|
+
],
|
|
39
|
+
validDomains: [
|
|
40
|
+
/* your valid domain */
|
|
41
|
+
],
|
|
42
|
+
timeout: 6000,
|
|
43
|
+
});
|
|
44
|
+
let responseJson = await response.json();
|
|
45
|
+
console.log('SSL Pinning server response: ', responseJson);
|
|
46
|
+
|
|
47
|
+
// ------- OR --------
|
|
48
|
+
|
|
49
|
+
// Hard Encrypt/Decrypt with sharedKey
|
|
31
50
|
const publicKey = await getPublicKey();
|
|
32
51
|
console.log('Public key: ', publicKey);
|
|
33
52
|
/*
|
package/android/build.gradle
CHANGED
|
@@ -1,60 +1,103 @@
|
|
|
1
1
|
buildscript {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
classpath 'com.android.tools.build:gradle:3.5.3'
|
|
11
|
-
}
|
|
12
|
-
}
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:7.2.1"
|
|
9
|
+
}
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
def isNewArchitectureEnabled() {
|
|
13
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
apply plugin: "com.android.library"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
if (isNewArchitectureEnabled()) {
|
|
22
|
+
apply plugin: "com.facebook.react"
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
minSdkVersion safeExtGet('SecuritySuite_minSdkVersion', 21)
|
|
25
|
-
targetSdkVersion safeExtGet('SecuritySuite_targetSdkVersion', 31)
|
|
26
|
-
versionCode 1
|
|
27
|
-
versionName "1.0"
|
|
25
|
+
def getExtOrDefault(name) {
|
|
26
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["SecuritySuite_" + name]
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
def getExtOrIntegerDefault(name) {
|
|
30
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["SecuritySuite_" + name]).toInteger()
|
|
31
|
+
}
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
def supportsNamespace() {
|
|
34
|
+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
35
|
+
def major = parsed[0].toInteger()
|
|
36
|
+
def minor = parsed[1].toInteger()
|
|
37
|
+
|
|
38
|
+
// Namespace support was added in 7.3.0
|
|
39
|
+
if (major == 7 && minor >= 3) {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return major >= 8
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
android {
|
|
47
|
+
if (supportsNamespace()) {
|
|
48
|
+
namespace "com.securitysuite"
|
|
49
|
+
} else {
|
|
50
|
+
sourceSets {
|
|
51
|
+
main {
|
|
52
|
+
manifest.srcFile "src/main/AndroidManifestDeprecated.xml"
|
|
53
|
+
}
|
|
38
54
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
58
|
+
|
|
59
|
+
defaultConfig {
|
|
60
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
61
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
62
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
63
|
+
}
|
|
64
|
+
buildTypes {
|
|
65
|
+
release {
|
|
66
|
+
minifyEnabled false
|
|
42
67
|
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lintOptions {
|
|
71
|
+
disable "GradleCompatible"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
compileOptions {
|
|
75
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
76
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
77
|
+
}
|
|
78
|
+
|
|
43
79
|
}
|
|
44
80
|
|
|
45
81
|
repositories {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
url("$rootDir/../node_modules/react-native/android")
|
|
50
|
-
}
|
|
51
|
-
google()
|
|
52
|
-
mavenCentral()
|
|
53
|
-
jcenter()
|
|
82
|
+
mavenCentral()
|
|
83
|
+
google()
|
|
84
|
+
jcenter()
|
|
54
85
|
}
|
|
55
86
|
|
|
87
|
+
|
|
56
88
|
dependencies {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
// For < 0.71, this will be from the local maven repo
|
|
90
|
+
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
91
|
+
//noinspection GradleDynamicVersion
|
|
92
|
+
implementation "com.facebook.react:react-native:+"
|
|
93
|
+
implementation "com.squareup.okhttp3:okhttp:3.0.1"
|
|
94
|
+
implementation "com.scottyab:rootbeer-lib:0.1.0"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (isNewArchitectureEnabled()) {
|
|
98
|
+
react {
|
|
99
|
+
jsRootDir = file("../src/")
|
|
100
|
+
libraryName = "SecuritySuite"
|
|
101
|
+
codegenJavaPackageName = "com.securitysuite"
|
|
102
|
+
}
|
|
60
103
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.securitysuite;
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.Callback;
|
|
4
4
|
import com.facebook.react.bridge.Promise;
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
6
6
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
7
7
|
import com.facebook.react.bridge.ReactMethod;
|
|
8
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
8
9
|
import com.facebook.react.module.annotations.ReactModule;
|
|
9
10
|
import com.scottyab.rootbeer.RootBeer;
|
|
10
11
|
|
|
@@ -188,6 +189,12 @@ public class SecuritySuiteModule extends ReactContextBaseJavaModule {
|
|
|
188
189
|
Settings.Secure.ANDROID_ID);
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
@ReactMethod
|
|
193
|
+
public void fetch(String url, final ReadableMap options, Callback callback) {
|
|
194
|
+
Sslpinning sslpinning = new Sslpinning(context);
|
|
195
|
+
sslpinning.fetch(url, options, callback);
|
|
196
|
+
}
|
|
197
|
+
|
|
191
198
|
@ReactMethod
|
|
192
199
|
public void deviceHasSecurityRisk(Promise promise) {
|
|
193
200
|
RootBeer rootBeer = new RootBeer(context);
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
package com.securitysuite;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.net.Uri;
|
|
5
|
+
|
|
6
|
+
import com.facebook.react.bridge.Arguments;
|
|
7
|
+
import com.facebook.react.bridge.Callback;
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
9
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
10
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
11
|
+
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
|
12
|
+
import com.facebook.react.bridge.ReadableType;
|
|
13
|
+
import com.facebook.react.bridge.WritableMap;
|
|
14
|
+
|
|
15
|
+
import org.json.JSONException;
|
|
16
|
+
|
|
17
|
+
import java.io.BufferedOutputStream;
|
|
18
|
+
import java.io.File;
|
|
19
|
+
import java.io.FileOutputStream;
|
|
20
|
+
import java.io.IOException;
|
|
21
|
+
import java.io.InputStream;
|
|
22
|
+
import java.io.OutputStream;
|
|
23
|
+
import java.net.URI;
|
|
24
|
+
import java.net.URISyntaxException;
|
|
25
|
+
import java.util.HashMap;
|
|
26
|
+
import java.util.Map;
|
|
27
|
+
import java.util.concurrent.TimeUnit;
|
|
28
|
+
|
|
29
|
+
import okhttp3.CertificatePinner;
|
|
30
|
+
import okhttp3.Headers;
|
|
31
|
+
import okhttp3.MediaType;
|
|
32
|
+
import okhttp3.MultipartBody;
|
|
33
|
+
import okhttp3.Request;
|
|
34
|
+
import okhttp3.OkHttpClient;
|
|
35
|
+
import okhttp3.RequestBody;
|
|
36
|
+
import okhttp3.Response;
|
|
37
|
+
|
|
38
|
+
public class Sslpinning {
|
|
39
|
+
private ReactApplicationContext context;
|
|
40
|
+
private static String content_type = "application/json; charset=utf-8";
|
|
41
|
+
public static MediaType mediaType = MediaType.parse(content_type);
|
|
42
|
+
String responseBodyString = "{}";
|
|
43
|
+
|
|
44
|
+
public Sslpinning(ReactApplicationContext context) {
|
|
45
|
+
this.context = context;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public void fetch(String url, final ReadableMap options, Callback callback) {
|
|
49
|
+
if (!isValidUrl(url)) {
|
|
50
|
+
callback.invoke(null, "url is invalid!");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
String hostname;
|
|
55
|
+
try {
|
|
56
|
+
hostname = getHostname(url);
|
|
57
|
+
} catch (URISyntaxException e) {
|
|
58
|
+
hostname = url;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!options.hasKey("certificates")) {
|
|
62
|
+
callback.invoke(null, "certificates is required!");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
ReadableArray hashes = options.getArray("certificates");
|
|
67
|
+
if (hashes == null || hashes.size() == 0) {
|
|
68
|
+
callback.invoke(null, "certificates is empty!");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
CertificatePinner certificatePinner = getCertificatePinner(hostname, options);
|
|
74
|
+
OkHttpClient client = getClient(options, certificatePinner);
|
|
75
|
+
|
|
76
|
+
Headers header = setHeader(options);
|
|
77
|
+
RequestBody body = setBody(context, options);
|
|
78
|
+
String method = getMethod(options);
|
|
79
|
+
|
|
80
|
+
Request request = new Request.Builder()
|
|
81
|
+
.url(url)
|
|
82
|
+
.headers(header)
|
|
83
|
+
.method(method, body)
|
|
84
|
+
.build();
|
|
85
|
+
|
|
86
|
+
WritableMap output = Arguments.createMap();
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
Response response = client.newCall(request).execute();
|
|
90
|
+
int responseCode = response.code();
|
|
91
|
+
|
|
92
|
+
byte[] bytes = response.body().bytes();
|
|
93
|
+
responseBodyString = new String(bytes, "UTF-8");
|
|
94
|
+
|
|
95
|
+
output.putInt("status", responseCode);
|
|
96
|
+
output.putString("url", request.url().toString());
|
|
97
|
+
|
|
98
|
+
if (!response.isSuccessful() || responseCode >= 400) {
|
|
99
|
+
output.putString("error", responseBodyString);
|
|
100
|
+
callback.invoke(null, output);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
output.putString("response", responseBodyString);
|
|
104
|
+
callback.invoke(output, null);
|
|
105
|
+
} catch (IOException e) {
|
|
106
|
+
output.putString("error", responseBodyString);
|
|
107
|
+
|
|
108
|
+
callback.invoke(null, output);
|
|
109
|
+
if (e instanceof java.net.SocketTimeoutException) {
|
|
110
|
+
System.err.print("Socket TimeOut");
|
|
111
|
+
} else {
|
|
112
|
+
e.printStackTrace();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch(JSONException e) {
|
|
116
|
+
callback.invoke(null, e);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private CertificatePinner getCertificatePinner(String hostname, ReadableMap options) {
|
|
121
|
+
CertificatePinner.Builder certificatePinner = new CertificatePinner.Builder();
|
|
122
|
+
|
|
123
|
+
ReadableArray hashes = options.getArray("certificates");
|
|
124
|
+
for (int i = 0; i < hashes.size(); i++) {
|
|
125
|
+
certificatePinner.add(hostname, "sha256/" + hashes.getString(i));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return certificatePinner.build();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private OkHttpClient getClient(ReadableMap options, CertificatePinner certificatePinner) throws JSONException {
|
|
132
|
+
if (options.hasKey("timeout")) {
|
|
133
|
+
int timeout = options.getInt("timeout");
|
|
134
|
+
return new OkHttpClient.Builder()
|
|
135
|
+
.connectTimeout(timeout, TimeUnit.MILLISECONDS)
|
|
136
|
+
.readTimeout(timeout, TimeUnit.MILLISECONDS)
|
|
137
|
+
.writeTimeout(timeout, TimeUnit.MILLISECONDS)
|
|
138
|
+
.certificatePinner(certificatePinner)
|
|
139
|
+
.build();
|
|
140
|
+
} else {
|
|
141
|
+
return new OkHttpClient.Builder()
|
|
142
|
+
.certificatePinner(certificatePinner)
|
|
143
|
+
.build();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private static String getHostname(String url) throws URISyntaxException {
|
|
148
|
+
URI uri = new URI(url);
|
|
149
|
+
String domain = uri.getHost();
|
|
150
|
+
return domain.startsWith("www.") ? domain.substring(4) : domain;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private String getMethod(ReadableMap options) {
|
|
154
|
+
String method = "GET";
|
|
155
|
+
if (options.hasKey("method")) {
|
|
156
|
+
method = options.getString("method");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return method;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private Headers setHeader(ReadableMap options) {
|
|
163
|
+
if (!options.hasKey("headers")) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
ReadableMap headers = options.getMap("headers");
|
|
168
|
+
Headers.Builder builder = new Headers.Builder();
|
|
169
|
+
|
|
170
|
+
Map<String, String> headersMap = readableMapToHashMap(headers);
|
|
171
|
+
for (Map.Entry<String, String> set : headersMap.entrySet()) {
|
|
172
|
+
builder.add(set.getKey(), set.getValue());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return builder.build();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private HashMap readableMapToHashMap(ReadableMap readableMap) {
|
|
179
|
+
if (readableMap == null) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
HashMap map = new HashMap<String, String>();
|
|
184
|
+
ReadableMapKeySetIterator keySetIterator = readableMap.keySetIterator();
|
|
185
|
+
while (keySetIterator.hasNextKey()) {
|
|
186
|
+
String key = keySetIterator.nextKey();
|
|
187
|
+
ReadableType type = readableMap.getType(key);
|
|
188
|
+
switch(type) {
|
|
189
|
+
case String:
|
|
190
|
+
map.put(key, readableMap.getString(key));
|
|
191
|
+
break;
|
|
192
|
+
case Map:
|
|
193
|
+
HashMap<String, Object> attributes = this.readableMapToHashMap(readableMap.getMap(key));
|
|
194
|
+
map.put(key, attributes);
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
// do nothing
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return map;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private static boolean isFilePart(ReadableArray part) {
|
|
205
|
+
if (part.getType(1) != ReadableType.Map) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
ReadableMap value = part.getMap(1);
|
|
209
|
+
return value.hasKey("type") && (value.hasKey("uri") || value.hasKey("path"));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private RequestBody setBody(ReactApplicationContext context, ReadableMap options) {
|
|
213
|
+
if (!options.hasKey("body")) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
RequestBody body = null;
|
|
218
|
+
ReadableType bodyType = options.getType("body");
|
|
219
|
+
switch (bodyType) {
|
|
220
|
+
case String:
|
|
221
|
+
body = RequestBody.create(mediaType, options.getString("body"));
|
|
222
|
+
break;
|
|
223
|
+
case Map:
|
|
224
|
+
ReadableMap bodyMap = options.getMap("body");
|
|
225
|
+
if (bodyMap.hasKey("formData")) {
|
|
226
|
+
ReadableMap formData = bodyMap.getMap("formData");
|
|
227
|
+
body = getBody(formData);
|
|
228
|
+
} else if (bodyMap.hasKey("_parts")) {
|
|
229
|
+
body = getBody(bodyMap);
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
return body;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private RequestBody getBody(ReadableMap body) {
|
|
237
|
+
MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
|
|
238
|
+
multipartBodyBuilder.setType((MediaType.parse("multipart/form-data")));
|
|
239
|
+
if (body.hasKey("_parts")) {
|
|
240
|
+
ReadableArray parts = body.getArray("_parts");
|
|
241
|
+
for (int i = 0; i < parts.size(); i++) {
|
|
242
|
+
ReadableArray part = parts.getArray(i);
|
|
243
|
+
String key = "";
|
|
244
|
+
if (part.getType(0) == ReadableType.String) {
|
|
245
|
+
key = part.getString(0);
|
|
246
|
+
} else if (part.getType(0) == ReadableType.Number) {
|
|
247
|
+
key = String.valueOf(part.getInt(0));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (isFilePart(part)) {
|
|
251
|
+
ReadableMap fileData = part.getMap(1);
|
|
252
|
+
addFormDataPart(this.context, multipartBodyBuilder, fileData, key);
|
|
253
|
+
} else {
|
|
254
|
+
String value = part.getString(1);
|
|
255
|
+
multipartBodyBuilder.addFormDataPart(key, value);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return multipartBodyBuilder.build();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private static void addFormDataPart(Context context, MultipartBody.Builder multipartBodyBuilder, ReadableMap fileData, String key) {
|
|
263
|
+
Uri _uri = Uri.parse("");
|
|
264
|
+
if (fileData.hasKey("uri")) {
|
|
265
|
+
_uri = Uri.parse(fileData.getString("uri"));
|
|
266
|
+
} else if (fileData.hasKey("path")) {
|
|
267
|
+
_uri = Uri.parse(fileData.getString("path"));
|
|
268
|
+
}
|
|
269
|
+
String type = fileData.getString("type");
|
|
270
|
+
String fileName = "";
|
|
271
|
+
if (fileData.hasKey("fileName")) {
|
|
272
|
+
fileName = fileData.getString("fileName");
|
|
273
|
+
} else if (fileData.hasKey("name")) {
|
|
274
|
+
fileName = fileData.getString("name");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
File file = getTempFile(context, _uri);
|
|
279
|
+
multipartBodyBuilder.addFormDataPart(key, fileName, RequestBody.create(MediaType.parse(type), file));
|
|
280
|
+
} catch (IOException e) {
|
|
281
|
+
e.printStackTrace();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public static File getTempFile(Context context, Uri uri) throws IOException {
|
|
286
|
+
File file = File.createTempFile("media", null);
|
|
287
|
+
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
|
288
|
+
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
|
|
289
|
+
byte[] buffer = new byte[1024];
|
|
290
|
+
int len;
|
|
291
|
+
while ((len = inputStream.read(buffer)) != -1)
|
|
292
|
+
outputStream.write(buffer, 0, len);
|
|
293
|
+
inputStream.close();
|
|
294
|
+
outputStream.close();
|
|
295
|
+
return file;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private boolean isValidUrl(String url) {
|
|
299
|
+
String regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
|
|
300
|
+
return url.matches(regex);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -16,6 +16,8 @@ RCT_EXTERN_METHOD(storageEncrypt:(NSString)input withSecretKey:(NSString*)secret
|
|
|
16
16
|
|
|
17
17
|
RCT_EXTERN_METHOD(storageDecrypt:(NSString)input withSecretKey:(NSString*)secretKey withHardEncryption:(BOOL)hardEncryption withCallback:(RCTResponseSenderBlock)callback)
|
|
18
18
|
|
|
19
|
+
RCT_EXTERN_METHOD(fetch:(NSString)url withData:(NSDictionary)data withCallback:(RCTResponseSenderBlock)callback)
|
|
20
|
+
|
|
19
21
|
RCT_EXTERN_METHOD(deviceHasSecurityRisk:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
|
|
20
22
|
|
|
21
23
|
+ (BOOL)requiresMainQueueSetup
|
package/ios/SecuritySuite.swift
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import IOSSecuritySuite
|
|
2
1
|
import Foundation
|
|
3
2
|
import CryptoKit
|
|
4
3
|
import SwiftUI
|
|
4
|
+
import Security
|
|
5
|
+
import CommonCrypto
|
|
6
|
+
import IOSSecuritySuite
|
|
5
7
|
|
|
6
8
|
@available(iOS 13.0, *)
|
|
7
9
|
@objc(SecuritySuite)
|
|
@@ -123,6 +125,58 @@ class SecuritySuite: NSObject {
|
|
|
123
125
|
func getDeviceId() -> String {
|
|
124
126
|
return UIDevice.current.identifierForVendor!.uuidString.replacingOccurrences(of: "-", with: "", options: [], range: nil)
|
|
125
127
|
}
|
|
128
|
+
|
|
129
|
+
@objc(fetch:withData:withCallback:)
|
|
130
|
+
func fetch(url: NSString, data: NSDictionary, callback: @escaping RCTResponseSenderBlock) -> Void {
|
|
131
|
+
let config = URLSessionConfiguration.default
|
|
132
|
+
config.httpShouldSetCookies = false
|
|
133
|
+
config.httpCookieAcceptPolicy = .never
|
|
134
|
+
config.networkServiceType = .responsiveData
|
|
135
|
+
config.shouldUseExtendedBackgroundIdleMode = true
|
|
136
|
+
|
|
137
|
+
let sslPinning = SSLPinning(data: data)
|
|
138
|
+
|
|
139
|
+
var request = URLRequest(url: URL(string: url as String)!)
|
|
140
|
+
|
|
141
|
+
if data["method"] != nil { request.httpMethod = data["method"] as! String } else { request.httpMethod = "POST" }
|
|
142
|
+
if data["body"] != nil { request.httpBody = (data["body"] as! String).data(using: .utf8)! } else { request.httpBody = "".data(using: .utf8)! }
|
|
143
|
+
if data["headers"] != nil { request.allHTTPHeaderFields = data["headers"] as! [String : String] }
|
|
144
|
+
if data["timeout"] != nil { request.timeoutInterval = data["timeout"] as! TimeInterval }
|
|
145
|
+
let session = URLSession(configuration: config, delegate: sslPinning, delegateQueue: .main)
|
|
146
|
+
let task = session.dataTask(with: request) { data, response, error in
|
|
147
|
+
let response = response as? HTTPURLResponse
|
|
148
|
+
|
|
149
|
+
if error == nil {
|
|
150
|
+
let responseCode = response?.statusCode
|
|
151
|
+
let responseString = String.init(decoding: data ?? .init(), as: UTF8.self)
|
|
152
|
+
let errorString = error?.localizedDescription
|
|
153
|
+
let responseJSON = try? JSONSerialization.jsonObject(with: data!, options: [])
|
|
154
|
+
|
|
155
|
+
var result:NSMutableDictionary = [
|
|
156
|
+
"status": response?.statusCode,
|
|
157
|
+
"url": url,
|
|
158
|
+
]
|
|
159
|
+
if errorString == nil && responseCode! < 400 {
|
|
160
|
+
result["response"] = responseString
|
|
161
|
+
result["responseJSON"] = responseJSON
|
|
162
|
+
callback([result, NSNull()])
|
|
163
|
+
} else {
|
|
164
|
+
result["error"] = responseString
|
|
165
|
+
result["errorJSON"] = responseJSON
|
|
166
|
+
do {
|
|
167
|
+
let jsonData = try JSONSerialization.data(withJSONObject: result)
|
|
168
|
+
callback([NSNull(), result])
|
|
169
|
+
} catch {
|
|
170
|
+
callback([NSNull(), "JSON_PARSE_ERROR"])
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
callback([NSNull(), "MUST_BE_UPDATE"])
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
task.resume()
|
|
179
|
+
}
|
|
126
180
|
|
|
127
181
|
@objc(deviceHasSecurityRisk:withRejecter:)
|
|
128
182
|
func deviceHasSecurityRisk(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
|