Add KeyPermanentlyInvalidatedException.

This enables users of AndroidKeyStore crypto to differentiate between
the key being unusable until the user is authenticated
(UserNotAuthenticatedException) and the key being permanently unusable
(KeyPermanentlyInvalidatedException). The latter is the case when the
secure lock screen has been disabled or reset, and, for keys that
require user authentication for every use, when a new fingerprint is
enrolled or all fingerprints are unenrolled.

NOTE: The KeyPermanentlyInvalidatedException subsumes/replaces the
NewFingerprintEnrolledException which has thus been removed. There
is no way to find out whether a key was permenently invalidated
specifically because a new fingerprint was added.

Bug: 20642549
Bug: 20526234
Change-Id: I0206cd99eef5c605c9c4d6afc5eea02eb3b1fe6b
diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java
index c9f06e9..5617836 100644
--- a/keystore/java/android/security/GateKeeper.java
+++ b/keystore/java/android/security/GateKeeper.java
@@ -15,13 +15,17 @@
     private GateKeeper() {}
 
     public static IGateKeeperService getService() {
-        return IGateKeeperService.Stub.asInterface(
+        IGateKeeperService service = IGateKeeperService.Stub.asInterface(
                 ServiceManager.getService("android.service.gatekeeper.IGateKeeperService"));
+        if (service == null) {
+            throw new IllegalStateException("Gatekeeper service not available");
+        }
+        return service;
     }
 
     public static long getSecureUserId() throws IllegalStateException {
         try {
-            return GateKeeper.getService().getSecureUserId(UserHandle.myUserId());
+            return getService().getSecureUserId(UserHandle.myUserId());
         } catch (RemoteException e) {
             throw new IllegalStateException(
                     "Failed to obtain secure user ID from gatekeeper", e);
diff --git a/keystore/java/android/security/KeyPermanentlyInvalidatedException.java b/keystore/java/android/security/KeyPermanentlyInvalidatedException.java
new file mode 100644
index 0000000..229eab0
--- /dev/null
+++ b/keystore/java/android/security/KeyPermanentlyInvalidatedException.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import java.security.InvalidKeyException;
+
+/**
+ * Indicates that the key can no longer be used because it has been permanently invalidated.
+ *
+ * <p>This can currently occur only for keys that require user authentication. Such keys are
+ * permanently invalidated once the secure lock screen is disabled (i.e., reconfigured to None,
+ * Swipe or other mode which does not authenticate the user) or when the secure lock screen is
+ * forcibly reset (e.g., by Device Admin). Additionally, keys configured to require user
+ * authentication for every use of the key are also permanently invalidated once a new fingerprint
+ * is enrolled or once no more fingerprints are enrolled.
+ */
+public class KeyPermanentlyInvalidatedException extends InvalidKeyException {
+
+    /**
+     * Constructs a new {@code KeyPermanentlyInvalidatedException} without detail message and cause.
+     */
+    public KeyPermanentlyInvalidatedException() {
+        super("Key permanently invalidated");
+    }
+
+    /**
+     * Constructs a new {@code KeyPermanentlyInvalidatedException} with the provided detail message
+     * and no cause.
+     */
+    public KeyPermanentlyInvalidatedException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@code KeyPermanentlyInvalidatedException} with the provided detail message
+     * and cause.
+     */
+    public KeyPermanentlyInvalidatedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 5d863c2..f3b447e 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -18,6 +18,8 @@
 
 import com.android.org.conscrypt.NativeConstants;
 
+import android.content.Context;
+import android.hardware.fingerprint.IFingerprintService;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -31,6 +33,7 @@
 import android.util.Log;
 
 import java.security.InvalidKeyException;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -490,7 +493,8 @@
     /**
      * Check if the operation referenced by {@code token} is currently authorized.
      *
-     * @param token An operation token returned by a call to {@link KeyStore.begin}.
+     * @param token An operation token returned by a call to
+     * {@link #begin(String, int, boolean, KeymasterArguments, byte[], KeymasterArguments) begin}.
      */
     public boolean isOperationAuthorized(IBinder token) {
         try {
@@ -561,27 +565,80 @@
      * Returns an {@link InvalidKeyException} corresponding to the provided
      * {@link KeyStoreException}.
      */
-    static InvalidKeyException getInvalidKeyException(KeyStoreException e) {
+    InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, KeyStoreException e) {
         switch (e.getErrorCode()) {
             case KeymasterDefs.KM_ERROR_KEY_EXPIRED:
                 return new KeyExpiredException();
             case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID:
                 return new KeyNotYetValidException();
             case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED:
-                return new UserNotAuthenticatedException();
-            // TODO: Handle TBD Keymaster error code "invalid key: new fingerprint enrolled"
-            // case KeymasterDefs.KM_ERROR_TBD
-            //     return new NewFingerprintEnrolledException();
+            {
+                // We now need to determine whether the key/operation can become usable if user
+                // authentication is performed, or whether it can never become usable again.
+                // User authentication requirements are contained in the key's characteristics. We
+                // need to check whether these requirements can be be satisfied by asking the user
+                // to authenticate.
+                KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+                int getKeyCharacteristicsErrorCode =
+                        getKeyCharacteristics(keystoreKeyAlias, null, null, keyCharacteristics);
+                if (getKeyCharacteristicsErrorCode != NO_ERROR) {
+                    return new InvalidKeyException(
+                            "Failed to obtained key characteristics",
+                            getKeyStoreException(getKeyCharacteristicsErrorCode));
+                }
+                List<Long> keySids =
+                        keyCharacteristics.getLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID);
+                if (keySids.isEmpty()) {
+                    // Key is not bound to any SIDs -- no amount of authentication will help here.
+                    return new KeyPermanentlyInvalidatedException();
+                }
+                long rootSid = GateKeeper.getSecureUserId();
+                if ((rootSid != 0) && (keySids.contains(Long.valueOf(rootSid)))) {
+                    // One of the key's SIDs is the current root SID -- user can be authenticated
+                    // against that SID.
+                    return new UserNotAuthenticatedException();
+                }
+
+                long fingerprintOnlySid = getFingerprintOnlySid();
+                if ((fingerprintOnlySid != 0)
+                        && (keySids.contains(Long.valueOf(fingerprintOnlySid)))) {
+                    // One of the key's SIDs is the current fingerprint SID -- user can be
+                    // authenticated against that SID.
+                    return new UserNotAuthenticatedException();
+                }
+
+                // None of the key's SIDs can ever be authenticated
+                return new KeyPermanentlyInvalidatedException();
+            }
             default:
                 return new InvalidKeyException("Keystore operation failed", e);
         }
     }
 
+    private static long getFingerprintOnlySid() {
+        IFingerprintService service = IFingerprintService.Stub.asInterface(
+                ServiceManager.getService(Context.FINGERPRINT_SERVICE));
+        if (service == null) {
+            return 0;
+        }
+
+        try {
+            long deviceId = 0; // TODO: plumb hardware id to FPMS
+            if (!service.isHardwareDetected(deviceId)) {
+                return 0;
+            }
+
+            return service.getAuthenticatorId();
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to communicate with fingerprint service", e);
+        }
+    }
+
     /**
      * Returns an {@link InvalidKeyException} corresponding to the provided keystore/keymaster error
      * code.
      */
-    static InvalidKeyException getInvalidKeyException(int errorCode) {
-        return getInvalidKeyException(getKeyStoreException(errorCode));
+    InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) {
+        return getInvalidKeyException(keystoreKeyAlias, getKeyStoreException(errorCode));
     }
 }
diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java
index 3b13e83..9393e32 100644
--- a/keystore/java/android/security/KeyStoreCipherSpi.java
+++ b/keystore/java/android/security/KeyStoreCipherSpi.java
@@ -303,7 +303,7 @@
                 case KeymasterDefs.KM_ERROR_INVALID_NONCE:
                     throw new InvalidAlgorithmParameterException("Invalid IV");
             }
-            throw KeyStore.getInvalidKeyException(opResult.resultCode);
+            throw mKeyStore.getInvalidKeyException(mKey.getAlias(), opResult.resultCode);
         }
 
         if (opResult.token == null) {
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
index 175369c..8d71d1d 100644
--- a/keystore/java/android/security/KeyStoreHmacSpi.java
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -169,7 +169,7 @@
         if (opResult == null) {
             throw new KeyStoreConnectException();
         } else if (opResult.resultCode != KeyStore.NO_ERROR) {
-            throw KeyStore.getInvalidKeyException(opResult.resultCode);
+            throw mKeyStore.getInvalidKeyException(mKey.getAlias(), opResult.resultCode);
         }
         if (opResult.token == null) {
             throw new IllegalStateException("Keystore returned null operation token");
diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java
index 7bf5475..3ccb588 100644
--- a/keystore/java/android/security/KeymasterUtils.java
+++ b/keystore/java/android/security/KeymasterUtils.java
@@ -18,12 +18,8 @@
 
 import android.content.Context;
 import android.hardware.fingerprint.FingerprintManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterDefs;
-import android.service.gatekeeper.IGateKeeperService;
 
 import libcore.util.EmptyArray;
 
@@ -347,20 +343,6 @@
         return result;
     }
 
-    private static long getRootSid() {
-        IGateKeeperService gatekeeperService = IGateKeeperService.Stub.asInterface(
-                ServiceManager.getService("android.service.gatekeeper.IGateKeeperService"));
-        if (gatekeeperService == null) {
-            throw new IllegalStateException("Gatekeeper service not available");
-        }
-
-        try {
-            return gatekeeperService.getSecureUserId(UserHandle.myUserId());
-        } catch (RemoteException e) {
-            throw new IllegalStateException("Failed to obtain root SID");
-        }
-    }
-
     /**
      * Adds keymaster arguments to express the key's authorization policy supported by user
      * authentication.
@@ -402,7 +384,7 @@
         } else {
             // The key is authorized for use for the specified amount of time after the user has
             // authenticated. Whatever unlocks the secure lock screen should authorize this key.
-            long rootSid = getRootSid();
+            long rootSid = GateKeeper.getSecureUserId();
             if (rootSid == 0) {
                 throw new IllegalStateException("Secure lock screen must be enabled"
                         + " to create keys requiring user authentication");
diff --git a/keystore/java/android/security/NewFingerprintEnrolledException.java b/keystore/java/android/security/NewFingerprintEnrolledException.java
deleted file mode 100644
index 4fe210b..0000000
--- a/keystore/java/android/security/NewFingerprintEnrolledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security;
-
-import java.security.InvalidKeyException;
-
-/**
- * Indicates that a cryptographic operation could not be performed because the key used by the
- * operation is permanently invalid because a new fingerprint was enrolled.
- */
-public class NewFingerprintEnrolledException extends InvalidKeyException {
-
-    /**
-     * Constructs a new {@code NewFingerprintEnrolledException} without detail message and cause.
-     */
-    public NewFingerprintEnrolledException() {
-        super("Invalid key: new fingerprint enrolled");
-    }
-
-    /**
-     * Constructs a new {@code NewFingerprintEnrolledException} with the provided detail message and
-     * no cause.
-     */
-    public NewFingerprintEnrolledException(String message) {
-        super(message);
-    }
-}
diff --git a/keystore/java/android/security/UserNotAuthenticatedException.java b/keystore/java/android/security/UserNotAuthenticatedException.java
index 66f4dd8..2954fa7 100644
--- a/keystore/java/android/security/UserNotAuthenticatedException.java
+++ b/keystore/java/android/security/UserNotAuthenticatedException.java
@@ -20,7 +20,7 @@
 
 /**
  * Indicates that a cryptographic operation could not be performed because the user has not been
- * authenticated recently enough.
+ * authenticated recently enough. Authenticating the user will resolve this issue.
  */
 public class UserNotAuthenticatedException extends InvalidKeyException {