Merge "KeyStore: Surface RKP failures" am: 6c82325675
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2049729 Change-Id: Ia726d3005e6971b4cf34c05b7cb70f7511263fd3 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -36138,10 +36138,12 @@ package android.security {
|
||||
|
||||
public class KeyStoreException extends java.lang.Exception {
|
||||
method public int getNumericErrorCode();
|
||||
method public int getRetryPolicy();
|
||||
method public boolean isSystemError();
|
||||
method public boolean isTransientFailure();
|
||||
method public boolean requiresUserAuthentication();
|
||||
field public static final int ERROR_ATTESTATION_CHALLENGE_TOO_LARGE = 9; // 0x9
|
||||
field public static final int ERROR_ATTESTATION_KEYS_UNAVAILABLE = 16; // 0x10
|
||||
field public static final int ERROR_ID_ATTESTATION_FAILURE = 8; // 0x8
|
||||
field public static final int ERROR_INCORRECT_USAGE = 13; // 0xd
|
||||
field public static final int ERROR_INTERNAL_SYSTEM_ERROR = 4; // 0x4
|
||||
@@ -36156,6 +36158,9 @@ package android.security {
|
||||
field public static final int ERROR_PERMISSION_DENIED = 5; // 0x5
|
||||
field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc
|
||||
field public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2; // 0x2
|
||||
field public static final int RETRY_NEVER = 1; // 0x1
|
||||
field public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3; // 0x3
|
||||
field public static final int RETRY_WITH_EXPONENTIAL_BACKOFF = 2; // 0x2
|
||||
}
|
||||
|
||||
@Deprecated public final class KeyStoreParameter implements java.security.KeyStore.ProtectionParameter {
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
import android.system.keystore2.ResponseCode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -36,6 +37,8 @@ import java.util.Map;
|
||||
* is likely to succeed.
|
||||
*/
|
||||
public class KeyStoreException extends Exception {
|
||||
private static final String TAG = "KeyStoreException";
|
||||
|
||||
/**
|
||||
* This error code is for mapping errors that the caller will not know about. If the caller is
|
||||
* targeting an API level earlier than the one the error was introduced in, then the error will
|
||||
@@ -114,6 +117,27 @@ public class KeyStoreException extends Exception {
|
||||
* The caller should re-create the crypto object and try again.
|
||||
*/
|
||||
public static final int ERROR_KEY_OPERATION_EXPIRED = 15;
|
||||
/**
|
||||
* There are no keys available for attestation.
|
||||
* This error is returned only on devices that rely solely on remotely-provisioned keys (see
|
||||
* <a href=
|
||||
* "https://android-developers.googleblog.com/2022/03/upgrading-android-attestation-remote.html"
|
||||
* >Remote Key Provisioning</a>).
|
||||
*
|
||||
* <p>On such a device, if the caller requests key generation and includes an attestation
|
||||
* challenge (indicating key attestation is required), the error will be returned in one of
|
||||
* the following cases:
|
||||
* <ul>
|
||||
* <li>The pool of remotely-provisioned keys has been exhausted.</li>
|
||||
* <li>The device is not registered with the key provisioning server.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>This error is a transient one if the pool of remotely-provisioned keys has been
|
||||
* exhausted. However, if the device is not registered with the server, or the key
|
||||
* provisioning server refuses key issuance, this is a permanent error.</p>
|
||||
*/
|
||||
public static final int ERROR_ATTESTATION_KEYS_UNAVAILABLE = 16;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@@ -132,11 +156,68 @@ public class KeyStoreException extends Exception {
|
||||
ERROR_UNIMPLEMENTED,
|
||||
ERROR_INCORRECT_USAGE,
|
||||
ERROR_KEY_NOT_TEMPORALLY_VALID,
|
||||
ERROR_KEY_OPERATION_EXPIRED
|
||||
ERROR_KEY_OPERATION_EXPIRED,
|
||||
ERROR_ATTESTATION_KEYS_UNAVAILABLE
|
||||
})
|
||||
public @interface PublicErrorCode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Never re-try the operation that led to this error, since it's a permanent error.
|
||||
*
|
||||
* This value is always returned when {@link #isTransientFailure()} is {@code false}.
|
||||
*/
|
||||
public static final int RETRY_NEVER = 1;
|
||||
/**
|
||||
* Re-try the operation that led to this error with an exponential back-off delay.
|
||||
* The first delay should be between 5 to 30 seconds, and each subsequent re-try should double
|
||||
* the delay time.
|
||||
*
|
||||
* This value is returned when {@link #isTransientFailure()} is {@code true}.
|
||||
*/
|
||||
public static final int RETRY_WITH_EXPONENTIAL_BACKOFF = 2;
|
||||
/**
|
||||
* Re-try the operation that led to this error when the device regains connectivity.
|
||||
* Remote provisioning of keys requires reaching the remote server, and the device is
|
||||
* currently unable to due that due to lack of network connectivity.
|
||||
*
|
||||
* This value is returned when {@link #isTransientFailure()} is {@code true}.
|
||||
*/
|
||||
public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, prefix = {"RETRY_"}, value = {
|
||||
RETRY_NEVER,
|
||||
RETRY_WITH_EXPONENTIAL_BACKOFF,
|
||||
RETRY_WHEN_CONNECTIVITY_AVAILABLE,
|
||||
})
|
||||
public @interface RetryPolicy {
|
||||
}
|
||||
|
||||
// RKP-specific error information.
|
||||
/**
|
||||
* Remote provisioning of attestation keys has completed successfully.
|
||||
* @hide */
|
||||
public static final int RKP_SUCCESS = 0;
|
||||
/**
|
||||
* Remotely-provisioned keys are temporarily unavailable. This could be because of RPC
|
||||
* error when talking to the remote provisioner or keys are being currently fetched and will
|
||||
* be available soon.
|
||||
* @hide */
|
||||
public static final int RKP_TEMPORARILY_UNAVAILABLE = 1;
|
||||
/**
|
||||
* Permanent failure: The RKP server has declined issuance of keys to this device. Either
|
||||
* because the device is not registered with the server or the server considers the device
|
||||
* not to be trustworthy.
|
||||
* @hide */
|
||||
public static final int RKP_SERVER_REFUSED_ISSUANCE = 2;
|
||||
/**
|
||||
* The RKP server is unavailable due to lack of connectivity. The caller should re-try
|
||||
* when the device has connectivity again.
|
||||
* @hide */
|
||||
public static final int RKP_FETCHING_PENDING_CONNECTIVITY = 3;
|
||||
|
||||
// Constants for encoding information about the error encountered:
|
||||
// Whether the error relates to the system state/implementation as a whole, or a specific key.
|
||||
private static final int IS_SYSTEM_ERROR = 1 << 1;
|
||||
@@ -148,6 +229,21 @@ public class KeyStoreException extends Exception {
|
||||
// The internal error code. NOT to be returned directly to callers or made part of the
|
||||
// public API.
|
||||
private final int mErrorCode;
|
||||
// The Remote Key Provisioning status. Applicable if and only if {@link #mErrorCode} is equal
|
||||
// to {@link ResponseCode.OUT_OF_KEYS}.
|
||||
private final int mRkpStatus;
|
||||
|
||||
private static int initializeRkpStatusForRegularErrors(int errorCode) {
|
||||
// Check if the system code mistakenly called a constructor of KeyStoreException with
|
||||
// the OUT_OF_KEYS error code but without RKP status.
|
||||
if (errorCode == ResponseCode.OUT_OF_KEYS) {
|
||||
Log.e(TAG, "RKP error code without RKP status");
|
||||
// Set RKP status to RKP_SERVER_REFUSED_ISSUANCE so that the caller never retries.
|
||||
return RKP_SERVER_REFUSED_ISSUANCE;
|
||||
} else {
|
||||
return RKP_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
@@ -155,6 +251,7 @@ public class KeyStoreException extends Exception {
|
||||
public KeyStoreException(int errorCode, @Nullable String message) {
|
||||
super(message);
|
||||
mErrorCode = errorCode;
|
||||
mRkpStatus = initializeRkpStatusForRegularErrors(errorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,6 +262,19 @@ public class KeyStoreException extends Exception {
|
||||
super(message + " (internal Keystore code: " + errorCode + " message: "
|
||||
+ keystoreErrorMessage + ")");
|
||||
mErrorCode = errorCode;
|
||||
mRkpStatus = initializeRkpStatusForRegularErrors(errorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public KeyStoreException(int errorCode, @Nullable String message, int rkpStatus) {
|
||||
super(message);
|
||||
mErrorCode = errorCode;
|
||||
mRkpStatus = rkpStatus;
|
||||
if (mErrorCode != ResponseCode.OUT_OF_KEYS) {
|
||||
Log.e(TAG, "Providing RKP status for error code " + errorCode + " has no effect.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,6 +308,17 @@ public class KeyStoreException extends Exception {
|
||||
*/
|
||||
public boolean isTransientFailure() {
|
||||
PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
|
||||
// Special-case handling for RKP failures:
|
||||
if (mRkpStatus != RKP_SUCCESS && mErrorCode == ResponseCode.OUT_OF_KEYS) {
|
||||
switch (mRkpStatus) {
|
||||
case RKP_TEMPORARILY_UNAVAILABLE:
|
||||
case RKP_FETCHING_PENDING_CONNECTIVITY:
|
||||
return true;
|
||||
case RKP_SERVER_REFUSED_ISSUANCE:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0;
|
||||
}
|
||||
|
||||
@@ -225,6 +346,34 @@ public class KeyStoreException extends Exception {
|
||||
return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the re-try policy for transient failures. Valid only if
|
||||
* {@link #isTransientFailure()} returns {@code True}.
|
||||
*/
|
||||
@RetryPolicy
|
||||
public int getRetryPolicy() {
|
||||
PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
|
||||
// Special-case handling for RKP failures:
|
||||
if (mRkpStatus != RKP_SUCCESS) {
|
||||
switch (mRkpStatus) {
|
||||
case RKP_TEMPORARILY_UNAVAILABLE:
|
||||
return RETRY_WITH_EXPONENTIAL_BACKOFF;
|
||||
case RKP_FETCHING_PENDING_CONNECTIVITY:
|
||||
return RETRY_WHEN_CONNECTIVITY_AVAILABLE;
|
||||
case RKP_SERVER_REFUSED_ISSUANCE:
|
||||
return RETRY_NEVER;
|
||||
default:
|
||||
return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0
|
||||
? RETRY_WITH_EXPONENTIAL_BACKOFF : RETRY_NEVER;
|
||||
}
|
||||
}
|
||||
if ((failureInfo.indicators & IS_TRANSIENT_ERROR) != 0) {
|
||||
return RETRY_WITH_EXPONENTIAL_BACKOFF;
|
||||
} else {
|
||||
return RETRY_NEVER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String errorCodes = String.format(" (public error code: %d internal Keystore code: %d)",
|
||||
@@ -469,5 +618,7 @@ public class KeyStoreException extends Exception {
|
||||
new PublicErrorInformation(0, ERROR_KEY_CORRUPTED));
|
||||
sErrorCodeToFailureInfo.put(ResponseCode.KEY_PERMANENTLY_INVALIDATED,
|
||||
new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
|
||||
sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS,
|
||||
new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import android.hardware.security.keymint.Tag;
|
||||
import android.os.Build;
|
||||
import android.os.RemoteException;
|
||||
import android.security.GenerateRkpKey;
|
||||
import android.security.GenerateRkpKeyException;
|
||||
import android.security.KeyPairGeneratorSpec;
|
||||
import android.security.KeyStore2;
|
||||
import android.security.KeyStoreException;
|
||||
@@ -618,18 +617,44 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
|
||||
|
||||
@Override
|
||||
public KeyPair generateKeyPair() {
|
||||
try {
|
||||
return generateKeyPairHelper();
|
||||
} catch (GenerateRkpKeyException e) {
|
||||
try {
|
||||
return generateKeyPairHelper();
|
||||
} catch (GenerateRkpKeyException f) {
|
||||
throw new ProviderException("Failed to provision new attestation keys.");
|
||||
GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
/**
|
||||
* NOTE: There is no need to delay between re-tries because the call to
|
||||
* GenerateRkpKey.notifyEmpty() will delay for a while before returning.
|
||||
*/
|
||||
result = generateKeyPairHelper();
|
||||
if (result.rkpStatus == KeyStoreException.RKP_SUCCESS) {
|
||||
return result.keyPair;
|
||||
}
|
||||
}
|
||||
|
||||
// RKP failure
|
||||
if (result.rkpStatus != KeyStoreException.RKP_SUCCESS) {
|
||||
KeyStoreException ksException = new KeyStoreException(ResponseCode.OUT_OF_KEYS,
|
||||
"Could not get RKP keys", result.rkpStatus);
|
||||
throw new ProviderException("Failed to provision new attestation keys.", ksException);
|
||||
}
|
||||
|
||||
return result.keyPair;
|
||||
}
|
||||
|
||||
private static class GenerateKeyPairHelperResult {
|
||||
// Zero indicates success, non-zero indicates failure. Values should be
|
||||
// {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
|
||||
// {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
|
||||
// {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
|
||||
public final int rkpStatus;
|
||||
@Nullable
|
||||
public final KeyPair keyPair;
|
||||
|
||||
private GenerateKeyPairHelperResult(int rkpStatus, KeyPair keyPair) {
|
||||
this.rkpStatus = rkpStatus;
|
||||
this.keyPair = keyPair;
|
||||
}
|
||||
}
|
||||
|
||||
private KeyPair generateKeyPairHelper() throws GenerateRkpKeyException {
|
||||
private GenerateKeyPairHelperResult generateKeyPairHelper() {
|
||||
if (mKeyStore == null || mSpec == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
@@ -679,7 +704,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
|
||||
Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
|
||||
}
|
||||
success = true;
|
||||
return new KeyPair(publicKey, publicKey.getPrivateKey());
|
||||
KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey());
|
||||
return new GenerateKeyPairHelperResult(0, kp);
|
||||
} catch (android.security.KeyStoreException e) {
|
||||
switch (e.getErrorCode()) {
|
||||
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
|
||||
@@ -688,11 +714,19 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
|
||||
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
|
||||
.currentApplication());
|
||||
try {
|
||||
//TODO: When detailed error information is available from the remote
|
||||
//provisioner, propagate it up.
|
||||
keyGen.notifyEmpty(securityLevel);
|
||||
} catch (RemoteException f) {
|
||||
throw new ProviderException("Failed to talk to RemoteProvisioner", f);
|
||||
KeyStoreException ksException = new KeyStoreException(
|
||||
ResponseCode.OUT_OF_KEYS,
|
||||
"Remote exception: " + f.getMessage(),
|
||||
KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
|
||||
throw new ProviderException("Failed to talk to RemoteProvisioner",
|
||||
ksException);
|
||||
}
|
||||
throw new GenerateRkpKeyException();
|
||||
return new GenerateKeyPairHelperResult(
|
||||
KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null);
|
||||
default:
|
||||
ProviderException p = new ProviderException("Failed to generate key pair.", e);
|
||||
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
|
||||
|
||||
Reference in New Issue
Block a user