Merge "KeyStore: Surface RKP failures" am: 6c82325675 am: a0e091c6f3

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2049729

Change-Id: I466711a2d3e8690dc0383140049bed75fa5bb31f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Treehugger Robot
2022-04-04 10:54:12 +00:00
committed by Automerger Merge Worker
3 changed files with 203 additions and 13 deletions

View File

@@ -36138,10 +36138,12 @@ package android.security {
public class KeyStoreException extends java.lang.Exception { public class KeyStoreException extends java.lang.Exception {
method public int getNumericErrorCode(); method public int getNumericErrorCode();
method public int getRetryPolicy();
method public boolean isSystemError(); method public boolean isSystemError();
method public boolean isTransientFailure(); method public boolean isTransientFailure();
method public boolean requiresUserAuthentication(); method public boolean requiresUserAuthentication();
field public static final int ERROR_ATTESTATION_CHALLENGE_TOO_LARGE = 9; // 0x9 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_ID_ATTESTATION_FAILURE = 8; // 0x8
field public static final int ERROR_INCORRECT_USAGE = 13; // 0xd field public static final int ERROR_INCORRECT_USAGE = 13; // 0xd
field public static final int ERROR_INTERNAL_SYSTEM_ERROR = 4; // 0x4 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_PERMISSION_DENIED = 5; // 0x5
field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc 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 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 { @Deprecated public final class KeyStoreParameter implements java.security.KeyStore.ProtectionParameter {

View File

@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.annotation.TestApi; import android.annotation.TestApi;
import android.security.keymaster.KeymasterDefs; import android.security.keymaster.KeymasterDefs;
import android.system.keystore2.ResponseCode; import android.system.keystore2.ResponseCode;
import android.util.Log;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@@ -36,6 +37,8 @@ import java.util.Map;
* is likely to succeed. * is likely to succeed.
*/ */
public class KeyStoreException extends Exception { 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 * 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 * 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. * The caller should re-create the crypto object and try again.
*/ */
public static final int ERROR_KEY_OPERATION_EXPIRED = 15; 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 */ /** @hide */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@@ -132,11 +156,68 @@ public class KeyStoreException extends Exception {
ERROR_UNIMPLEMENTED, ERROR_UNIMPLEMENTED,
ERROR_INCORRECT_USAGE, ERROR_INCORRECT_USAGE,
ERROR_KEY_NOT_TEMPORALLY_VALID, ERROR_KEY_NOT_TEMPORALLY_VALID,
ERROR_KEY_OPERATION_EXPIRED ERROR_KEY_OPERATION_EXPIRED,
ERROR_ATTESTATION_KEYS_UNAVAILABLE
}) })
public @interface PublicErrorCode { 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: // Constants for encoding information about the error encountered:
// Whether the error relates to the system state/implementation as a whole, or a specific key. // 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; 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 // The internal error code. NOT to be returned directly to callers or made part of the
// public API. // public API.
private final int mErrorCode; 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 * @hide
@@ -155,6 +251,7 @@ public class KeyStoreException extends Exception {
public KeyStoreException(int errorCode, @Nullable String message) { public KeyStoreException(int errorCode, @Nullable String message) {
super(message); super(message);
mErrorCode = errorCode; mErrorCode = errorCode;
mRkpStatus = initializeRkpStatusForRegularErrors(errorCode);
} }
/** /**
@@ -165,6 +262,19 @@ public class KeyStoreException extends Exception {
super(message + " (internal Keystore code: " + errorCode + " message: " super(message + " (internal Keystore code: " + errorCode + " message: "
+ keystoreErrorMessage + ")"); + keystoreErrorMessage + ")");
mErrorCode = errorCode; 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() { public boolean isTransientFailure() {
PublicErrorInformation failureInfo = getErrorInformation(mErrorCode); 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; return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0;
} }
@@ -225,6 +346,34 @@ public class KeyStoreException extends Exception {
return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0; 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 @Override
public String toString() { public String toString() {
String errorCodes = String.format(" (public error code: %d internal Keystore code: %d)", 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)); new PublicErrorInformation(0, ERROR_KEY_CORRUPTED));
sErrorCodeToFailureInfo.put(ResponseCode.KEY_PERMANENTLY_INVALIDATED, sErrorCodeToFailureInfo.put(ResponseCode.KEY_PERMANENTLY_INVALIDATED,
new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST)); new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS,
new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
} }
} }

View File

@@ -28,7 +28,6 @@ import android.hardware.security.keymint.Tag;
import android.os.Build; import android.os.Build;
import android.os.RemoteException; import android.os.RemoteException;
import android.security.GenerateRkpKey; import android.security.GenerateRkpKey;
import android.security.GenerateRkpKeyException;
import android.security.KeyPairGeneratorSpec; import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2; import android.security.KeyStore2;
import android.security.KeyStoreException; import android.security.KeyStoreException;
@@ -618,18 +617,44 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
@Override @Override
public KeyPair generateKeyPair() { public KeyPair generateKeyPair() {
try { GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null);
return generateKeyPairHelper(); for (int i = 0; i < 2; i++) {
} catch (GenerateRkpKeyException e) { /**
try { * NOTE: There is no need to delay between re-tries because the call to
return generateKeyPairHelper(); * GenerateRkpKey.notifyEmpty() will delay for a while before returning.
} catch (GenerateRkpKeyException f) { */
throw new ProviderException("Failed to provision new attestation keys."); result = generateKeyPairHelper();
} if (result.rkpStatus == KeyStoreException.RKP_SUCCESS) {
return result.keyPair;
} }
} }
private KeyPair generateKeyPairHelper() throws GenerateRkpKeyException { // 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 GenerateKeyPairHelperResult generateKeyPairHelper() {
if (mKeyStore == null || mSpec == null) { if (mKeyStore == null || mSpec == null) {
throw new IllegalStateException("Not initialized"); 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); Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
} }
success = true; 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) { } catch (android.security.KeyStoreException e) {
switch (e.getErrorCode()) { switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
@@ -688,11 +714,19 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
.currentApplication()); .currentApplication());
try { try {
//TODO: When detailed error information is available from the remote
//provisioner, propagate it up.
keyGen.notifyEmpty(securityLevel); keyGen.notifyEmpty(securityLevel);
} catch (RemoteException f) { } 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: default:
ProviderException p = new ProviderException("Failed to generate key pair.", e); ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {