diff options
Diffstat (limited to 'keystore')
21 files changed, 1192 insertions, 114 deletions
diff --git a/keystore/Android.bp b/keystore/Android.bp new file mode 100644 index 000000000000..5db668e48431 --- /dev/null +++ b/keystore/Android.bp @@ -0,0 +1,31 @@ +// +// Copyright (C) 2021 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 { + default_applicable_licenses: ["frameworks_base_keystore_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "frameworks_base_keystore_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java new file mode 100644 index 000000000000..c81c8c54d88a --- /dev/null +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.security.usermanager.IKeystoreUserManager; +import android.system.keystore2.ResponseCode; +import android.util.Log; + +/** + * @hide This is the client side for IKeystoreUserManager AIDL. + * It shall only be used by the LockSettingsService. + */ +public class AndroidKeyStoreMaintenance { + private static final String TAG = "AndroidKeyStoreMaintenance"; + + public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR; + + private static IKeystoreUserManager getService() { + return IKeystoreUserManager.Stub.asInterface( + ServiceManager.checkService("android.security.usermanager")); + } + + /** + * Informs keystore2 about adding a user + * + * @param userId - Android user id of the user being added + * @return 0 if successful or a {@code ResponseCode} + * @hide + */ + public static int onUserAdded(@NonNull int userId) { + if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0; + try { + getService().onUserAdded(userId); + return 0; + } catch (ServiceSpecificException e) { + Log.e(TAG, "onUserAdded failed", e); + return e.errorCode; + } catch (Exception e) { + Log.e(TAG, "Can not connect to keystore", e); + return SYSTEM_ERROR; + } + } + + /** + * Informs keystore2 about removing a usergit mer + * + * @param userId - Android user id of the user being removed + * @return 0 if successful or a {@code ResponseCode} + * @hide + */ + public static int onUserRemoved(int userId) { + if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0; + try { + getService().onUserRemoved(userId); + return 0; + } catch (ServiceSpecificException e) { + Log.e(TAG, "onUserRemoved failed", e); + return e.errorCode; + } catch (Exception e) { + Log.e(TAG, "Can not connect to keystore", e); + return SYSTEM_ERROR; + } + } + + /** + * Informs keystore2 about changing user's password + * + * @param userId - Android user id of the user + * @param password - a secret derived from the synthetic password provided by the + * LockSettingService + * @return 0 if successful or a {@code ResponseCode} + * @hide + */ + public static int onUserPasswordChanged(int userId, @Nullable byte[] password) { + if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0; + try { + getService().onUserPasswordChanged(userId, password); + return 0; + } catch (ServiceSpecificException e) { + Log.e(TAG, "onUserPasswordChanged failed", e); + return e.errorCode; + } catch (Exception e) { + Log.e(TAG, "Can not connect to keystore", e); + return SYSTEM_ERROR; + } + } +} diff --git a/keystore/java/android/security/AppUriAuthenticationPolicy.aidl b/keystore/java/android/security/AppUriAuthenticationPolicy.aidl new file mode 100644 index 000000000000..5c52c86f0426 --- /dev/null +++ b/keystore/java/android/security/AppUriAuthenticationPolicy.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2020 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; + +parcelable AppUriAuthenticationPolicy; diff --git a/keystore/java/android/security/AppUriAuthenticationPolicy.java b/keystore/java/android/security/AppUriAuthenticationPolicy.java new file mode 100644 index 000000000000..0244ce97c0d4 --- /dev/null +++ b/keystore/java/android/security/AppUriAuthenticationPolicy.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2020 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * The app-URI authentication policy is set by the credential management app. This policy determines + * which alias for a private key and certificate pair should be used for authentication. + * <p> + * The authentication policy should be added as a parameter when calling + * {@link KeyChain#createManageCredentialsIntent}. + * <p> + * Example: + * <pre>{@code + * AppUriAuthenticationPolicy authenticationPolicy = new AppUriAuthenticationPolicy.Builder() + * .addAppAndUriMapping("com.test.pkg", testUri, "testAlias") + * .addAppAndUriMapping("com.test2.pkg", testUri1, "testAlias2") + * .addAppAndUriMapping("com.test2.pkg", testUri2, "testAlias2") + * .build(); + * Intent requestIntent = KeyChain.createManageCredentialsIntent(authenticationPolicy); + * }</pre> + * <p> + */ +public final class AppUriAuthenticationPolicy implements Parcelable { + + private static final String KEY_AUTHENTICATION_POLICY_APP_TO_URIS = + "authentication_policy_app_to_uris"; + private static final String KEY_AUTHENTICATION_POLICY_APP = "policy_app"; + + /** + * The mappings from an app and list of URIs to a list of aliases, which will be used for + * authentication. + * <p> + * appPackageName -> uri -> alias + */ + @NonNull + private final Map<String, UrisToAliases> mAppToUris; + + private AppUriAuthenticationPolicy(@NonNull Map<String, UrisToAliases> appToUris) { + Objects.requireNonNull(appToUris); + this.mAppToUris = appToUris; + } + + /** + * Builder class for {@link AppUriAuthenticationPolicy} objects. + */ + public static final class Builder { + private Map<String, UrisToAliases> mPackageNameToUris; + + /** + * Initialize a new Builder to construct an {@link AppUriAuthenticationPolicy}. + */ + public Builder() { + mPackageNameToUris = new HashMap<>(); + } + + /** + * Adds mappings from an app and URI to an alias, which will be used for authentication. + * <p> + * If this method is called with a package name and URI that was previously added, the + * previous alias will be overwritten. + * + * @param appPackageName The app's package name to authenticate the user to. + * @param uri The URI to authenticate the user to. + * @param alias The alias which will be used for authentication. + * + * @return the same Builder instance. + */ + @NonNull + public Builder addAppAndUriMapping(@NonNull String appPackageName, @NonNull Uri uri, + @NonNull String alias) { + Objects.requireNonNull(appPackageName); + Objects.requireNonNull(uri); + Objects.requireNonNull(alias); + UrisToAliases urisToAliases = + mPackageNameToUris.getOrDefault(appPackageName, new UrisToAliases()); + urisToAliases.addUriToAlias(uri, alias); + mPackageNameToUris.put(appPackageName, urisToAliases); + return this; + } + + /** + * Adds mappings from an app and list of URIs to a list of aliases, which will be used for + * authentication. + * <p> + * appPackageName -> uri -> alias + * + * @hide + */ + @NonNull + public Builder addAppAndUriMapping(@NonNull String appPackageName, + @NonNull UrisToAliases urisToAliases) { + Objects.requireNonNull(appPackageName); + Objects.requireNonNull(urisToAliases); + mPackageNameToUris.put(appPackageName, urisToAliases); + return this; + } + + /** + * Combines all of the attributes that have been set on the {@link Builder} + * + * @return a new {@link AppUriAuthenticationPolicy} object. + */ + @NonNull + public AppUriAuthenticationPolicy build() { + return new AppUriAuthenticationPolicy(mPackageNameToUris); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeMap(mAppToUris); + } + + @NonNull + public static final Parcelable.Creator<AppUriAuthenticationPolicy> CREATOR = + new Parcelable.Creator<AppUriAuthenticationPolicy>() { + @Override + public AppUriAuthenticationPolicy createFromParcel(Parcel in) { + Map<String, UrisToAliases> appToUris = new HashMap<>(); + in.readMap(appToUris, UrisToAliases.class.getClassLoader()); + return new AppUriAuthenticationPolicy(appToUris); + } + + @Override + public AppUriAuthenticationPolicy[] newArray(int size) { + return new AppUriAuthenticationPolicy[size]; + } + }; + + @Override + public String toString() { + return "AppUriAuthenticationPolicy{" + + "mPackageNameToUris=" + mAppToUris + + '}'; + } + + /** + * Return the authentication policy mapping, which determines which alias for a private key + * and certificate pair should be used for authentication. + * <p> + * appPackageName -> uri -> alias + */ + @NonNull + public Map<String, Map<Uri, String>> getAppAndUriMappings() { + Map<String, Map<Uri, String>> appAndUris = new HashMap<>(); + for (Map.Entry<String, UrisToAliases> entry : mAppToUris.entrySet()) { + appAndUris.put(entry.getKey(), entry.getValue().getUrisToAliases()); + } + return appAndUris; + } + + /** + * Restore a previously saved {@link AppUriAuthenticationPolicy} from XML. + * + * @hide + */ + @Nullable + public static AppUriAuthenticationPolicy readFromXml(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + AppUriAuthenticationPolicy.Builder builder = new AppUriAuthenticationPolicy.Builder(); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (!parser.getName().equals(KEY_AUTHENTICATION_POLICY_APP_TO_URIS)) { + continue; + } + String app = parser.getAttributeValue(null, KEY_AUTHENTICATION_POLICY_APP); + UrisToAliases urisToAliases = UrisToAliases.readFromXml(parser); + builder.addAppAndUriMapping(app, urisToAliases); + } + return builder.build(); + } + + /** + * Save the {@link AppUriAuthenticationPolicy} to XML. + * + * @hide + */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + for (Map.Entry<String, UrisToAliases> appsToUris : mAppToUris.entrySet()) { + out.startTag(null, KEY_AUTHENTICATION_POLICY_APP_TO_URIS); + out.attribute(null, KEY_AUTHENTICATION_POLICY_APP, appsToUris.getKey()); + appsToUris.getValue().writeToXml(out); + out.endTag(null, KEY_AUTHENTICATION_POLICY_APP_TO_URIS); + } + } + + /** + * Get the set of aliases found in the policy. + * + * @hide + */ + public Set<String> getAliases() { + Set<String> aliases = new HashSet<>(); + for (UrisToAliases appsToUris : mAppToUris.values()) { + aliases.addAll(appsToUris.getUrisToAliases().values()); + } + return aliases; + } + +} diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java index 21d23b1b2575..50a90820117d 100644 --- a/keystore/java/android/security/Authorization.java +++ b/keystore/java/android/security/Authorization.java @@ -33,20 +33,12 @@ import android.util.Log; */ public class Authorization { private static final String TAG = "KeystoreAuthorization"; - private static IKeystoreAuthorization sIKeystoreAuthorization; public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR; - public Authorization() { - sIKeystoreAuthorization = null; - } - - private static synchronized IKeystoreAuthorization getService() { - if (sIKeystoreAuthorization == null) { - sIKeystoreAuthorization = IKeystoreAuthorization.Stub.asInterface( + private static IKeystoreAuthorization getService() { + return IKeystoreAuthorization.Stub.asInterface( ServiceManager.checkService("android.security.authorization")); - } - return sIKeystoreAuthorization; } /** @@ -55,12 +47,12 @@ public class Authorization { * @param authToken created by Android authenticators. * @return 0 if successful or {@code ResponseCode.SYSTEM_ERROR}. */ - public int addAuthToken(@NonNull HardwareAuthToken authToken) { + public static int addAuthToken(@NonNull HardwareAuthToken authToken) { if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0; try { getService().addAuthToken(authToken); return 0; - } catch (RemoteException e) { + } catch (RemoteException | NullPointerException e) { Log.w(TAG, "Can not connect to keystore", e); return SYSTEM_ERROR; } catch (ServiceSpecificException e) { @@ -73,7 +65,7 @@ public class Authorization { * @param authToken * @return 0 if successful or a {@code ResponseCode}. */ - public int addAuthToken(@NonNull byte[] authToken) { + public static int addAuthToken(@NonNull byte[] authToken) { return addAuthToken(AuthTokenUtils.toHardwareAuthToken(authToken)); } @@ -86,7 +78,7 @@ public class Authorization { * * @return 0 if successful or a {@code ResponseCode}. */ - public int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId, + public static int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId, @Nullable byte[] syntheticPassword) { if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0; try { @@ -96,7 +88,7 @@ public class Authorization { getService().onLockScreenEvent(LockScreenEvent.UNLOCK, userId, syntheticPassword); } return 0; - } catch (RemoteException e) { + } catch (RemoteException | NullPointerException e) { Log.w(TAG, "Can not connect to keystore", e); return SYSTEM_ERROR; } catch (ServiceSpecificException e) { diff --git a/keystore/java/android/security/CredentialManagementApp.java b/keystore/java/android/security/CredentialManagementApp.java new file mode 100644 index 000000000000..cbb23015dbe8 --- /dev/null +++ b/keystore/java/android/security/CredentialManagementApp.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Objects; + +/** + * The credential management app has the ability to manage the user's KeyChain credentials on + * unmanaged devices. {@link KeyChain#createManageCredentialsIntent} should be used by an app to + * request to become the credential management app. The user must approve this request before the + * app can manage the user's credentials. + * <p> + * Note: there can only be one credential management on the device. If another app requests to + * become the credential management app and the user approves, then the existing credential + * management app will no longer be able to manage credentials. + * <p> + * The requesting credential management app should include its authentication policy in the + * requesting intent. The authentication policy declares which certificates should be used for a + * given list of apps and URIs. + * + * @hide + * @see AppUriAuthenticationPolicy + */ +public class CredentialManagementApp { + + private static final String TAG = "CredentialManagementApp"; + private static final String KEY_PACKAGE_NAME = "package_name"; + + /** + * The credential management app's package name + */ + @NonNull + private final String mPackageName; + + /** + * The mappings from an app and list of URIs to a list of aliases, which will be used for + * authentication. + * <p> + * appPackageName -> uri -> alias + */ + @NonNull + private AppUriAuthenticationPolicy mAuthenticationPolicy; + + public CredentialManagementApp(@NonNull String packageName, + @NonNull AppUriAuthenticationPolicy authenticationPolicy) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(authenticationPolicy); + mPackageName = packageName; + mAuthenticationPolicy = authenticationPolicy; + } + + /** + * Returns the package name of the credential management app. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the authentication policy of the credential management app. + */ + @NonNull + public AppUriAuthenticationPolicy getAuthenticationPolicy() { + return mAuthenticationPolicy; + } + + /** + * Sets the authentication policy of the credential management app. + */ + public void setAuthenticationPolicy(@Nullable AppUriAuthenticationPolicy authenticationPolicy) { + Objects.requireNonNull(authenticationPolicy); + mAuthenticationPolicy = authenticationPolicy; + } + + /** + * Restore a previously saved {@link CredentialManagementApp} from XML. + */ + @Nullable + public static CredentialManagementApp readFromXml(@NonNull XmlPullParser parser) { + try { + String packageName = parser.getAttributeValue(null, KEY_PACKAGE_NAME); + AppUriAuthenticationPolicy policy = AppUriAuthenticationPolicy.readFromXml(parser); + return new CredentialManagementApp(packageName, policy); + } catch (XmlPullParserException | IOException e) { + Log.w(TAG, "Reading from xml failed", e); + } + return null; + } + + /** + * Save the {@link CredentialManagementApp} to XML. + */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + out.attribute(null, KEY_PACKAGE_NAME, mPackageName); + if (mAuthenticationPolicy != null) { + mAuthenticationPolicy.writeToXml(out); + } + } +} diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index 9e1fb54bedbe..ae9f866459d6 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -49,6 +49,8 @@ public class Credentials { public static final String INSTALL_AS_USER_ACTION = "android.credentials.INSTALL_AS_USER"; + public static final String ACTION_MANAGE_CREDENTIALS = "android.security.MANAGE_CREDENTIALS"; + /** * Key prefix for CA certificates. * diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index 1ae6a631dbcb..f708298a2cbd 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -18,6 +18,8 @@ package android.security; import android.content.pm.StringParceledListSlice; import android.security.keymaster.KeymasterCertificateChain; import android.security.keystore.ParcelableKeyGenParameterSpec; +import android.security.AppUriAuthenticationPolicy; +import android.net.Uri; /** * Caller is required to ensure that {@link KeyStore#unlock @@ -47,6 +49,7 @@ interface IKeyChainService { in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias, int uid); boolean removeKeyPair(String alias); boolean containsKeyPair(String alias); + int[] getGrants(String alias); // APIs used by Settings boolean deleteCaCertificate(String alias); @@ -56,6 +59,12 @@ interface IKeyChainService { boolean containsCaAlias(String alias); byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem); List<String> getCaCertificateChainAliases(String rootAlias, boolean includeDeletedSystem); + void setCredentialManagementApp(String packageName, in AppUriAuthenticationPolicy policy); + boolean hasCredentialManagementApp(); + String getCredentialManagementAppPackageName(); + AppUriAuthenticationPolicy getCredentialManagementAppPolicy(); + String getPredefinedAliasForPackageAndUri(String packageName, in Uri uri); + void removeCredentialManagementApp(); // APIs used by KeyChainActivity void setGrant(int uid, String alias, boolean value); diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 6df62c0a0cba..63690d3c1567 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -15,6 +15,8 @@ */ package android.security; +import static android.security.Credentials.ACTION_MANAGE_CREDENTIALS; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -122,6 +124,11 @@ public final class KeyChain { private static final String CERT_INSTALLER_PACKAGE = "com.android.certinstaller"; /** + * Package name for Settings. + */ + private static final String SETTINGS_PACKAGE = "com.android.settings"; + + /** * Extra for use with {@link #ACTION_CHOOSER} * @hide Also used by KeyChainActivity implementation */ @@ -202,6 +209,20 @@ public final class KeyChain { public static final String EXTRA_PKCS12 = "PKCS12"; /** + * Extra used by {@link #createManageCredentialsIntent(AppUriAuthenticationPolicy)} to specify + * the authentication policy of the credential management app. + * + * <p>The authentication policy declares which alias for a private key and certificate pair + * should be used for authentication, given a list of apps and URIs. + * + * <p>The extra value should be a {@link AppUriAuthenticationPolicy}. + * + * @hide + */ + public static final String EXTRA_AUTHENTICATION_POLICY = + "android.security.extra.AUTHENTICATION_POLICY"; + + /** * Broadcast Action: Indicates the trusted storage has changed. Sent when * one of this happens: * @@ -386,6 +407,23 @@ public final class KeyChain { } /** + * Returns an {@code Intent} that should be used by an app to request to manage the user's + * credentials. This is limited to unmanaged devices. The authentication policy must be + * provided to be able to make this request successfully. + * + * @param policy The authentication policy determines which alias for a private key and + * certificate pair should be used for authentication. + */ + @NonNull + public static Intent createManageCredentialsIntent(@NonNull AppUriAuthenticationPolicy policy) { + Intent intent = new Intent(ACTION_MANAGE_CREDENTIALS); + intent.setComponent(ComponentName.createRelative(SETTINGS_PACKAGE, + ".security.RequestManageCredentials")); + intent.putExtra(EXTRA_AUTHENTICATION_POLICY, policy); + return intent; + } + + /** * Launches an {@code Activity} for the user to select the alias * for a private key and certificate pair for authentication. The * selected alias or null will be returned via the diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index e19d88c182ff..198df40c7d7b 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -996,7 +996,7 @@ public class KeyStore { */ public int addAuthToken(byte[] authToken) { try { - new Authorization().addAuthToken(authToken); + Authorization.addAuthToken(authToken); return mBinder.addAuthToken(authToken); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java index f7477bf92c81..476e4d7b7b18 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -107,7 +107,6 @@ public class KeyStore2 { try { return request.execute(service); } catch (ServiceSpecificException e) { - Log.e(TAG, "KeyStore exception", e); throw getKeyStoreException(e.errorCode); } catch (RemoteException e) { if (firstTry) { diff --git a/keystore/java/android/security/UrisToAliases.java b/keystore/java/android/security/UrisToAliases.java new file mode 100644 index 000000000000..65d433abe166 --- /dev/null +++ b/keystore/java/android/security/UrisToAliases.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2020 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * The mapping from URI to alias, which determines the alias to use when the user visits a URI. + * This mapping is part of the {@link AppUriAuthenticationPolicy}, which specifies which app this + * mapping should be used for. + * + * @hide + * @see AppUriAuthenticationPolicy + */ +public final class UrisToAliases implements Parcelable { + + private static final String KEY_AUTHENTICATION_POLICY_URI_TO_ALIAS = + "authentication_policy_uri_to_alias"; + private static final String KEY_AUTHENTICATION_POLICY_URI = "policy_uri"; + private static final String KEY_AUTHENTICATION_POLICY_ALIAS = "policy_alias"; + + /** + * The mappings from URIs to aliases, which will be used for authentication. + */ + @NonNull + private final Map<Uri, String> mUrisToAliases; + + public UrisToAliases() { + this.mUrisToAliases = new HashMap<>(); + } + + private UrisToAliases(@NonNull Map<Uri, String> urisToAliases) { + this.mUrisToAliases = urisToAliases; + } + + @NonNull + public static final Creator<UrisToAliases> CREATOR = new Creator<UrisToAliases>() { + @Override + public UrisToAliases createFromParcel(Parcel in) { + Map<Uri, String> urisToAliases = new HashMap<>(); + in.readMap(urisToAliases, String.class.getClassLoader()); + return new UrisToAliases(urisToAliases); + } + + @Override + public UrisToAliases[] newArray(int size) { + return new UrisToAliases[size]; + } + }; + + /** + * Returns the mapping from URIs to aliases. + */ + @NonNull + public Map<Uri, String> getUrisToAliases() { + return Collections.unmodifiableMap(mUrisToAliases); + } + + /** + * Adds mapping from an URI to an alias. + */ + public void addUriToAlias(@NonNull Uri uri, @NonNull String alias) { + mUrisToAliases.put(uri, alias); + } + + /** + * Restore a previously saved {@link UrisToAliases} from XML. + */ + @Nullable + public static UrisToAliases readFromXml(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + Map<Uri, String> urisToAliases = new HashMap<>(); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (!parser.getName().equals(KEY_AUTHENTICATION_POLICY_URI_TO_ALIAS)) { + continue; + } + Uri uri = Uri.parse(parser.getAttributeValue(null, KEY_AUTHENTICATION_POLICY_URI)); + String alias = parser.getAttributeValue(null, KEY_AUTHENTICATION_POLICY_ALIAS); + urisToAliases.put(uri, alias); + } + return new UrisToAliases(urisToAliases); + } + + /** + * Save the {@link UrisToAliases} to XML. + */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + for (Map.Entry<Uri, String> urisToAliases : mUrisToAliases.entrySet()) { + out.startTag(null, KEY_AUTHENTICATION_POLICY_URI_TO_ALIAS); + out.attribute(null, KEY_AUTHENTICATION_POLICY_URI, urisToAliases.getKey().toString()); + out.attribute(null, KEY_AUTHENTICATION_POLICY_ALIAS, urisToAliases.getValue()); + out.endTag(null, KEY_AUTHENTICATION_POLICY_URI_TO_ALIAS); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeMap(mUrisToAliases); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 334b1110d651..988838b46334 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -17,6 +17,7 @@ package android.security.keystore; import android.annotation.Nullable; +import android.content.Context; import android.os.Build; import android.security.Credentials; import android.security.KeyPairGeneratorSpec; @@ -25,6 +26,8 @@ import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterCertificateChain; import android.security.keymaster.KeymasterDefs; +import android.telephony.TelephonyManager; +import android.util.ArraySet; import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector; import com.android.internal.org.bouncycastle.asn1.ASN1InputStream; @@ -477,11 +480,11 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato success = true; return keyPair; - } catch (ProviderException e) { + } catch (ProviderException | IllegalArgumentException | DeviceIdAttestationException e) { if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { throw new SecureKeyImportUnavailableException(e); } else { - throw e; + throw new ProviderException(e); } } finally { if (!success) { @@ -491,7 +494,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair) - throws ProviderException { + throws ProviderException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); if (challenge != null) { KeymasterArguments args = new KeymasterArguments(); @@ -510,6 +513,60 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato Build.MODEL.getBytes(StandardCharsets.UTF_8)); } + int[] idTypes = mSpec.getAttestationIds(); + if (idTypes != null) { + final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); + for (int idType : idTypes) { + idTypesSet.add(idType); + } + TelephonyManager telephonyService = null; + if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI) + || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) { + telephonyService = + (TelephonyManager) KeyStore.getApplicationContext().getSystemService( + Context.TELEPHONY_SERVICE); + if (telephonyService == null) { + throw new DeviceIdAttestationException( + "Unable to access telephony service"); + } + } + for (final Integer idType : idTypesSet) { + switch (idType) { + case AttestationUtils.ID_TYPE_SERIAL: + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL, + Build.getSerial().getBytes(StandardCharsets.UTF_8) + ); + break; + case AttestationUtils.ID_TYPE_IMEI: { + final String imei = telephonyService.getImei(0); + if (imei == null) { + throw new DeviceIdAttestationException("Unable to retrieve IMEI"); + } + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI, + imei.getBytes(StandardCharsets.UTF_8) + ); + break; + } + case AttestationUtils.ID_TYPE_MEID: { + final String meid = telephonyService.getMeid(0); + if (meid == null) { + throw new DeviceIdAttestationException("Unable to retrieve MEID"); + } + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID, + meid.getBytes(StandardCharsets.UTF_8) + ); + break; + } + case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: { + args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION); + break; + } + default: + throw new IllegalArgumentException("Unknown device ID type " + idType); + } + } + } + return getAttestationChain(privateKeyAlias, keyPair, args); } @@ -547,7 +604,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } - private KeymasterArguments constructKeyGenerationArguments() { + private KeymasterArguments constructKeyGenerationArguments() + throws IllegalArgumentException, DeviceIdAttestationException { KeymasterArguments args = new KeymasterArguments(); args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); @@ -565,9 +623,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mSpec.getKeyValidityForConsumptionEnd()); addAlgorithmSpecificParameters(args); - if (mSpec.isUniqueIdIncluded()) + if (mSpec.isUniqueIdIncluded()) { args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID); - + } return args; } diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java index c8c1de4a5e83..f22b6041800f 100644 --- a/keystore/java/android/security/keystore/ArrayUtils.java +++ b/keystore/java/android/security/keystore/ArrayUtils.java @@ -34,6 +34,14 @@ public abstract class ArrayUtils { return ((array != null) && (array.length > 0)) ? array.clone() : array; } + /** + * Clones an array if it is not null and has a length greater than 0. Otherwise, returns the + * array. + */ + public static int[] cloneIfNotEmpty(int[] array) { + return ((array != null) && (array.length > 0)) ? array.clone() : array; + } + public static byte[] cloneIfNotEmpty(byte[] array) { return ((array != null) && (array.length > 0)) ? array.clone() : array; } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index c2a7b2ee6323..c79c12cd3343 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -236,10 +236,51 @@ import javax.security.auth.x500.X500Principal; * keyStore.load(null); * key = (SecretKey) keyStore.getKey("key2", null); * }</pre> + * + * <p><h3 id="example:ecdh">Example: EC key for ECDH key agreement</h3> + * This example illustrates how to generate an elliptic curve key pair, used to establish a shared + * secret with another party using ECDH key agreement. + * <pre> {@code + * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( + * KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); + * keyPairGenerator.initialize( + * new KeyGenParameterSpec.Builder( + * "eckeypair", + * KeyProperties.PURPOSE_AGREE_KEY) + * .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) + * .build()); + * KeyPair myKeyPair = keyPairGenerator.generateKeyPair(); + * + * // Exchange public keys with server. A new ephemeral key MUST be used for every message. + * PublicKey serverEphemeralPublicKey; // Ephemeral key received from server. + * + * // Create a shared secret based on our private key and the other party's public key. + * KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "AndroidKeyStore"); + * keyAgreement.init(myKeyPair.getPrivate()); + * keyAgreement.doPhase(serverEphemeralPublicKey, true); + * byte[] sharedSecret = keyAgreement.generateSecret(); + * + * // sharedSecret cannot safely be used as a key yet. We must run it through a key derivation + * // function with some other data: "salt" and "info". Salt is an optional random value, + * // omitted in this example. It's good practice to include both public keys and any other + * // key negotiation data in info. Here we use the public keys and a label that indicates + * // messages encrypted with this key are coming from the server. + * byte[] salt = {}; + * ByteArrayOutputStream info = new ByteArrayOutputStream(); + * info.write("ECDH secp256r1 AES-256-GCM-SIV\0".getBytes(StandardCharsets.UTF_8)); + * info.write(myKeyPair.getPublic().getEncoded()); + * info.write(serverEphemeralPublicKey.getEncoded()); + * + * // This example uses the Tink library and the HKDF key derivation function. + * AesGcmSiv key = new AesGcmSiv(Hkdf.computeHkdf( + * "HMACSHA256", sharedSecret, salt, info.toByteArray(), 32)); + * byte[] associatedData = {}; + * return key.decrypt(ciphertext, associatedData); + * } */ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs { - - private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake"); + private static final X500Principal DEFAULT_CERT_SUBJECT = + new X500Principal("CN=Android Keystore Key"); private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1"); private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970 private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048 @@ -267,6 +308,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mUserPresenceRequired; private final byte[] mAttestationChallenge; private final boolean mDevicePropertiesAttestationIncluded; + private final int[] mAttestationIds; private final boolean mUniqueIdIncluded; private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mInvalidatedByBiometricEnrollment; @@ -275,6 +317,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mUnlockedDeviceRequired; private final boolean mCriticalToDeviceEncryption; private final int mMaxUsageCount; + private final String mAttestKeyAlias; /* * ***NOTE***: All new fields MUST also be added to the following: * ParcelableKeyGenParameterSpec class. @@ -308,6 +351,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean userPresenceRequired, byte[] attestationChallenge, boolean devicePropertiesAttestationIncluded, + int[] attestationIds, boolean uniqueIdIncluded, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, @@ -315,7 +359,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean userConfirmationRequired, boolean unlockedDeviceRequired, boolean criticalToDeviceEncryption, - int maxUsageCount) { + int maxUsageCount, + String attestKeyAlias) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); } @@ -361,6 +406,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationType = userAuthenticationType; mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge); mDevicePropertiesAttestationIncluded = devicePropertiesAttestationIncluded; + mAttestationIds = attestationIds; mUniqueIdIncluded = uniqueIdIncluded; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; @@ -369,6 +415,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUnlockedDeviceRequired = unlockedDeviceRequired; mCriticalToDeviceEncryption = criticalToDeviceEncryption; mMaxUsageCount = maxUsageCount; + mAttestKeyAlias = attestKeyAlias; } /** @@ -720,6 +767,25 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * @hide + * Allows the caller to specify device IDs to be attested to in the certificate for the + * generated key pair. These values are the enums specified in + * {@link android.security.keystore.AttestationUtils} + * + * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL + * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI + * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID + * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION + * + * @return integer array representing the requested device IDs to attest. + */ + @SystemApi + @Nullable + public int[] getAttestationIds() { + return Utils.cloneIfNotNull(mAttestationIds); + } + + /** * @hide This is a system-only API * * Returns {@code true} if the attestation certificate will contain a unique ID field. @@ -806,6 +872,18 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Returns the alias of the attestation key that will be used to sign the attestation + * certificate of the generated key. Note that an attestation certificate will only be + * generated if an attestation challenge is set. + * + * @see Builder#setAttestKeyAlias(String) + */ + @Nullable + public String getAttestKeyAlias() { + return mAttestKeyAlias; + } + + /** * Builder of {@link KeyGenParameterSpec} instances. */ public final static class Builder { @@ -834,6 +912,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mUserPresenceRequired = false; private byte[] mAttestationChallenge = null; private boolean mDevicePropertiesAttestationIncluded = false; + private int[] mAttestationIds = null; private boolean mUniqueIdIncluded = false; private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; @@ -842,6 +921,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mUnlockedDeviceRequired = false; private boolean mCriticalToDeviceEncryption = false; private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; + private String mAttestKeyAlias = null; /** * Creates a new instance of the {@code Builder}. @@ -902,6 +982,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mAttestationChallenge = sourceSpec.getAttestationChallenge(); mDevicePropertiesAttestationIncluded = sourceSpec.isDevicePropertiesAttestationIncluded(); + mAttestationIds = sourceSpec.getAttestationIds(); mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded(); mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody(); mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment(); @@ -910,6 +991,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired(); mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption(); mMaxUsageCount = sourceSpec.getMaxUsageCount(); + mAttestKeyAlias = sourceSpec.getAttestKeyAlias(); } /** @@ -1473,6 +1555,26 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * @hide + * Sets which IDs to attest in the attestation certificate for the key. The acceptable + * values in this integer array are the enums specified in + * {@link android.security.keystore.AttestationUtils} + * + * @param attestationIds the array of ID types to attest to in the certificate. + * + * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL + * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI + * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID + * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION + */ + @SystemApi + @NonNull + public Builder setAttestationIds(@NonNull int[] attestationIds) { + mAttestationIds = attestationIds; + return this; + } + + /** * @hide Only system apps can use this method. * * Sets whether to include a temporary unique ID field in the attestation certificate. @@ -1610,6 +1712,28 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Sets the alias of the attestation key that will be used to sign the attestation + * certificate for the generated key pair, if an attestation challenge is set with {@link + * #setAttestationChallenge}. If an attestKeyAlias is set but no challenge, {@link + * java.security.KeyPairGenerator#initialize} will throw {@link + * java.security.InvalidAlgorithmParameterException}. + * + * <p>If the attestKeyAlias is set to null (the default), Android Keystore will select an + * appropriate system-provided attestation signing key. If not null, the alias must + * reference an Android Keystore Key that was created with {@link + * android.security.keystore.KeyProperties#PURPOSE_ATTEST_KEY}, or key generation will throw + * {@link java.security.InvalidAlgorithmParameterException}. + * + * @param attestKeyAlias the alias of the attestation key to be used to sign the + * attestation certificate. + */ + @NonNull + public Builder setAttestKeyAlias(@Nullable String attestKeyAlias) { + mAttestKeyAlias = attestKeyAlias; + return this; + } + + /** * Builds an instance of {@code KeyGenParameterSpec}. */ @NonNull @@ -1638,6 +1762,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserPresenceRequired, mAttestationChallenge, mDevicePropertiesAttestationIncluded, + mAttestationIds, mUniqueIdIncluded, mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, @@ -1645,7 +1770,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserConfirmationRequired, mUnlockedDeviceRequired, mCriticalToDeviceEncryption, - mMaxUsageCount); + mMaxUsageCount, + mAttestKeyAlias); } } } diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 3ebca6ad302d..7b0fa91380e1 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -70,6 +70,7 @@ public abstract class KeyProperties { PURPOSE_VERIFY, PURPOSE_WRAP_KEY, PURPOSE_AGREE_KEY, + PURPOSE_ATTEST_KEY, }) public @interface PurposeEnum {} @@ -100,10 +101,26 @@ public abstract class KeyProperties { /** * Purpose of key: creating a shared ECDH secret through key agreement. + * + * <p>A key having this purpose can be combined with the elliptic curve public key of another + * party to establish a shared secret over an insecure channel. It should be used as a + * parameter to {@link javax.crypto.KeyAgreement#init(java.security.Key)} (a complete example is + * available <a + * href="{@docRoot}reference/android/security/keystore/KeyGenParameterSpec#example:ecdh" + * >here</a>). + * See <a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman">this + * article</a> for a more detailed explanation. */ public static final int PURPOSE_AGREE_KEY = 1 << 6; /** + * Purpose of key: Signing attestaions. This purpose is incompatible with all others, meaning + * that when generating a key with PURPOSE_ATTEST_KEY, no other purposes may be specified. In + * addition, PURPOSE_ATTEST_KEY may not be specified for imported keys. + */ + public static final int PURPOSE_ATTEST_KEY = 1 << 7; + + /** * @hide */ public static abstract class Purpose { @@ -123,6 +140,8 @@ public abstract class KeyProperties { return KeymasterDefs.KM_PURPOSE_WRAP; case PURPOSE_AGREE_KEY: return KeymasterDefs.KM_PURPOSE_AGREE_KEY; + case PURPOSE_ATTEST_KEY: + return KeymasterDefs.KM_PURPOSE_ATTEST_KEY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } @@ -142,6 +161,8 @@ public abstract class KeyProperties { return PURPOSE_WRAP_KEY; case KeymasterDefs.KM_PURPOSE_AGREE_KEY: return PURPOSE_AGREE_KEY; + case KeymasterDefs.KM_PURPOSE_ATTEST_KEY: + return PURPOSE_ATTEST_KEY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 76ce23efd05b..673491ef523e 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -588,7 +588,8 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID; private boolean mCriticalToDeviceEncryption = false; private boolean mIsStrongBoxBacked = false; - private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; + private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; + private String mAttestKeyAlias = null; /** * Creates a new instance of the {@code Builder}. diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 8163472abdfb..c20cf01a993e 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -101,6 +101,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isUserPresenceRequired()); out.writeByteArray(mSpec.getAttestationChallenge()); out.writeBoolean(mSpec.isDevicePropertiesAttestationIncluded()); + out.writeIntArray(mSpec.getAttestationIds()); out.writeBoolean(mSpec.isUniqueIdIncluded()); out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody()); out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment()); @@ -109,6 +110,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isUnlockedDeviceRequired()); out.writeBoolean(mSpec.isCriticalToDeviceEncryption()); out.writeInt(mSpec.getMaxUsageCount()); + out.writeString(mSpec.getAttestKeyAlias()); } private static Date readDateOrNull(Parcel in) { @@ -160,6 +162,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean userPresenceRequired = in.readBoolean(); final byte[] attestationChallenge = in.createByteArray(); final boolean devicePropertiesAttestationIncluded = in.readBoolean(); + final int[] attestationIds = in.createIntArray(); final boolean uniqueIdIncluded = in.readBoolean(); final boolean userAuthenticationValidWhileOnBody = in.readBoolean(); final boolean invalidatedByBiometricEnrollment = in.readBoolean(); @@ -168,6 +171,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean unlockedDeviceRequired = in.readBoolean(); final boolean criticalToDeviceEncryption = in.readBoolean(); final int maxUsageCount = in.readInt(); + final String attestKeyAlias = in.readString(); // The KeyGenParameterSpec is intentionally not constructed using a Builder here: // The intention is for this class to break if new parameters are added to the // KeyGenParameterSpec constructor (whereas using a builder would silently drop them). @@ -195,6 +199,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { userPresenceRequired, attestationChallenge, devicePropertiesAttestationIncluded, + attestationIds, uniqueIdIncluded, userAuthenticationValidWhileOnBody, invalidatedByBiometricEnrollment, @@ -202,7 +207,8 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { userConfirmationRequired, unlockedDeviceRequired, criticalToDeviceEncryption, - maxUsageCount); + maxUsageCount, + attestKeyAlias); } public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() { diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java index 5722c7b53ef4..e58b1ccb5370 100644 --- a/keystore/java/android/security/keystore/Utils.java +++ b/keystore/java/android/security/keystore/Utils.java @@ -33,4 +33,8 @@ abstract class Utils { static byte[] cloneIfNotNull(byte[] value) { return (value != null) ? value.clone() : null; } + + static int[] cloneIfNotNull(int[] value) { + return (value != null) ? value.clone() : null; + } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 70e30d2de5a1..b3bfd6a3a97a 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -18,26 +18,36 @@ package android.security.keystore2; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.SecurityLevel; +import android.hardware.security.keymint.Tag; import android.os.Build; import android.security.KeyPairGeneratorSpec; +import android.security.KeyStore; import android.security.KeyStore2; import android.security.KeyStoreException; import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keystore.ArrayUtils; +import android.security.keystore.AttestationUtils; +import android.security.keystore.DeviceIdAttestationException; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeymasterUtils; import android.security.keystore.SecureKeyImportUnavailableException; import android.security.keystore.StrongBoxUnavailableException; +import android.system.keystore2.Authorization; import android.system.keystore2.Domain; import android.system.keystore2.IKeystoreSecurityLevel; import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; import android.system.keystore2.KeyMetadata; import android.system.keystore2.ResponseCode; +import android.telephony.TelephonyManager; +import android.util.ArraySet; import android.util.Log; import libcore.util.EmptyArray; @@ -55,6 +65,7 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -63,6 +74,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; /** * Provides a way to create instances of a KeyPair which will be placed in the @@ -147,6 +159,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private int mKeymasterAlgorithm = -1; private int mKeySizeBits; private SecureRandom mRng; + private KeyDescriptor mAttestKeyDescriptor; private int[] mKeymasterPurposes; private int[] mKeymasterBlockModes; @@ -191,83 +204,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato // Legacy/deprecated spec KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; try { - KeyGenParameterSpec.Builder specBuilder; - String specKeyAlgorithm = legacySpec.getKeyType(); - if (specKeyAlgorithm != null) { - // Spec overrides the generator's default key algorithm - try { - keymasterAlgorithm = - KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( - specKeyAlgorithm); - } catch (IllegalArgumentException e) { - throw new InvalidAlgorithmParameterException( - "Invalid key type in parameters", e); - } - } - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_EC: - specBuilder = new KeyGenParameterSpec.Builder( - legacySpec.getKeystoreAlias(), - KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - // Authorized to be used with any digest (including no digest). - // MD5 was never offered for Android Keystore for ECDSA. - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - break; - case KeymasterDefs.KM_ALGORITHM_RSA: - specBuilder = new KeyGenParameterSpec.Builder( - legacySpec.getKeystoreAlias(), - KeyProperties.PURPOSE_ENCRYPT - | KeyProperties.PURPOSE_DECRYPT - | KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - // Authorized to be used with any digest (including no digest). - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - // Authorized to be used with any encryption and signature padding - // schemes (including no padding). - specBuilder.setEncryptionPaddings( - KeyProperties.ENCRYPTION_PADDING_NONE, - KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, - KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); - specBuilder.setSignaturePaddings( - KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, - KeyProperties.SIGNATURE_PADDING_RSA_PSS); - // Disable randomized encryption requirement to support encryption - // padding NONE above. - specBuilder.setRandomizedEncryptionRequired(false); - break; - default: - throw new ProviderException( - "Unsupported algorithm: " + mKeymasterAlgorithm); - } - - if (legacySpec.getKeySize() != -1) { - specBuilder.setKeySize(legacySpec.getKeySize()); - } - if (legacySpec.getAlgorithmParameterSpec() != null) { - specBuilder.setAlgorithmParameterSpec( - legacySpec.getAlgorithmParameterSpec()); - } - specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); - specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); - specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); - specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); - specBuilder.setUserAuthenticationRequired(false); - - spec = specBuilder.build(); + keymasterAlgorithm = getKeymasterAlgorithmFromLegacy(keymasterAlgorithm, + legacySpec); + spec = buildKeyGenParameterSpecFromLegacy(legacySpec, keymasterAlgorithm); } catch (NullPointerException | IllegalArgumentException e) { throw new InvalidAlgorithmParameterException(e); } @@ -336,6 +275,10 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mJcaKeyAlgorithm = jcaKeyAlgorithm; mRng = random; mKeyStore = KeyStore2.getInstance(); + + mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec); + checkAttestKeyPurpose(spec); + success = true; } finally { if (!success) { @@ -344,6 +287,156 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } + private void checkAttestKeyPurpose(KeyGenParameterSpec spec) + throws InvalidAlgorithmParameterException { + if ((spec.getPurposes() & KeyProperties.PURPOSE_ATTEST_KEY) != 0 + && spec.getPurposes() != KeyProperties.PURPOSE_ATTEST_KEY) { + throw new InvalidAlgorithmParameterException( + "PURPOSE_ATTEST_KEY may not be specified with any other purposes"); + } + } + + private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec) + throws InvalidAlgorithmParameterException { + if (spec.getAttestKeyAlias() != null) { + KeyDescriptor attestKeyDescriptor = new KeyDescriptor(); + attestKeyDescriptor.domain = Domain.APP; + attestKeyDescriptor.alias = spec.getAttestKeyAlias(); + try { + KeyEntryResponse attestKey = mKeyStore.getKeyEntry(attestKeyDescriptor); + checkAttestKeyChallenge(spec); + checkAttestKeyPurpose(attestKey.metadata.authorizations); + checkAttestKeySecurityLevel(spec, attestKey); + } catch (KeyStoreException e) { + throw new InvalidAlgorithmParameterException("Invalid attestKeyAlias", e); + } + return attestKeyDescriptor; + } + return null; + } + + private void checkAttestKeyChallenge(KeyGenParameterSpec spec) + throws InvalidAlgorithmParameterException { + if (spec.getAttestationChallenge() == null) { + throw new InvalidAlgorithmParameterException( + "AttestKey specified but no attestation challenge provided"); + } + } + + private void checkAttestKeyPurpose(Authorization[] keyAuths) + throws InvalidAlgorithmParameterException { + Predicate<Authorization> isAttestKeyPurpose = x -> x.keyParameter.tag == Tag.PURPOSE + && x.keyParameter.value.getKeyPurpose() == KeyPurpose.ATTEST_KEY; + + if (Arrays.stream(keyAuths).noneMatch(isAttestKeyPurpose)) { + throw new InvalidAlgorithmParameterException( + ("Invalid attestKey, does not have PURPOSE_ATTEST_KEY")); + } + } + + private void checkAttestKeySecurityLevel(KeyGenParameterSpec spec, KeyEntryResponse key) + throws InvalidAlgorithmParameterException { + boolean attestKeyInStrongBox = key.metadata.keySecurityLevel == SecurityLevel.STRONGBOX; + if (spec.isStrongBoxBacked() != attestKeyInStrongBox) { + if (attestKeyInStrongBox) { + throw new InvalidAlgorithmParameterException( + "Invalid security level: Cannot sign non-StrongBox key with " + + "StrongBox attestKey"); + + } else { + throw new InvalidAlgorithmParameterException( + "Invalid security level: Cannot sign StrongBox key with " + + "non-StrongBox attestKey"); + } + } + } + + private int getKeymasterAlgorithmFromLegacy(int keymasterAlgorithm, + KeyPairGeneratorSpec legacySpec) throws InvalidAlgorithmParameterException { + String specKeyAlgorithm = legacySpec.getKeyType(); + if (specKeyAlgorithm != null) { + // Spec overrides the generator's default key algorithm + try { + keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + specKeyAlgorithm); + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException( + "Invalid key type in parameters", e); + } + } + return keymasterAlgorithm; + } + + private KeyGenParameterSpec buildKeyGenParameterSpecFromLegacy(KeyPairGeneratorSpec legacySpec, + int keymasterAlgorithm) { + KeyGenParameterSpec.Builder specBuilder; + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + // MD5 was never offered for Android Keystore for ECDSA. + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + // Authorized to be used with any encryption and signature padding + // schemes (including no padding). + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, + KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, + KeyProperties.SIGNATURE_PADDING_RSA_PSS); + // Disable randomized encryption requirement to support encryption + // padding NONE above. + specBuilder.setRandomizedEncryptionRequired(false); + break; + default: + throw new ProviderException( + "Unsupported algorithm: " + mKeymasterAlgorithm); + } + + if (legacySpec.getKeySize() != -1) { + specBuilder.setKeySize(legacySpec.getKeySize()); + } + if (legacySpec.getAlgorithmParameterSpec() != null) { + specBuilder.setAlgorithmParameterSpec( + legacySpec.getAlgorithmParameterSpec()); + } + specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); + specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); + specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); + specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); + specBuilder.setUserAuthenticationRequired(false); + + return specBuilder.build(); + } + private void resetAll() { mEntryAlias = null; mEntryUid = KeyProperties.NAMESPACE_APPLICATION; @@ -458,7 +551,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato try { KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel); - KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null, + KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, mAttestKeyDescriptor, constructKeyGenerationArguments(), flags, additionalEntropy); AndroidKeyStorePublicKey publicKey = @@ -478,7 +571,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } throw p; } - } catch (UnrecoverableKeyException e) { + } catch (UnrecoverableKeyException | IllegalArgumentException + | DeviceIdAttestationException e) { throw new ProviderException( "Failed to construct key object from newly generated key pair.", e); } finally { @@ -496,7 +590,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } private void addAttestationParameters(@NonNull List<KeyParameter> params) - throws ProviderException { + throws ProviderException, IllegalArgumentException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); if (challenge != null) { @@ -526,15 +620,69 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato Build.MODEL.getBytes(StandardCharsets.UTF_8) )); } - } else { - if (mSpec.isDevicePropertiesAttestationIncluded()) { - throw new ProviderException("An attestation challenge must be provided when " - + "requesting device properties attestation."); + + int[] idTypes = mSpec.getAttestationIds(); + if (idTypes == null) { + return; + } + final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); + for (int idType : idTypes) { + idTypesSet.add(idType); + } + TelephonyManager telephonyService = null; + if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI) + || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) { + telephonyService = + (TelephonyManager) KeyStore.getApplicationContext().getSystemService( + Context.TELEPHONY_SERVICE); + if (telephonyService == null) { + throw new DeviceIdAttestationException("Unable to access telephony service"); + } + } + for (final Integer idType : idTypesSet) { + switch (idType) { + case AttestationUtils.ID_TYPE_SERIAL: + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL, + Build.getSerial().getBytes(StandardCharsets.UTF_8) + )); + break; + case AttestationUtils.ID_TYPE_IMEI: { + final String imei = telephonyService.getImei(0); + if (imei == null) { + throw new DeviceIdAttestationException("Unable to retrieve IMEI"); + } + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI, + imei.getBytes(StandardCharsets.UTF_8) + )); + break; + } + case AttestationUtils.ID_TYPE_MEID: { + final String meid = telephonyService.getMeid(0); + if (meid == null) { + throw new DeviceIdAttestationException("Unable to retrieve MEID"); + } + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID, + meid.getBytes(StandardCharsets.UTF_8) + )); + break; + } + case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: { + params.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION)); + break; + } + default: + throw new IllegalArgumentException("Unknown device ID type " + idType); + } } } } - private Collection<KeyParameter> constructKeyGenerationArguments() { + private Collection<KeyParameter> constructKeyGenerationArguments() + throws DeviceIdAttestationException, IllegalArgumentException { List<KeyParameter> params = new ArrayList<>(); params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits)); params.add(KeyStore2ParameterUtils.makeEnum( diff --git a/keystore/tests/Android.bp b/keystore/tests/Android.bp index e9b22c140742..2315a8568c64 100644 --- a/keystore/tests/Android.bp +++ b/keystore/tests/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_keystore_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_keystore_license"], +} + android_test { name: "KeystoreTests", // LOCAL_MODULE := keystore |