react-native-security-suite 0.1.2 → 0.3.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 (38) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +40 -3
  3. package/android/build.gradle +85 -43
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +1 -3
  6. package/android/src/main/AndroidManifestDeprecated.xml +3 -0
  7. package/android/src/main/java/com/securitysuite/SecuritySuiteModule.java +202 -0
  8. package/android/src/main/java/com/securitysuite/SecuritySuitePackage.java +28 -0
  9. package/android/src/main/java/com/securitysuite/StorageEncryption.java +52 -0
  10. package/ios/DataHashingMethods.swift +196 -0
  11. package/ios/SecuritySuite-Bridging-Header.h +1 -0
  12. package/ios/SecuritySuite.mm +26 -0
  13. package/ios/SecuritySuite.swift +129 -0
  14. package/ios/SecuritySuite.xcodeproj/project.pbxproj +13 -17
  15. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
  16. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  17. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcuserdata/mohammadnavabi.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  18. package/ios/SecuritySuite.xcodeproj/xcuserdata/mohammadnavabi.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  19. package/ios/StorageEncryption.swift +81 -0
  20. package/lib/commonjs/helpers.js +16 -0
  21. package/lib/commonjs/helpers.js.map +1 -0
  22. package/lib/commonjs/index.js +158 -6
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/module/helpers.js +9 -0
  25. package/lib/module/helpers.js.map +1 -0
  26. package/lib/module/index.js +147 -1
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/typescript/helpers.d.ts +2 -0
  29. package/lib/typescript/helpers.d.ts.map +1 -0
  30. package/lib/typescript/index.d.ts +20 -0
  31. package/lib/typescript/index.d.ts.map +1 -0
  32. package/package.json +54 -33
  33. package/react-native-security-suite.podspec +18 -2
  34. package/src/helpers.ts +8 -0
  35. package/src/index.tsx +203 -3
  36. package/android/src/main/java/com/reactnativesecuritysuite/SecuritySuiteModule.java +0 -38
  37. package/android/src/main/java/com/reactnativesecuritysuite/SecuritySuitePackage.java +0 -28
  38. package/ios/SecuritySuite.m +0 -12
package/LICENSE CHANGED
@@ -1,7 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Mohammad Navabi
4
-
3
+ Copyright (c) 2023 Mohammad Navabi
5
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
5
  of this software and associated documentation files (the "Software"), to deal
7
6
  in the Software without restriction, including without limitation the rights
package/README.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # react-native-security-suite
2
2
 
3
- Security suite for detection android root and ios jailbreak devices
3
+ Security solutions for Android and iOS
4
+ A native implementation encryption/decryption
5
+ Root/Jailbreak detection
4
6
 
5
7
  ## Installation
6
8
 
9
+ ```sh
10
+ yarn add react-native-security-suite
11
+ ```
12
+
7
13
  ```sh
8
14
  npm install react-native-security-suite
9
15
  ```
@@ -11,11 +17,42 @@ npm install react-native-security-suite
11
17
  ## Usage
12
18
 
13
19
  ```js
14
- import { deviceHasSecurityRisk } from 'react-native-security-suite';
20
+ import {
21
+ getPublicKey,
22
+ getSharedKey,
23
+ encryptBySharedKey,
24
+ decryptBySharedKey,
25
+ encrypt,
26
+ decrypt,
27
+ deviceHasSecurityRisk,
28
+ } from 'react-native-security-suite';
15
29
 
16
30
  // ...
31
+ const publicKey = await getPublicKey();
32
+ console.log('Public key: ', publicKey);
33
+ /*
34
+ * Sending the publicKey to the server and receiving the SERVER_PUBLIC_KEY
35
+ * Using the SERVER_PUBLIC_KEY to generate sharedKey
36
+ */
37
+ const sharedKey = await getSharedKey('SERVER_PUBLIC_KEY');
38
+ console.log('Shared key: ', sharedKey);
39
+ // Encrypt/Decrypt by sharedKey
40
+ const hardEncrypted = await encryptBySharedKey('STR_FOR_ENCRYPT');
41
+ console.log('Encrypted result: ', hardEncrypted);
42
+ const hardDecrypted = await decryptBySharedKey('STR_FOR_DECRYPT');
43
+ console.log('Decrypted result: ', hardDecrypted);
44
+
45
+ // ------- OR --------
46
+
47
+ // Soft Encrypt/Decrypt without sharedKey
48
+ const softEncrypted = await encrypt('STR_FOR_ENCRYPT');
49
+ console.log('Encrypted result: ', softEncrypted);
50
+ const softDecrypted = await decrypt('STR_FOR_DECRYPT');
51
+ console.log('Decrypted result: ', softDecrypted);
17
52
 
18
- const result = await deviceHasSecurityRisk();
53
+ // Root/Jailbreak detection
54
+ const isRiskyDevice = await deviceHasSecurityRisk();
55
+ console.log('Root/Jailbreak detection result: ', isRiskyDevice);
19
56
  ```
20
57
 
21
58
  ## Contributing
@@ -1,60 +1,102 @@
1
1
  buildscript {
2
- if (project == rootProject) {
3
- repositories {
4
- google()
5
- mavenCentral()
6
- jcenter()
7
- }
8
-
9
- dependencies {
10
- classpath 'com.android.tools.build:gradle:3.5.3'
11
- }
12
- }
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+
7
+ dependencies {
8
+ classpath "com.android.tools.build:gradle:7.2.1"
9
+ }
13
10
  }
14
11
 
15
- apply plugin: 'com.android.library'
12
+ def isNewArchitectureEnabled() {
13
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
14
+ }
15
+
16
+ apply plugin: "com.android.library"
17
+
18
+
19
+ def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
16
20
 
17
- def safeExtGet(prop, fallback) {
18
- rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
21
+ if (isNewArchitectureEnabled()) {
22
+ apply plugin: "com.facebook.react"
19
23
  }
20
24
 
21
- android {
22
- compileSdkVersion safeExtGet('SecuritySuite_compileSdkVersion', 31)
23
- defaultConfig {
24
- minSdkVersion safeExtGet('SecuritySuite_minSdkVersion', 21)
25
- targetSdkVersion safeExtGet('SecuritySuite_targetSdkVersion', 31)
26
- versionCode 1
27
- versionName "1.0"
25
+ def getExtOrDefault(name) {
26
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["SecuritySuite_" + name]
27
+ }
28
28
 
29
- }
29
+ def getExtOrIntegerDefault(name) {
30
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["SecuritySuite_" + name]).toInteger()
31
+ }
30
32
 
31
- buildTypes {
32
- release {
33
- minifyEnabled false
34
- }
35
- }
36
- lintOptions {
37
- disable 'GradleCompatible'
33
+ def supportsNamespace() {
34
+ def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
35
+ def major = parsed[0].toInteger()
36
+ def minor = parsed[1].toInteger()
37
+
38
+ // Namespace support was added in 7.3.0
39
+ if (major == 7 && minor >= 3) {
40
+ return true
41
+ }
42
+
43
+ return major >= 8
44
+ }
45
+
46
+ android {
47
+ if (supportsNamespace()) {
48
+ namespace "com.securitysuite"
49
+ } else {
50
+ sourceSets {
51
+ main {
52
+ manifest.srcFile "src/main/AndroidManifestDeprecated.xml"
53
+ }
38
54
  }
39
- compileOptions {
40
- sourceCompatibility JavaVersion.VERSION_1_8
41
- targetCompatibility JavaVersion.VERSION_1_8
55
+ }
56
+
57
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
58
+
59
+ defaultConfig {
60
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
61
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
62
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
63
+ }
64
+ buildTypes {
65
+ release {
66
+ minifyEnabled false
42
67
  }
68
+ }
69
+
70
+ lintOptions {
71
+ disable "GradleCompatible"
72
+ }
73
+
74
+ compileOptions {
75
+ sourceCompatibility JavaVersion.VERSION_1_8
76
+ targetCompatibility JavaVersion.VERSION_1_8
77
+ }
78
+
43
79
  }
44
80
 
45
81
  repositories {
46
- mavenLocal()
47
- maven {
48
- // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
49
- url("$rootDir/../node_modules/react-native/android")
50
- }
51
- google()
52
- mavenCentral()
53
- jcenter()
82
+ mavenCentral()
83
+ google()
84
+ jcenter()
54
85
  }
55
86
 
87
+
56
88
  dependencies {
57
- //noinspection GradleDynamicVersion
58
- implementation "com.facebook.react:react-native:+" // From node_modules
59
- implementation "com.scottyab:rootbeer-lib:0.1.0"
89
+ // For < 0.71, this will be from the local maven repo
90
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
91
+ //noinspection GradleDynamicVersion
92
+ implementation "com.facebook.react:react-native:+"
93
+ implementation "com.scottyab:rootbeer-lib:0.1.0"
94
+ }
95
+
96
+ if (isNewArchitectureEnabled()) {
97
+ react {
98
+ jsRootDir = file("../src/")
99
+ libraryName = "SecuritySuite"
100
+ codegenJavaPackageName = "com.securitysuite"
101
+ }
60
102
  }
@@ -0,0 +1,5 @@
1
+ SecuritySuite_kotlinVersion=1.7.0
2
+ SecuritySuite_minSdkVersion=21
3
+ SecuritySuite_targetSdkVersion=31
4
+ SecuritySuite_compileSdkVersion=31
5
+ SecuritySuite_ndkversion=21.4.7075529
@@ -1,4 +1,2 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
- package="com.reactnativesecuritysuite">
3
-
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
4
2
  </manifest>
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.securitysuite">
3
+ </manifest>
@@ -0,0 +1,202 @@
1
+ package com.securitysuite;
2
+
3
+ import com.facebook.react.bridge.Callback;
4
+ import com.facebook.react.bridge.Promise;
5
+ import com.facebook.react.bridge.ReactApplicationContext;
6
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
7
+ import com.facebook.react.bridge.ReactMethod;
8
+ import com.facebook.react.module.annotations.ReactModule;
9
+ import com.scottyab.rootbeer.RootBeer;
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
+
38
+ @ReactModule(name = SecuritySuiteModule.NAME)
39
+ public class SecuritySuiteModule extends ReactContextBaseJavaModule {
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
+ }
56
+
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));
67
+ }
68
+ }
69
+
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();
88
+ }
89
+ return desSpec;
90
+ }
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
+ }
106
+
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));
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
+ }
196
+
197
+ @Override
198
+ @NonNull
199
+ public String getName() {
200
+ return NAME;
201
+ }
202
+ }
@@ -0,0 +1,28 @@
1
+ package com.securitysuite;
2
+
3
+ import androidx.annotation.NonNull;
4
+
5
+ import com.facebook.react.ReactPackage;
6
+ import com.facebook.react.bridge.NativeModule;
7
+ import com.facebook.react.bridge.ReactApplicationContext;
8
+ import com.facebook.react.uimanager.ViewManager;
9
+
10
+ import java.util.ArrayList;
11
+ import java.util.Collections;
12
+ import java.util.List;
13
+
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
+ }
22
+
23
+ @NonNull
24
+ @Override
25
+ public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
26
+ return Collections.emptyList();
27
+ }
28
+ }
@@ -0,0 +1,52 @@
1
+ package com.securitysuite;
2
+
3
+ import android.util.Base64;
4
+
5
+ import java.security.SecureRandom;
6
+ import java.util.Arrays;
7
+
8
+ import javax.crypto.Cipher;
9
+ import javax.crypto.spec.IvParameterSpec;
10
+ import javax.crypto.spec.SecretKeySpec;
11
+
12
+ public class StorageEncryption {
13
+
14
+ public static String encrypt(String input, String encryptionKey, Boolean hardEncryption) {
15
+ try {
16
+ byte[] iv = new byte[16];
17
+ if (hardEncryption) {
18
+ new SecureRandom().nextBytes(iv);
19
+ }
20
+
21
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
22
+ cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKey.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
23
+ byte[] cipherText = cipher.doFinal(input.getBytes("utf-8"));
24
+ byte[] ivAndCipherText = getCombinedArray(iv, cipherText);
25
+ return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP);
26
+ } catch (Exception e) {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ public static String decrypt(String encoded, String encryptionKey) {
32
+ try {
33
+ byte[] ivAndCipherText = Base64.decode(encoded, Base64.NO_WRAP);
34
+ byte[] iv = Arrays.copyOfRange(ivAndCipherText, 0, 16);
35
+ byte[] cipherText = Arrays.copyOfRange(ivAndCipherText, 16, ivAndCipherText.length);
36
+
37
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
38
+ cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(encryptionKey.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
39
+ return new String(cipher.doFinal(cipherText), "utf-8");
40
+ } catch (Exception e) {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ private static byte[] getCombinedArray(byte[] one, byte[] two) {
46
+ byte[] combined = new byte[one.length + two.length];
47
+ for (int i = 0; i < combined.length; ++i) {
48
+ combined[i] = i < one.length ? one[i] : two[i - one.length];
49
+ }
50
+ return combined;
51
+ }
52
+ }