KeyStore.java revision e35d49f0d2853b79470ec890113bf4dcef03ab88
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.security;
18
19import android.app.ActivityThread;
20import android.app.Application;
21import android.app.KeyguardManager;
22import com.android.org.conscrypt.NativeConstants;
23
24import android.content.Context;
25import android.hardware.fingerprint.FingerprintManager;
26import android.os.Binder;
27import android.os.IBinder;
28import android.os.Process;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.os.UserHandle;
32import android.security.keymaster.ExportResult;
33import android.security.keymaster.KeyCharacteristics;
34import android.security.keymaster.KeymasterArguments;
35import android.security.keymaster.KeymasterBlob;
36import android.security.keymaster.KeymasterDefs;
37import android.security.keymaster.OperationResult;
38import android.security.keystore.KeyExpiredException;
39import android.security.keystore.KeyNotYetValidException;
40import android.security.keystore.KeyPermanentlyInvalidatedException;
41import android.security.keystore.KeyProperties;
42import android.security.keystore.UserNotAuthenticatedException;
43import android.util.Log;
44
45import java.security.InvalidKeyException;
46import java.util.List;
47import java.util.Locale;
48
49/**
50 * @hide This should not be made public in its present form because it
51 * assumes that private and secret key bytes are available and would
52 * preclude the use of hardware crypto.
53 */
54public class KeyStore {
55    private static final String TAG = "KeyStore";
56
57    // ResponseCodes
58    public static final int NO_ERROR = 1;
59    public static final int LOCKED = 2;
60    public static final int UNINITIALIZED = 3;
61    public static final int SYSTEM_ERROR = 4;
62    public static final int PROTOCOL_ERROR = 5;
63    public static final int PERMISSION_DENIED = 6;
64    public static final int KEY_NOT_FOUND = 7;
65    public static final int VALUE_CORRUPTED = 8;
66    public static final int UNDEFINED_ACTION = 9;
67    public static final int WRONG_PASSWORD = 10;
68
69    /**
70     * Per operation authentication is needed before this operation is valid.
71     * This is returned from {@link #begin} when begin succeeds but the operation uses
72     * per-operation authentication and must authenticate before calling {@link #update} or
73     * {@link #finish}.
74     */
75    public static final int OP_AUTH_NEEDED = 15;
76
77    // Used for UID field to indicate the calling UID.
78    public static final int UID_SELF = -1;
79
80    // Flags for "put" "import" and "generate"
81    public static final int FLAG_NONE = 0;
82
83    /**
84     * Indicates that this key (or key pair) must be encrypted at rest. This will protect the key
85     * (or key pair) with the secure lock screen credential (e.g., password, PIN, or pattern).
86     *
87     * <p>Note that this requires that the secure lock screen (e.g., password, PIN, pattern) is set
88     * up, otherwise key (or key pair) generation or import will fail. Moreover, this key (or key
89     * pair) will be deleted when the secure lock screen is disabled or reset (e.g., by the user or
90     * a Device Administrator). Finally, this key (or key pair) cannot be used until the user
91     * unlocks the secure lock screen after boot.
92     *
93     * @see KeyguardManager#isDeviceSecure()
94     */
95    public static final int FLAG_ENCRYPTED = 1;
96
97    // States
98    public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
99
100    private int mError = NO_ERROR;
101
102    private final IKeystoreService mBinder;
103    private final Context mContext;
104
105    private IBinder mToken;
106
107    private KeyStore(IKeystoreService binder) {
108        mBinder = binder;
109        mContext = getApplicationContext();
110    }
111
112    public static Context getApplicationContext() {
113        ActivityThread activityThread = ActivityThread.currentActivityThread();
114        if (activityThread == null) {
115            throw new IllegalStateException(
116                    "Failed to obtain application Context: no ActivityThread");
117        }
118        Application application = activityThread.getApplication();
119        if (application == null) {
120            throw new IllegalStateException(
121                    "Failed to obtain application Context: no Application");
122        }
123        return application;
124    }
125
126    public static KeyStore getInstance() {
127        IKeystoreService keystore = IKeystoreService.Stub.asInterface(ServiceManager
128                .getService("android.security.keystore"));
129        return new KeyStore(keystore);
130    }
131
132    private synchronized IBinder getToken() {
133        if (mToken == null) {
134            mToken = new Binder();
135        }
136        return mToken;
137    }
138
139    public static int getKeyTypeForAlgorithm(@KeyProperties.KeyAlgorithmEnum String keyType) {
140        if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyType)) {
141            return NativeConstants.EVP_PKEY_RSA;
142        } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyType)) {
143            return NativeConstants.EVP_PKEY_EC;
144        } else {
145            return -1;
146        }
147    }
148
149    public State state(int userId) {
150        final int ret;
151        try {
152            ret = mBinder.getState(userId);
153        } catch (RemoteException e) {
154            Log.w(TAG, "Cannot connect to keystore", e);
155            throw new AssertionError(e);
156        }
157
158        switch (ret) {
159            case NO_ERROR: return State.UNLOCKED;
160            case LOCKED: return State.LOCKED;
161            case UNINITIALIZED: return State.UNINITIALIZED;
162            default: throw new AssertionError(mError);
163        }
164    }
165
166    public State state() {
167        return state(UserHandle.myUserId());
168    }
169
170    public boolean isUnlocked() {
171        return state() == State.UNLOCKED;
172    }
173
174    public byte[] get(String key) {
175        try {
176            return mBinder.get(key);
177        } catch (RemoteException e) {
178            Log.w(TAG, "Cannot connect to keystore", e);
179            return null;
180        }
181    }
182
183    public boolean put(String key, byte[] value, int uid, int flags) {
184        try {
185            return mBinder.insert(key, value, uid, flags) == NO_ERROR;
186        } catch (RemoteException e) {
187            Log.w(TAG, "Cannot connect to keystore", e);
188            return false;
189        }
190    }
191
192    public boolean delete(String key, int uid) {
193        try {
194            return mBinder.del(key, uid) == NO_ERROR;
195        } catch (RemoteException e) {
196            Log.w(TAG, "Cannot connect to keystore", e);
197            return false;
198        }
199    }
200
201    public boolean delete(String key) {
202        return delete(key, UID_SELF);
203    }
204
205    public boolean contains(String key, int uid) {
206        try {
207            return mBinder.exist(key, uid) == NO_ERROR;
208        } catch (RemoteException e) {
209            Log.w(TAG, "Cannot connect to keystore", e);
210            return false;
211        }
212    }
213
214    public boolean contains(String key) {
215        return contains(key, UID_SELF);
216    }
217
218    /**
219     * List all entries in the keystore for {@code uid} starting with {@code prefix}.
220     */
221    public String[] list(String prefix, int uid) {
222        try {
223            return mBinder.list(prefix, uid);
224        } catch (RemoteException e) {
225            Log.w(TAG, "Cannot connect to keystore", e);
226            return null;
227        }
228    }
229
230    public String[] list(String prefix) {
231        return list(prefix, UID_SELF);
232    }
233
234    public String[] saw(String prefix, int uid) {
235        return list(prefix, uid);
236    }
237
238    public String[] saw(String prefix) {
239        return saw(prefix, UID_SELF);
240    }
241
242    public boolean reset() {
243        try {
244            return mBinder.reset() == NO_ERROR;
245        } catch (RemoteException e) {
246            Log.w(TAG, "Cannot connect to keystore", e);
247            return false;
248        }
249    }
250
251    /**
252     * Attempt to lock the keystore for {@code user}.
253     *
254     * @param user Android user to lock.
255     * @return whether {@code user}'s keystore was locked.
256     */
257    public boolean lock(int userId) {
258        try {
259            return mBinder.lock(userId) == NO_ERROR;
260        } catch (RemoteException e) {
261            Log.w(TAG, "Cannot connect to keystore", e);
262            return false;
263        }
264    }
265
266    public boolean lock() {
267        return lock(UserHandle.myUserId());
268    }
269
270    /**
271     * Attempt to unlock the keystore for {@code user} with the password {@code password}.
272     * This is required before keystore entries created with FLAG_ENCRYPTED can be accessed or
273     * created.
274     *
275     * @param user Android user ID to operate on
276     * @param password user's keystore password. Should be the most recent value passed to
277     * {@link #onUserPasswordChanged} for the user.
278     *
279     * @return whether the keystore was unlocked.
280     */
281    public boolean unlock(int userId, String password) {
282        try {
283            mError = mBinder.unlock(userId, password);
284            return mError == NO_ERROR;
285        } catch (RemoteException e) {
286            Log.w(TAG, "Cannot connect to keystore", e);
287            return false;
288        }
289    }
290
291    public boolean unlock(String password) {
292        return unlock(UserHandle.getUserId(Process.myUid()), password);
293    }
294
295    /**
296     * Check if the keystore for {@code userId} is empty.
297     */
298    public boolean isEmpty(int userId) {
299        try {
300            return mBinder.isEmpty(userId) != 0;
301        } catch (RemoteException e) {
302            Log.w(TAG, "Cannot connect to keystore", e);
303            return false;
304        }
305    }
306
307    public boolean isEmpty() {
308        return isEmpty(UserHandle.myUserId());
309    }
310
311    public boolean generate(String key, int uid, int keyType, int keySize, int flags,
312            byte[][] args) {
313        try {
314            return mBinder.generate(key, uid, keyType, keySize, flags,
315                    new KeystoreArguments(args)) == NO_ERROR;
316        } catch (RemoteException e) {
317            Log.w(TAG, "Cannot connect to keystore", e);
318            return false;
319        }
320    }
321
322    public boolean importKey(String keyName, byte[] key, int uid, int flags) {
323        try {
324            return mBinder.import_key(keyName, key, uid, flags) == NO_ERROR;
325        } catch (RemoteException e) {
326            Log.w(TAG, "Cannot connect to keystore", e);
327            return false;
328        }
329    }
330
331    public byte[] getPubkey(String key) {
332        try {
333            return mBinder.get_pubkey(key);
334        } catch (RemoteException e) {
335            Log.w(TAG, "Cannot connect to keystore", e);
336            return null;
337        }
338    }
339
340    public boolean delKey(String key, int uid) {
341        return delete(key, uid);
342    }
343
344    public boolean delKey(String key) {
345        return delKey(key, UID_SELF);
346    }
347
348    public byte[] sign(String key, byte[] data) {
349        try {
350            return mBinder.sign(key, data);
351        } catch (RemoteException e) {
352            Log.w(TAG, "Cannot connect to keystore", e);
353            return null;
354        }
355    }
356
357    public boolean verify(String key, byte[] data, byte[] signature) {
358        try {
359            return mBinder.verify(key, data, signature) == NO_ERROR;
360        } catch (RemoteException e) {
361            Log.w(TAG, "Cannot connect to keystore", e);
362            return false;
363        }
364    }
365
366    public boolean grant(String key, int uid) {
367        try {
368            return mBinder.grant(key, uid) == NO_ERROR;
369        } catch (RemoteException e) {
370            Log.w(TAG, "Cannot connect to keystore", e);
371            return false;
372        }
373    }
374
375    public boolean ungrant(String key, int uid) {
376        try {
377            return mBinder.ungrant(key, uid) == NO_ERROR;
378        } catch (RemoteException e) {
379            Log.w(TAG, "Cannot connect to keystore", e);
380            return false;
381        }
382    }
383
384    /**
385     * Returns the last modification time of the key in milliseconds since the
386     * epoch. Will return -1L if the key could not be found or other error.
387     */
388    public long getmtime(String key) {
389        try {
390            final long millis = mBinder.getmtime(key);
391            if (millis == -1L) {
392                return -1L;
393            }
394
395            return millis * 1000L;
396        } catch (RemoteException e) {
397            Log.w(TAG, "Cannot connect to keystore", e);
398            return -1L;
399        }
400    }
401
402    public boolean duplicate(String srcKey, int srcUid, String destKey, int destUid) {
403        try {
404            return mBinder.duplicate(srcKey, srcUid, destKey, destUid) == NO_ERROR;
405        } catch (RemoteException e) {
406            Log.w(TAG, "Cannot connect to keystore", e);
407            return false;
408        }
409    }
410
411    // TODO remove this when it's removed from Settings
412    public boolean isHardwareBacked() {
413        return isHardwareBacked("RSA");
414    }
415
416    public boolean isHardwareBacked(String keyType) {
417        try {
418            return mBinder.is_hardware_backed(keyType.toUpperCase(Locale.US)) == NO_ERROR;
419        } catch (RemoteException e) {
420            Log.w(TAG, "Cannot connect to keystore", e);
421            return false;
422        }
423    }
424
425    public boolean clearUid(int uid) {
426        try {
427            return mBinder.clear_uid(uid) == NO_ERROR;
428        } catch (RemoteException e) {
429            Log.w(TAG, "Cannot connect to keystore", e);
430            return false;
431        }
432    }
433
434    public int getLastError() {
435        return mError;
436    }
437
438    public boolean addRngEntropy(byte[] data) {
439        try {
440            return mBinder.addRngEntropy(data) == NO_ERROR;
441        } catch (RemoteException e) {
442            Log.w(TAG, "Cannot connect to keystore", e);
443            return false;
444        }
445    }
446
447    public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid,
448            int flags, KeyCharacteristics outCharacteristics) {
449        try {
450            return mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
451        } catch (RemoteException e) {
452            Log.w(TAG, "Cannot connect to keystore", e);
453            return SYSTEM_ERROR;
454        }
455    }
456
457    public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int flags,
458            KeyCharacteristics outCharacteristics) {
459        return generateKey(alias, args, entropy, UID_SELF, flags, outCharacteristics);
460    }
461
462    public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId,
463            KeyCharacteristics outCharacteristics) {
464        try {
465            return mBinder.getKeyCharacteristics(alias, clientId, appId, outCharacteristics);
466        } catch (RemoteException e) {
467            Log.w(TAG, "Cannot connect to keystore", e);
468            return SYSTEM_ERROR;
469        }
470    }
471
472    public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
473            int uid, int flags, KeyCharacteristics outCharacteristics) {
474        try {
475            return mBinder.importKey(alias, args, format, keyData, uid, flags,
476                    outCharacteristics);
477        } catch (RemoteException e) {
478            Log.w(TAG, "Cannot connect to keystore", e);
479            return SYSTEM_ERROR;
480        }
481    }
482
483    public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
484            int flags, KeyCharacteristics outCharacteristics) {
485        return importKey(alias, args, format, keyData, UID_SELF, flags, outCharacteristics);
486    }
487
488    public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
489            KeymasterBlob appId) {
490        try {
491            return mBinder.exportKey(alias, format, clientId, appId);
492        } catch (RemoteException e) {
493            Log.w(TAG, "Cannot connect to keystore", e);
494            return null;
495        }
496    }
497
498    public OperationResult begin(String alias, int purpose, boolean pruneable,
499            KeymasterArguments args, byte[] entropy, KeymasterArguments outArgs) {
500        try {
501            return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, outArgs);
502        } catch (RemoteException e) {
503            Log.w(TAG, "Cannot connect to keystore", e);
504            return null;
505        }
506    }
507
508    public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) {
509        try {
510            return mBinder.update(token, arguments, input);
511        } catch (RemoteException e) {
512            Log.w(TAG, "Cannot connect to keystore", e);
513            return null;
514        }
515    }
516
517    public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) {
518        try {
519            return mBinder.finish(token, arguments, signature);
520        } catch (RemoteException e) {
521            Log.w(TAG, "Cannot connect to keystore", e);
522            return null;
523        }
524    }
525
526    public int abort(IBinder token) {
527        try {
528            return mBinder.abort(token);
529        } catch (RemoteException e) {
530            Log.w(TAG, "Cannot connect to keystore", e);
531            return SYSTEM_ERROR;
532        }
533    }
534
535    /**
536     * Check if the operation referenced by {@code token} is currently authorized.
537     *
538     * @param token An operation token returned by a call to
539     * {@link #begin(String, int, boolean, KeymasterArguments, byte[], KeymasterArguments) begin}.
540     */
541    public boolean isOperationAuthorized(IBinder token) {
542        try {
543            return mBinder.isOperationAuthorized(token);
544        } catch (RemoteException e) {
545            Log.w(TAG, "Cannot connect to keystore", e);
546            return false;
547        }
548    }
549
550    /**
551     * Add an authentication record to the keystore authorization table.
552     *
553     * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster.
554     * @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to
555     * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
556     */
557    public int addAuthToken(byte[] authToken) {
558        try {
559            return mBinder.addAuthToken(authToken);
560        } catch (RemoteException e) {
561            Log.w(TAG, "Cannot connect to keystore", e);
562            return SYSTEM_ERROR;
563        }
564    }
565
566    /**
567     * Notify keystore that a user's password has changed.
568     *
569     * @param userId the user whose password changed.
570     * @param newPassword the new password or "" if the password was removed.
571     */
572    public boolean onUserPasswordChanged(int userId, String newPassword) {
573        // Parcel.cpp doesn't support deserializing null strings and treats them as "". Make that
574        // explicit here.
575        if (newPassword == null) {
576            newPassword = "";
577        }
578        try {
579            return mBinder.onUserPasswordChanged(userId, newPassword) == NO_ERROR;
580        } catch (RemoteException e) {
581            Log.w(TAG, "Cannot connect to keystore", e);
582            return false;
583        }
584    }
585
586    /**
587     * Notify keystore that a user was added.
588     *
589     * @param userId the new user.
590     * @param parentId the parent of the new user, or -1 if the user has no parent. If parentId is
591     * specified then the new user's keystore will be intialized with the same secure lockscreen
592     * password as the parent.
593     */
594    public void onUserAdded(int userId, int parentId) {
595        try {
596            mBinder.onUserAdded(userId, parentId);
597        } catch (RemoteException e) {
598            Log.w(TAG, "Cannot connect to keystore", e);
599        }
600    }
601
602    /**
603     * Notify keystore that a user was added.
604     *
605     * @param userId the new user.
606     */
607    public void onUserAdded(int userId) {
608        onUserAdded(userId, -1);
609    }
610
611    /**
612     * Notify keystore that a user was removed.
613     *
614     * @param userId the removed user.
615     */
616    public void onUserRemoved(int userId) {
617        try {
618            mBinder.onUserRemoved(userId);
619        } catch (RemoteException e) {
620            Log.w(TAG, "Cannot connect to keystore", e);
621        }
622    }
623
624    public boolean onUserPasswordChanged(String newPassword) {
625        return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword);
626    }
627
628    /**
629     * Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
630     * code.
631     */
632    public static KeyStoreException getKeyStoreException(int errorCode) {
633        if (errorCode > 0) {
634            // KeyStore layer error
635            switch (errorCode) {
636                case NO_ERROR:
637                    return new KeyStoreException(errorCode, "OK");
638                case LOCKED:
639                    return new KeyStoreException(errorCode, "User authentication required");
640                case UNINITIALIZED:
641                    return new KeyStoreException(errorCode, "Keystore not initialized");
642                case SYSTEM_ERROR:
643                    return new KeyStoreException(errorCode, "System error");
644                case PERMISSION_DENIED:
645                    return new KeyStoreException(errorCode, "Permission denied");
646                case KEY_NOT_FOUND:
647                    return new KeyStoreException(errorCode, "Key not found");
648                case VALUE_CORRUPTED:
649                    return new KeyStoreException(errorCode, "Key blob corrupted");
650                case OP_AUTH_NEEDED:
651                    return new KeyStoreException(errorCode, "Operation requires authorization");
652                default:
653                    return new KeyStoreException(errorCode, String.valueOf(errorCode));
654            }
655        } else {
656            // Keymaster layer error
657            switch (errorCode) {
658                case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT:
659                    // The name of this parameter significantly differs between Keymaster and
660                    // framework APIs. Use the framework wording to make life easier for developers.
661                    return new KeyStoreException(errorCode,
662                            "Invalid user authentication validity duration");
663                default:
664                    return new KeyStoreException(errorCode,
665                            KeymasterDefs.getErrorMessage(errorCode));
666            }
667        }
668    }
669
670    /**
671     * Returns an {@link InvalidKeyException} corresponding to the provided
672     * {@link KeyStoreException}.
673     */
674    public InvalidKeyException getInvalidKeyException(
675            String keystoreKeyAlias, KeyStoreException e) {
676        switch (e.getErrorCode()) {
677            case LOCKED:
678                return new UserNotAuthenticatedException();
679            case KeymasterDefs.KM_ERROR_KEY_EXPIRED:
680                return new KeyExpiredException();
681            case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID:
682                return new KeyNotYetValidException();
683            case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED:
684            case OP_AUTH_NEEDED:
685            {
686                // We now need to determine whether the key/operation can become usable if user
687                // authentication is performed, or whether it can never become usable again.
688                // User authentication requirements are contained in the key's characteristics. We
689                // need to check whether these requirements can be be satisfied by asking the user
690                // to authenticate.
691                KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
692                int getKeyCharacteristicsErrorCode =
693                        getKeyCharacteristics(keystoreKeyAlias, null, null, keyCharacteristics);
694                if (getKeyCharacteristicsErrorCode != NO_ERROR) {
695                    return new InvalidKeyException(
696                            "Failed to obtained key characteristics",
697                            getKeyStoreException(getKeyCharacteristicsErrorCode));
698                }
699                List<Long> keySids =
700                        keyCharacteristics.getLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID);
701                if (keySids.isEmpty()) {
702                    // Key is not bound to any SIDs -- no amount of authentication will help here.
703                    return new KeyPermanentlyInvalidatedException();
704                }
705                long rootSid = GateKeeper.getSecureUserId();
706                if ((rootSid != 0) && (keySids.contains(Long.valueOf(rootSid)))) {
707                    // One of the key's SIDs is the current root SID -- user can be authenticated
708                    // against that SID.
709                    return new UserNotAuthenticatedException();
710                }
711
712                long fingerprintOnlySid = getFingerprintOnlySid();
713                if ((fingerprintOnlySid != 0)
714                        && (keySids.contains(Long.valueOf(fingerprintOnlySid)))) {
715                    // One of the key's SIDs is the current fingerprint SID -- user can be
716                    // authenticated against that SID.
717                    return new UserNotAuthenticatedException();
718                }
719
720                // None of the key's SIDs can ever be authenticated
721                return new KeyPermanentlyInvalidatedException();
722            }
723            default:
724                return new InvalidKeyException("Keystore operation failed", e);
725        }
726    }
727
728    private long getFingerprintOnlySid() {
729        FingerprintManager fingerprintManager =
730                mContext.getSystemService(FingerprintManager.class);
731        if (fingerprintManager == null) {
732            return 0;
733        }
734
735        if (!fingerprintManager.isHardwareDetected()) {
736            return 0;
737        }
738
739        return fingerprintManager.getAuthenticatorId();
740    }
741
742    /**
743     * Returns an {@link InvalidKeyException} corresponding to the provided keystore/keymaster error
744     * code.
745     */
746    public InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) {
747        return getInvalidKeyException(keystoreKeyAlias, getKeyStoreException(errorCode));
748    }
749}
750