@capgo/capacitor-native-biometric 8.3.4 → 8.3.6

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.
@@ -4,17 +4,34 @@ import android.content.Intent;
4
4
  import android.os.Build;
5
5
  import android.os.Bundle;
6
6
  import android.os.Handler;
7
+ import android.security.keystore.KeyGenParameterSpec;
8
+ import android.security.keystore.KeyPermanentlyInvalidatedException;
9
+ import android.security.keystore.KeyProperties;
7
10
  import androidx.annotation.NonNull;
8
11
  import androidx.appcompat.app.AppCompatActivity;
9
12
  import androidx.biometric.BiometricManager;
10
13
  import androidx.biometric.BiometricPrompt;
11
14
  import ee.forgr.biometric.capacitornativebiometric.R;
15
+ import java.io.IOException;
16
+ import java.security.GeneralSecurityException;
17
+ import java.security.KeyStore;
18
+ import java.security.KeyStoreException;
19
+ import java.security.NoSuchAlgorithmException;
20
+ import java.security.UnrecoverableKeyException;
21
+ import java.security.cert.CertificateException;
12
22
  import java.util.Objects;
13
23
  import java.util.concurrent.Executor;
24
+ import javax.crypto.Cipher;
25
+ import javax.crypto.KeyGenerator;
26
+ import javax.crypto.SecretKey;
14
27
 
15
28
  public class AuthActivity extends AppCompatActivity {
16
29
 
30
+ private static final String AUTH_KEY_ALIAS = "NativeBiometricAuthKey";
31
+ private static final String AUTH_TRANSFORMATION = "AES/GCM/NoPadding";
32
+
17
33
  private BiometricPrompt biometricPrompt;
34
+ private Cipher authCipher;
18
35
  private int maxAttempts;
19
36
  private int counter = 0;
20
37
 
@@ -83,6 +100,10 @@ public class AuthActivity extends AppCompatActivity {
83
100
  @Override
84
101
  public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
85
102
  super.onAuthenticationSucceeded(result);
103
+ if (!validateCryptoObject(result)) {
104
+ finishActivity("error", 10, "Biometric security check failed");
105
+ return;
106
+ }
86
107
  finishActivity();
87
108
  }
88
109
 
@@ -99,7 +120,12 @@ public class AuthActivity extends AppCompatActivity {
99
120
  }
100
121
  );
101
122
 
102
- biometricPrompt.authenticate(promptInfo);
123
+ BiometricPrompt.CryptoObject cryptoObject = createCryptoObject();
124
+ if (cryptoObject == null) {
125
+ finishActivity("error", 0, "Biometric crypto object unavailable");
126
+ return;
127
+ }
128
+ biometricPrompt.authenticate(promptInfo, cryptoObject);
103
129
  }
104
130
 
105
131
  void finishActivity() {
@@ -119,6 +145,95 @@ public class AuthActivity extends AppCompatActivity {
119
145
  finish();
120
146
  }
121
147
 
148
+ private BiometricPrompt.CryptoObject createCryptoObject() {
149
+ try {
150
+ authCipher = createCipher();
151
+ return new BiometricPrompt.CryptoObject(authCipher);
152
+ } catch (GeneralSecurityException | IOException e) {
153
+ return null;
154
+ }
155
+ }
156
+
157
+ private Cipher createCipher() throws GeneralSecurityException, IOException {
158
+ SecretKey secretKey = getOrCreateSecretKey();
159
+ Cipher cipher = Cipher.getInstance(AUTH_TRANSFORMATION);
160
+ try {
161
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
162
+ } catch (KeyPermanentlyInvalidatedException e) {
163
+ deleteSecretKey();
164
+ secretKey = getOrCreateSecretKey();
165
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
166
+ }
167
+ return cipher;
168
+ }
169
+
170
+ private SecretKey getOrCreateSecretKey() throws GeneralSecurityException, IOException {
171
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
172
+ try {
173
+ keyStore.load(null);
174
+ } catch (CertificateException e) {
175
+ throw new GeneralSecurityException("Failed to load AndroidKeyStore", e);
176
+ }
177
+ if (!keyStore.containsAlias(AUTH_KEY_ALIAS)) {
178
+ generateSecretKey();
179
+ }
180
+ try {
181
+ return (SecretKey) keyStore.getKey(AUTH_KEY_ALIAS, null);
182
+ } catch (UnrecoverableKeyException e) {
183
+ throw new GeneralSecurityException("Failed to retrieve biometric auth key", e);
184
+ }
185
+ }
186
+
187
+ private void generateSecretKey() throws GeneralSecurityException {
188
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
189
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
190
+ AUTH_KEY_ALIAS,
191
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
192
+ )
193
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
194
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
195
+ .setUserAuthenticationRequired(true);
196
+
197
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
198
+ builder.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG);
199
+ } else {
200
+ builder.setUserAuthenticationValidityDurationSeconds(1);
201
+ }
202
+
203
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
204
+ builder.setInvalidatedByBiometricEnrollment(true);
205
+ }
206
+
207
+ keyGenerator.init(builder.build());
208
+ keyGenerator.generateKey();
209
+ }
210
+
211
+ private void deleteSecretKey() throws GeneralSecurityException, IOException {
212
+ try {
213
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
214
+ keyStore.load(null);
215
+ keyStore.deleteEntry(AUTH_KEY_ALIAS);
216
+ } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
217
+ throw new GeneralSecurityException("Failed to delete biometric auth key", e);
218
+ }
219
+ }
220
+
221
+ private boolean validateCryptoObject(BiometricPrompt.AuthenticationResult result) {
222
+ BiometricPrompt.CryptoObject cryptoObject = result.getCryptoObject();
223
+ if (cryptoObject == null || cryptoObject.getCipher() == null) {
224
+ return false;
225
+ }
226
+ if (authCipher != null && cryptoObject.getCipher() != authCipher) {
227
+ return false;
228
+ }
229
+ try {
230
+ cryptoObject.getCipher().doFinal(new byte[] { 0x00 });
231
+ return true;
232
+ } catch (GeneralSecurityException | IllegalStateException e) {
233
+ return false;
234
+ }
235
+ }
236
+
122
237
  /**
123
238
  * Convert Auth Error Codes to plugin expected Biometric Auth Errors (in README.md)
124
239
  * This way both iOS and Android return the same error codes for the same authentication failure reasons.
@@ -11,7 +11,7 @@ import LocalAuthentication
11
11
 
12
12
  @objc(NativeBiometricPlugin)
13
13
  public class NativeBiometricPlugin: CAPPlugin, CAPBridgedPlugin {
14
- private let pluginVersion: String = "8.3.4"
14
+ private let pluginVersion: String = "8.3.6"
15
15
  public let identifier = "NativeBiometricPlugin"
16
16
  public let jsName = "NativeBiometric"
17
17
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-biometric",
3
- "version": "8.3.4",
3
+ "version": "8.3.6",
4
4
  "description": "This plugin gives access to the native biometric apis for android and iOS",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",