summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <treehugger-gerrit@google.com> 2022-04-04 10:32:11 +0000
committer Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> 2022-04-04 10:32:11 +0000
commita0e091c6f3cb56b29f4fb6026d43ba59ebfabe1c (patch)
tree8b9e664282e05a73588fcdb99574e16bff439a20
parentd7659e60143ee8073f75092d84bc395bc9efd53a (diff)
parent6c82325675fc139788cd59822d16ab872718c170 (diff)
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>
-rw-r--r--core/api/current.txt5
-rw-r--r--keystore/java/android/security/KeyStoreException.java153
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java58
3 files changed, 203 insertions, 13 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 8f74ae197b73..641b1a3697e2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -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 {
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 54184dbf6e08..1a81dda8d56c 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -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));
}
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index e7961c94928c..5950b5bc7231 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -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) {