@capgo/capacitor-native-biometric 5.0.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 (125) hide show
  1. package/CapgoCapacitorNativeBiometric.podspec +13 -0
  2. package/LICENSE +21 -0
  3. package/android/.gradle/8.0.2/checksums/checksums.lock +0 -0
  4. package/android/.gradle/8.0.2/dependencies-accessors/dependencies-accessors.lock +0 -0
  5. package/android/.gradle/8.0.2/dependencies-accessors/gc.properties +0 -0
  6. package/android/.gradle/8.0.2/executionHistory/executionHistory.bin +0 -0
  7. package/android/.gradle/8.0.2/executionHistory/executionHistory.lock +0 -0
  8. package/android/.gradle/8.0.2/fileChanges/last-build.bin +0 -0
  9. package/android/.gradle/8.0.2/fileHashes/fileHashes.bin +0 -0
  10. package/android/.gradle/8.0.2/fileHashes/fileHashes.lock +0 -0
  11. package/android/.gradle/8.0.2/fileHashes/resourceHashesCache.bin +0 -0
  12. package/android/.gradle/8.0.2/gc.properties +0 -0
  13. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  14. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  15. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/android.iml +40 -0
  18. package/android/build.gradle +59 -0
  19. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  20. package/android/gradle/wrapper/gradle-wrapper.properties +6 -0
  21. package/android/gradle.properties +20 -0
  22. package/android/gradlew +244 -0
  23. package/android/gradlew.bat +92 -0
  24. package/android/local.properties +8 -0
  25. package/android/proguard-rules.pro +21 -0
  26. package/android/settings.gradle +2 -0
  27. package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +28 -0
  28. package/android/src/main/AndroidManifest.xml +12 -0
  29. package/android/src/main/java/ee/forgr/biometric/AuthActivity.java +129 -0
  30. package/android/src/main/java/ee/forgr/biometric/NativeBiometric.java +457 -0
  31. package/android/src/main/res/layout/activity_auth_acitivy.xml +12 -0
  32. package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
  33. package/android/src/main/res/navigation/nav_graph.xml +28 -0
  34. package/android/src/main/res/values/colors.xml +3 -0
  35. package/android/src/main/res/values/dimens.xml +3 -0
  36. package/android/src/main/res/values/strings.xml +12 -0
  37. package/android/src/main/res/values/styles.xml +16 -0
  38. package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +18 -0
  39. package/dist/docs.json +426 -0
  40. package/dist/esm/definitions.d.ts +91 -0
  41. package/dist/esm/definitions.js +11 -0
  42. package/dist/esm/definitions.js.map +1 -0
  43. package/dist/esm/index.d.ts +4 -0
  44. package/dist/esm/index.js +7 -0
  45. package/dist/esm/index.js.map +1 -0
  46. package/dist/esm/web.d.ts +10 -0
  47. package/dist/esm/web.js +22 -0
  48. package/dist/esm/web.js.map +1 -0
  49. package/dist/plugin.cjs.js +47 -0
  50. package/dist/plugin.cjs.js.map +1 -0
  51. package/dist/plugin.js +50 -0
  52. package/dist/plugin.js.map +1 -0
  53. package/ios/Plugin/Info.plist +24 -0
  54. package/ios/Plugin/Plugin.h +10 -0
  55. package/ios/Plugin/Plugin.m +12 -0
  56. package/ios/Plugin/Plugin.swift +264 -0
  57. package/ios/Plugin.xcodeproj/project.pbxproj +554 -0
  58. package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  59. package/ios/Plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  60. package/ios/Plugin.xcodeproj/project.xcworkspace/xcuserdata/pilito.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  61. package/ios/Plugin.xcodeproj/xcuserdata/jmartinez.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  62. package/ios/Plugin.xcodeproj/xcuserdata/josemartinez.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  63. package/ios/Plugin.xcodeproj/xcuserdata/pilito.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  64. package/ios/Plugin.xcworkspace/contents.xcworkspacedata +10 -0
  65. package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  66. package/ios/Plugin.xcworkspace/xcuserdata/jmartinez.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  67. package/ios/Plugin.xcworkspace/xcuserdata/josemartinez.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  68. package/ios/Plugin.xcworkspace/xcuserdata/pilito.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  69. package/ios/PluginTests/Info.plist +22 -0
  70. package/ios/PluginTests/PluginTests.swift +35 -0
  71. package/ios/Podfile +16 -0
  72. package/ios/Podfile.lock +22 -0
  73. package/ios/Pods/Local Podspecs/Capacitor.podspec.json +34 -0
  74. package/ios/Pods/Local Podspecs/CapacitorCordova.podspec.json +26 -0
  75. package/ios/Pods/Manifest.lock +22 -0
  76. package/ios/Pods/Pods.xcodeproj/project.pbxproj +1626 -0
  77. package/ios/Pods/Pods.xcodeproj/xcuserdata/jmartinez.xcuserdatad/xcschemes/Capacitor.xcscheme +60 -0
  78. package/ios/Pods/Pods.xcodeproj/xcuserdata/jmartinez.xcuserdatad/xcschemes/CapacitorCordova.xcscheme +58 -0
  79. package/ios/Pods/Pods.xcodeproj/xcuserdata/jmartinez.xcuserdatad/xcschemes/Pods-Plugin.xcscheme +58 -0
  80. package/ios/Pods/Pods.xcodeproj/xcuserdata/jmartinez.xcuserdatad/xcschemes/Pods-PluginTests.xcscheme +58 -0
  81. package/ios/Pods/Pods.xcodeproj/xcuserdata/jmartinez.xcuserdatad/xcschemes/xcschememanagement.plist +39 -0
  82. package/ios/Pods/Pods.xcodeproj/xcuserdata/josemartinez.xcuserdatad/xcschemes/Capacitor.xcscheme +60 -0
  83. package/ios/Pods/Pods.xcodeproj/xcuserdata/josemartinez.xcuserdatad/xcschemes/CapacitorCordova.xcscheme +58 -0
  84. package/ios/Pods/Pods.xcodeproj/xcuserdata/josemartinez.xcuserdatad/xcschemes/Pods-Plugin.xcscheme +58 -0
  85. package/ios/Pods/Pods.xcodeproj/xcuserdata/josemartinez.xcuserdatad/xcschemes/Pods-PluginTests.xcscheme +58 -0
  86. package/ios/Pods/Pods.xcodeproj/xcuserdata/josemartinez.xcuserdatad/xcschemes/xcschememanagement.plist +39 -0
  87. package/ios/Pods/Pods.xcodeproj/xcuserdata/martindonadieu.xcuserdatad/xcschemes/Capacitor.xcscheme +58 -0
  88. package/ios/Pods/Pods.xcodeproj/xcuserdata/martindonadieu.xcuserdatad/xcschemes/CapacitorCordova.xcscheme +58 -0
  89. package/ios/Pods/Pods.xcodeproj/xcuserdata/martindonadieu.xcuserdatad/xcschemes/Pods-Plugin.xcscheme +58 -0
  90. package/ios/Pods/Pods.xcodeproj/xcuserdata/martindonadieu.xcuserdatad/xcschemes/Pods-PluginTests.xcscheme +58 -0
  91. package/ios/Pods/Pods.xcodeproj/xcuserdata/martindonadieu.xcuserdatad/xcschemes/xcschememanagement.plist +31 -0
  92. package/ios/Pods/Pods.xcodeproj/xcuserdata/pilito.xcuserdatad/xcschemes/xcschememanagement.plist +29 -0
  93. package/ios/Pods/Target Support Files/Capacitor/Capacitor-Info.plist +26 -0
  94. package/ios/Pods/Target Support Files/Capacitor/Capacitor-dummy.m +5 -0
  95. package/ios/Pods/Target Support Files/Capacitor/Capacitor-prefix.pch +12 -0
  96. package/ios/Pods/Target Support Files/Capacitor/Capacitor-umbrella.h +23 -0
  97. package/ios/Pods/Target Support Files/Capacitor/Capacitor.debug.xcconfig +16 -0
  98. package/ios/Pods/Target Support Files/Capacitor/Capacitor.modulemap +8 -0
  99. package/ios/Pods/Target Support Files/Capacitor/Capacitor.release.xcconfig +16 -0
  100. package/ios/Pods/Target Support Files/CapacitorCordova/CapacitorCordova-Info.plist +26 -0
  101. package/ios/Pods/Target Support Files/CapacitorCordova/CapacitorCordova-dummy.m +5 -0
  102. package/ios/Pods/Target Support Files/CapacitorCordova/CapacitorCordova-prefix.pch +12 -0
  103. package/ios/Pods/Target Support Files/CapacitorCordova/CapacitorCordova-umbrella.h +32 -0
  104. package/ios/Pods/Target Support Files/CapacitorCordova/CapacitorCordova.debug.xcconfig +13 -0
  105. package/ios/Pods/Target Support Files/CapacitorCordova/CapacitorCordova.modulemap +6 -0
  106. package/ios/Pods/Target Support Files/CapacitorCordova/CapacitorCordova.release.xcconfig +13 -0
  107. package/ios/Pods/Target Support Files/Pods-Plugin/Pods-Plugin-Info.plist +26 -0
  108. package/ios/Pods/Target Support Files/Pods-Plugin/Pods-Plugin-acknowledgements.markdown +53 -0
  109. package/ios/Pods/Target Support Files/Pods-Plugin/Pods-Plugin-acknowledgements.plist +91 -0
  110. package/ios/Pods/Target Support Files/Pods-Plugin/Pods-Plugin-dummy.m +5 -0
  111. package/ios/Pods/Target Support Files/Pods-Plugin/Pods-Plugin-umbrella.h +16 -0
  112. package/ios/Pods/Target Support Files/Pods-Plugin/Pods-Plugin.debug.xcconfig +14 -0
  113. package/ios/Pods/Target Support Files/Pods-Plugin/Pods-Plugin.modulemap +6 -0
  114. package/ios/Pods/Target Support Files/Pods-Plugin/Pods-Plugin.release.xcconfig +14 -0
  115. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests-Info.plist +26 -0
  116. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests-acknowledgements.markdown +53 -0
  117. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests-acknowledgements.plist +91 -0
  118. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests-dummy.m +5 -0
  119. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests-frameworks.sh +188 -0
  120. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests-umbrella.h +16 -0
  121. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.debug.xcconfig +15 -0
  122. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.modulemap +6 -0
  123. package/ios/Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.release.xcconfig +15 -0
  124. package/package.json +80 -0
  125. package/readme.md +284 -0
@@ -0,0 +1,129 @@
1
+ package ee.forgr.biometric;
2
+
3
+ import android.content.Intent;
4
+ import android.os.Build;
5
+ import android.os.Bundle;
6
+ import android.os.Handler;
7
+ import android.util.Log;
8
+ import android.view.View;
9
+ import androidx.annotation.NonNull;
10
+ import androidx.appcompat.app.AppCompatActivity;
11
+ import androidx.appcompat.widget.Toolbar;
12
+ import androidx.biometric.BiometricPrompt;
13
+ import ee.forgr.biometric.capacitornativebiometric.R;
14
+ import com.google.android.material.floatingactionbutton.FloatingActionButton;
15
+ import com.google.android.material.snackbar.Snackbar;
16
+ import java.util.concurrent.Executor;
17
+
18
+ public class AuthActivity extends AppCompatActivity {
19
+
20
+ private Executor executor;
21
+ private int maxAttempts;
22
+ private int counter = 0;
23
+
24
+ @Override
25
+ protected void onCreate(Bundle savedInstanceState) {
26
+ super.onCreate(savedInstanceState);
27
+ setContentView(R.layout.activity_auth_acitivy);
28
+
29
+ maxAttempts = getIntent().getIntExtra("maxAttempts", 1);
30
+
31
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
32
+ executor = this.getMainExecutor();
33
+ } else {
34
+ executor =
35
+ new Executor() {
36
+ @Override
37
+ public void execute(Runnable command) {
38
+ new Handler().post(command);
39
+ }
40
+ };
41
+ }
42
+
43
+ BiometricPrompt.PromptInfo.Builder builder =
44
+ new BiometricPrompt.PromptInfo.Builder()
45
+ .setTitle(
46
+ getIntent().hasExtra("title")
47
+ ? getIntent().getStringExtra("title")
48
+ : "Authenticate"
49
+ )
50
+ .setSubtitle(
51
+ getIntent().hasExtra("subtitle")
52
+ ? getIntent().getStringExtra("subtitle")
53
+ : null
54
+ )
55
+ .setDescription(
56
+ getIntent().hasExtra("description")
57
+ ? getIntent().getStringExtra("description")
58
+ : null
59
+ );
60
+
61
+ boolean useFallback = getIntent().getBooleanExtra("useFallback", false);
62
+
63
+ if (useFallback) {
64
+ builder.setDeviceCredentialAllowed(true);
65
+ } else {
66
+ builder.setNegativeButtonText(
67
+ getIntent().hasExtra("negativeButtonText")
68
+ ? getIntent().getStringExtra("negativeButtonText")
69
+ : "Cancel"
70
+ );
71
+ }
72
+
73
+ BiometricPrompt.PromptInfo promptInfo = builder.build();
74
+
75
+ BiometricPrompt biometricPrompt = new BiometricPrompt(
76
+ this,
77
+ executor,
78
+ new BiometricPrompt.AuthenticationCallback() {
79
+ @Override
80
+ public void onAuthenticationError(
81
+ int errorCode,
82
+ @NonNull CharSequence errString
83
+ ) {
84
+ super.onAuthenticationError(errorCode, errString);
85
+
86
+ finishActivity(errString.toString(), errorCode);
87
+ }
88
+
89
+ @Override
90
+ public void onAuthenticationSucceeded(
91
+ @NonNull BiometricPrompt.AuthenticationResult result
92
+ ) {
93
+ super.onAuthenticationSucceeded(result);
94
+ finishActivity("success", 0);
95
+ }
96
+
97
+ @Override
98
+ public void onAuthenticationFailed() {
99
+ super.onAuthenticationFailed();
100
+ counter++;
101
+ if (counter == maxAttempts) finishActivity("failed");
102
+ }
103
+ }
104
+ );
105
+
106
+ biometricPrompt.authenticate(promptInfo);
107
+ }
108
+
109
+ void finishActivity(String result) {
110
+ Intent intent = new Intent();
111
+ intent.putExtra("result", "failed");
112
+ intent.putExtra("errorDetails", "Authentication failed.");
113
+ setResult(RESULT_OK, intent);
114
+ finish();
115
+ }
116
+
117
+ void finishActivity(String result, int errorCode) {
118
+ Intent intent = new Intent();
119
+ if (errorCode != 0) {
120
+ intent.putExtra("result", "error");
121
+ intent.putExtra("errorDetails", result);
122
+ intent.putExtra("errorCode", String.valueOf(errorCode));
123
+ } else {
124
+ intent.putExtra("result", result);
125
+ }
126
+ setResult(RESULT_OK, intent);
127
+ finish();
128
+ }
129
+ }
@@ -0,0 +1,457 @@
1
+ package ee.forgr.biometric;
2
+
3
+ import android.app.Activity;
4
+ import android.app.KeyguardManager;
5
+ import android.content.Context;
6
+ import android.content.Intent;
7
+ import android.content.SharedPreferences;
8
+ import android.content.pm.PackageManager;
9
+ import android.os.Build;
10
+ import android.security.KeyPairGeneratorSpec;
11
+ import android.security.keystore.KeyGenParameterSpec;
12
+ import android.security.keystore.KeyProperties;
13
+ import android.util.Base64;
14
+ import androidx.activity.result.ActivityResult;
15
+ import androidx.biometric.BiometricManager;
16
+ import com.getcapacitor.JSObject;
17
+ import com.getcapacitor.Plugin;
18
+ import com.getcapacitor.PluginCall;
19
+ import com.getcapacitor.PluginMethod;
20
+ import com.getcapacitor.annotation.ActivityCallback;
21
+ import com.getcapacitor.annotation.CapacitorPlugin;
22
+ import java.io.ByteArrayInputStream;
23
+ import java.io.ByteArrayOutputStream;
24
+ import java.io.IOException;
25
+ import java.security.GeneralSecurityException;
26
+ import java.security.InvalidAlgorithmParameterException;
27
+ import java.security.InvalidKeyException;
28
+ import java.security.Key;
29
+ import java.security.KeyPairGenerator;
30
+ import java.security.KeyStore;
31
+ import java.security.KeyStoreException;
32
+ import java.security.NoSuchAlgorithmException;
33
+ import java.security.NoSuchProviderException;
34
+ import java.security.SecureRandom;
35
+ import java.security.UnrecoverableEntryException;
36
+ import java.security.cert.CertificateException;
37
+ import java.util.ArrayList;
38
+ import javax.crypto.Cipher;
39
+ import javax.crypto.CipherInputStream;
40
+ import javax.crypto.CipherOutputStream;
41
+ import javax.crypto.KeyGenerator;
42
+ import javax.crypto.NoSuchPaddingException;
43
+ import javax.crypto.spec.GCMParameterSpec;
44
+ import javax.crypto.spec.SecretKeySpec;
45
+
46
+ @CapacitorPlugin(name = "NativeBiometric")
47
+ public class NativeBiometric extends Plugin {
48
+
49
+ private BiometricManager biometricManager;
50
+ //protected final static int AUTH_CODE = 0102;
51
+
52
+ private static final int NONE = 0;
53
+ private static final int FINGERPRINT = 3;
54
+ private static final int FACE_AUTHENTICATION = 4;
55
+ private static final int IRIS_AUTHENTICATION = 5;
56
+ private static final int MULTIPLE = 6;
57
+
58
+ private KeyStore keyStore;
59
+ private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
60
+ private static final String TRANSFORMATION = "AES/GCM/NoPadding";
61
+ private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
62
+ private static final String AES_MODE = "AES/ECB/PKCS7Padding";
63
+ private static final byte[] FIXED_IV = new byte[12];
64
+ private static final String ENCRYPTED_KEY = "NativeBiometricKey";
65
+ private static final String NATIVE_BIOMETRIC_SHARED_PREFERENCES =
66
+ "NativeBiometricSharedPreferences";
67
+
68
+ private SharedPreferences encryptedSharedPreferences;
69
+
70
+ private int getAvailableFeature() {
71
+ // default to none
72
+ int type = NONE;
73
+
74
+ // if has fingerprint
75
+ if (
76
+ getContext()
77
+ .getPackageManager()
78
+ .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
79
+ ) {
80
+ type = FINGERPRINT;
81
+ }
82
+
83
+ // if has face auth
84
+ if (
85
+ getContext()
86
+ .getPackageManager()
87
+ .hasSystemFeature(PackageManager.FEATURE_FACE)
88
+ ) {
89
+ // if also has fingerprint
90
+ if (type != NONE) return MULTIPLE;
91
+
92
+ type = FACE_AUTHENTICATION;
93
+ }
94
+
95
+ // if has iris auth
96
+ if (
97
+ getContext()
98
+ .getPackageManager()
99
+ .hasSystemFeature(PackageManager.FEATURE_IRIS)
100
+ ) {
101
+ // if also has fingerprint or face auth
102
+ if (type != NONE) return MULTIPLE;
103
+
104
+ type = IRIS_AUTHENTICATION;
105
+ }
106
+
107
+ return type;
108
+ }
109
+
110
+ @PluginMethod
111
+ public void isAvailable(PluginCall call) {
112
+ JSObject ret = new JSObject();
113
+
114
+ biometricManager = BiometricManager.from(getContext());
115
+ int canAuthenticateResult = biometricManager.canAuthenticate();
116
+
117
+ switch (canAuthenticateResult) {
118
+ case BiometricManager.BIOMETRIC_SUCCESS:
119
+ ret.put("isAvailable", true);
120
+ break;
121
+ default:
122
+ ret.put("isAvailable", false);
123
+ ret.put("errorCode", canAuthenticateResult);
124
+ break;
125
+ }
126
+
127
+ ret.put("biometryType", getAvailableFeature());
128
+ call.resolve(ret);
129
+ }
130
+
131
+ @PluginMethod
132
+ public void verifyIdentity(final PluginCall call) {
133
+ Intent intent = new Intent(getContext(), AuthActivity.class);
134
+
135
+ intent.putExtra("title", call.getString("title", "Authenticate"));
136
+
137
+ if (call.hasOption("subtitle")) intent.putExtra(
138
+ "subtitle",
139
+ call.getString("subtitle")
140
+ );
141
+
142
+ if (call.hasOption("description")) intent.putExtra(
143
+ "description",
144
+ call.getString("description")
145
+ );
146
+
147
+ if (call.hasOption("negativeButtonText")) intent.putExtra(
148
+ "negativeButtonText",
149
+ call.getString("negativeButtonText")
150
+ );
151
+
152
+ if (call.hasOption("maxAttempts")) intent.putExtra(
153
+ "maxAttempts",
154
+ call.getInt("maxAttempts")
155
+ );
156
+
157
+ boolean useFallback = call.getBoolean("useFallback", false);
158
+
159
+ if (useFallback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
160
+ KeyguardManager keyguardManager = (KeyguardManager) getActivity()
161
+ .getSystemService(Context.KEYGUARD_SERVICE);
162
+ useFallback = keyguardManager.isDeviceSecure();
163
+ }
164
+
165
+ intent.putExtra("useFallback", useFallback);
166
+
167
+ startActivityForResult(call, intent, "verifyResult");
168
+ }
169
+
170
+ @PluginMethod
171
+ public void setCredentials(final PluginCall call) {
172
+ String username = call.getString("username", null);
173
+ String password = call.getString("password", null);
174
+ String KEY_ALIAS = call.getString("server", null);
175
+
176
+ if (username != null && password != null && KEY_ALIAS != null) {
177
+ try {
178
+ SharedPreferences.Editor editor = getContext()
179
+ .getSharedPreferences(
180
+ NATIVE_BIOMETRIC_SHARED_PREFERENCES,
181
+ Context.MODE_PRIVATE
182
+ )
183
+ .edit();
184
+ editor.putString("username", encryptString(username, KEY_ALIAS));
185
+ editor.putString("password", encryptString(password, KEY_ALIAS));
186
+ editor.apply();
187
+ call.resolve();
188
+ } catch (GeneralSecurityException e) {
189
+ call.reject("Failed to save credentials", e);
190
+ e.printStackTrace();
191
+ } catch (IOException e) {
192
+ call.reject("Failed to save credentials", e);
193
+ e.printStackTrace();
194
+ }
195
+ } else {
196
+ call.reject("Missing properties");
197
+ }
198
+ }
199
+
200
+ @PluginMethod
201
+ public void getCredentials(final PluginCall call) {
202
+ String KEY_ALIAS = call.getString("server", null);
203
+
204
+ SharedPreferences sharedPreferences = getContext()
205
+ .getSharedPreferences(
206
+ NATIVE_BIOMETRIC_SHARED_PREFERENCES,
207
+ Context.MODE_PRIVATE
208
+ );
209
+ String username = sharedPreferences.getString("username", null);
210
+ String password = sharedPreferences.getString("password", null);
211
+ if (KEY_ALIAS != null) {
212
+ if (username != null && password != null) {
213
+ try {
214
+ JSObject jsObject = new JSObject();
215
+ jsObject.put("username", decryptString(username, KEY_ALIAS));
216
+ jsObject.put("password", decryptString(password, KEY_ALIAS));
217
+ call.resolve(jsObject);
218
+ } catch (GeneralSecurityException e) {
219
+ call.reject("Failed to get credentials", e);
220
+ } catch (IOException e) {
221
+ call.reject("Failed to get credentials", e);
222
+ }
223
+ } else {
224
+ call.reject("No credentials found");
225
+ }
226
+ } else {
227
+ call.reject("No server name was provided");
228
+ }
229
+ }
230
+
231
+ @ActivityCallback
232
+ private void verifyResult(PluginCall call, ActivityResult result) {
233
+ if (result.getResultCode() == Activity.RESULT_OK) {
234
+ Intent data = result.getData();
235
+ if (data.hasExtra("result")) {
236
+ switch (data.getStringExtra("result")) {
237
+ case "success":
238
+ call.resolve();
239
+ break;
240
+ case "failed":
241
+ call.reject(
242
+ data.getStringExtra("errorDetails"),
243
+ data.getStringExtra("errorCode")
244
+ );
245
+ break;
246
+ default:
247
+ call.reject(
248
+ "Verification error: " + data.getStringExtra("result"),
249
+ data.getStringExtra("errorCode")
250
+ );
251
+ break;
252
+ }
253
+ }
254
+ } else {
255
+ call.reject("Something went wrong.");
256
+ }
257
+ }
258
+
259
+ @PluginMethod
260
+ public void deleteCredentials(final PluginCall call) {
261
+ String KEY_ALIAS = call.getString("server", null);
262
+
263
+ if (KEY_ALIAS != null) {
264
+ try {
265
+ getKeyStore().deleteEntry(KEY_ALIAS);
266
+ SharedPreferences.Editor editor = getContext()
267
+ .getSharedPreferences(
268
+ NATIVE_BIOMETRIC_SHARED_PREFERENCES,
269
+ Context.MODE_PRIVATE
270
+ )
271
+ .edit();
272
+ editor.clear();
273
+ editor.apply();
274
+ call.resolve();
275
+ } catch (KeyStoreException e) {
276
+ call.reject("Failed to delete", e);
277
+ } catch (CertificateException e) {
278
+ call.reject("Failed to delete", e);
279
+ } catch (NoSuchAlgorithmException e) {
280
+ call.reject("Failed to delete", e);
281
+ } catch (IOException e) {
282
+ call.reject("Failed to delete", e);
283
+ }
284
+ } else {
285
+ call.reject("No server name was provided");
286
+ }
287
+ }
288
+
289
+ private String encryptString(String stringToEncrypt, String KEY_ALIAS)
290
+ throws GeneralSecurityException, IOException {
291
+ Cipher cipher;
292
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
293
+ cipher = Cipher.getInstance(TRANSFORMATION);
294
+ cipher.init(
295
+ Cipher.ENCRYPT_MODE,
296
+ getKey(KEY_ALIAS),
297
+ new GCMParameterSpec(128, FIXED_IV)
298
+ );
299
+ } else {
300
+ cipher = Cipher.getInstance(AES_MODE, "BC");
301
+ cipher.init(Cipher.ENCRYPT_MODE, getKey(KEY_ALIAS));
302
+ }
303
+ byte[] encodedBytes = cipher.doFinal(stringToEncrypt.getBytes("UTF-8"));
304
+ return Base64.encodeToString(encodedBytes, Base64.DEFAULT);
305
+ }
306
+
307
+ private String decryptString(String stringToDecrypt, String KEY_ALIAS)
308
+ throws GeneralSecurityException, IOException {
309
+ byte[] encryptedData = Base64.decode(stringToDecrypt, Base64.DEFAULT);
310
+
311
+ Cipher cipher;
312
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
313
+ cipher = Cipher.getInstance(TRANSFORMATION);
314
+ cipher.init(
315
+ Cipher.DECRYPT_MODE,
316
+ getKey(KEY_ALIAS),
317
+ new GCMParameterSpec(128, FIXED_IV)
318
+ );
319
+ } else {
320
+ cipher = Cipher.getInstance(AES_MODE, "BC");
321
+ cipher.init(Cipher.DECRYPT_MODE, getKey(KEY_ALIAS));
322
+ }
323
+ byte[] decryptedData = cipher.doFinal(encryptedData);
324
+ return new String(decryptedData, "UTF-8");
325
+ }
326
+
327
+ private Key generateKey(String KEY_ALIAS)
328
+ throws GeneralSecurityException, IOException {
329
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
330
+ KeyGenerator generator = KeyGenerator.getInstance(
331
+ KeyProperties.KEY_ALGORITHM_AES,
332
+ ANDROID_KEY_STORE
333
+ );
334
+ generator.init(
335
+ new KeyGenParameterSpec.Builder(
336
+ KEY_ALIAS,
337
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
338
+ )
339
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
340
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
341
+ .setRandomizedEncryptionRequired(false)
342
+ .build()
343
+ );
344
+ return generator.generateKey();
345
+ } else {
346
+ return getAESKey(KEY_ALIAS);
347
+ }
348
+ }
349
+
350
+ private Key getKey(String KEY_ALIAS)
351
+ throws GeneralSecurityException, IOException {
352
+ KeyStore.SecretKeyEntry secretKeyEntry =
353
+ (KeyStore.SecretKeyEntry) getKeyStore().getEntry(KEY_ALIAS, null);
354
+ if (secretKeyEntry != null) {
355
+ return secretKeyEntry.getSecretKey();
356
+ }
357
+ return generateKey(KEY_ALIAS);
358
+ }
359
+
360
+ private KeyStore getKeyStore()
361
+ throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
362
+ if (keyStore != null) {
363
+ return keyStore;
364
+ } else {
365
+ keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
366
+ keyStore.load(null);
367
+ return keyStore;
368
+ }
369
+ }
370
+
371
+ private Key getAESKey(String KEY_ALIAS)
372
+ throws CertificateException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, UnrecoverableEntryException, IOException, InvalidAlgorithmParameterException {
373
+ SharedPreferences sharedPreferences = getContext()
374
+ .getSharedPreferences("", Context.MODE_PRIVATE);
375
+ String encryptedKeyB64 = sharedPreferences.getString(ENCRYPTED_KEY, null);
376
+ if (encryptedKeyB64 == null) {
377
+ byte[] key = new byte[16];
378
+ SecureRandom secureRandom = new SecureRandom();
379
+ secureRandom.nextBytes(key);
380
+ byte[] encryptedKey = rsaEncrypt(key, KEY_ALIAS);
381
+ encryptedKeyB64 = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
382
+ SharedPreferences.Editor edit = sharedPreferences.edit();
383
+ edit.putString(ENCRYPTED_KEY, encryptedKeyB64);
384
+ edit.apply();
385
+ return new SecretKeySpec(key, "AES");
386
+ } else {
387
+ byte[] encryptedKey = Base64.decode(encryptedKeyB64, Base64.DEFAULT);
388
+ byte[] key = rsaDecrypt(encryptedKey, KEY_ALIAS);
389
+ return new SecretKeySpec(key, "AES");
390
+ }
391
+ }
392
+
393
+ private KeyStore.PrivateKeyEntry getPrivateKeyEntry(String KEY_ALIAS)
394
+ throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, CertificateException, KeyStoreException, IOException, UnrecoverableEntryException {
395
+ KeyStore.PrivateKeyEntry privateKeyEntry =
396
+ (KeyStore.PrivateKeyEntry) getKeyStore().getEntry(KEY_ALIAS, null);
397
+
398
+ if (privateKeyEntry == null) {
399
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
400
+ KeyProperties.KEY_ALGORITHM_RSA,
401
+ ANDROID_KEY_STORE
402
+ );
403
+ keyPairGenerator.initialize(
404
+ new KeyPairGeneratorSpec.Builder(getContext())
405
+ .setAlias(KEY_ALIAS)
406
+ .build()
407
+ );
408
+ keyPairGenerator.generateKeyPair();
409
+ }
410
+
411
+ return privateKeyEntry;
412
+ }
413
+
414
+ private byte[] rsaEncrypt(byte[] secret, String KEY_ALIAS)
415
+ throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableEntryException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
416
+ KeyStore.PrivateKeyEntry privateKeyEntry = getPrivateKeyEntry(KEY_ALIAS);
417
+ // Encrypt the text
418
+ Cipher inputCipher = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL");
419
+ inputCipher.init(
420
+ Cipher.ENCRYPT_MODE,
421
+ privateKeyEntry.getCertificate().getPublicKey()
422
+ );
423
+
424
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
425
+ CipherOutputStream cipherOutputStream = new CipherOutputStream(
426
+ outputStream,
427
+ inputCipher
428
+ );
429
+ cipherOutputStream.write(secret);
430
+ cipherOutputStream.close();
431
+
432
+ byte[] vals = outputStream.toByteArray();
433
+ return vals;
434
+ }
435
+
436
+ private byte[] rsaDecrypt(byte[] encrypted, String KEY_ALIAS)
437
+ throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException, CertificateException, InvalidAlgorithmParameterException {
438
+ KeyStore.PrivateKeyEntry privateKeyEntry = getPrivateKeyEntry(KEY_ALIAS);
439
+ Cipher output = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL");
440
+ output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());
441
+ CipherInputStream cipherInputStream = new CipherInputStream(
442
+ new ByteArrayInputStream(encrypted),
443
+ output
444
+ );
445
+ ArrayList<Byte> values = new ArrayList<>();
446
+ int nextByte;
447
+ while ((nextByte = cipherInputStream.read()) != -1) {
448
+ values.add((byte) nextByte);
449
+ }
450
+
451
+ byte[] bytes = new byte[values.size()];
452
+ for (int i = 0; i < bytes.length; i++) {
453
+ bytes[i] = values.get(i).byteValue();
454
+ }
455
+ return bytes;
456
+ }
457
+ }
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+ xmlns:app="http://schemas.android.com/apk/res-auto"
4
+ xmlns:tools="http://schemas.android.com/tools"
5
+ android:layout_width="match_parent"
6
+ android:layout_height="match_parent"
7
+ tools:context="ee.forgr.biometric.AuthAcitivy">
8
+
9
+
10
+
11
+
12
+ </androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+ xmlns:app="http://schemas.android.com/apk/res-auto"
4
+ xmlns:tools="http://schemas.android.com/tools"
5
+ android:layout_width="match_parent"
6
+ android:layout_height="match_parent"
7
+ tools:context="com.getcapacitor.BridgeActivity"
8
+ >
9
+
10
+ <WebView
11
+ android:id="@+id/webview"
12
+ android:layout_width="fill_parent"
13
+ android:layout_height="fill_parent" />
14
+
15
+ </androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,28 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <navigation xmlns:android="http://schemas.android.com/apk/res/android"
3
+ xmlns:app="http://schemas.android.com/apk/res-auto"
4
+ xmlns:tools="http://schemas.android.com/tools"
5
+ android:id="@+id/nav_graph"
6
+ app:startDestination="@id/FirstFragment">
7
+
8
+ <fragment
9
+ android:id="@+id/FirstFragment"
10
+ android:name="ee.forgr.biometric.FirstFragment"
11
+ android:label="@string/first_fragment_label"
12
+ tools:layout="@layout/fragment_first">
13
+
14
+ <action
15
+ android:id="@+id/action_FirstFragment_to_SecondFragment"
16
+ app:destination="@id/SecondFragment" />
17
+ </fragment>
18
+ <fragment
19
+ android:id="@+id/SecondFragment"
20
+ android:name="ee.forgr.biometric.SecondFragment"
21
+ android:label="@string/second_fragment_label"
22
+ tools:layout="@layout/fragment_second">
23
+
24
+ <action
25
+ android:id="@+id/action_SecondFragment_to_FirstFragment"
26
+ app:destination="@id/FirstFragment" />
27
+ </fragment>
28
+ </navigation>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ </resources>
@@ -0,0 +1,3 @@
1
+ <resources>
2
+ <dimen name="fab_margin">16dp</dimen>
3
+ </resources>
@@ -0,0 +1,12 @@
1
+ <resources>
2
+ <string name="my_string">Just a simple string</string>
3
+ <string name="title_activity_auth_activity">AuthActivity</string>
4
+ <!-- Strings used for fragments for navigation -->
5
+ <string name="first_fragment_label">First Fragment</string>
6
+ <string name="second_fragment_label">Second Fragment</string>
7
+ <string name="next">Next</string>
8
+ <string name="previous">Previous</string>
9
+
10
+ <string name="hello_first_fragment">Hello first fragment</string>
11
+ <string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
12
+ </resources>
@@ -0,0 +1,16 @@
1
+ <resources>
2
+
3
+ <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
4
+
5
+ <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
6
+
7
+ <style name="AppTheme.Transparent" parent="Theme.AppCompat.NoActionBar">
8
+ <item name="android:windowIsTranslucent">true</item>
9
+ <item name="android:windowBackground">@android:color/transparent</item>
10
+ <item name="android:windowContentOverlay">@null</item>
11
+ <item name="android:windowNoTitle">true</item>
12
+ <item name="android:windowIsFloating">true</item>
13
+ <item name="android:backgroundDimEnabled">false</item>
14
+ </style>
15
+
16
+ </resources>