diff options
Diffstat (limited to 'keystore')
8 files changed, 162 insertions, 75 deletions
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 @@ public abstract class GateKeeper { 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..1563863 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -18,6 +18,8 @@ package android.security; 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.security.keymaster.OperationResult; import android.util.Log; import java.security.InvalidKeyException; +import java.util.List; import java.util.Locale; /** @@ -490,7 +493,8 @@ public class KeyStore { /** * 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 { @@ -539,6 +543,8 @@ public class KeyStore { return new KeyStoreException(errorCode, "Key not found"); case VALUE_CORRUPTED: return new KeyStoreException(errorCode, "Key blob corrupted"); + case OP_AUTH_NEEDED: + return new KeyStoreException(errorCode, "Operation requires authorization"); default: return new KeyStoreException(errorCode, String.valueOf(errorCode)); } @@ -561,27 +567,81 @@ public class KeyStore { * 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(); + case OP_AUTH_NEEDED: + { + // 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..917f716 100644 --- a/keystore/java/android/security/KeyStoreCipherSpi.java +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -298,17 +298,20 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry mAdditionalEntropyForBegin = null; if (opResult == null) { throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { + } else if ((opResult.resultCode != KeyStore.NO_ERROR) + && (opResult.resultCode != KeyStore.OP_AUTH_NEEDED)) { switch (opResult.resultCode) { 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) { throw new IllegalStateException("Keystore returned null operation token"); } + // The operation handle/token is now either valid for use immediately or needs to be + // authorized through user authentication (if the error code was OP_AUTH_NEEDED). mOperationToken = opResult.token; mOperationHandle = opResult.operationHandle; loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); @@ -317,6 +320,16 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( mKeyStore, opResult.token)); + + if (opResult.resultCode != KeyStore.NO_ERROR) { + // The operation requires user authentication. Check whether such authentication is + // possible (e.g., the key may have been permanently invalidated). + InvalidKeyException e = + mKeyStore.getInvalidKeyException(mKey.getAlias(), opResult.resultCode); + if (!(e instanceof UserNotAuthenticatedException)) { + throw e; + } + } } @Override diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java index 175369c..4590b9c 100644 --- a/keystore/java/android/security/KeyStoreHmacSpi.java +++ b/keystore/java/android/security/KeyStoreHmacSpi.java @@ -168,17 +168,31 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp new KeymasterArguments()); if (opResult == null) { throw new KeyStoreConnectException(); - } else if (opResult.resultCode != KeyStore.NO_ERROR) { - throw KeyStore.getInvalidKeyException(opResult.resultCode); + } else if ((opResult.resultCode != KeyStore.NO_ERROR) + && (opResult.resultCode != KeyStore.OP_AUTH_NEEDED)) { + throw mKeyStore.getInvalidKeyException(mKey.getAlias(), opResult.resultCode); } + if (opResult.token == null) { throw new IllegalStateException("Keystore returned null operation token"); } + // The operation handle/token is now either valid for use immediately or needs to be + // authorized through user authentication (if the error code was OP_AUTH_NEEDED). mOperationToken = opResult.token; mOperationHandle = opResult.operationHandle; mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( mKeyStore, mOperationToken)); + + if (opResult.resultCode != KeyStore.NO_ERROR) { + // The operation requires user authentication. Check whether such authentication is + // possible (e.g., the key may have been permanently invalidated). + InvalidKeyException e = + mKeyStore.getInvalidKeyException(mKey.getAlias(), opResult.resultCode); + if (!(e instanceof UserNotAuthenticatedException)) { + throw e; + } + } } @Override 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 @@ package android.security; 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 @@ public abstract class KeymasterUtils { 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 @@ public abstract class KeymasterUtils { } 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 @@ import java.security.InvalidKeyException; /** * 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 { |