import * as app from "tns-core-modules/application" ; import { AndroidActivityResultEventData } from "tns-core-modules/application" ; import { ad as androidUtils } from "tns-core-modules/utils/utils" ; import { BiometricIDAvailableResult , ERROR_CODES , FingerprintAuthApi , VerifyFingerprintOptions , VerifyFingerprintWithCustomFallbackOptions } from "./fingerprint-auth.common" ; declare const com ; const KEY_NAME = "fingerprintauth" ; const SECRET_BYTE_ARRAY = Array . create ( "byte" , 16 ) ; const REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 788 ; export class FingerprintAuth implements FingerprintAuthApi { private keyguardManager : android . app . KeyguardManager ; private fingerPrintManager : any ; constructor ( ) { this . keyguardManager = androidUtils . getApplicationContext ( ) . getSystemService ( "keyguard" ) ; } available ( ) { return new Promise ( ( resolve , reject ) => { try { if ( ! this . keyguardManager || ! this . keyguardManager . isKeyguardSecure ( ) ) { resolve ( { any : false } ) ; return ; } if ( android . os . Build . VERSION . SDK_INT < 23 ) { reject ( `Your api version doesn't support fingerprint authentication` ) ; return ; } const fingerprintManager = androidUtils . getApplicationContext ( ) . getSystemService ( "fingerprint" ) as android . hardware . fingerprint . FingerprintManager ; if ( ! fingerprintManager || ! fingerprintManager . isHardwareDetected ( ) ) { reject ( `Device doesn't support fingerprint authentication` ) ; } else if ( ! fingerprintManager . hasEnrolledFingerprints ( ) ) { if ( this . keyguardManager . isDeviceSecure ( ) ) { resolve ( { any : true , touch : false } ) ; } else { reject ( `User hasn't enrolled any fingerprints to authenticate with` ) ; } } else { resolve ( { any : true , touch : true } ) ; } } catch ( ex ) { console . log ( `fingerprint-auth.available: ${ ex }` ) ; reject ( ex ) ; } } ) ; } didFingerprintDatabaseChange ( ) { return new Promise ( ( resolve , reject ) => { resolve ( false ) ; } ) ; } private verifyWithCustomAndroidUI ( resolve , reject , authenticationCallback ) { this . fingerPrintManager . authenticate ( authenticationCallback , this . getActivity ( ) . getSupportFragmentManager ( ) ) ; } verifyFingerprint ( options ) { return new Promise ( ( resolve , reject ) => { try { const hasSupportFragment = this . getActivity ( ) . getSupportFragmentManager !== undefined ; if ( options . useCustomAndroidUI && ! hasSupportFragment ) { reject ( { code : ERROR_CODES . DEVELOPER_ERROR , message : "Custom Fingerprint UI requires changes to AndroidManifest.xml. See the nativescript-fingerprint-auth documentation." } ) ; } else if ( options . useCustomAndroidUI && hasSupportFragment ) { if ( ! this . fingerPrintManager ) { this . fingerPrintManager = new com . jesusm . kfingerprintmanager . KFingerprintManager ( androidUtils . getApplicationContext ( ) , KEY_NAME ) ; } const that = this ; const callback = new com . jesusm . kfingerprintmanager . KFingerprintManager . AuthenticationCallback ( { attempts : 0 , onAuthenticationFailedWithHelp ( help ) { if ( ++ this . attempts < 3 ) { setTimeout ( ( ) => that . verifyWithCustomAndroidUI ( resolve , reject , this ) , 50 ) ; } else { reject ( { code : ERROR_CODES . RECOVERABLE_ERROR , message : help } ) ; } } , onAuthenticationSuccess ( ) { resolve ( ) ; } , onSuccessWithManualPassword ( password ) { resolve ( password ) ; } , onFingerprintNotRecognized ( ) { if ( ++ this . attempts < 3 ) { setTimeout ( ( ) => that . verifyWithCustomAndroidUI ( resolve , reject , this ) , 50 ) ; } else { reject ( { code : ERROR_CODES . NOT_RECOGNIZED , message : "Fingerprint not recognized." } ) ; } } , onFingerprintNotAvailable ( ) { reject ( { code : ERROR_CODES . NOT_CONFIGURED , message : 'Secure lock screen hasn\'t been set up.\n Go to "Settings -> Security -> Screenlock" to set up a lock screen.' } ) ; } , onCancelled ( ) { reject ( { code : ERROR_CODES . PASSWORD_FALLBACK_SELECTED , message : "Cancelled by user" } ) ; } } ) ; this . verifyWithCustomAndroidUI ( resolve , reject , callback ) ; } else { const onActivityResult = ( data ) => { if ( data . requestCode === REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS ) { if ( data . resultCode === android . app . Activity . RESULT_OK ) { resolve ( ) ; } else { reject ( { code : ERROR_CODES . USER_CANCELLED , message : "User cancelled." } ) ; } } app . android . off ( app . AndroidApplication . activityResultEvent , onActivityResult ) ; } ; app . android . on ( app . AndroidApplication . activityResultEvent , onActivityResult ) ; if ( ! this . keyguardManager ) { reject ( { code : ERROR_CODES . NOT_AVAILABLE , message : "Keyguard manager not available." } ) ; } if ( this . keyguardManager && ! this . keyguardManager . isKeyguardSecure ( ) ) { reject ( { code : ERROR_CODES . NOT_CONFIGURED , message : 'Secure lock screen hasn\'t been set up.\n Go to "Settings -> Security -> Screenlock" to set up a lock screen.' } ) ; } FingerprintAuth . createKey ( options ) ; const tryEncryptResult = this . tryEncrypt ( options ) ; if ( tryEncryptResult === undefined ) { } else if ( tryEncryptResult === true ) { resolve ( ) ; } else { reject ( { code : ERROR_CODES . UNEXPECTED_ERROR , message : "See the console for error logs." } ) ; } } } catch ( ex ) { console . log ( `Error in fingerprint-auth.verifyFingerprint: ${ ex }` ) ; reject ( { code : ERROR_CODES . UNEXPECTED_ERROR , message : ex } ) ; } } ) ; } verifyFingerprintWithCustomFallback ( options ) { return this . verifyFingerprint ( options ) ; } close ( ) { const fragmentManager = this . getActivity ( ) . getSupportFragmentManager ( ) ; const fragmentTag = "KFingerprintManager:fingerprintDialog" ; const fragment = fragmentManager . findFragmentByTag ( fragmentTag ) ; if ( fragment ) { fragmentManager . beginTransaction ( ) . remove ( fragment ) . commit ( ) ; } else { } } /**
     * Creates a symmetric key in the Android Key Store which can only be used after the user has
     * authenticated with device credentials within the last X seconds.
     */ private static createKey ( options ) { try { const keyStore = java . security . KeyStore . getInstance ( "AndroidKeyStore" ) ; keyStore . load ( null ) ; const keyGenerator = javax . crypto . KeyGenerator . getInstance ( android . security . keystore . KeyProperties . KEY_ALGORITHM_AES , "AndroidKeyStore" ) ; keyGenerator . init ( new android . security . keystore . KeyGenParameterSpec . Builder ( KEY_NAME , android . security . keystore . KeyProperties . PURPOSE_ENCRYPT | android . security . keystore . KeyProperties . PURPOSE_DECRYPT ) . setBlockModes ( [ android . security . keystore . KeyProperties . BLOCK_MODE_CBC ] ) . setUserAuthenticationRequired ( true ) . setUserAuthenticationValidityDurationSeconds ( options && options . authenticationValidityDuration ? options . authenticationValidityDuration : 5 ) . setEncryptionPaddings ( [ android . security . keystore . KeyProperties . ENCRYPTION_PADDING_PKCS7 ] ) . build ( ) ) ; keyGenerator . generateKey ( ) ; } catch ( error ) { if ( `${ error . nativeException }` . indexOf ( "java.security.NoSuchAlgorithmException:" ) > - 1 ) { } } } private tryEncrypt ( options ) { try { const keyStore = java . security . KeyStore . getInstance ( "AndroidKeyStore" ) ; keyStore . load ( null ) ; const secretKey = keyStore . getKey ( KEY_NAME , null ) ; const cipher = javax . crypto . Cipher . getInstance ( `${ android . security . keystore . KeyProperties . KEY_ALGORITHM_AES }/${ android . security . keystore . KeyProperties . BLOCK_MODE_CBC }/${ android . security . keystore . KeyProperties . ENCRYPTION_PADDING_PKCS7 }` ) ; cipher . init ( javax . crypto . Cipher . ENCRYPT_MODE , secretKey ) ; cipher . doFinal ( SECRET_BYTE_ARRAY ) ; return true ; } catch ( error ) { if ( `${ error . nativeException }` . indexOf ( "android.security.keystore.UserNotAuthenticatedException" ) > - 1 ) { this . showAuthenticationScreen ( options ) ; return undefined ; } else if ( `${ error . nativeException }` . indexOf ( "android.security.keystore.KeyPermanentlyInvalidatedException" ) > - 1 ) { console . log ( error ) ; } else { console . log ( error ) ; } return false ; } } /**
     * Starts the built-in Android ConfirmDeviceCredential activity.
     */ private showAuthenticationScreen ( options ) { const intent = ( this . keyguardManager as any ) . createConfirmDeviceCredentialIntent ( options && options . title ? options . title : null , options && options . message ? options . message : null ) ; if ( intent !== null ) { this . getActivity ( ) . startActivityForResult ( intent , REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS ) ; } } private getActivity ( ) { return app . android . foregroundActivity || app . android . startActivity ; } }