react-native-security-suite 0.9.21 → 1.0.0-rc.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.
Files changed (189) hide show
  1. package/README.md +233 -65
  2. package/android/build.gradle +11 -0
  3. package/android/gradle.properties +1 -1
  4. package/android/src/main/java/com/securitysuite/CryptoConfig.java +158 -0
  5. package/android/src/main/java/com/securitysuite/CryptoUtils.java +152 -0
  6. package/android/src/main/java/com/securitysuite/EcdhKeyStore.java +60 -0
  7. package/android/src/main/java/com/securitysuite/HeaderSanitizer.java +75 -0
  8. package/android/src/main/java/com/securitysuite/JWSGenerator.java +237 -32
  9. package/android/src/main/java/com/securitysuite/JwsFetchPayload.java +81 -0
  10. package/android/src/main/java/com/securitysuite/Obfuscation.java +57 -0
  11. package/android/src/main/java/com/securitysuite/SecureStorageNative.java +211 -0
  12. package/android/src/main/java/com/securitysuite/SecureView.java +2 -10
  13. package/android/src/main/java/com/securitysuite/SecureWindowHelper.java +30 -0
  14. package/android/src/main/java/com/securitysuite/SecuritySuiteModule.java +310 -102
  15. package/android/src/main/java/com/securitysuite/Sslpinning.java +219 -106
  16. package/android/src/main/java/com/securitysuite/security/AppIntegrityChecker.java +133 -0
  17. package/android/src/main/java/com/securitysuite/security/EmulatorDetector.java +145 -0
  18. package/android/src/main/java/com/securitysuite/security/RuntimeDetector.java +234 -0
  19. package/android/src/test/java/com/securitysuite/JWSGeneratorTest.java +153 -0
  20. package/android/src/test/java/com/securitysuite/SecureStorageNativeTest.java +37 -0
  21. package/ios/CryptoConfig.swift +124 -0
  22. package/ios/JWSGenerator.swift +288 -0
  23. package/ios/JWSGeneratorTests.swift +168 -0
  24. package/ios/KeychainHelper.swift +104 -0
  25. package/ios/Obfuscation.swift +42 -0
  26. package/ios/SecureStorageNative.swift +84 -0
  27. package/ios/Security/AppIntegrityChecker.swift +85 -0
  28. package/ios/Security/EmulatorDetector.swift +45 -0
  29. package/ios/Security/RuntimeDetector.swift +107 -0
  30. package/ios/SecuritySuite.mm +28 -4
  31. package/ios/SecuritySuite.swift +407 -131
  32. package/ios/SslPinning.swift +242 -263
  33. package/lib/commonjs/clipboard/index.js +3 -0
  34. package/lib/commonjs/clipboard/index.js.map +1 -0
  35. package/lib/commonjs/crypto/index.js +39 -0
  36. package/lib/commonjs/crypto/index.js.map +1 -0
  37. package/lib/commonjs/device/index.js +40 -0
  38. package/lib/commonjs/device/index.js.map +1 -0
  39. package/lib/commonjs/errors.js +62 -0
  40. package/lib/commonjs/errors.js.map +1 -0
  41. package/lib/commonjs/index.js +220 -151
  42. package/lib/commonjs/index.js.map +1 -1
  43. package/lib/commonjs/integrity/index.js +40 -0
  44. package/lib/commonjs/integrity/index.js.map +1 -0
  45. package/lib/commonjs/jws.js +141 -0
  46. package/lib/commonjs/jws.js.map +1 -0
  47. package/lib/commonjs/legacy/cryptoOptions.js +20 -0
  48. package/lib/commonjs/legacy/cryptoOptions.js.map +1 -0
  49. package/lib/commonjs/native/bridge.js +23 -0
  50. package/lib/commonjs/native/bridge.js.map +1 -0
  51. package/lib/commonjs/network/index.js +3 -0
  52. package/lib/commonjs/network/index.js.map +1 -0
  53. package/lib/commonjs/risk/score.js +36 -0
  54. package/lib/commonjs/risk/score.js.map +1 -0
  55. package/lib/commonjs/runtime/index.js +31 -0
  56. package/lib/commonjs/runtime/index.js.map +1 -0
  57. package/lib/commonjs/screen/index.js +13 -0
  58. package/lib/commonjs/screen/index.js.map +1 -0
  59. package/lib/commonjs/securitySuite/index.js +42 -0
  60. package/lib/commonjs/securitySuite/index.js.map +1 -0
  61. package/lib/commonjs/storage/index.js +3 -0
  62. package/lib/commonjs/storage/index.js.map +1 -0
  63. package/lib/commonjs/types/detection.js +2 -0
  64. package/lib/commonjs/types/detection.js.map +1 -0
  65. package/lib/module/clipboard/index.js +3 -0
  66. package/lib/module/clipboard/index.js.map +1 -0
  67. package/lib/module/crypto/index.js +35 -0
  68. package/lib/module/crypto/index.js.map +1 -0
  69. package/lib/module/device/index.js +36 -0
  70. package/lib/module/device/index.js.map +1 -0
  71. package/lib/module/errors.js +55 -0
  72. package/lib/module/errors.js.map +1 -0
  73. package/lib/module/index.js +147 -148
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/integrity/index.js +36 -0
  76. package/lib/module/integrity/index.js.map +1 -0
  77. package/lib/module/jws.js +127 -0
  78. package/lib/module/jws.js.map +1 -0
  79. package/lib/module/legacy/cryptoOptions.js +16 -0
  80. package/lib/module/legacy/cryptoOptions.js.map +1 -0
  81. package/lib/module/native/bridge.js +19 -0
  82. package/lib/module/native/bridge.js.map +1 -0
  83. package/lib/module/network/index.js +3 -0
  84. package/lib/module/network/index.js.map +1 -0
  85. package/lib/module/risk/score.js +32 -0
  86. package/lib/module/risk/score.js.map +1 -0
  87. package/lib/module/runtime/index.js +27 -0
  88. package/lib/module/runtime/index.js.map +1 -0
  89. package/lib/module/screen/index.js +5 -0
  90. package/lib/module/screen/index.js.map +1 -0
  91. package/lib/module/securitySuite/index.js +38 -0
  92. package/lib/module/securitySuite/index.js.map +1 -0
  93. package/lib/module/storage/index.js +3 -0
  94. package/lib/module/storage/index.js.map +1 -0
  95. package/lib/module/types/detection.js +2 -0
  96. package/lib/module/types/detection.js.map +1 -0
  97. package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts +215 -0
  98. package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts.map +1 -0
  99. package/lib/typescript/commonjs/src/SecureView.d.ts +1 -1
  100. package/lib/typescript/commonjs/src/SecureView.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/src/clipboard/index.d.ts +2 -0
  102. package/lib/typescript/commonjs/src/clipboard/index.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/src/crypto/index.d.ts +15 -0
  104. package/lib/typescript/commonjs/src/crypto/index.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/src/device/index.d.ts +11 -0
  106. package/lib/typescript/commonjs/src/device/index.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/src/errors.d.ts +17 -0
  108. package/lib/typescript/commonjs/src/errors.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/src/index.d.ts +77 -24
  111. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/src/integrity/index.d.ts +6 -0
  113. package/lib/typescript/commonjs/src/integrity/index.d.ts.map +1 -0
  114. package/lib/typescript/commonjs/src/jws.d.ts +44 -0
  115. package/lib/typescript/commonjs/src/jws.d.ts.map +1 -0
  116. package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts +35 -0
  117. package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/src/native/bridge.d.ts +12 -0
  119. package/lib/typescript/commonjs/src/native/bridge.d.ts.map +1 -0
  120. package/lib/typescript/commonjs/src/network/index.d.ts +2 -0
  121. package/lib/typescript/commonjs/src/network/index.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/src/risk/score.d.ts +12 -0
  123. package/lib/typescript/commonjs/src/risk/score.d.ts.map +1 -0
  124. package/lib/typescript/commonjs/src/runtime/index.d.ts +6 -0
  125. package/lib/typescript/commonjs/src/runtime/index.d.ts.map +1 -0
  126. package/lib/typescript/commonjs/src/screen/index.d.ts +3 -0
  127. package/lib/typescript/commonjs/src/screen/index.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/src/securitySuite/index.d.ts +6 -0
  129. package/lib/typescript/commonjs/src/securitySuite/index.d.ts.map +1 -0
  130. package/lib/typescript/commonjs/src/storage/index.d.ts +2 -0
  131. package/lib/typescript/commonjs/src/storage/index.d.ts.map +1 -0
  132. package/lib/typescript/commonjs/src/types/detection.d.ts +41 -0
  133. package/lib/typescript/commonjs/src/types/detection.d.ts.map +1 -0
  134. package/lib/typescript/module/docs/api-v1-proposal.d.ts +215 -0
  135. package/lib/typescript/module/docs/api-v1-proposal.d.ts.map +1 -0
  136. package/lib/typescript/module/src/SecureView.d.ts +1 -1
  137. package/lib/typescript/module/src/SecureView.d.ts.map +1 -1
  138. package/lib/typescript/module/src/clipboard/index.d.ts +2 -0
  139. package/lib/typescript/module/src/clipboard/index.d.ts.map +1 -0
  140. package/lib/typescript/module/src/crypto/index.d.ts +15 -0
  141. package/lib/typescript/module/src/crypto/index.d.ts.map +1 -0
  142. package/lib/typescript/module/src/device/index.d.ts +11 -0
  143. package/lib/typescript/module/src/device/index.d.ts.map +1 -0
  144. package/lib/typescript/module/src/errors.d.ts +17 -0
  145. package/lib/typescript/module/src/errors.d.ts.map +1 -0
  146. package/lib/typescript/module/src/helpers.d.ts.map +1 -1
  147. package/lib/typescript/module/src/index.d.ts +77 -24
  148. package/lib/typescript/module/src/index.d.ts.map +1 -1
  149. package/lib/typescript/module/src/integrity/index.d.ts +6 -0
  150. package/lib/typescript/module/src/integrity/index.d.ts.map +1 -0
  151. package/lib/typescript/module/src/jws.d.ts +44 -0
  152. package/lib/typescript/module/src/jws.d.ts.map +1 -0
  153. package/lib/typescript/module/src/legacy/cryptoOptions.d.ts +35 -0
  154. package/lib/typescript/module/src/legacy/cryptoOptions.d.ts.map +1 -0
  155. package/lib/typescript/module/src/native/bridge.d.ts +12 -0
  156. package/lib/typescript/module/src/native/bridge.d.ts.map +1 -0
  157. package/lib/typescript/module/src/network/index.d.ts +2 -0
  158. package/lib/typescript/module/src/network/index.d.ts.map +1 -0
  159. package/lib/typescript/module/src/risk/score.d.ts +12 -0
  160. package/lib/typescript/module/src/risk/score.d.ts.map +1 -0
  161. package/lib/typescript/module/src/runtime/index.d.ts +6 -0
  162. package/lib/typescript/module/src/runtime/index.d.ts.map +1 -0
  163. package/lib/typescript/module/src/screen/index.d.ts +3 -0
  164. package/lib/typescript/module/src/screen/index.d.ts.map +1 -0
  165. package/lib/typescript/module/src/securitySuite/index.d.ts +6 -0
  166. package/lib/typescript/module/src/securitySuite/index.d.ts.map +1 -0
  167. package/lib/typescript/module/src/storage/index.d.ts +2 -0
  168. package/lib/typescript/module/src/storage/index.d.ts.map +1 -0
  169. package/lib/typescript/module/src/types/detection.d.ts +41 -0
  170. package/lib/typescript/module/src/types/detection.d.ts.map +1 -0
  171. package/package.json +2 -4
  172. package/src/clipboard/index.ts +1 -0
  173. package/src/crypto/index.ts +49 -0
  174. package/src/device/index.ts +47 -0
  175. package/src/errors.ts +84 -0
  176. package/src/index.tsx +293 -195
  177. package/src/integrity/index.ts +46 -0
  178. package/src/jws.ts +213 -0
  179. package/src/legacy/cryptoOptions.ts +49 -0
  180. package/src/native/bridge.ts +37 -0
  181. package/src/network/index.ts +1 -0
  182. package/src/risk/score.ts +49 -0
  183. package/src/runtime/index.ts +43 -0
  184. package/src/screen/index.ts +2 -0
  185. package/src/securitySuite/index.ts +45 -0
  186. package/src/storage/index.ts +1 -0
  187. package/src/types/detection.ts +46 -0
  188. package/android/src/main/java/com/securitysuite/StorageEncryption.java +0 -52
  189. package/ios/StorageEncryption.swift +0 -89
@@ -31,8 +31,6 @@ import java.util.HashMap;
31
31
  import java.util.Map;
32
32
  import java.util.concurrent.TimeUnit;
33
33
 
34
- import javax.crypto.SecretKey;
35
-
36
34
  import okhttp3.CertificatePinner;
37
35
  import okhttp3.Headers;
38
36
  import okhttp3.MediaType;
@@ -45,10 +43,10 @@ import okhttp3.Response;
45
43
  import okio.Buffer;
46
44
 
47
45
  public class Sslpinning {
48
- private ReactApplicationContext context;
46
+ private final ReactApplicationContext context;
49
47
  private static String hostname = "";
50
- private static String content_type = "application/json; charset=utf-8";
51
- public static MediaType mediaType = MediaType.parse(content_type);
48
+ private static final String CONTENT_TYPE = "application/json; charset=utf-8";
49
+ public static final MediaType mediaType = MediaType.parse(CONTENT_TYPE);
52
50
  String responseBodyString = "{}";
53
51
  Callback callback;
54
52
 
@@ -56,22 +54,34 @@ public class Sslpinning {
56
54
  this.context = context;
57
55
  }
58
56
 
59
- public void fetch(String url, final ReadableMap options, SecretKey sharedKey, Callback callback) {
57
+ public void fetch(String url, final ReadableMap options, Callback callback) {
60
58
  this.callback = callback;
61
59
 
62
- if (!isValidUrl(url)) {
63
- callback.invoke(null, "url is invalid!");
60
+ if (!CryptoUtils.isHttpsUrl(url)) {
61
+ callback.invoke(null, "Only HTTPS URLs are allowed");
62
+ return;
63
+ }
64
+
65
+ PinningConfig pinningConfig = PinningConfig.fromOptions(options);
66
+ if (pinningConfig.hasError()) {
67
+ callback.invoke(null, pinningConfig.getError());
64
68
  return;
65
69
  }
66
70
 
67
71
  try {
68
72
  this.hostname = getHostname(url);
69
73
  } catch (URISyntaxException e) {
70
- this.hostname = url;
74
+ callback.invoke(null, "Invalid URL hostname");
75
+ return;
76
+ }
77
+
78
+ if (pinningConfig.isEnabled() && !isValidDomain(this.hostname, options)) {
79
+ callback.invoke(null, "Hostname '" + this.hostname + "' is not in validDomains");
80
+ return;
71
81
  }
72
82
 
73
83
  try {
74
- OkHttpClient client = getClient(options);
84
+ OkHttpClient client = getClient(options, pinningConfig);
75
85
 
76
86
  Headers header = setHeader(options);
77
87
  String method = getMethod(options);
@@ -82,34 +92,68 @@ public class Sslpinning {
82
92
  return;
83
93
  }
84
94
 
85
- // JWS handler
86
95
  String jwsHeader = "";
87
- if (options.hasKey("keyId") && options.hasKey("requestId")) {
88
- String keyId = options.getString("keyId");
89
- String requestId = options.getString("requestId");
96
+ String jwsHeaderName = "X-Request-Signature";
97
+ if (options.hasKey("jws") && options.getMap("jws") != null) {
98
+ ReadableMap jwsOptions = options.getMap("jws");
99
+ if (jwsOptions == null || !jwsOptions.hasKey("secret")) {
100
+ callback.invoke(null, "JWS secret is required and must be a non-empty string");
101
+ return;
102
+ }
103
+ String secret = jwsOptions.getString("secret");
104
+ if (secret == null || secret.trim().isEmpty()) {
105
+ callback.invoke(null, "JWS secret is required and must be a non-empty string");
106
+ return;
107
+ }
90
108
 
91
- byte[] payload = new byte[0];
92
- if (requestBody != null) {
93
- Buffer buffer = new Buffer();
94
- try {
95
- requestBody.writeTo(buffer);
96
- payload = buffer.readByteArray();
97
- } catch (IOException e) {
98
- e.printStackTrace();
99
- }
109
+ if (jwsOptions.hasKey("headerName") && jwsOptions.getString("headerName") != null) {
110
+ jwsHeaderName = jwsOptions.getString("headerName");
100
111
  }
101
112
 
113
+ byte[] requestBodyBytes = extractPayload(requestBody);
114
+ String payloadString = JwsFetchPayload.build(url, method, requestBodyBytes, jwsOptions);
115
+ boolean detached = jwsOptions.hasKey("detached") && jwsOptions.getBoolean("detached");
116
+ String algorithm = jwsOptions.hasKey("algorithm") ? jwsOptions.getString("algorithm") : null;
117
+ ReadableMap headers = jwsOptions.hasKey("headers") ? jwsOptions.getMap("headers") : null;
118
+
102
119
  JWSGenerator jwsGenerator = new JWSGenerator();
103
- jwsHeader = jwsGenerator.jwsHeader(payload, keyId, requestId, sharedKey);
120
+ jwsHeader = jwsGenerator.generate(
121
+ payloadString,
122
+ secret,
123
+ algorithm,
124
+ headers,
125
+ detached
126
+ );
127
+ } else if (options.hasKey("keyId") && options.hasKey("requestId")) {
128
+ if (!options.hasKey("secret")) {
129
+ callback.invoke(null, "JWS secret is required. Pass options.jws.secret or options.secret for legacy signing.");
130
+ return;
131
+ }
132
+ String secret = options.getString("secret");
133
+ if (secret == null || secret.trim().isEmpty()) {
134
+ callback.invoke(null, "JWS secret is required and must be a non-empty string");
135
+ return;
136
+ }
137
+ String keyId = options.getString("keyId");
138
+ String requestId = options.getString("requestId");
139
+ byte[] payload = extractPayload(requestBody);
140
+ JWSGenerator jwsGenerator = new JWSGenerator();
141
+ jwsHeader = jwsGenerator.jwsHeader(payload, keyId, requestId, secret);
142
+ jwsHeaderName = "X-JWS-Signature";
104
143
  }
105
144
 
106
- Request request = new Request.Builder()
145
+ Request.Builder requestBuilder = new Request.Builder()
107
146
  .url(url)
108
- .headers(header)
109
- .addHeader("X-JWS-Signature", jwsHeader)
110
- .method(method, requestBody)
111
- .build();
147
+ .method(method, requestBody);
112
148
 
149
+ if (header != null) {
150
+ requestBuilder.headers(header);
151
+ }
152
+ if (!jwsHeader.isEmpty()) {
153
+ requestBuilder.addHeader(jwsHeaderName, jwsHeader);
154
+ }
155
+
156
+ Request request = requestBuilder.build();
113
157
  WritableMap output = Arguments.createMap();
114
158
 
115
159
  try {
@@ -122,7 +166,6 @@ public class Sslpinning {
122
166
  output.putInt("status", responseCode);
123
167
  output.putString("url", request.url().toString());
124
168
 
125
- // response time
126
169
  long tx = response.sentRequestAtMillis();
127
170
  long rx = response.receivedResponseAtMillis();
128
171
  output.putString("duration", (rx - tx) + "ms");
@@ -135,37 +178,48 @@ public class Sslpinning {
135
178
  output.putString("response", responseBodyString);
136
179
  callback.invoke(output, null);
137
180
  } catch (IOException e) {
138
- output.putString("error", responseBodyString);
139
-
181
+ output.putString("error", e.getMessage() != null ? e.getMessage() : responseBodyString);
140
182
  callback.invoke(null, output);
141
- if (e instanceof SocketTimeoutException) {
142
- System.err.print("Socket TimeOut");
143
- } else {
183
+ if (!(e instanceof SocketTimeoutException)) {
144
184
  e.printStackTrace();
145
185
  }
146
186
  }
147
- } catch (JSONException e) {
148
- callback.invoke(null, e);
187
+ } catch (Exception e) {
188
+ callback.invoke(null, e.getMessage() != null ? e.getMessage() : "Request failed");
189
+ }
190
+ }
191
+
192
+ private byte[] extractPayload(RequestBody requestBody) throws IOException {
193
+ if (requestBody == null) {
194
+ return new byte[0];
149
195
  }
196
+ Buffer buffer = new Buffer();
197
+ requestBody.writeTo(buffer);
198
+ return buffer.readByteArray();
150
199
  }
151
200
 
152
201
  private CertificatePinner getCertificatePinner(ReadableMap options) {
153
202
  CertificatePinner.Builder certificatePinner = new CertificatePinner.Builder();
154
-
155
203
  ReadableArray hashes = options.getArray("certificates");
204
+
156
205
  for (int i = 0; i < hashes.size(); i++) {
157
- certificatePinner.add(this.hostname, "sha256/" + hashes.getString(i).replaceAll("sha256/", ""));
206
+ String pin = CryptoUtils.normalizePinHash(hashes.getString(i));
207
+ if (pin.isEmpty()) {
208
+ throw new IllegalArgumentException("Invalid certificate/public key pin at index " + i);
209
+ }
210
+ // OkHttp CertificatePinner pins SPKI SHA-256 hashes (public key pinning).
211
+ certificatePinner.add(this.hostname, "sha256/" + pin);
158
212
  }
159
213
 
160
214
  return certificatePinner.build();
161
215
  }
162
216
 
163
- private OkHttpClient getClient(ReadableMap options) throws JSONException {
217
+ private OkHttpClient getClient(ReadableMap options, PinningConfig pinningConfig) {
164
218
  OkHttpClient.Builder builder = new OkHttpClient.Builder();
165
219
 
166
- if (options.hasKey("certificates")) {
167
- CertificatePinner certificatePinner = getCertificatePinner(options);
168
- builder.certificatePinner(certificatePinner);
220
+ // Fail-closed: once pinning is configured, OkHttp enforces pins with no system-trust fallback.
221
+ if (pinningConfig.isEnabled()) {
222
+ builder.certificatePinner(getCertificatePinner(options));
169
223
  }
170
224
 
171
225
  if (options.hasKey("timeout")) {
@@ -175,25 +229,34 @@ public class Sslpinning {
175
229
  .writeTimeout(timeout, TimeUnit.MILLISECONDS);
176
230
  }
177
231
 
178
- if (options.hasKey("loggerIsEnabled") && options.getBoolean("loggerIsEnabled")) {
179
- builder.addInterceptor(new ChuckerInterceptor.Builder(context).build());
232
+ // Hard-gate network logging to debug builds only.
233
+ if (options.hasKey("loggerIsEnabled")
234
+ && options.getBoolean("loggerIsEnabled")
235
+ && BuildConfig.DEBUG) {
236
+ ChuckerInterceptor.Builder chuckerBuilder = new ChuckerInterceptor.Builder(context);
237
+ for (String header : HeaderSanitizer.SENSITIVE_HEADERS) {
238
+ chuckerBuilder.redactHeader(header);
239
+ }
240
+ builder.addInterceptor(chuckerBuilder.build());
180
241
  }
181
242
 
182
243
  return builder.build();
183
244
  }
184
245
 
185
246
  private static String getHostname(String url) throws URISyntaxException {
186
- URI uri = new URI(url);
247
+ URI uri = new URI(url.trim());
187
248
  String domain = uri.getHost();
249
+ if (domain == null) {
250
+ throw new URISyntaxException(url, "Missing host");
251
+ }
188
252
  return domain.startsWith("www.") ? domain.substring(4) : domain;
189
253
  }
190
254
 
191
255
  private String getMethod(ReadableMap options) {
192
256
  String method = "GET";
193
- if (options.hasKey("method")) {
194
- method = options.getString("method");
257
+ if (options.hasKey("method") && options.getString("method") != null) {
258
+ method = options.getString("method").toUpperCase();
195
259
  }
196
-
197
260
  return method;
198
261
  }
199
262
 
@@ -204,38 +267,26 @@ public class Sslpinning {
204
267
 
205
268
  ReadableMap headers = options.getMap("headers");
206
269
  Headers.Builder builder = new Headers.Builder();
207
-
208
270
  Map<String, String> headersMap = readableMapToHashMap(headers);
209
271
  for (Map.Entry<String, String> set : headersMap.entrySet()) {
210
272
  builder.add(set.getKey(), set.getValue());
211
273
  }
212
-
213
274
  return builder.build();
214
275
  }
215
276
 
216
- private HashMap readableMapToHashMap(ReadableMap readableMap) {
277
+ private HashMap<String, String> readableMapToHashMap(ReadableMap readableMap) {
278
+ HashMap<String, String> map = new HashMap<>();
217
279
  if (readableMap == null) {
218
- return null;
280
+ return map;
219
281
  }
220
282
 
221
- HashMap map = new HashMap<String, String>();
222
283
  ReadableMapKeySetIterator keySetIterator = readableMap.keySetIterator();
223
284
  while (keySetIterator.hasNextKey()) {
224
285
  String key = keySetIterator.nextKey();
225
- ReadableType type = readableMap.getType(key);
226
- switch (type) {
227
- case String:
228
- map.put(key, readableMap.getString(key));
229
- break;
230
- case Map:
231
- HashMap<String, Object> attributes = this.readableMapToHashMap(readableMap.getMap(key));
232
- map.put(key, attributes);
233
- break;
234
- default:
235
- // do nothing
286
+ if (readableMap.getType(key) == ReadableType.String) {
287
+ map.put(key, readableMap.getString(key));
236
288
  }
237
289
  }
238
-
239
290
  return map;
240
291
  }
241
292
 
@@ -248,36 +299,31 @@ public class Sslpinning {
248
299
  }
249
300
 
250
301
  private RequestBody setBody(ReadableMap options) {
251
- RequestBody body = null;
252
-
253
302
  if (!options.hasKey("body")) {
254
- return body;
303
+ return null;
255
304
  }
256
305
 
257
306
  ReadableType bodyType = options.getType("body");
258
307
  switch (bodyType) {
259
308
  case String:
260
- body = RequestBody.create(mediaType, options.getString("body"));
261
- break;
309
+ return RequestBody.create(mediaType, options.getString("body"));
262
310
  case Map:
263
311
  ReadableMap bodyMap = options.getMap("body");
264
312
  if (bodyMap.hasKey("formData")) {
265
- ReadableMap formData = bodyMap.getMap("formData");
266
- body = getBody(formData);
313
+ return getBody(bodyMap.getMap("formData"));
267
314
  } else if (bodyMap.hasKey("_parts")) {
268
- body = getBody(bodyMap);
315
+ return getBody(bodyMap);
269
316
  } else {
270
- body = RequestBody.create(mediaType, bodyMap.toString());
317
+ return RequestBody.create(mediaType, bodyMap.toString());
271
318
  }
272
- break;
319
+ default:
320
+ return null;
273
321
  }
274
-
275
- return body;
276
322
  }
277
323
 
278
324
  private RequestBody getBody(ReadableMap body) {
279
- MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
280
- multipartBodyBuilder.setType((MediaType.parse("multipart/form-data")));
325
+ MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder()
326
+ .setType(MultipartBody.FORM);
281
327
  if (body.hasKey("_parts")) {
282
328
  ReadableArray parts = body.getArray("_parts");
283
329
  for (int i = 0; i < parts.size(); i++) {
@@ -293,53 +339,120 @@ public class Sslpinning {
293
339
  ReadableMap fileData = part.getMap(1);
294
340
  addFormDataPart(this.context, multipartBodyBuilder, fileData, key);
295
341
  } else {
296
- String value = part.getString(1);
297
- multipartBodyBuilder.addFormDataPart(key, value);
342
+ multipartBodyBuilder.addFormDataPart(key, part.getString(1));
298
343
  }
299
344
  }
300
345
  }
301
346
  return multipartBodyBuilder.build();
302
347
  }
303
348
 
304
- private static void addFormDataPart(Context context, MultipartBody.Builder multipartBodyBuilder, ReadableMap fileData,
305
- String key) {
306
- Uri _uri = Uri.parse("");
349
+ private static void addFormDataPart(
350
+ Context context,
351
+ MultipartBody.Builder multipartBodyBuilder,
352
+ ReadableMap fileData,
353
+ String key
354
+ ) {
355
+ Uri uri = Uri.parse("");
307
356
  if (fileData.hasKey("uri")) {
308
- _uri = Uri.parse(fileData.getString("uri"));
357
+ uri = Uri.parse(fileData.getString("uri"));
309
358
  } else if (fileData.hasKey("path")) {
310
- _uri = Uri.parse(fileData.getString("path"));
359
+ uri = Uri.parse(fileData.getString("path"));
311
360
  }
312
361
  String type = fileData.getString("type");
313
- String fileName = "";
314
- if (fileData.hasKey("fileName")) {
315
- fileName = fileData.getString("fileName");
316
- } else if (fileData.hasKey("name")) {
317
- fileName = fileData.getString("name");
318
- }
362
+ String fileName = fileData.hasKey("fileName")
363
+ ? fileData.getString("fileName")
364
+ : fileData.getString("name");
319
365
 
320
366
  try {
321
- File file = getTempFile(context, _uri);
367
+ File file = getTempFile(context, uri);
322
368
  multipartBodyBuilder.addFormDataPart(key, fileName, RequestBody.create(MediaType.parse(type), file));
323
369
  } catch (IOException e) {
324
- e.printStackTrace();
370
+ throw new IllegalStateException("Failed to read multipart file", e);
325
371
  }
326
372
  }
327
373
 
328
374
  public static File getTempFile(Context context, Uri uri) throws IOException {
329
375
  File file = File.createTempFile("media", null);
330
- InputStream inputStream = context.getContentResolver().openInputStream(uri);
331
- OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
332
- byte[] buffer = new byte[1024];
333
- int len;
334
- while ((len = inputStream.read(buffer)) != -1)
335
- outputStream.write(buffer, 0, len);
336
- inputStream.close();
337
- outputStream.close();
376
+ try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
377
+ OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file))) {
378
+ if (inputStream == null) {
379
+ throw new IOException("Unable to open URI: " + uri);
380
+ }
381
+ byte[] buffer = new byte[1024];
382
+ int len;
383
+ while ((len = inputStream.read(buffer)) != -1) {
384
+ outputStream.write(buffer, 0, len);
385
+ }
386
+ }
338
387
  return file;
339
388
  }
340
389
 
341
- private boolean isValidUrl(String url) {
342
- String regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
343
- return url.matches(regex);
390
+ private boolean isValidDomain(String hostname, ReadableMap options) {
391
+ ReadableArray domains = options.getArray("validDomains");
392
+ if (domains == null || domains.size() == 0) {
393
+ return false;
394
+ }
395
+
396
+ for (int i = 0; i < domains.size(); i++) {
397
+ String domain = domains.getString(i);
398
+ if (domain == null || domain.trim().isEmpty()) {
399
+ continue;
400
+ }
401
+ String normalized = domain.trim().toLowerCase();
402
+ String host = hostname.toLowerCase();
403
+ if (host.equals(normalized) || host.endsWith("." + normalized)) {
404
+ return true;
405
+ }
406
+ }
407
+ return false;
408
+ }
409
+
410
+ static final class PinningConfig {
411
+ private final boolean enabled;
412
+ private final String error;
413
+
414
+ private PinningConfig(boolean enabled, String error) {
415
+ this.enabled = enabled;
416
+ this.error = error;
417
+ }
418
+
419
+ static PinningConfig fromOptions(ReadableMap options) {
420
+ boolean hasCertificates = options.hasKey("certificates");
421
+ boolean hasValidDomains = options.hasKey("validDomains");
422
+
423
+ if (hasCertificates != hasValidDomains) {
424
+ return new PinningConfig(
425
+ false,
426
+ "SSL pinning requires both 'certificates' (SPKI SHA-256 hashes) and 'validDomains'"
427
+ );
428
+ }
429
+
430
+ if (!hasCertificates) {
431
+ return new PinningConfig(false, null);
432
+ }
433
+
434
+ ReadableArray certs = options.getArray("certificates");
435
+ ReadableArray domains = options.getArray("validDomains");
436
+ if (certs == null || certs.size() == 0) {
437
+ return new PinningConfig(false, "At least one certificate/public key pin is required");
438
+ }
439
+ if (domains == null || domains.size() == 0) {
440
+ return new PinningConfig(false, "At least one valid domain is required");
441
+ }
442
+
443
+ return new PinningConfig(true, null);
444
+ }
445
+
446
+ boolean isEnabled() {
447
+ return enabled;
448
+ }
449
+
450
+ boolean hasError() {
451
+ return error != null;
452
+ }
453
+
454
+ String getError() {
455
+ return error;
456
+ }
344
457
  }
345
458
  }
@@ -0,0 +1,133 @@
1
+ package com.securitysuite.security;
2
+
3
+ import android.content.Context;
4
+ import android.content.pm.ApplicationInfo;
5
+ import android.content.pm.PackageInfo;
6
+ import android.content.pm.PackageManager;
7
+ import android.content.pm.Signature;
8
+ import android.os.Build;
9
+
10
+ import com.facebook.react.bridge.Arguments;
11
+ import com.facebook.react.bridge.WritableMap;
12
+
13
+ import java.security.MessageDigest;
14
+ import java.util.Arrays;
15
+ import java.util.HashSet;
16
+ import java.util.Locale;
17
+ import java.util.Set;
18
+
19
+ /**
20
+ * Native app integrity checks (Android).
21
+ */
22
+ public final class AppIntegrityChecker {
23
+ private static final Set<String> TRUSTED_INSTALLERS = new HashSet<>(Arrays.asList(
24
+ "com.android.vending",
25
+ "com.google.android.packageinstaller",
26
+ "com.android.packageinstaller"
27
+ ));
28
+
29
+ private AppIntegrityChecker() {}
30
+
31
+ public static WritableMap verify(Context context) {
32
+ WritableMap result = Arguments.createMap();
33
+ PackageManager pm = context.getPackageManager();
34
+ String packageName = context.getPackageName();
35
+
36
+ boolean debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
37
+ String buildType = debuggable ? "debug" : "release";
38
+ String signingSha256 = getSigningCertificateSha256(pm, packageName);
39
+ boolean validSignature = signingSha256 != null && !signingSha256.isEmpty();
40
+
41
+ String installer = getInstallerPackageName(pm, packageName);
42
+ boolean installerTrusted = installer == null
43
+ ? debuggable
44
+ : TRUSTED_INSTALLERS.contains(installer);
45
+
46
+ boolean tampered = !validSignature
47
+ || (!debuggable && installer != null && !installerTrusted)
48
+ || hasSuspiciousSplitSource(context.getApplicationInfo());
49
+
50
+ result.putBoolean("validSignature", validSignature);
51
+ result.putBoolean("debuggable", debuggable);
52
+ result.putBoolean("tampered", tampered);
53
+ result.putBoolean("installerTrusted", installerTrusted);
54
+ result.putString("buildType", buildType);
55
+ if (signingSha256 != null) {
56
+ result.putString("signingCertificateSha256", signingSha256);
57
+ }
58
+ if (installer != null) {
59
+ result.putString("installerPackage", installer);
60
+ } else {
61
+ result.putNull("installerPackage");
62
+ }
63
+ result.putString("bundleIdentifier", packageName);
64
+ return result;
65
+ }
66
+
67
+ private static String getSigningCertificateSha256(PackageManager pm, String packageName) {
68
+ try {
69
+ Signature[] signatures;
70
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
71
+ PackageInfo info = pm.getPackageInfo(
72
+ packageName,
73
+ PackageManager.GET_SIGNING_CERTIFICATES
74
+ );
75
+ if (info.signingInfo == null) {
76
+ return null;
77
+ }
78
+ signatures = info.signingInfo.hasMultipleSigners()
79
+ ? info.signingInfo.getApkContentsSigners()
80
+ : info.signingInfo.getSigningCertificateHistory();
81
+ } else {
82
+ @SuppressWarnings("deprecation")
83
+ PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
84
+ @SuppressWarnings("deprecation")
85
+ Signature[] legacySignatures = info.signatures;
86
+ signatures = legacySignatures;
87
+ }
88
+
89
+ if (signatures == null || signatures.length == 0) {
90
+ return null;
91
+ }
92
+
93
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
94
+ byte[] hash = digest.digest(signatures[0].toByteArray());
95
+ return bytesToHex(hash).toLowerCase(Locale.US);
96
+ } catch (Exception ignored) {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ private static String getInstallerPackageName(PackageManager pm, String packageName) {
102
+ try {
103
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
104
+ return pm.getInstallSourceInfo(packageName).getInstallingPackageName();
105
+ }
106
+ @SuppressWarnings("deprecation")
107
+ String installer = pm.getInstallerPackageName(packageName);
108
+ return installer;
109
+ } catch (Exception ignored) {
110
+ return null;
111
+ }
112
+ }
113
+
114
+ private static boolean hasSuspiciousSplitSource(ApplicationInfo info) {
115
+ if (info.splitSourceDirs == null) {
116
+ return false;
117
+ }
118
+ for (String split : info.splitSourceDirs) {
119
+ if (split != null && split.toLowerCase(Locale.US).contains("/data/local/tmp")) {
120
+ return true;
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+
126
+ private static String bytesToHex(byte[] bytes) {
127
+ StringBuilder builder = new StringBuilder(bytes.length * 2);
128
+ for (byte value : bytes) {
129
+ builder.append(String.format(Locale.US, "%02x", value));
130
+ }
131
+ return builder.toString();
132
+ }
133
+ }