Skip to content

Commit 825b1f2

Browse files
Merge pull request #3 from NMA-SPMS/master
Add android implementation
2 parents e9659e4 + 20ad327 commit 825b1f2

File tree

3 files changed

+204
-8
lines changed

3 files changed

+204
-8
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ Want a nicer guide than these raw code samples? Read [Nic Raboy's blog post abou
3535

3636
```js
3737
touchid.verifyFingerprint({
38-
message: 'Scan yer finger' // optional, shown in the fingerprint dialog (default: 'Scan your finger').
38+
message: 'Scan yer finger', // optional, shown in the fingerprint dialog (default on ios: 'Scan your finger', default on android: 'We are doing this for your own security.').
39+
title: 'Android title' // optional title for android,(default: 'Please confirm your credentials.')
3940
}).then(
4041
function() {
4142
console.log("Fingerprint was OK");
@@ -48,6 +49,8 @@ Want a nicer guide than these raw code samples? Read [Nic Raboy's blog post abou
4849

4950
### function: verifyFingerprintWithCustomFallback
5051

52+
#### Note: not implemented in android yet
53+
5154
```js
5255
touchid.verifyFingerprintWithCustomFallback({
5356
message: 'Scan yer finger', // optional, shown in the fingerprint dialog (default: 'Scan your finger').
@@ -71,6 +74,8 @@ So instead of checking the fingerprint after `available` add another check.
7174
In case `didFingerprintDatabaseChange` returns `true` you probably want to re-authenticate your user
7275
before accepting valid fingerprints again.
7376

77+
#### Note: not implemented in android yet
78+
7479
```js
7580
touchid.available().then(
7681
function(avail) {

touchid.android.js

Lines changed: 190 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,196 @@
1-
exports.available = function () {
1+
2+
var app = require('application');
3+
var utils = require('utils/utils');
4+
5+
var KeyguardManager = android.app.KeyguardManager;
6+
var ActivityCompat = android.support.v4.app.ActivityCompat;
7+
var Manifest = android.Manifest;
8+
var PackageManager = android.content.pm.PackageManager;
9+
var KeyStore = java.security.KeyStore;
10+
var Cipher = javax.crypto.Cipher;
11+
var KeyGenerator = javax.crypto.KeyGenerator;
12+
var KeyProperties = android.security.keystore.KeyProperties;
13+
var SecretKey = javax.crypto.SecretKey;
14+
var KeyGenParameterSpec = android.security.keystore.KeyGenParameterSpec;
15+
16+
17+
//var FingerprintManager;
18+
19+
//var KEYGUARD_SYSTEM_SERVICE = "keyguard";
20+
var KEY_NAME = 'fingerprintscanner';
21+
var SECRET_BYTE_ARRAY = Array.create('byte', 16);
22+
var REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1;
23+
var AUTHENTICATION_DURATION = 15; // in seconds
24+
25+
var activity = null;
26+
27+
var keyguardManager = null;
28+
29+
var title = null;
30+
var message = null;
31+
32+
var available = function () {
33+
return new Promise(function (resolve, reject) {
34+
35+
keyguardManager = utils.ad.getApplicationContext().getSystemService("keyguard");
36+
37+
if (!keyguardManager.isKeyguardSecure()) {
38+
resolve(false);
39+
return;
40+
}
41+
42+
if (android.os.Build.VERSION.SDK_INT >= 23) { //23 == android.os.BUILD.M
43+
//Fingerprint API only available on from Android 6.0 (M)
44+
var fingerprintManager = utils.ad.getApplicationContext().getSystemService("fingerprint");
45+
if (!fingerprintManager.isHardwareDetected()) {
46+
// Device doesn't support fingerprint authentication
47+
reject('Device doesn\'t support fingerprint authentication');
48+
} else if (!fingerprintManager.hasEnrolledFingerprints()) {
49+
// User hasn't enrolled any fingerprints to authenticate with
50+
reject('User hasn\'t enrolled any fingerprints to authenticate with');
51+
} else {
52+
resolve(true);
53+
}
54+
}else{
55+
reject('Your api version don\'t support fingerprint auth');
56+
}
57+
58+
resolve(true);
59+
});
60+
};
61+
62+
var didFingerprintDatabaseChange = function () {
63+
return new Promise(function (resolve, reject) {
64+
resolve('Not yet implemented!');
65+
});
66+
};
67+
68+
69+
var verifyFingerprint = function (arg) {
70+
if(arg){
71+
if(arg.title) title = arg.title;
72+
if(arg.message) message = arg.message;
73+
}
274
return new Promise(function (resolve, reject) {
3-
resolve(false);
75+
activity = app.android.foregroundActivity;
76+
try {
77+
activity.onActivityResult = function onActivityResult(requestCode, resultCode, data) {
78+
if (requestCode === REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
79+
if (resultCode === android.app.Activity.RESULT_OK) {
80+
// the user has just authenticated via the ConfirmDeviceCredential activity
81+
resolve('Congrats! You have just been authenticated successfully!');
82+
} else {
83+
// the user has quit the activity without providing credentials
84+
reject('The last authentication attempt was cancelled.');
85+
}
86+
}
87+
};
88+
89+
if (keyguardManager == null) {
90+
reject('Sorry, your device does not support keyguardManager.');
91+
}
92+
if (keyguardManager && !keyguardManager.isKeyguardSecure()) {
93+
reject('Secure lock screen hasn\'t been set up.\n Go to "Settings -> Security -> Screenlock" to set up a lock screen.');
94+
}
95+
96+
createKey();
97+
tryEncrypt();
98+
99+
} catch (ex) {
100+
console.log("Error in verifyFingerprint: " + ex);
101+
reject(ex);
102+
}
4103
});
5104
};
6105

7-
// shouldn't be called anyway because 'available' returned false
8-
exports.verifyFingerprint = function () {
106+
107+
var verifyFingerprintWithCustomFallback = function (arg) {
9108
return new Promise(function (resolve, reject) {
10-
reject("Not available");
109+
try {
110+
resolve('Not implemented');
111+
} catch (ex) {
112+
console.log("Error in verifyFingerprint: " + ex);
113+
reject(ex);
114+
}
11115
});
12-
};
116+
};
117+
118+
/**
119+
* Creates a symmetric key in the Android Key Store which can only be used after the user has
120+
* authenticated with device credentials within the last X seconds.
121+
*/
122+
function createKey() {
123+
try {
124+
var keyStore = KeyStore.getInstance('AndroidKeyStore');
125+
keyStore.load(null);
126+
var keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, 'AndroidKeyStore');
127+
128+
keyGenerator.init(
129+
new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
130+
.setBlockModes([KeyProperties.BLOCK_MODE_CBC])
131+
.setUserAuthenticationRequired(true)
132+
.setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION)
133+
.setEncryptionPaddings([KeyProperties.ENCRYPTION_PADDING_PKCS7])
134+
.build()
135+
);
136+
keyGenerator.generateKey();
137+
} catch (error) {
138+
// checks if the AES algorithm is implemented by the AndroidKeyStore
139+
if ((error.nativeException + '').indexOf('java.security.NoSuchAlgorithmException:') > -1) {
140+
//You need a device with API level >= 23 in order to detect if the user has already been authenticated in the last x seconds.
141+
}
142+
}
143+
}
144+
145+
146+
function tryEncrypt() {
147+
try {
148+
var keyStore = KeyStore.getInstance('AndroidKeyStore');
149+
keyStore.load(null);
150+
var secretKey = keyStore.getKey(KEY_NAME, null);
151+
152+
var cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" +
153+
KeyProperties.BLOCK_MODE_CBC + "/" +
154+
KeyProperties.ENCRYPTION_PADDING_PKCS7);
155+
156+
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
157+
cipher.doFinal(SECRET_BYTE_ARRAY);
158+
159+
return true;
160+
} catch (error) {
161+
if ((error.nativeException + '').indexOf('android.security.keystore.UserNotAuthenticatedException') > -1) {
162+
// the user must provide their credentials in order to proceed
163+
showAuthenticationScreen();
164+
} else if((error.nativeException + '').indexOf('android.security.keystore.KeyPermanentlyInvalidatedException') > -1){
165+
//Invalid fingerprint
166+
console.log(error);
167+
} else {
168+
console.log(error);
169+
}
170+
171+
return false;
172+
}
173+
}
174+
175+
/**
176+
* Starts the built-in Android ConfirmDeviceCredential activity.
177+
*/
178+
function showAuthenticationScreen() {
179+
// title and description are optional, if you want the defaults,
180+
// you must pass nulls to the factory function
181+
182+
if(title === null) title = 'Please confirm your credentials.';
183+
if(message === null) message = 'We are doing this for your own security.';
184+
var intent = keyguardManager.createConfirmDeviceCredentialIntent(title, message);
185+
186+
if (intent != null) {
187+
activity.startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
188+
}
189+
}
190+
191+
192+
193+
exports.available = available;
194+
exports.didFingerprintDatabaseChange = didFingerprintDatabaseChange;
195+
exports.verifyFingerprint = verifyFingerprint;
196+
exports.verifyFingerprintWithCustomFallback = verifyFingerprintWithCustomFallback;

touchid.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ declare module "nativescript-touchid" {
22

33
export interface VerifyFingerprintOptions {
44
/**
5-
* The optional message in the fingerprint dialog.
5+
* The optional message in the fingerprint dialog on ios and page description on android.
66
* Default: 'Scan your finger'.
77
*/
88
message?: string;
9+
10+
/**
11+
* The optional title in the fingerprint page for android.
12+
* Default: 'We are doing this for your own security'.
13+
*/
14+
15+
title?: string;
916
}
1017

1118
export interface verifyFingerprintWithCustomFallbackOptions {

0 commit comments

Comments
 (0)