summaryrefslogtreecommitdiff
path: root/keystore
diff options
context:
space:
mode:
Diffstat (limited to 'keystore')
-rw-r--r--keystore/Android.bp31
-rw-r--r--keystore/java/android/security/AndroidKeyStoreMaintenance.java105
-rw-r--r--keystore/java/android/security/AppUriAuthenticationPolicy.aidl19
-rw-r--r--keystore/java/android/security/AppUriAuthenticationPolicy.java241
-rw-r--r--keystore/java/android/security/Authorization.java22
-rw-r--r--keystore/java/android/security/CredentialManagementApp.java123
-rw-r--r--keystore/java/android/security/Credentials.java2
-rw-r--r--keystore/java/android/security/IKeyChainService.aidl9
-rw-r--r--keystore/java/android/security/KeyChain.java38
-rw-r--r--keystore/java/android/security/KeyStore.java2
-rw-r--r--keystore/java/android/security/KeyStore2.java1
-rw-r--r--keystore/java/android/security/UrisToAliases.java138
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java70
-rw-r--r--keystore/java/android/security/keystore/ArrayUtils.java8
-rw-r--r--keystore/java/android/security/keystore/KeyGenParameterSpec.java134
-rw-r--r--keystore/java/android/security/keystore/KeyProperties.java21
-rw-r--r--keystore/java/android/security/keystore/KeyProtection.java3
-rw-r--r--keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java8
-rw-r--r--keystore/java/android/security/keystore/Utils.java4
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java318
-rw-r--r--keystore/tests/Android.bp9
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