react-native-security-suite 0.1.2 → 0.2.0

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 (55) hide show
  1. package/README.md +40 -3
  2. package/android/.gradle/7.4/checksums/checksums.lock +0 -0
  3. package/android/.gradle/7.4/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/7.4/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/7.4/dependencies-accessors/dependencies-accessors.lock +0 -0
  6. package/android/.gradle/7.4/dependencies-accessors/gc.properties +0 -0
  7. package/android/.gradle/7.4/executionHistory/executionHistory.bin +0 -0
  8. package/android/.gradle/7.4/executionHistory/executionHistory.lock +0 -0
  9. package/android/.gradle/7.4/fileChanges/last-build.bin +0 -0
  10. package/android/.gradle/7.4/fileHashes/fileHashes.bin +0 -0
  11. package/android/.gradle/7.4/fileHashes/fileHashes.lock +0 -0
  12. package/android/.gradle/7.4/fileHashes/resourceHashesCache.bin +0 -0
  13. package/android/.gradle/7.4/gc.properties +0 -0
  14. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  15. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  16. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  17. package/android/.gradle/file-system.probe +0 -0
  18. package/android/.gradle/vcs-1/gc.properties +0 -0
  19. package/android/.idea/compiler.xml +6 -0
  20. package/android/.idea/gradle.xml +17 -0
  21. package/android/.idea/jarRepositories.xml +40 -0
  22. package/android/.idea/misc.xml +10 -0
  23. package/android/.idea/vcs.xml +6 -0
  24. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  25. package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  26. package/android/gradlew +234 -0
  27. package/android/gradlew.bat +89 -0
  28. package/android/local.properties +8 -0
  29. package/android/src/main/java/com/reactnativesecuritysuite/SecuritySuiteModule.java +182 -18
  30. package/android/src/main/java/com/reactnativesecuritysuite/SecuritySuitePackage.java +12 -12
  31. package/android/src/main/java/com/reactnativesecuritysuite/StorageEncryption.java +53 -0
  32. package/ios/DataHashingMethods.swift +196 -0
  33. package/ios/SecuritySuite-Bridging-Header.h +1 -0
  34. package/ios/SecuritySuite.m +14 -0
  35. package/ios/SecuritySuite.swift +129 -0
  36. package/ios/SecuritySuite.xcodeproj/project.pbxproj +9 -15
  37. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  38. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  39. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcuserdata/Navabi.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  40. package/ios/SecuritySuite.xcodeproj/xcuserdata/Navabi.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  41. package/ios/StorageEncryption.swift +81 -0
  42. package/lib/commonjs/helpers.js +19 -0
  43. package/lib/commonjs/helpers.js.map +1 -0
  44. package/lib/commonjs/index.js +173 -1
  45. package/lib/commonjs/index.js.map +1 -1
  46. package/lib/module/helpers.js +10 -0
  47. package/lib/module/helpers.js.map +1 -0
  48. package/lib/module/index.js +143 -0
  49. package/lib/module/index.js.map +1 -1
  50. package/lib/typescript/helpers.d.ts +1 -0
  51. package/lib/typescript/index.d.ts +19 -0
  52. package/package.json +12 -1
  53. package/react-native-security-suite.podspec +1 -1
  54. package/src/helpers.ts +8 -0
  55. package/src/index.tsx +195 -3
@@ -1,7 +1,6 @@
1
1
  package com.reactnativesecuritysuite;
2
2
 
3
- import androidx.annotation.NonNull;
4
-
3
+ import com.facebook.react.bridge.Callback;
5
4
  import com.facebook.react.bridge.Promise;
6
5
  import com.facebook.react.bridge.ReactApplicationContext;
7
6
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
@@ -9,30 +8,195 @@ import com.facebook.react.bridge.ReactMethod;
9
8
  import com.facebook.react.module.annotations.ReactModule;
10
9
  import com.scottyab.rootbeer.RootBeer;
11
10
 
11
+ import androidx.annotation.NonNull;
12
+
13
+ import android.provider.Settings;
14
+ import android.util.Base64;
15
+ import android.util.Log;
16
+
17
+ import java.nio.charset.Charset;
18
+ import java.security.InvalidAlgorithmParameterException;
19
+ import java.security.InvalidKeyException;
20
+ import java.security.KeyFactory;
21
+ import java.security.KeyPair;
22
+ import java.security.KeyPairGenerator;
23
+ import java.security.NoSuchAlgorithmException;
24
+ import java.security.PrivateKey;
25
+ import java.security.PublicKey;
26
+ import java.security.spec.ECGenParameterSpec;
27
+ import java.security.spec.X509EncodedKeySpec;
28
+
29
+ import javax.crypto.BadPaddingException;
30
+ import javax.crypto.Cipher;
31
+ import javax.crypto.IllegalBlockSizeException;
32
+ import javax.crypto.KeyAgreement;
33
+ import javax.crypto.NoSuchPaddingException;
34
+ import javax.crypto.SecretKey;
35
+ import javax.crypto.spec.GCMParameterSpec;
36
+ import javax.crypto.spec.SecretKeySpec;
37
+
12
38
  @ReactModule(name = SecuritySuiteModule.NAME)
13
39
  public class SecuritySuiteModule extends ReactContextBaseJavaModule {
14
- public static final String NAME = "SecuritySuite";
15
- private ReactApplicationContext context;
40
+ public static final String NAME = "SecuritySuite";
41
+ private ReactApplicationContext context;
42
+
43
+ KeyPairGenerator kpg;
44
+ KeyPair kp;
45
+ PublicKey publicKey;
46
+ PublicKey serverPublicKey;
47
+ PrivateKey privateKey;
48
+ String sharedKey;
49
+ SecretKey secretKey;
50
+
51
+ public SecuritySuiteModule(ReactApplicationContext reactContext) {
52
+ super(reactContext);
53
+ context = reactContext;
54
+ generateKeyPair();
55
+ }
16
56
 
17
- public SecuritySuiteModule(ReactApplicationContext reactContext) {
18
- super(reactContext);
19
- context = reactContext;
57
+ private void generateKeyPair() {
58
+ try {
59
+ kpg = KeyPairGenerator.getInstance("EC");
60
+ ECGenParameterSpec prime256v1ParamSpec = new ECGenParameterSpec("secp256r1");
61
+ kpg.initialize(prime256v1ParamSpec);
62
+ kp = kpg.genKeyPair();
63
+ publicKey = kp.getPublic();
64
+ privateKey = kp.getPrivate();
65
+ } catch (Exception e) {
66
+ Log.e("generateKeyPair Error: ", String.valueOf(e));
20
67
  }
68
+ }
21
69
 
22
- @Override
23
- @NonNull
24
- public String getName() {
25
- return NAME;
70
+ @ReactMethod
71
+ public void getPublicKey(Promise promise) {
72
+ String base64DEREncoded = Base64.encodeToString(publicKey.getEncoded(), Base64.DEFAULT);
73
+ promise.resolve(base64DEREncoded);
74
+ }
75
+
76
+ private static SecretKey agreeSecretKey(PrivateKey prk_self, PublicKey pbk_peer, boolean lastPhase) throws Exception {
77
+ SecretKey desSpec;
78
+ try {
79
+ KeyAgreement keyAgree = KeyAgreement.getInstance("ECDH");
80
+ keyAgree.init(prk_self);
81
+ keyAgree.doPhase(pbk_peer, true);
82
+ byte[] sec = keyAgree.generateSecret();
83
+ desSpec = new SecretKeySpec(sec, "AES");
84
+ } catch (NoSuchAlgorithmException e) {
85
+ throw new Exception();
86
+ } catch (InvalidKeyException e) {
87
+ throw new Exception();
26
88
  }
89
+ return desSpec;
90
+ }
27
91
 
92
+ @ReactMethod
93
+ public void getSharedKey(String serverPK, Promise promise) {
94
+ try {
95
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(serverPK.getBytes(), Base64.DEFAULT)); // Change ASN1 to publicKey
96
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
97
+ serverPublicKey = keyFactory.generatePublic(keySpec);
98
+ secretKey = agreeSecretKey(kp.getPrivate(), serverPublicKey, true);
99
+ sharedKey = Base64.encodeToString(secretKey.getEncoded(), Base64.DEFAULT);
100
+ promise.resolve(sharedKey);
101
+ } catch (Exception e) {
102
+ Log.e("getSharedKey Error: ", String.valueOf(e));
103
+ promise.reject(String.valueOf(e));
104
+ }
105
+ }
28
106
 
29
- // Example method
30
- // See https://reactnative.dev/docs/native-modules-android
31
- @ReactMethod
32
- public void deviceHasSecurityRisk(Promise promise) {
33
- RootBeer rootBeer = new RootBeer(context);
34
- promise.resolve(rootBeer.isRooted());
107
+ @ReactMethod
108
+ public void encrypt(String input, Promise promise) {
109
+ try {
110
+ byte[] inputByte = input.getBytes();
111
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
112
+ byte[] decodedKey = Base64.decode(sharedKey, Base64.DEFAULT);
113
+ SecretKey secretKeySpec = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
114
+ cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
115
+ byte[] iv = cipher.getIV();
116
+ assert iv.length == 12;
117
+ byte[] cipherText = cipher.doFinal(inputByte);
118
+ if (cipherText.length != inputByte.length + 16)
119
+ throw new IllegalStateException();
120
+ byte[] output = new byte[12 + inputByte.length + 16];
121
+ System.arraycopy(iv, 0, output, 0, 12);
122
+ System.arraycopy(cipherText, 0, output, 12, cipherText.length);
123
+ promise.resolve(Base64.encodeToString(output, Base64.NO_WRAP));
124
+ } catch (Exception e) {
125
+ Log.e("encrypt Error: ", String.valueOf(e));
126
+ promise.reject(String.valueOf(e));
35
127
  }
128
+ }
129
+
130
+ @ReactMethod
131
+ public void decrypt(String input, Promise promise) {
132
+ try {
133
+ byte[] inputBytes = Base64.decode(input.getBytes(), Base64.DEFAULT);
134
+ if (inputBytes.length < 12 + 16) throw new IllegalArgumentException();
135
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
136
+ GCMParameterSpec params = new GCMParameterSpec(128, inputBytes, 0, 12);
137
+ byte[] decodedKey = Base64.decode(sharedKey, Base64.DEFAULT);
138
+ SecretKey secretKeySpec = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
139
+ cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, params);
140
+ byte[] plaintext = cipher.doFinal(inputBytes, 12, inputBytes.length - 12);
141
+ String decrypted = new String(plaintext, Charset.forName("UTF-8"));
142
+ promise.resolve(decrypted);
143
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
144
+ promise.reject(e);
145
+ }
146
+ }
147
+
148
+ @ReactMethod
149
+ public void getDeviceId(Callback callback) {
150
+ try {
151
+ String deviceId = getAndroidId();
152
+ callback.invoke(deviceId, null);
153
+ } catch (Exception e) {
154
+ callback.invoke(null, e);
155
+ }
156
+ }
157
+
158
+ @ReactMethod
159
+ public void storageEncrypt(String input, String secretKey, Boolean hardEncryption, Callback callback) {
160
+ try {
161
+ String key = getAndroidId();
162
+ if (secretKey != null) {
163
+ key = secretKey;
164
+ }
165
+ String encryptedMessage = StorageEncryption.encrypt(input, key, hardEncryption);
166
+ callback.invoke(encryptedMessage, null);
167
+ } catch (Exception e) {
168
+ callback.invoke(null, e);
169
+ }
170
+ }
171
+
172
+ @ReactMethod
173
+ public void storageDecrypt(String input, String secretKey, Boolean hardEncryption, Callback callback) {
174
+ try {
175
+ String key = getAndroidId();
176
+ if (secretKey != null) {
177
+ key = secretKey;
178
+ }
179
+ String decryptedMessage = StorageEncryption.decrypt(input, key);
180
+ callback.invoke(decryptedMessage, null);
181
+ } catch (Exception e) {
182
+ callback.invoke(null, e.getMessage());
183
+ }
184
+ }
185
+
186
+ private String getAndroidId() {
187
+ return Settings.Secure.getString(context.getContentResolver(),
188
+ Settings.Secure.ANDROID_ID);
189
+ }
190
+
191
+ @ReactMethod
192
+ public void deviceHasSecurityRisk(Promise promise) {
193
+ RootBeer rootBeer = new RootBeer(context);
194
+ promise.resolve(rootBeer.isRooted());
195
+ }
36
196
 
37
- public static native int nativeMultiply(int a, int b);
197
+ @Override
198
+ @NonNull
199
+ public String getName() {
200
+ return NAME;
201
+ }
38
202
  }
@@ -12,17 +12,17 @@ import java.util.Collections;
12
12
  import java.util.List;
13
13
 
14
14
  public class SecuritySuitePackage implements ReactPackage {
15
- @NonNull
16
- @Override
17
- public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
18
- List<NativeModule> modules = new ArrayList<>();
19
- modules.add(new SecuritySuiteModule(reactContext));
20
- return modules;
21
- }
15
+ @NonNull
16
+ @Override
17
+ public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
18
+ List<NativeModule> modules = new ArrayList<>();
19
+ modules.add(new SecuritySuiteModule(reactContext));
20
+ return modules;
21
+ }
22
22
 
23
- @NonNull
24
- @Override
25
- public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
26
- return Collections.emptyList();
27
- }
23
+ @NonNull
24
+ @Override
25
+ public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
26
+ return Collections.emptyList();
27
+ }
28
28
  }
@@ -0,0 +1,53 @@
1
+ package com.reactnativesecuritysuite;
2
+
3
+
4
+ import android.util.Base64;
5
+
6
+ import java.security.SecureRandom;
7
+ import java.util.Arrays;
8
+
9
+ import javax.crypto.Cipher;
10
+ import javax.crypto.spec.IvParameterSpec;
11
+ import javax.crypto.spec.SecretKeySpec;
12
+
13
+ public class StorageEncryption {
14
+
15
+ public static String encrypt(String input, String encryptionKey, Boolean hardEncryption) {
16
+ try {
17
+ byte[] iv = new byte[16];
18
+ if (hardEncryption) {
19
+ new SecureRandom().nextBytes(iv);
20
+ }
21
+
22
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
23
+ cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKey.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
24
+ byte[] cipherText = cipher.doFinal(input.getBytes("utf-8"));
25
+ byte[] ivAndCipherText = getCombinedArray(iv, cipherText);
26
+ return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP);
27
+ } catch (Exception e) {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ public static String decrypt(String encoded, String encryptionKey) {
33
+ try {
34
+ byte[] ivAndCipherText = Base64.decode(encoded, Base64.NO_WRAP);
35
+ byte[] iv = Arrays.copyOfRange(ivAndCipherText, 0, 16);
36
+ byte[] cipherText = Arrays.copyOfRange(ivAndCipherText, 16, ivAndCipherText.length);
37
+
38
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
39
+ cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(encryptionKey.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
40
+ return new String(cipher.doFinal(cipherText), "utf-8");
41
+ } catch (Exception e) {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ private static byte[] getCombinedArray(byte[] one, byte[] two) {
47
+ byte[] combined = new byte[one.length + two.length];
48
+ for (int i = 0; i < combined.length; ++i) {
49
+ combined[i] = i < one.length ? one[i] : two[i - one.length];
50
+ }
51
+ return combined;
52
+ }
53
+ }
@@ -0,0 +1,196 @@
1
+ //
2
+ // Data+HashingMethods.swift
3
+ //
4
+ // Created by Amir Sepehrom on 5/31/21.
5
+ //
6
+
7
+ import Foundation
8
+ import Security
9
+ import CommonCrypto
10
+
11
+ extension SecKey {
12
+
13
+ fileprivate subscript(attribute attr: CFString) -> Any? {
14
+ let attributes = SecKeyCopyAttributes(self) as? [CFString: Any]
15
+ return attributes?[attr]
16
+ }
17
+
18
+ /// Returns key length.
19
+ public var keySize: Int {
20
+ // swiftlint: disable force_cast
21
+ return self[attribute: kSecAttrKeySizeInBits] as! Int
22
+ }
23
+
24
+ /// Returns encryption method of key.
25
+ public var type: KeyType {
26
+ // swiftlint: disable force_cast
27
+ let type = self[attribute: kSecAttrType] as! CFString
28
+ return KeyType(rawValue: type)!
29
+ }
30
+
31
+ /// Public ASN1 header appropriate for current key.
32
+ fileprivate func asn1Header() throws -> Data {
33
+ return try type.asn1Header(keySize: keySize)
34
+ }
35
+
36
+ /// Exporable key data with ASN1 header.
37
+ public func bytes() throws -> Data {
38
+ return try asn1Header() + rawBytes()
39
+ }
40
+
41
+ /// Raw bytes for key that generated by Security framework.
42
+ public func rawBytes() throws -> Data {
43
+ var error: Unmanaged<CFError>?
44
+
45
+ guard let keyData = SecKeyCopyExternalRepresentation(self, &error) as Data? else {
46
+ throw error?.takeRetainedValue() ?? CryptographyError.invalidKey
47
+ }
48
+ return keyData
49
+ }
50
+
51
+ /// Encryption method
52
+ public enum KeyType: RawRepresentable {
53
+ /// Eliptic curve
54
+ case ec
55
+ /// RSA
56
+ case rsa
57
+
58
+ /// Initilize from kSecAttrKeyType string constant.
59
+ public init?(rawValue: CFString) {
60
+ switch rawValue {
61
+ case kSecAttrKeyTypeEC, kSecAttrKeyTypeECSECPrimeRandom:
62
+ self = .ec
63
+ case kSecAttrKeyTypeRSA:
64
+ self = .rsa
65
+ default:
66
+ return nil
67
+ }
68
+ }
69
+
70
+ /// kSecAttrKeyType counterpart of option.
71
+ public var rawValue: CFString {
72
+ switch self {
73
+ case .ec:
74
+ return kSecAttrKeyTypeECSECPrimeRandom
75
+ case .rsa:
76
+ return kSecAttrKeyTypeRSA
77
+ }
78
+ }
79
+
80
+ /// ASN1 headers for encrypting public keys.
81
+ public struct ASN1 {
82
+ public static let rsa2048 = Data(base64Encoded: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A")!
83
+ public static let rsa4096 = Data(base64Encoded: "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A")!
84
+ public static let ec256 = Data(base64Encoded: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgA=")!
85
+ public static let ec384 = Data(base64Encoded: "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgA=")!
86
+ public static let ec521 = Data(base64Encoded: "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAA==")!
87
+ }
88
+
89
+ func asn1Header(keySize: Int) throws -> Data {
90
+ switch (self, keySize) {
91
+ case (.ec, 256):
92
+ return ASN1.ec256
93
+ case (.ec, 384):
94
+ return ASN1.ec384
95
+ case (.ec, 521):
96
+ return ASN1.ec521
97
+ case (.rsa, 2048):
98
+ return ASN1.rsa2048
99
+ case (.rsa, 4096):
100
+ return ASN1.rsa4096
101
+ default:
102
+ throw CryptographyError.unsupported
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ public enum CryptographyError: Error {
109
+ /// Key data is not valid or not conform to expected type and length.
110
+ case invalidKey
111
+ /// Encryption failed.
112
+ case failedEncryption
113
+ /// Decryption failed.
114
+ case failedDecryption
115
+ /// Data that passed to encrypt/decrypt is empty.
116
+ case emptyData
117
+ /// Encrypted data is not valid or encrypted with another key type.
118
+ case invalidData
119
+ /// Encryption/Decryption method is not supported.
120
+ case unsupported
121
+
122
+ // Enclave
123
+ /// Private key is not exist on the device enclave.
124
+ case keyNotFound
125
+ /// Operation with private key which saved in enclave is not posssible.
126
+ case notAllowed
127
+ }
128
+
129
+ public enum Digest: String {
130
+ case md5 = "MD5"
131
+ case sha1 = "SHA1"
132
+ case sha256 = "SHA256"
133
+ case sha384 = "SHA384"
134
+ case sha512 = "SHA512"
135
+
136
+ public var length: Int {
137
+ switch self {
138
+ case .md5:
139
+ return Int(CC_MD5_DIGEST_LENGTH)
140
+ case .sha1:
141
+ return Int(CC_SHA1_DIGEST_LENGTH)
142
+ case .sha256:
143
+ return Int(CC_SHA256_DIGEST_LENGTH)
144
+ case .sha384:
145
+ return Int(CC_SHA384_DIGEST_LENGTH)
146
+ case .sha512:
147
+ return Int(CC_SHA512_DIGEST_LENGTH)
148
+ }
149
+ }
150
+
151
+ public var hmacAlgorithm: CCHmacAlgorithm {
152
+ switch self {
153
+ case .md5:
154
+ return CCHmacAlgorithm(kCCHmacAlgMD5)
155
+ case .sha1:
156
+ return CCHmacAlgorithm(kCCHmacAlgSHA1)
157
+ case .sha256:
158
+ return CCHmacAlgorithm(kCCHmacAlgSHA256)
159
+ case .sha384:
160
+ return CCHmacAlgorithm(kCCHmacAlgSHA384)
161
+ case .sha512:
162
+ return CCHmacAlgorithm(kCCHmacAlgSHA512)
163
+ }
164
+ }
165
+ }
166
+
167
+ extension Data {
168
+ /**
169
+ Calculates hash digest of data.
170
+
171
+ - Parameter digest: digest type. Currently only SHA is supported.
172
+ - Returns: A data object with length equal to digest length.
173
+ */
174
+ public func hash(digest: Digest) -> Data {
175
+ guard !isEmpty else { return Data() }
176
+ var result = [UInt8](repeating: 0, count: digest.length)
177
+ self.withUnsafeBytes { (buf: UnsafeRawBufferPointer) -> Void in
178
+ let ptr = buf.baseAddress!
179
+ let dataLen = CC_LONG(buf.count)
180
+ switch digest {
181
+ case .md5:
182
+ CC_MD5(ptr, dataLen, &result)
183
+ case .sha1:
184
+ CC_SHA1(ptr, dataLen, &result)
185
+ case .sha256:
186
+ CC_SHA256(ptr, dataLen, &result)
187
+ case .sha384:
188
+ CC_SHA384(ptr, dataLen, &result)
189
+ case .sha512:
190
+ CC_SHA512(ptr, dataLen, &result)
191
+ }
192
+ }
193
+
194
+ return Data(result)
195
+ }
196
+ }
@@ -1,2 +1,3 @@
1
1
  #import <React/RCTBridgeModule.h>
2
2
  #import <React/RCTViewManager.h>
3
+ #import <CommonCrypto/CommonCrypto.h>
@@ -2,6 +2,20 @@
2
2
 
3
3
  @interface RCT_EXTERN_MODULE(SecuritySuite, NSObject)
4
4
 
5
+ RCT_EXTERN_METHOD(getPublicKey:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
6
+
7
+ RCT_EXTERN_METHOD(getSharedKey:(NSString)serverPK withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
8
+
9
+ RCT_EXTERN_METHOD(encrypt:(NSString)input withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
10
+
11
+ RCT_EXTERN_METHOD(decrypt:(NSString)input withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
12
+
13
+ RCT_EXTERN_METHOD(getDeviceId:(RCTResponseSenderBlock)callback)
14
+
15
+ RCT_EXTERN_METHOD(storageEncrypt:(NSString)input withSecretKey:(NSString*)secretKey withHardEncryption:(BOOL)hardEncryption withCallback:(RCTResponseSenderBlock)callback)
16
+
17
+ RCT_EXTERN_METHOD(storageDecrypt:(NSString)input withSecretKey:(NSString*)secretKey withHardEncryption:(BOOL)hardEncryption withCallback:(RCTResponseSenderBlock)callback)
18
+
5
19
  RCT_EXTERN_METHOD(deviceHasSecurityRisk:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
6
20
 
7
21
  + (BOOL)requiresMainQueueSetup
@@ -1,7 +1,128 @@
1
1
  import IOSSecuritySuite
2
+ import Foundation
3
+ import CryptoKit
4
+ import SwiftUI
2
5
 
6
+ @available(iOS 13.0, *)
3
7
  @objc(SecuritySuite)
4
8
  class SecuritySuite: NSObject {
9
+ var privateKey: String!,
10
+ publicKey: String!,
11
+ sharedKey: String!,
12
+ keyData: Data!
13
+
14
+ @objc(getPublicKey:withRejecter:)
15
+ func getPublicKey(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
16
+ do {
17
+ privateKey = P256.KeyAgreement.PrivateKey().rawRepresentation.base64EncodedString()
18
+ keyData = Data(base64Encoded: privateKey as! String)!
19
+ publicKey = try? (ASN1.ec256 + [0x04] + P256.KeyAgreement.PrivateKey(rawRepresentation: keyData).publicKey.rawRepresentation).base64EncodedString()
20
+
21
+ resolve(publicKey)
22
+ } catch {
23
+ reject("error", "GET_PUBLIC_KEY_ERROR", nil)
24
+ }
25
+ }
26
+
27
+ @objc(getSharedKey:withResolver:withRejecter:)
28
+ func getSharedKey(serverPK: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
29
+ do {
30
+ print("privateKey", privateKey)
31
+ guard let serverPublicKeyData = Data(base64Encoded: serverPK as String),
32
+ let privateKeyData = Data(base64Encoded: privateKey as String) else { return }
33
+
34
+ sharedKey = try? P256.KeyAgreement.PrivateKey(rawRepresentation: privateKeyData).sharedSecretFromKeyAgreement(with: .init(rawRepresentation: serverPublicKeyData.dropFirst(ASN1.ec256.count + 1)))
35
+ .withUnsafeBytes({ Data(buffer: $0.bindMemory(to: UInt8.self)) })
36
+ .base64EncodedString()
37
+
38
+ resolve(sharedKey)
39
+ } catch {
40
+ reject("error", "GET_SHARED_KEY_ERROR", nil)
41
+ }
42
+ }
43
+
44
+ @objc(encrypt:withResolver:withRejecter:)
45
+ func encrypt(input: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
46
+ do {
47
+ guard let keyData = Data(base64Encoded: sharedKey) else {
48
+ reject("error", "DECRYPT_SHARED_KEY_ERROR", nil)
49
+ return
50
+ }
51
+ let data = Data((input as String).utf8)
52
+ if keyData == nil {
53
+ reject("error", "keyData is null", nil)
54
+ return
55
+ }
56
+ let key = SymmetricKey(data: keyData)
57
+ let output = try? AES.GCM.seal(data, using: key).combined?.base64EncodedString()
58
+ resolve(output)
59
+ } catch {
60
+ reject("error", "ENCRYPT_ERROR", nil)
61
+ }
62
+ }
63
+
64
+ @objc(decrypt:withResolver:withRejecter:)
65
+ func decrypt(input: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
66
+ do {
67
+ guard let keyData = Data(base64Encoded: sharedKey) else {
68
+ reject("error", "DECRYPT_SHARED_KEY_ERROR", nil)
69
+ return
70
+ }
71
+ guard let data = Data(base64Encoded: "\(input)") else {
72
+ reject("error", "INPUT_ERROR", nil)
73
+ return
74
+ }
75
+ let key = SymmetricKey(data: keyData)
76
+ let output = ( try? AES.GCM.open(.init(combined: data), using: key)).map({String(decoding: $0, as: UTF8.self)})
77
+
78
+ resolve(output)
79
+ } catch {
80
+ reject("error", "DECRYPT_ERROR", nil)
81
+ }
82
+ }
83
+
84
+ @objc(storageEncrypt:withSecretKey:withHardEncryption:withCallback:)
85
+ func storageEncrypt(input: NSString, secretKey: NSString, hardEncryption: Bool, callback: RCTResponseSenderBlock) -> Void {
86
+ do {
87
+ var encryptionKey = getDeviceId();
88
+ if secretKey != nil {
89
+ encryptionKey = secretKey as String;
90
+ }
91
+ let storageEncryption: StorageEncryption = StorageEncryption()
92
+ let encrypted = try? storageEncryption.encrypt(plain: "\(input)", encryptionKey: encryptionKey, hardEncryption: hardEncryption)
93
+ callback([encrypted, NSNull()])
94
+ } catch {
95
+ callback([NSNull(), "SOFT_DECRYPT_ERROR"])
96
+ }
97
+ }
98
+
99
+ @objc(storageDecrypt:withSecretKey:withHardEncryption:withCallback:)
100
+ func storageDecrypt(input: NSString, secretKey: NSString, hardEncryption: Bool, callback: RCTResponseSenderBlock) -> Void {
101
+ do {
102
+ var encryptionKey = getDeviceId()
103
+ if secretKey != nil {
104
+ encryptionKey = secretKey as String;
105
+ }
106
+ let storageEncryption: StorageEncryption = StorageEncryption()
107
+ let decrypted = try? storageEncryption.decrypt(decoded: "\(input)", encryptionKey: encryptionKey, hardEncryption: hardEncryption)
108
+ callback([decrypted, NSNull()])
109
+ } catch {
110
+ callback([NSNull(), "SOFT_DECRYPT_ERROR"])
111
+ }
112
+ }
113
+
114
+ @objc(getDeviceId:)
115
+ func getDeviceId(callback: RCTResponseSenderBlock) -> Void {
116
+ do {
117
+ callback([getDeviceId(), NSNull()]);
118
+ } catch {
119
+ callback([NSNull(), "GET_DEVICE_ID_ERROR"]);
120
+ }
121
+ }
122
+
123
+ func getDeviceId() -> String {
124
+ return UIDevice.current.identifierForVendor!.uuidString.replacingOccurrences(of: "-", with: "", options: [], range: nil)
125
+ }
5
126
 
6
127
  @objc(deviceHasSecurityRisk:withRejecter:)
7
128
  func deviceHasSecurityRisk(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
@@ -13,3 +134,11 @@ class SecuritySuite: NSObject {
13
134
  }
14
135
  }
15
136
  }
137
+
138
+ struct ASN1 {
139
+ static let rsa2048 = Data(base64Encoded: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A")!
140
+ static let rsa4096 = Data(base64Encoded: "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A")!
141
+ static let ec256 = Data(base64Encoded: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgA=")!
142
+ static let ec384 = Data(base64Encoded: "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgA=")!
143
+ static let ec521 = Data(base64Encoded: "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQ=")!
144
+ }