diff options
author | David Zeuthen <zeuthen@google.com> | 2022-11-14 15:17:07 -0500 |
---|---|---|
committer | David Zeuthen <zeuthen@google.com> | 2022-12-09 13:05:50 -0500 |
commit | c505485c2dff2b91dd627b679a13ea2db55e12d3 (patch) | |
tree | 7dde9445bae32aeeae3975061bb08eb49956fe35 /identity | |
parent | 55f62fc1252f45803263d9d88a9fe7179d334172 (diff) | |
download | base-c505485c2dff2b91dd627b679a13ea2db55e12d3.tar.gz |
identity: Add support for setting minimum validity period for AuthKey.
This change adds support for specifying that an AuthKey should be
replaced if it's going to expire within a certain amount of time
configurable by the application. This also adds a way for the
application to learn about the expiration time of currently configured
AuthKeys.
Combined these two changes allow an application to get a perfect
picture of which AuthKeys are available, when they expire, and allows
the application to refresh AuthKeys well ahead of expiration dates.
Also remove wrong comment that storeStaticAuthenticationDate() variant
taking an expiration is only available in feature version 202101. It's
available on all feature versions.
Bug: 241912421
Test: atest VtsHalIdentityTargetTest
Test: atest android.security.identity.cts
Change-Id: Ib79da64abfa25b37ed73a37ce78fedd4ef7d1ece
Diffstat (limited to 'identity')
3 files changed, 143 insertions, 8 deletions
diff --git a/identity/java/android/security/identity/AuthenticationKeyMetadata.java b/identity/java/android/security/identity/AuthenticationKeyMetadata.java new file mode 100644 index 000000000000..d4c28f8d459a --- /dev/null +++ b/identity/java/android/security/identity/AuthenticationKeyMetadata.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 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.identity; + +import android.annotation.IntRange; +import android.annotation.NonNull; + +import java.time.Instant; + +/** + * Data about authentication keys. + */ +public class AuthenticationKeyMetadata { + private int mUsageCount; + private Instant mExpirationDate; + + AuthenticationKeyMetadata(int usageCount, Instant expirationDate) { + mUsageCount = usageCount; + mExpirationDate = expirationDate; + } + + /** + * Gets usage count for the authentication key. + * + * @return the usage count + */ + public @IntRange(from = 0) int getUsageCount() { + return mUsageCount; + } + + /** + * Gets expiration date for the authentication key. + * + * @return the expiration date of the authentication key. + */ + public @NonNull Instant getExpirationDate() { + return mExpirationDate; + } +} diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java index 06953ba83289..449c7a706753 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredential.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java @@ -380,8 +380,14 @@ class CredstoreIdentityCredential extends IdentityCredential { @Override public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) { + setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, 0); + } + + @Override + public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey, + long minValidTimeMillis) { try { - mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey); + mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, minValidTimeMillis); } catch (android.os.RemoteException e) { throw new RuntimeException("Unexpected RemoteException ", e); } catch (android.os.ServiceSpecificException e) { @@ -481,6 +487,34 @@ class CredstoreIdentityCredential extends IdentityCredential { } @Override + public @NonNull List<AuthenticationKeyMetadata> getAuthenticationKeyMetadata() { + try { + int[] usageCount = mBinder.getAuthenticationDataUsageCount(); + long[] expirationsMillis = mBinder.getAuthenticationDataExpirations(); + if (usageCount.length != expirationsMillis.length) { + throw new IllegalStateException("Size og usageCount and expirationMillis differ"); + } + List<AuthenticationKeyMetadata> mds = new ArrayList<>(); + for (int n = 0; n < expirationsMillis.length; n++) { + AuthenticationKeyMetadata md = null; + long expirationMillis = expirationsMillis[n]; + if (expirationMillis != Long.MAX_VALUE) { + md = new AuthenticationKeyMetadata( + usageCount[n], + Instant.ofEpochMilli(expirationMillis)); + } + mds.add(md); + } + return mds; + } catch (android.os.RemoteException e) { + throw new IllegalStateException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new IllegalStateException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) { try { byte[] proofOfOwnership = mBinder.proveOwnership(challenge); diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java index 3988c0c82b6e..2dd9778a6fe7 100644 --- a/identity/java/android/security/identity/IdentityCredential.java +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -16,6 +16,7 @@ package android.security.identity; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -25,6 +26,7 @@ import java.security.PublicKey; import java.security.cert.X509Certificate; import java.time.Instant; import java.util.Collection; +import java.util.List; import java.util.Map; /** @@ -297,7 +299,7 @@ public abstract class IdentityCredential { * session transcripts. * @throws NoAuthenticationKeyAvailableException if authentication keys were never * provisioned, the method - * {@link #setAvailableAuthenticationKeys(int, int)} + * {@link #setAvailableAuthenticationKeys(int, int, long)} * was called with {@code keyCount} set to 0, * the method * {@link #setAllowUsingExhaustedKeys(boolean)} @@ -331,19 +333,25 @@ public abstract class IdentityCredential { * for which this method has not been called behave as though it had been called wit * {@code keyCount} 0 and {@code maxUsesPerKey} 1. * + * <p>The effect of this method is like calling + * {@link #setAvailableAuthenticationKeys(int, int, long)} with the last parameter is set to 0. + * * @param keyCount The number of active, certified dynamic authentication keys the * {@code IdentityCredential} will try to keep available. This value * must be non-negative. * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's * eligible for replacement. This value must be greater than zero. + * @deprecated Use {@link #setAvailableAuthenticationKeys(int, int, long)} instead. */ + @Deprecated public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey); /** * Gets a collection of dynamic authentication keys that need certification. * * <p>When there aren't enough certified dynamic authentication keys, either because the key - * count has been increased or because one or more keys have reached their usage count, this + * count has been increased or because one or more keys have reached their usage count or + * it if a key is too close to its expiration date, this * method will generate replacement keys and certificates and return them for issuer * certification. The issuer certificates and associated static authentication data must then * be provided back to the Identity Credential using @@ -401,11 +409,6 @@ public abstract class IdentityCredential { * This should only be called for an authenticated key returned by * {@link #getAuthKeysNeedingCertification()}. * - * <p>This is only implemented in feature version 202101 or later. If not implemented, the call - * fails with {@link UnsupportedOperationException}. See - * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known - * feature versions. - * * @param authenticationKey The dynamic authentication key for which certification and * associated static * authentication data is being provided. @@ -427,7 +430,9 @@ public abstract class IdentityCredential { * Get the number of times the dynamic authentication keys have been used. * * @return int array of dynamic authentication key usage counts. + * @deprecated Use {@link #getAuthenticationKeyMetadata()} instead. */ + @Deprecated public @NonNull abstract int[] getAuthenticationDataUsageCount(); /** @@ -520,4 +525,47 @@ public abstract class IdentityCredential { public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) { throw new UnsupportedOperationException(); } + + /** + * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain, + * the number of times each should be used, and the minimum amount of time it's valid for. + * + * <p>The Identity Credential system will select the least-used dynamic authentication key each + * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. Identity Credentials + * for which this method has not been called behave as though it had been called wit + * {@code keyCount} 0, {@code maxUsesPerKey} 1, and {@code minValidTimeMillis} 0. + * + * <p>Applications can use {@link #getAuthenticationKeyMetadata()} to get a picture of the + * usage andtime left of each configured authentication key. This can be used to determine + * how urgent it is recertify new authentication keys via the + * {@link #getAuthKeysNeedingCertification()} method. + * + * @param keyCount The number of active, certified dynamic authentication keys the + * {@code IdentityCredential} will try to keep available. This value + * must be non-negative. + * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's + * eligible for replacement. This value must be greater than zero. + * @param minValidTimeMillis If a key has less time left than this value it will be eliglible + * for replacement. This value must be non-negative. + */ + public void setAvailableAuthenticationKeys( + @IntRange(from = 0) int keyCount, + @IntRange(from = 1) int maxUsesPerKey, + @IntRange(from = 0) long minValidTimeMillis) { + throw new UnsupportedOperationException(); + } + + /** + * Get information about dynamic authentication keys. + * + * <p>The returned list may have <code>null</code> values if certification for the dynamic + * authentication key is pending. + * + * <p>The list is always <code>keyCount</code> elements long. + * + * @return list of authentication key metadata objects. + */ + public @NonNull List<AuthenticationKeyMetadata> getAuthenticationKeyMetadata() { + throw new UnsupportedOperationException(); + } } |