Loading core/api/current.txt +14 −0 Original line number Diff line number Diff line Loading @@ -40978,6 +40978,19 @@ package android.se.omapi { package android.security { public final class AppUriAuthenticationPolicy implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Map<java.lang.String,java.util.Map<android.net.Uri,java.lang.String>> getAppAndUriMappings(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.security.AppUriAuthenticationPolicy> CREATOR; } public static final class AppUriAuthenticationPolicy.Builder { ctor public AppUriAuthenticationPolicy.Builder(); method @NonNull public android.security.AppUriAuthenticationPolicy.Builder addAppAndUriMapping(@NonNull String, @NonNull android.net.Uri, @NonNull String); method @NonNull public android.security.AppUriAuthenticationPolicy build(); } public final class AttestedKeyPair { ctor public AttestedKeyPair(@Nullable java.security.KeyPair, @NonNull java.util.List<java.security.cert.Certificate>); method @NonNull public java.util.List<java.security.cert.Certificate> getAttestationRecord(); Loading Loading @@ -41025,6 +41038,7 @@ package android.security { method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable String, int, @Nullable String); method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable android.net.Uri, @Nullable String); method @NonNull public static android.content.Intent createInstallIntent(); method @NonNull public static android.content.Intent createManageCredentialsIntent(@NonNull android.security.AppUriAuthenticationPolicy); method @Nullable @WorkerThread public static java.security.cert.X509Certificate[] getCertificateChain(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; method @Nullable @WorkerThread public static java.security.PrivateKey getPrivateKey(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; method @Deprecated public static boolean isBoundKeyAlgorithm(@NonNull String); core/tests/coretests/src/android/security/CredentialManagementAppTest.java 0 → 100644 +185 −0 Original line number Diff line number Diff line /* * 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 static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import android.net.Uri; import android.util.Xml; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.Map; @SmallTest @RunWith(AndroidJUnit4.class) public final class CredentialManagementAppTest { private static final String TEST_PACKAGE_NAME_1 = "com.android.test"; private static final String TEST_PACKAGE_NAME_2 = "com.android.test2"; private static final Uri TEST_URI_1 = Uri.parse("test.com"); private static final Uri TEST_URI_2 = Uri.parse("test2.com"); private static final String TEST_ALIAS_1 = "testAlias"; private static final String TEST_ALIAS_2 = "testAlias2"; private static final String PACKAGE_NAME = "com.android.cred.mng.pkg"; private static final AppUriAuthenticationPolicy AUTHENTICATION_POLICY = new AppUriAuthenticationPolicy.Builder() .addAppAndUriMapping(TEST_PACKAGE_NAME_1, TEST_URI_1, TEST_ALIAS_1) .build(); private static final CredentialManagementApp CREDENTIAL_MANAGEMENT_APP = new CredentialManagementApp(PACKAGE_NAME, AUTHENTICATION_POLICY); private static final String TAG_CREDENTIAL_MANAGEMENT_APP = "credential-management-app"; @Test public void credentialManagementApp_getters() { CredentialManagementApp credentialManagementApp = new CredentialManagementApp(PACKAGE_NAME, AUTHENTICATION_POLICY); assertThat(credentialManagementApp.getPackageName(), is(PACKAGE_NAME)); assertThat(credentialManagementApp.getAuthenticationPolicy(), is(AUTHENTICATION_POLICY)); } @Test public void setAuthenticationPolicy_updatesAuthenticationPolicy() { CredentialManagementApp credentialManagementApp = new CredentialManagementApp(PACKAGE_NAME, AUTHENTICATION_POLICY); AppUriAuthenticationPolicy updatedAuthenticationPolicy = new AppUriAuthenticationPolicy.Builder().addAppAndUriMapping( TEST_PACKAGE_NAME_2, TEST_URI_2, TEST_ALIAS_2).build(); credentialManagementApp.setAuthenticationPolicy(updatedAuthenticationPolicy); assertThat(credentialManagementApp.getAuthenticationPolicy(), is(updatedAuthenticationPolicy)); } @Test public void constructor_nullPackageName_throwException() { try { new CredentialManagementApp(/* packageName= */ null, AUTHENTICATION_POLICY); fail("Shall not take null inputs"); } catch (NullPointerException expected) { // Expected behavior, nothing to do. } } @Test public void constructor_nullAuthenticationPolicy_throwException() { try { new CredentialManagementApp(PACKAGE_NAME, /* authenticationPolicy= */ null); fail("Shall not take null inputs"); } catch (NullPointerException expected) { // Expected behavior, nothing to do. } } @Test public void writeToXmlAndReadFromXml() throws IOException, XmlPullParserException { File xmlFile = writeToXml(CREDENTIAL_MANAGEMENT_APP); CredentialManagementApp loadedCredentialManagementApp = readFromXml(xmlFile); assertCredentialManagementAppsEqual(loadedCredentialManagementApp, CREDENTIAL_MANAGEMENT_APP); } private File writeToXml(CredentialManagementApp credentialManagementApp) throws IOException { File file = File.createTempFile("temp", "credmng"); final FileOutputStream out = new FileOutputStream(file); XmlSerializer xml = Xml.newSerializer(); xml.setOutput(out, StandardCharsets.UTF_8.name()); xml.startDocument(null, true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, TAG_CREDENTIAL_MANAGEMENT_APP); credentialManagementApp.writeToXml(xml); xml.endTag(null, TAG_CREDENTIAL_MANAGEMENT_APP); xml.endDocument(); out.close(); return file; } private CredentialManagementApp readFromXml(File file) throws IOException, XmlPullParserException { CredentialManagementApp credentialManagementApp = null; final XmlPullParser parser = Xml.newPullParser(); final FileInputStream in = new FileInputStream(file); parser.setInput(in, StandardCharsets.UTF_8.name()); int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } String tag = parser.getName(); if (TAG_CREDENTIAL_MANAGEMENT_APP.equals(tag)) { credentialManagementApp = CredentialManagementApp.readFromXml(parser); } return credentialManagementApp; } private void assertCredentialManagementAppsEqual(CredentialManagementApp actual, CredentialManagementApp expected) { assertThat(actual.getPackageName(), is(expected.getPackageName())); assertAuthenticationPoliciesEqual(actual.getAuthenticationPolicy(), expected.getAuthenticationPolicy()); } private void assertAuthenticationPoliciesEqual(AppUriAuthenticationPolicy actual, AppUriAuthenticationPolicy expected) { Iterator<Map.Entry<String, Map<Uri, String>>> actualIter = actual.getAppAndUriMappings().entrySet().iterator(); Iterator<Map.Entry<String, Map<Uri, String>>> expectedIter = expected.getAppAndUriMappings().entrySet().iterator(); assertThat(actual.getAppAndUriMappings().size(), is(expected.getAppAndUriMappings().size())); while (actualIter.hasNext()) { Map.Entry<String, Map<Uri, String>> actualAppToUri = actualIter.next(); Map.Entry<String, Map<Uri, String>> expectedAppToUri = expectedIter.next(); assertThat(actualAppToUri.getKey(), is(expectedAppToUri.getKey())); assertUrisToAliasesEqual(actualAppToUri.getValue(), expectedAppToUri.getValue()); } } private void assertUrisToAliasesEqual(Map<Uri, String> actual, Map<Uri, String> expected) { Iterator<Map.Entry<Uri, String>> actualIter = actual.entrySet().iterator(); Iterator<Map.Entry<Uri, String>> expectedIter = expected.entrySet().iterator(); assertThat(actual.size(), is(expected.size())); while (actualIter.hasNext()) { Map.Entry<Uri, String> actualUriToAlias = actualIter.next(); Map.Entry<Uri, String> expectedUriToAlias = expectedIter.next(); assertThat(actualUriToAlias.getKey(), is(expectedUriToAlias.getKey())); assertThat(actualUriToAlias.getValue(), is(expectedUriToAlias.getValue())); } } } keystore/java/android/security/AppUriAuthenticationPolicy.aidl 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * 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; keystore/java/android/security/AppUriAuthenticationPolicy.java 0 → 100644 +226 −0 Original line number Diff line number Diff line /* * 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.Map; import java.util.Objects; /** * 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); } } } keystore/java/android/security/CredentialManagementApp.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * 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); } } } Loading
core/api/current.txt +14 −0 Original line number Diff line number Diff line Loading @@ -40978,6 +40978,19 @@ package android.se.omapi { package android.security { public final class AppUriAuthenticationPolicy implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Map<java.lang.String,java.util.Map<android.net.Uri,java.lang.String>> getAppAndUriMappings(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.security.AppUriAuthenticationPolicy> CREATOR; } public static final class AppUriAuthenticationPolicy.Builder { ctor public AppUriAuthenticationPolicy.Builder(); method @NonNull public android.security.AppUriAuthenticationPolicy.Builder addAppAndUriMapping(@NonNull String, @NonNull android.net.Uri, @NonNull String); method @NonNull public android.security.AppUriAuthenticationPolicy build(); } public final class AttestedKeyPair { ctor public AttestedKeyPair(@Nullable java.security.KeyPair, @NonNull java.util.List<java.security.cert.Certificate>); method @NonNull public java.util.List<java.security.cert.Certificate> getAttestationRecord(); Loading Loading @@ -41025,6 +41038,7 @@ package android.security { method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable String, int, @Nullable String); method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable android.net.Uri, @Nullable String); method @NonNull public static android.content.Intent createInstallIntent(); method @NonNull public static android.content.Intent createManageCredentialsIntent(@NonNull android.security.AppUriAuthenticationPolicy); method @Nullable @WorkerThread public static java.security.cert.X509Certificate[] getCertificateChain(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; method @Nullable @WorkerThread public static java.security.PrivateKey getPrivateKey(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; method @Deprecated public static boolean isBoundKeyAlgorithm(@NonNull String);
core/tests/coretests/src/android/security/CredentialManagementAppTest.java 0 → 100644 +185 −0 Original line number Diff line number Diff line /* * 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 static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import android.net.Uri; import android.util.Xml; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.Map; @SmallTest @RunWith(AndroidJUnit4.class) public final class CredentialManagementAppTest { private static final String TEST_PACKAGE_NAME_1 = "com.android.test"; private static final String TEST_PACKAGE_NAME_2 = "com.android.test2"; private static final Uri TEST_URI_1 = Uri.parse("test.com"); private static final Uri TEST_URI_2 = Uri.parse("test2.com"); private static final String TEST_ALIAS_1 = "testAlias"; private static final String TEST_ALIAS_2 = "testAlias2"; private static final String PACKAGE_NAME = "com.android.cred.mng.pkg"; private static final AppUriAuthenticationPolicy AUTHENTICATION_POLICY = new AppUriAuthenticationPolicy.Builder() .addAppAndUriMapping(TEST_PACKAGE_NAME_1, TEST_URI_1, TEST_ALIAS_1) .build(); private static final CredentialManagementApp CREDENTIAL_MANAGEMENT_APP = new CredentialManagementApp(PACKAGE_NAME, AUTHENTICATION_POLICY); private static final String TAG_CREDENTIAL_MANAGEMENT_APP = "credential-management-app"; @Test public void credentialManagementApp_getters() { CredentialManagementApp credentialManagementApp = new CredentialManagementApp(PACKAGE_NAME, AUTHENTICATION_POLICY); assertThat(credentialManagementApp.getPackageName(), is(PACKAGE_NAME)); assertThat(credentialManagementApp.getAuthenticationPolicy(), is(AUTHENTICATION_POLICY)); } @Test public void setAuthenticationPolicy_updatesAuthenticationPolicy() { CredentialManagementApp credentialManagementApp = new CredentialManagementApp(PACKAGE_NAME, AUTHENTICATION_POLICY); AppUriAuthenticationPolicy updatedAuthenticationPolicy = new AppUriAuthenticationPolicy.Builder().addAppAndUriMapping( TEST_PACKAGE_NAME_2, TEST_URI_2, TEST_ALIAS_2).build(); credentialManagementApp.setAuthenticationPolicy(updatedAuthenticationPolicy); assertThat(credentialManagementApp.getAuthenticationPolicy(), is(updatedAuthenticationPolicy)); } @Test public void constructor_nullPackageName_throwException() { try { new CredentialManagementApp(/* packageName= */ null, AUTHENTICATION_POLICY); fail("Shall not take null inputs"); } catch (NullPointerException expected) { // Expected behavior, nothing to do. } } @Test public void constructor_nullAuthenticationPolicy_throwException() { try { new CredentialManagementApp(PACKAGE_NAME, /* authenticationPolicy= */ null); fail("Shall not take null inputs"); } catch (NullPointerException expected) { // Expected behavior, nothing to do. } } @Test public void writeToXmlAndReadFromXml() throws IOException, XmlPullParserException { File xmlFile = writeToXml(CREDENTIAL_MANAGEMENT_APP); CredentialManagementApp loadedCredentialManagementApp = readFromXml(xmlFile); assertCredentialManagementAppsEqual(loadedCredentialManagementApp, CREDENTIAL_MANAGEMENT_APP); } private File writeToXml(CredentialManagementApp credentialManagementApp) throws IOException { File file = File.createTempFile("temp", "credmng"); final FileOutputStream out = new FileOutputStream(file); XmlSerializer xml = Xml.newSerializer(); xml.setOutput(out, StandardCharsets.UTF_8.name()); xml.startDocument(null, true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, TAG_CREDENTIAL_MANAGEMENT_APP); credentialManagementApp.writeToXml(xml); xml.endTag(null, TAG_CREDENTIAL_MANAGEMENT_APP); xml.endDocument(); out.close(); return file; } private CredentialManagementApp readFromXml(File file) throws IOException, XmlPullParserException { CredentialManagementApp credentialManagementApp = null; final XmlPullParser parser = Xml.newPullParser(); final FileInputStream in = new FileInputStream(file); parser.setInput(in, StandardCharsets.UTF_8.name()); int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } String tag = parser.getName(); if (TAG_CREDENTIAL_MANAGEMENT_APP.equals(tag)) { credentialManagementApp = CredentialManagementApp.readFromXml(parser); } return credentialManagementApp; } private void assertCredentialManagementAppsEqual(CredentialManagementApp actual, CredentialManagementApp expected) { assertThat(actual.getPackageName(), is(expected.getPackageName())); assertAuthenticationPoliciesEqual(actual.getAuthenticationPolicy(), expected.getAuthenticationPolicy()); } private void assertAuthenticationPoliciesEqual(AppUriAuthenticationPolicy actual, AppUriAuthenticationPolicy expected) { Iterator<Map.Entry<String, Map<Uri, String>>> actualIter = actual.getAppAndUriMappings().entrySet().iterator(); Iterator<Map.Entry<String, Map<Uri, String>>> expectedIter = expected.getAppAndUriMappings().entrySet().iterator(); assertThat(actual.getAppAndUriMappings().size(), is(expected.getAppAndUriMappings().size())); while (actualIter.hasNext()) { Map.Entry<String, Map<Uri, String>> actualAppToUri = actualIter.next(); Map.Entry<String, Map<Uri, String>> expectedAppToUri = expectedIter.next(); assertThat(actualAppToUri.getKey(), is(expectedAppToUri.getKey())); assertUrisToAliasesEqual(actualAppToUri.getValue(), expectedAppToUri.getValue()); } } private void assertUrisToAliasesEqual(Map<Uri, String> actual, Map<Uri, String> expected) { Iterator<Map.Entry<Uri, String>> actualIter = actual.entrySet().iterator(); Iterator<Map.Entry<Uri, String>> expectedIter = expected.entrySet().iterator(); assertThat(actual.size(), is(expected.size())); while (actualIter.hasNext()) { Map.Entry<Uri, String> actualUriToAlias = actualIter.next(); Map.Entry<Uri, String> expectedUriToAlias = expectedIter.next(); assertThat(actualUriToAlias.getKey(), is(expectedUriToAlias.getKey())); assertThat(actualUriToAlias.getValue(), is(expectedUriToAlias.getValue())); } } }
keystore/java/android/security/AppUriAuthenticationPolicy.aidl 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * 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;
keystore/java/android/security/AppUriAuthenticationPolicy.java 0 → 100644 +226 −0 Original line number Diff line number Diff line /* * 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.Map; import java.util.Objects; /** * 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); } } }
keystore/java/android/security/CredentialManagementApp.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * 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); } } }