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.
- package/README.md +233 -65
- package/android/build.gradle +11 -0
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/securitysuite/CryptoConfig.java +158 -0
- package/android/src/main/java/com/securitysuite/CryptoUtils.java +152 -0
- package/android/src/main/java/com/securitysuite/EcdhKeyStore.java +60 -0
- package/android/src/main/java/com/securitysuite/HeaderSanitizer.java +75 -0
- package/android/src/main/java/com/securitysuite/JWSGenerator.java +237 -32
- package/android/src/main/java/com/securitysuite/JwsFetchPayload.java +81 -0
- package/android/src/main/java/com/securitysuite/Obfuscation.java +57 -0
- package/android/src/main/java/com/securitysuite/SecureStorageNative.java +211 -0
- package/android/src/main/java/com/securitysuite/SecureView.java +2 -10
- package/android/src/main/java/com/securitysuite/SecureWindowHelper.java +30 -0
- package/android/src/main/java/com/securitysuite/SecuritySuiteModule.java +310 -102
- package/android/src/main/java/com/securitysuite/Sslpinning.java +219 -106
- package/android/src/main/java/com/securitysuite/security/AppIntegrityChecker.java +133 -0
- package/android/src/main/java/com/securitysuite/security/EmulatorDetector.java +145 -0
- package/android/src/main/java/com/securitysuite/security/RuntimeDetector.java +234 -0
- package/android/src/test/java/com/securitysuite/JWSGeneratorTest.java +153 -0
- package/android/src/test/java/com/securitysuite/SecureStorageNativeTest.java +37 -0
- package/ios/CryptoConfig.swift +124 -0
- package/ios/JWSGenerator.swift +288 -0
- package/ios/JWSGeneratorTests.swift +168 -0
- package/ios/KeychainHelper.swift +104 -0
- package/ios/Obfuscation.swift +42 -0
- package/ios/SecureStorageNative.swift +84 -0
- package/ios/Security/AppIntegrityChecker.swift +85 -0
- package/ios/Security/EmulatorDetector.swift +45 -0
- package/ios/Security/RuntimeDetector.swift +107 -0
- package/ios/SecuritySuite.mm +28 -4
- package/ios/SecuritySuite.swift +407 -131
- package/ios/SslPinning.swift +242 -263
- package/lib/commonjs/clipboard/index.js +3 -0
- package/lib/commonjs/clipboard/index.js.map +1 -0
- package/lib/commonjs/crypto/index.js +39 -0
- package/lib/commonjs/crypto/index.js.map +1 -0
- package/lib/commonjs/device/index.js +40 -0
- package/lib/commonjs/device/index.js.map +1 -0
- package/lib/commonjs/errors.js +62 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/index.js +220 -151
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/integrity/index.js +40 -0
- package/lib/commonjs/integrity/index.js.map +1 -0
- package/lib/commonjs/jws.js +141 -0
- package/lib/commonjs/jws.js.map +1 -0
- package/lib/commonjs/legacy/cryptoOptions.js +20 -0
- package/lib/commonjs/legacy/cryptoOptions.js.map +1 -0
- package/lib/commonjs/native/bridge.js +23 -0
- package/lib/commonjs/native/bridge.js.map +1 -0
- package/lib/commonjs/network/index.js +3 -0
- package/lib/commonjs/network/index.js.map +1 -0
- package/lib/commonjs/risk/score.js +36 -0
- package/lib/commonjs/risk/score.js.map +1 -0
- package/lib/commonjs/runtime/index.js +31 -0
- package/lib/commonjs/runtime/index.js.map +1 -0
- package/lib/commonjs/screen/index.js +13 -0
- package/lib/commonjs/screen/index.js.map +1 -0
- package/lib/commonjs/securitySuite/index.js +42 -0
- package/lib/commonjs/securitySuite/index.js.map +1 -0
- package/lib/commonjs/storage/index.js +3 -0
- package/lib/commonjs/storage/index.js.map +1 -0
- package/lib/commonjs/types/detection.js +2 -0
- package/lib/commonjs/types/detection.js.map +1 -0
- package/lib/module/clipboard/index.js +3 -0
- package/lib/module/clipboard/index.js.map +1 -0
- package/lib/module/crypto/index.js +35 -0
- package/lib/module/crypto/index.js.map +1 -0
- package/lib/module/device/index.js +36 -0
- package/lib/module/device/index.js.map +1 -0
- package/lib/module/errors.js +55 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +147 -148
- package/lib/module/index.js.map +1 -1
- package/lib/module/integrity/index.js +36 -0
- package/lib/module/integrity/index.js.map +1 -0
- package/lib/module/jws.js +127 -0
- package/lib/module/jws.js.map +1 -0
- package/lib/module/legacy/cryptoOptions.js +16 -0
- package/lib/module/legacy/cryptoOptions.js.map +1 -0
- package/lib/module/native/bridge.js +19 -0
- package/lib/module/native/bridge.js.map +1 -0
- package/lib/module/network/index.js +3 -0
- package/lib/module/network/index.js.map +1 -0
- package/lib/module/risk/score.js +32 -0
- package/lib/module/risk/score.js.map +1 -0
- package/lib/module/runtime/index.js +27 -0
- package/lib/module/runtime/index.js.map +1 -0
- package/lib/module/screen/index.js +5 -0
- package/lib/module/screen/index.js.map +1 -0
- package/lib/module/securitySuite/index.js +38 -0
- package/lib/module/securitySuite/index.js.map +1 -0
- package/lib/module/storage/index.js +3 -0
- package/lib/module/storage/index.js.map +1 -0
- package/lib/module/types/detection.js +2 -0
- package/lib/module/types/detection.js.map +1 -0
- package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts +215 -0
- package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SecureView.d.ts +1 -1
- package/lib/typescript/commonjs/src/SecureView.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/clipboard/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/clipboard/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/crypto/index.d.ts +15 -0
- package/lib/typescript/commonjs/src/crypto/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/device/index.d.ts +11 -0
- package/lib/typescript/commonjs/src/device/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/errors.d.ts +17 -0
- package/lib/typescript/commonjs/src/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +77 -24
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/integrity/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/integrity/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/jws.d.ts +44 -0
- package/lib/typescript/commonjs/src/jws.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts +35 -0
- package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/native/bridge.d.ts +12 -0
- package/lib/typescript/commonjs/src/native/bridge.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/network/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/network/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/risk/score.d.ts +12 -0
- package/lib/typescript/commonjs/src/risk/score.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/runtime/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/runtime/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/screen/index.d.ts +3 -0
- package/lib/typescript/commonjs/src/screen/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/securitySuite/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/securitySuite/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/storage/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/types/detection.d.ts +41 -0
- package/lib/typescript/commonjs/src/types/detection.d.ts.map +1 -0
- package/lib/typescript/module/docs/api-v1-proposal.d.ts +215 -0
- package/lib/typescript/module/docs/api-v1-proposal.d.ts.map +1 -0
- package/lib/typescript/module/src/SecureView.d.ts +1 -1
- package/lib/typescript/module/src/SecureView.d.ts.map +1 -1
- package/lib/typescript/module/src/clipboard/index.d.ts +2 -0
- package/lib/typescript/module/src/clipboard/index.d.ts.map +1 -0
- package/lib/typescript/module/src/crypto/index.d.ts +15 -0
- package/lib/typescript/module/src/crypto/index.d.ts.map +1 -0
- package/lib/typescript/module/src/device/index.d.ts +11 -0
- package/lib/typescript/module/src/device/index.d.ts.map +1 -0
- package/lib/typescript/module/src/errors.d.ts +17 -0
- package/lib/typescript/module/src/errors.d.ts.map +1 -0
- package/lib/typescript/module/src/helpers.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +77 -24
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/integrity/index.d.ts +6 -0
- package/lib/typescript/module/src/integrity/index.d.ts.map +1 -0
- package/lib/typescript/module/src/jws.d.ts +44 -0
- package/lib/typescript/module/src/jws.d.ts.map +1 -0
- package/lib/typescript/module/src/legacy/cryptoOptions.d.ts +35 -0
- package/lib/typescript/module/src/legacy/cryptoOptions.d.ts.map +1 -0
- package/lib/typescript/module/src/native/bridge.d.ts +12 -0
- package/lib/typescript/module/src/native/bridge.d.ts.map +1 -0
- package/lib/typescript/module/src/network/index.d.ts +2 -0
- package/lib/typescript/module/src/network/index.d.ts.map +1 -0
- package/lib/typescript/module/src/risk/score.d.ts +12 -0
- package/lib/typescript/module/src/risk/score.d.ts.map +1 -0
- package/lib/typescript/module/src/runtime/index.d.ts +6 -0
- package/lib/typescript/module/src/runtime/index.d.ts.map +1 -0
- package/lib/typescript/module/src/screen/index.d.ts +3 -0
- package/lib/typescript/module/src/screen/index.d.ts.map +1 -0
- package/lib/typescript/module/src/securitySuite/index.d.ts +6 -0
- package/lib/typescript/module/src/securitySuite/index.d.ts.map +1 -0
- package/lib/typescript/module/src/storage/index.d.ts +2 -0
- package/lib/typescript/module/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/module/src/types/detection.d.ts +41 -0
- package/lib/typescript/module/src/types/detection.d.ts.map +1 -0
- package/package.json +2 -4
- package/src/clipboard/index.ts +1 -0
- package/src/crypto/index.ts +49 -0
- package/src/device/index.ts +47 -0
- package/src/errors.ts +84 -0
- package/src/index.tsx +293 -195
- package/src/integrity/index.ts +46 -0
- package/src/jws.ts +213 -0
- package/src/legacy/cryptoOptions.ts +49 -0
- package/src/native/bridge.ts +37 -0
- package/src/network/index.ts +1 -0
- package/src/risk/score.ts +49 -0
- package/src/runtime/index.ts +43 -0
- package/src/screen/index.ts +2 -0
- package/src/securitySuite/index.ts +45 -0
- package/src/storage/index.ts +1 -0
- package/src/types/detection.ts +46 -0
- package/android/src/main/java/com/securitysuite/StorageEncryption.java +0 -52
- package/ios/StorageEncryption.swift +0 -89
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
package com.securitysuite.security;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.hardware.Sensor;
|
|
5
|
+
import android.hardware.SensorManager;
|
|
6
|
+
import android.os.Build;
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.bridge.Arguments;
|
|
9
|
+
import com.facebook.react.bridge.WritableArray;
|
|
10
|
+
import com.facebook.react.bridge.WritableMap;
|
|
11
|
+
import com.scottyab.rootbeer.RootBeer;
|
|
12
|
+
|
|
13
|
+
import java.util.ArrayList;
|
|
14
|
+
import java.util.List;
|
|
15
|
+
import java.util.Locale;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Emulator and device environment detection (Android).
|
|
19
|
+
*/
|
|
20
|
+
public final class EmulatorDetector {
|
|
21
|
+
private EmulatorDetector() {}
|
|
22
|
+
|
|
23
|
+
public static WritableMap detect(Context context) {
|
|
24
|
+
WritableMap result = Arguments.createMap();
|
|
25
|
+
List<String> indicators = new ArrayList<>();
|
|
26
|
+
|
|
27
|
+
collectBuildIndicators(indicators);
|
|
28
|
+
collectQemuIndicators(indicators);
|
|
29
|
+
collectSensorIndicators(context, indicators);
|
|
30
|
+
|
|
31
|
+
RootBeer rootBeer = new RootBeer(context);
|
|
32
|
+
if (rootBeer.isEmulator()) {
|
|
33
|
+
indicators.add("RootBeer.isEmulator");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
result.putBoolean("isEmulator", !indicators.isEmpty());
|
|
37
|
+
result.putBoolean("isSimulator", false);
|
|
38
|
+
result.putArray("indicators", toStringArray(indicators));
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private static void collectBuildIndicators(List<String> indicators) {
|
|
43
|
+
String fingerprint = safeLower(Build.FINGERPRINT);
|
|
44
|
+
String model = safeLower(Build.MODEL);
|
|
45
|
+
String manufacturer = safeLower(Build.MANUFACTURER);
|
|
46
|
+
String hardware = safeLower(Build.HARDWARE);
|
|
47
|
+
String product = safeLower(Build.PRODUCT);
|
|
48
|
+
String brand = safeLower(Build.BRAND);
|
|
49
|
+
String device = safeLower(Build.DEVICE);
|
|
50
|
+
|
|
51
|
+
if (containsAny(fingerprint, "generic", "unknown", "test-keys", "emulator")) {
|
|
52
|
+
indicators.add("Build.FINGERPRINT");
|
|
53
|
+
}
|
|
54
|
+
if (containsAny(model, "google_sdk", "emulator", "android sdk built for x86", "sdk_gphone")) {
|
|
55
|
+
indicators.add("Build.MODEL");
|
|
56
|
+
}
|
|
57
|
+
if (containsAny(manufacturer, "genymotion", "unknown")) {
|
|
58
|
+
indicators.add("Build.MANUFACTURER");
|
|
59
|
+
}
|
|
60
|
+
if (containsAny(hardware, "goldfish", "ranchu", "qemu")) {
|
|
61
|
+
indicators.add("Build.HARDWARE");
|
|
62
|
+
}
|
|
63
|
+
if (containsAny(product, "sdk", "google_sdk", "sdk_gphone", "vbox")) {
|
|
64
|
+
indicators.add("Build.PRODUCT");
|
|
65
|
+
}
|
|
66
|
+
if (containsAny(brand, "generic")) {
|
|
67
|
+
indicators.add("Build.BRAND");
|
|
68
|
+
}
|
|
69
|
+
if (containsAny(device, "generic", "emu64a", "goldfish")) {
|
|
70
|
+
indicators.add("Build.DEVICE");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private static void collectQemuIndicators(List<String> indicators) {
|
|
75
|
+
String qemu = getSystemProperty("ro.kernel.qemu");
|
|
76
|
+
if ("1".equals(qemu)) {
|
|
77
|
+
indicators.add("ro.kernel.qemu");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
String[] props = {
|
|
81
|
+
"ro.hardware",
|
|
82
|
+
"ro.product.device",
|
|
83
|
+
"ro.product.model",
|
|
84
|
+
"ro.product.name"
|
|
85
|
+
};
|
|
86
|
+
for (String prop : props) {
|
|
87
|
+
String value = safeLower(getSystemProperty(prop));
|
|
88
|
+
if (containsAny(value, "goldfish", "ranchu", "qemu", "sdk_gphone", "emulator")) {
|
|
89
|
+
indicators.add(prop);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private static void collectSensorIndicators(Context context, List<String> indicators) {
|
|
95
|
+
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
|
96
|
+
if (sensorManager == null) {
|
|
97
|
+
indicators.add("SensorManager unavailable");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
|
102
|
+
Sensor gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
|
|
103
|
+
if (accelerometer == null) {
|
|
104
|
+
indicators.add("Missing accelerometer");
|
|
105
|
+
}
|
|
106
|
+
if (gyroscope == null) {
|
|
107
|
+
indicators.add("Missing gyroscope");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private static String getSystemProperty(String key) {
|
|
112
|
+
try {
|
|
113
|
+
Class<?> systemProperties = Class.forName("android.os.SystemProperties");
|
|
114
|
+
return (String) systemProperties
|
|
115
|
+
.getMethod("get", String.class)
|
|
116
|
+
.invoke(null, key);
|
|
117
|
+
} catch (Exception ignored) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private static boolean containsAny(String value, String... needles) {
|
|
123
|
+
if (value == null || value.isEmpty()) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
for (String needle : needles) {
|
|
127
|
+
if (value.contains(needle)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private static String safeLower(String value) {
|
|
135
|
+
return value == null ? "" : value.toLowerCase(Locale.US);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static WritableArray toStringArray(List<String> values) {
|
|
139
|
+
WritableArray array = Arguments.createArray();
|
|
140
|
+
for (String value : values) {
|
|
141
|
+
array.pushString(value);
|
|
142
|
+
}
|
|
143
|
+
return array;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
package com.securitysuite.security;
|
|
2
|
+
|
|
3
|
+
import java.io.BufferedReader;
|
|
4
|
+
import java.io.File;
|
|
5
|
+
import java.io.FileReader;
|
|
6
|
+
import java.io.IOException;
|
|
7
|
+
import java.net.InetSocketAddress;
|
|
8
|
+
import java.net.Socket;
|
|
9
|
+
import java.util.ArrayList;
|
|
10
|
+
import java.util.Arrays;
|
|
11
|
+
import java.util.HashSet;
|
|
12
|
+
import java.util.List;
|
|
13
|
+
import java.util.Locale;
|
|
14
|
+
import java.util.Set;
|
|
15
|
+
|
|
16
|
+
import android.os.Debug;
|
|
17
|
+
|
|
18
|
+
import com.facebook.react.bridge.Arguments;
|
|
19
|
+
import com.facebook.react.bridge.WritableArray;
|
|
20
|
+
import com.facebook.react.bridge.WritableMap;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Native runtime instrumentation and debugger detection (Android).
|
|
24
|
+
*/
|
|
25
|
+
public final class RuntimeDetector {
|
|
26
|
+
private static final int[] FRIDA_PORTS = {27042, 27043};
|
|
27
|
+
private static final Set<String> SUSPICIOUS_MAP_KEYWORDS = new HashSet<>(Arrays.asList(
|
|
28
|
+
"frida", "frida-agent", "frida-gadget", "linjector", "xposed", "lsposed",
|
|
29
|
+
"substrate", "magisk", "zygisk", "libhooker"
|
|
30
|
+
));
|
|
31
|
+
private static final Set<String> SUSPICIOUS_THREAD_NAMES = new HashSet<>(Arrays.asList(
|
|
32
|
+
"gmain", "gdbus", "pool-frida", "frida-agent", "frida-server"
|
|
33
|
+
));
|
|
34
|
+
|
|
35
|
+
private RuntimeDetector() {}
|
|
36
|
+
|
|
37
|
+
public static WritableMap detect() {
|
|
38
|
+
WritableMap result = Arguments.createMap();
|
|
39
|
+
List<String> suspiciousLibraries = scanProcMaps();
|
|
40
|
+
List<Integer> suspiciousPorts = scanFridaPorts();
|
|
41
|
+
boolean fridaDetected = !suspiciousLibraries.isEmpty()
|
|
42
|
+
|| !suspiciousPorts.isEmpty()
|
|
43
|
+
|| hasSuspiciousThreads();
|
|
44
|
+
boolean debuggerAttached = Debug.isDebuggerConnected() || Debug.waitingForDebugger() || hasTracerPid();
|
|
45
|
+
boolean xposedDetected = detectXposed();
|
|
46
|
+
boolean magiskDetected = detectMagisk();
|
|
47
|
+
|
|
48
|
+
result.putBoolean("debuggerAttached", debuggerAttached);
|
|
49
|
+
result.putBoolean("fridaDetected", fridaDetected);
|
|
50
|
+
result.putBoolean("xposedDetected", xposedDetected);
|
|
51
|
+
result.putBoolean("magiskDetected", magiskDetected);
|
|
52
|
+
result.putArray("suspiciousLibraries", toStringArray(suspiciousLibraries));
|
|
53
|
+
result.putArray("suspiciousPorts", toIntArray(suspiciousPorts));
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private static List<String> scanProcMaps() {
|
|
58
|
+
List<String> matches = new ArrayList<>();
|
|
59
|
+
File maps = new File("/proc/self/maps");
|
|
60
|
+
if (!maps.canRead()) {
|
|
61
|
+
return matches;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try (BufferedReader reader = new BufferedReader(new FileReader(maps))) {
|
|
65
|
+
String line;
|
|
66
|
+
while ((line = reader.readLine()) != null) {
|
|
67
|
+
String lower = line.toLowerCase(Locale.US);
|
|
68
|
+
for (String keyword : SUSPICIOUS_MAP_KEYWORDS) {
|
|
69
|
+
if (lower.contains(keyword)) {
|
|
70
|
+
matches.add(keyword);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch (IOException ignored) {
|
|
76
|
+
// Best-effort detection.
|
|
77
|
+
}
|
|
78
|
+
return matches;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private static List<Integer> scanFridaPorts() {
|
|
82
|
+
List<Integer> openPorts = new ArrayList<>();
|
|
83
|
+
for (int port : FRIDA_PORTS) {
|
|
84
|
+
if (isLocalPortOpen(port)) {
|
|
85
|
+
openPorts.add(port);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return openPorts;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private static boolean isLocalPortOpen(int port) {
|
|
92
|
+
Socket socket = new Socket();
|
|
93
|
+
try {
|
|
94
|
+
socket.connect(new InetSocketAddress("127.0.0.1", port), 300);
|
|
95
|
+
return true;
|
|
96
|
+
} catch (IOException ignored) {
|
|
97
|
+
return false;
|
|
98
|
+
} finally {
|
|
99
|
+
try {
|
|
100
|
+
socket.close();
|
|
101
|
+
} catch (IOException ignored) {
|
|
102
|
+
// Ignore close failures.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private static boolean hasSuspiciousThreads() {
|
|
108
|
+
File taskDir = new File("/proc/self/task");
|
|
109
|
+
File[] tasks = taskDir.listFiles();
|
|
110
|
+
if (tasks == null) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (File task : tasks) {
|
|
115
|
+
File comm = new File(task, "comm");
|
|
116
|
+
if (!comm.canRead()) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
try (BufferedReader reader = new BufferedReader(new FileReader(comm))) {
|
|
120
|
+
String name = reader.readLine();
|
|
121
|
+
if (name == null) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
String normalized = name.trim().toLowerCase(Locale.US);
|
|
125
|
+
if (SUSPICIOUS_THREAD_NAMES.contains(normalized)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
} catch (IOException ignored) {
|
|
129
|
+
// Continue scanning other tasks.
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private static boolean hasTracerPid() {
|
|
136
|
+
File status = new File("/proc/self/status");
|
|
137
|
+
if (!status.canRead()) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try (BufferedReader reader = new BufferedReader(new FileReader(status))) {
|
|
142
|
+
String line;
|
|
143
|
+
while ((line = reader.readLine()) != null) {
|
|
144
|
+
if (line.startsWith("TracerPid:")) {
|
|
145
|
+
String pid = line.substring("TracerPid:".length()).trim();
|
|
146
|
+
return !"0".equals(pid);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (IOException ignored) {
|
|
150
|
+
// Best-effort detection.
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private static boolean detectXposed() {
|
|
156
|
+
String[] paths = {
|
|
157
|
+
"/system/framework/XposedBridge.jar",
|
|
158
|
+
"/system/lib/libxposed_art.so",
|
|
159
|
+
"/system/lib64/libxposed_art.so",
|
|
160
|
+
"/data/adb/lspd",
|
|
161
|
+
"/data/adb/modules/zygisk_lsposed"
|
|
162
|
+
};
|
|
163
|
+
for (String path : paths) {
|
|
164
|
+
if (new File(path).exists()) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
throw new Exception("xposed_probe");
|
|
171
|
+
} catch (Exception exception) {
|
|
172
|
+
for (StackTraceElement element : exception.getStackTrace()) {
|
|
173
|
+
String className = element.getClassName().toLowerCase(Locale.US);
|
|
174
|
+
if (className.contains("de.robv.android.xposed")
|
|
175
|
+
|| className.contains("org.lsposed")
|
|
176
|
+
|| className.contains("io.github.lsposed")) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private static boolean detectMagisk() {
|
|
185
|
+
String[] paths = {
|
|
186
|
+
"/sbin/.magisk",
|
|
187
|
+
"/sbin/.core",
|
|
188
|
+
"/data/adb/magisk",
|
|
189
|
+
"/data/adb/modules",
|
|
190
|
+
"/cache/.disable_magisk"
|
|
191
|
+
};
|
|
192
|
+
for (String path : paths) {
|
|
193
|
+
if (new File(path).exists()) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
String[] props = {"ro.boot.vbmeta.device_state", "ro.boot.verifiedbootstate"};
|
|
199
|
+
for (String prop : props) {
|
|
200
|
+
String value = getSystemProperty(prop);
|
|
201
|
+
if (value != null && value.toLowerCase(Locale.US).contains("orange")) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private static String getSystemProperty(String key) {
|
|
209
|
+
try {
|
|
210
|
+
Class<?> systemProperties = Class.forName("android.os.SystemProperties");
|
|
211
|
+
return (String) systemProperties
|
|
212
|
+
.getMethod("get", String.class)
|
|
213
|
+
.invoke(null, key);
|
|
214
|
+
} catch (Exception ignored) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private static WritableArray toStringArray(List<String> values) {
|
|
220
|
+
WritableArray array = Arguments.createArray();
|
|
221
|
+
for (String value : values) {
|
|
222
|
+
array.pushString(value);
|
|
223
|
+
}
|
|
224
|
+
return array;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private static WritableArray toIntArray(List<Integer> values) {
|
|
228
|
+
WritableArray array = Arguments.createArray();
|
|
229
|
+
for (Integer value : values) {
|
|
230
|
+
array.pushInt(value);
|
|
231
|
+
}
|
|
232
|
+
return array;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
package com.securitysuite;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments;
|
|
4
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
5
|
+
import com.facebook.react.bridge.WritableMap;
|
|
6
|
+
|
|
7
|
+
import org.junit.Test;
|
|
8
|
+
|
|
9
|
+
import javax.crypto.Mac;
|
|
10
|
+
import javax.crypto.spec.SecretKeySpec;
|
|
11
|
+
|
|
12
|
+
import java.nio.charset.StandardCharsets;
|
|
13
|
+
import java.util.Base64;
|
|
14
|
+
|
|
15
|
+
import static org.junit.Assert.assertEquals;
|
|
16
|
+
import static org.junit.Assert.assertFalse;
|
|
17
|
+
import static org.junit.Assert.assertTrue;
|
|
18
|
+
|
|
19
|
+
public class JWSGeneratorTest {
|
|
20
|
+
private static final String SECRET = "secret";
|
|
21
|
+
|
|
22
|
+
@Test
|
|
23
|
+
public void omittedPayloadProducesThreeSegmentsWithEmptyMiddle() throws Exception {
|
|
24
|
+
assertEmptyPayloadCase(null);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Test
|
|
28
|
+
public void nullPayloadProducesThreeSegmentsWithEmptyMiddle() throws Exception {
|
|
29
|
+
assertEmptyPayloadCase("");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
public void emptyStringPayloadProducesThreeSegmentsWithEmptyMiddle() throws Exception {
|
|
34
|
+
assertEmptyPayloadCase("");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Test
|
|
38
|
+
public void stringNullPayloadIsNotEmpty() throws Exception {
|
|
39
|
+
JWSGenerator generator = new JWSGenerator();
|
|
40
|
+
String jws = generator.generate("null", SECRET, "HS256", headersWithKid(), false);
|
|
41
|
+
String[] segments = jws.split("\\.", -1);
|
|
42
|
+
|
|
43
|
+
assertEquals(3, segments.length);
|
|
44
|
+
assertEquals(base64Url("null"), segments[1]);
|
|
45
|
+
assertFalse(segments[1].isEmpty());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Test
|
|
49
|
+
public void stringUndefinedPayloadIsNotEmpty() throws Exception {
|
|
50
|
+
JWSGenerator generator = new JWSGenerator();
|
|
51
|
+
String jws = generator.generate("undefined", SECRET, "HS256", headersWithKid(), false);
|
|
52
|
+
String[] segments = jws.split("\\.", -1);
|
|
53
|
+
|
|
54
|
+
assertEquals(3, segments.length);
|
|
55
|
+
assertEquals(base64Url("undefined"), segments[1]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Test
|
|
59
|
+
public void objectPayloadIsBase64UrlEncodedJson() throws Exception {
|
|
60
|
+
JWSGenerator generator = new JWSGenerator();
|
|
61
|
+
String payload = "{\"amount\":1000}";
|
|
62
|
+
String jws = generator.generate(payload, SECRET, "HS256", headersWithKid(), false);
|
|
63
|
+
String[] segments = jws.split("\\.", -1);
|
|
64
|
+
|
|
65
|
+
assertEquals(3, segments.length);
|
|
66
|
+
assertEquals(base64Url(payload), segments[1]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Test(expected = IllegalArgumentException.class)
|
|
70
|
+
public void algorithmMismatchThrows() throws Exception {
|
|
71
|
+
WritableMap headers = Arguments.createMap();
|
|
72
|
+
headers.putString("alg", "HS256");
|
|
73
|
+
headers.putString("kid", "test-key");
|
|
74
|
+
|
|
75
|
+
JWSGenerator generator = new JWSGenerator();
|
|
76
|
+
generator.generate("", SECRET, "HS512", headers, false);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Test
|
|
80
|
+
public void algorithmFromHeaderIsUsed() throws Exception {
|
|
81
|
+
WritableMap headers = Arguments.createMap();
|
|
82
|
+
headers.putString("alg", "HS384");
|
|
83
|
+
headers.putString("kid", "test-key");
|
|
84
|
+
|
|
85
|
+
JWSGenerator generator = new JWSGenerator();
|
|
86
|
+
String jws = generator.generate("", SECRET, null, headers, false);
|
|
87
|
+
String protectedHeaderJson = new String(
|
|
88
|
+
Base64.getUrlDecoder().decode(jws.split("\\.", -1)[0]),
|
|
89
|
+
StandardCharsets.UTF_8
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
assertTrue(protectedHeaderJson.contains("\"alg\":\"HS384\""));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test
|
|
96
|
+
public void defaultAlgorithmIsHs256() throws Exception {
|
|
97
|
+
JWSGenerator generator = new JWSGenerator();
|
|
98
|
+
String jws = generator.generate("", SECRET, null, headersWithKid(), false);
|
|
99
|
+
String protectedHeaderJson = new String(
|
|
100
|
+
Base64.getUrlDecoder().decode(jws.split("\\.", -1)[0]),
|
|
101
|
+
StandardCharsets.UTF_8
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
assertTrue(protectedHeaderJson.contains("\"alg\":\"HS256\""));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Test
|
|
108
|
+
public void detachedOutputUsesDoubleDot() throws Exception {
|
|
109
|
+
JWSGenerator generator = new JWSGenerator();
|
|
110
|
+
String jws = generator.generate("payload", SECRET, "HS256", headersWithKid(), true);
|
|
111
|
+
|
|
112
|
+
assertTrue(jws.contains(".."));
|
|
113
|
+
assertEquals(2, jws.split("\\.\\.", -1).length);
|
|
114
|
+
assertTrue(generator.verify(jws, "payload", SECRET, "HS256", true));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private void assertEmptyPayloadCase(String payload) throws Exception {
|
|
118
|
+
JWSGenerator generator = new JWSGenerator();
|
|
119
|
+
String jws = generator.generate(payload, SECRET, "HS256", headersWithKid(), false);
|
|
120
|
+
String[] segments = jws.split("\\.", -1);
|
|
121
|
+
|
|
122
|
+
assertEquals(3, segments.length);
|
|
123
|
+
assertEquals("", segments[1]);
|
|
124
|
+
assertTrue(jws.contains(".."));
|
|
125
|
+
assertTrue(generator.verify(jws, payload == null ? "" : payload, SECRET, "HS256", false));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private ReadableMap headersWithKid() {
|
|
129
|
+
WritableMap headers = Arguments.createMap();
|
|
130
|
+
headers.putString("kid", "test-key");
|
|
131
|
+
return headers;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private String base64Url(String value) {
|
|
135
|
+
return Base64.getUrlEncoder().withoutPadding()
|
|
136
|
+
.encodeToString(value.getBytes(StandardCharsets.UTF_8));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private String signReference(String protectedHeader, String payloadString, String algorithm)
|
|
140
|
+
throws Exception {
|
|
141
|
+
String encodedPayload = payloadString == null || payloadString.isEmpty()
|
|
142
|
+
? ""
|
|
143
|
+
: base64Url(payloadString);
|
|
144
|
+
String signingInput = protectedHeader + "." + encodedPayload;
|
|
145
|
+
String macAlgorithm = CryptoUtils.hmacAlgorithmForJws(algorithm);
|
|
146
|
+
Mac mac = Mac.getInstance(macAlgorithm);
|
|
147
|
+
mac.init(new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), macAlgorithm));
|
|
148
|
+
String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(mac.doFinal(
|
|
149
|
+
signingInput.getBytes(StandardCharsets.UTF_8)
|
|
150
|
+
));
|
|
151
|
+
return protectedHeader + "." + encodedPayload + "." + signature;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.securitysuite;
|
|
2
|
+
|
|
3
|
+
import org.junit.Test;
|
|
4
|
+
|
|
5
|
+
import static org.junit.Assert.assertEquals;
|
|
6
|
+
import static org.junit.Assert.assertTrue;
|
|
7
|
+
|
|
8
|
+
public class SecureStorageNativeTest {
|
|
9
|
+
@Test
|
|
10
|
+
public void secureStorageException_includesOperationAndFailureMessage() {
|
|
11
|
+
Exception wrapped =
|
|
12
|
+
invokeSecureStorageException(
|
|
13
|
+
"setItem", new IllegalArgumentException("Storage key is required"));
|
|
14
|
+
|
|
15
|
+
assertTrue(wrapped.getMessage().startsWith("Secure storage operation failed"));
|
|
16
|
+
assertTrue(wrapped.getMessage().contains("(setItem)"));
|
|
17
|
+
assertTrue(wrapped.getMessage().contains("Storage key is required"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test
|
|
21
|
+
public void secureStorageException_usesExceptionTypeWhenMessageMissing() {
|
|
22
|
+
Exception wrapped = invokeSecureStorageException("getItem", new RuntimeException());
|
|
23
|
+
|
|
24
|
+
assertEquals(
|
|
25
|
+
"Secure storage operation failed (getItem): RuntimeException",
|
|
26
|
+
wrapped.getMessage());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private static Exception invokeSecureStorageException(String operation, Exception cause)
|
|
30
|
+
throws Exception {
|
|
31
|
+
var method =
|
|
32
|
+
SecureStorageNative.class.getDeclaredMethod(
|
|
33
|
+
"secureStorageException", String.class, Exception.class);
|
|
34
|
+
method.setAccessible(true);
|
|
35
|
+
return (Exception) method.invoke(null, operation, cause);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
@available(iOS 13.0, *)
|
|
4
|
+
struct CryptoConfig {
|
|
5
|
+
static let defaultKeyAgreement = "ECDH"
|
|
6
|
+
static let defaultKeyFactory = "EC"
|
|
7
|
+
static let defaultEncryptionKeyAlgorithm = "AES"
|
|
8
|
+
static let defaultHmacKeyAlgorithm = "HmacSHA256"
|
|
9
|
+
static let defaultCipherTransformation = "AES/GCM/NoPadding"
|
|
10
|
+
static let defaultGcmTagLength = 128
|
|
11
|
+
static let defaultGcmIvLength = 12
|
|
12
|
+
|
|
13
|
+
let keyAgreementAlgorithm: String
|
|
14
|
+
let keyFactoryAlgorithm: String
|
|
15
|
+
let encryptionKeyAlgorithm: String
|
|
16
|
+
let hmacKeyAlgorithm: String
|
|
17
|
+
let cipherTransformation: String
|
|
18
|
+
let gcmTagLength: Int
|
|
19
|
+
let gcmIvLength: Int
|
|
20
|
+
|
|
21
|
+
static func defaults() -> CryptoConfig {
|
|
22
|
+
CryptoConfig(
|
|
23
|
+
keyAgreementAlgorithm: defaultKeyAgreement,
|
|
24
|
+
keyFactoryAlgorithm: defaultKeyFactory,
|
|
25
|
+
encryptionKeyAlgorithm: defaultEncryptionKeyAlgorithm,
|
|
26
|
+
hmacKeyAlgorithm: defaultHmacKeyAlgorithm,
|
|
27
|
+
cipherTransformation: defaultCipherTransformation,
|
|
28
|
+
gcmTagLength: defaultGcmTagLength,
|
|
29
|
+
gcmIvLength: defaultGcmIvLength
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static func from(dictionary: NSDictionary?) throws -> CryptoConfig {
|
|
34
|
+
guard let dictionary = dictionary else {
|
|
35
|
+
return defaults()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return CryptoConfig(
|
|
39
|
+
keyAgreementAlgorithm: try validateKeyAgreement(
|
|
40
|
+
dictionary["keyAgreementAlgorithm"] as? String ?? defaultKeyAgreement
|
|
41
|
+
),
|
|
42
|
+
keyFactoryAlgorithm: try validateKeyFactory(
|
|
43
|
+
dictionary["keyFactoryAlgorithm"] as? String ?? defaultKeyFactory
|
|
44
|
+
),
|
|
45
|
+
encryptionKeyAlgorithm: try validateEncryptionKeyAlgorithm(
|
|
46
|
+
dictionary["encryptionKeyAlgorithm"] as? String ?? defaultEncryptionKeyAlgorithm
|
|
47
|
+
),
|
|
48
|
+
hmacKeyAlgorithm: try validateHmacKeyAlgorithm(
|
|
49
|
+
dictionary["hmacKeyAlgorithm"] as? String ?? defaultHmacKeyAlgorithm
|
|
50
|
+
),
|
|
51
|
+
cipherTransformation: try validateCipherTransformation(
|
|
52
|
+
dictionary["cipherTransformation"] as? String ?? defaultCipherTransformation
|
|
53
|
+
),
|
|
54
|
+
gcmTagLength: dictionary["gcmTagLength"] as? Int ?? defaultGcmTagLength,
|
|
55
|
+
gcmIvLength: dictionary["gcmIvLength"] as? Int ?? defaultGcmIvLength
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func merged(with dictionary: NSDictionary?) throws -> CryptoConfig {
|
|
60
|
+
guard let dictionary = dictionary else { return self }
|
|
61
|
+
var merged: [String: Any] = [
|
|
62
|
+
"keyAgreementAlgorithm": keyAgreementAlgorithm,
|
|
63
|
+
"keyFactoryAlgorithm": keyFactoryAlgorithm,
|
|
64
|
+
"encryptionKeyAlgorithm": encryptionKeyAlgorithm,
|
|
65
|
+
"hmacKeyAlgorithm": hmacKeyAlgorithm,
|
|
66
|
+
"cipherTransformation": cipherTransformation,
|
|
67
|
+
"gcmTagLength": gcmTagLength,
|
|
68
|
+
"gcmIvLength": gcmIvLength,
|
|
69
|
+
]
|
|
70
|
+
for (key, value) in dictionary {
|
|
71
|
+
if let stringKey = key as? String {
|
|
72
|
+
merged[stringKey] = value
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return try CryptoConfig.from(dictionary: merged as NSDictionary)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private static func validateKeyAgreement(_ algorithm: String) throws -> String {
|
|
79
|
+
guard algorithm == "ECDH" else {
|
|
80
|
+
throw NSError(domain: "CryptoConfig", code: 1, userInfo: [
|
|
81
|
+
NSLocalizedDescriptionKey: "Unsupported keyAgreementAlgorithm: \(algorithm)",
|
|
82
|
+
])
|
|
83
|
+
}
|
|
84
|
+
return algorithm
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private static func validateKeyFactory(_ algorithm: String) throws -> String {
|
|
88
|
+
guard algorithm == "EC" else {
|
|
89
|
+
throw NSError(domain: "CryptoConfig", code: 2, userInfo: [
|
|
90
|
+
NSLocalizedDescriptionKey: "Unsupported keyFactoryAlgorithm: \(algorithm)",
|
|
91
|
+
])
|
|
92
|
+
}
|
|
93
|
+
return algorithm
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private static func validateEncryptionKeyAlgorithm(_ algorithm: String) throws -> String {
|
|
97
|
+
guard algorithm == "AES" else {
|
|
98
|
+
throw NSError(domain: "CryptoConfig", code: 3, userInfo: [
|
|
99
|
+
NSLocalizedDescriptionKey: "Unsupported encryptionKeyAlgorithm: \(algorithm)",
|
|
100
|
+
])
|
|
101
|
+
}
|
|
102
|
+
return algorithm
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private static func validateHmacKeyAlgorithm(_ algorithm: String) throws -> String {
|
|
106
|
+
switch algorithm {
|
|
107
|
+
case "HmacSHA256", "HmacSHA384", "HmacSHA512":
|
|
108
|
+
return algorithm
|
|
109
|
+
default:
|
|
110
|
+
throw NSError(domain: "CryptoConfig", code: 4, userInfo: [
|
|
111
|
+
NSLocalizedDescriptionKey: "Unsupported hmacKeyAlgorithm: \(algorithm)",
|
|
112
|
+
])
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private static func validateCipherTransformation(_ transformation: String) throws -> String {
|
|
117
|
+
guard transformation == "AES/GCM/NoPadding" else {
|
|
118
|
+
throw NSError(domain: "CryptoConfig", code: 5, userInfo: [
|
|
119
|
+
NSLocalizedDescriptionKey: "Unsupported cipherTransformation: \(transformation)",
|
|
120
|
+
])
|
|
121
|
+
}
|
|
122
|
+
return transformation
|
|
123
|
+
}
|
|
124
|
+
}
|