1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.Activity; 22 import android.net.Uri; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import org.xmlpull.v1.XmlPullParser; 27 import org.xmlpull.v1.XmlPullParserException; 28 import org.xmlpull.v1.XmlSerializer; 29 30 import java.io.IOException; 31 import java.security.Principal; 32 import java.util.HashMap; 33 import java.util.HashSet; 34 import java.util.Map; 35 import java.util.Objects; 36 import java.util.Set; 37 38 /** 39 * The app-URI authentication policy is set by the credential management app. This policy determines 40 * which alias for a private key and certificate pair should be used for authentication. 41 * <p> 42 * The authentication policy should be added as a parameter when calling 43 * {@link KeyChain#createManageCredentialsIntent}. 44 * <p> 45 * Example: 46 * <pre>{@code 47 * AppUriAuthenticationPolicy authenticationPolicy = new AppUriAuthenticationPolicy.Builder() 48 * .addAppAndUriMapping("com.test.pkg", testUri, "testAlias") 49 * .addAppAndUriMapping("com.test2.pkg", testUri1, "testAlias2") 50 * .addAppAndUriMapping("com.test2.pkg", testUri2, "testAlias2") 51 * .build(); 52 * Intent requestIntent = KeyChain.createManageCredentialsIntent(authenticationPolicy); 53 * }</pre> 54 * <p> 55 */ 56 public final class AppUriAuthenticationPolicy implements Parcelable { 57 58 private static final String KEY_AUTHENTICATION_POLICY_APP_TO_URIS = 59 "authentication_policy_app_to_uris"; 60 private static final String KEY_AUTHENTICATION_POLICY_APP = "policy_app"; 61 62 /** 63 * The mappings from an app and list of URIs to a list of aliases, which will be used for 64 * authentication. 65 * <p> 66 * appPackageName -> uri -> alias 67 */ 68 @NonNull 69 private final Map<String, UrisToAliases> mAppToUris; 70 AppUriAuthenticationPolicy(@onNull Map<String, UrisToAliases> appToUris)71 private AppUriAuthenticationPolicy(@NonNull Map<String, UrisToAliases> appToUris) { 72 Objects.requireNonNull(appToUris); 73 this.mAppToUris = appToUris; 74 } 75 76 /** 77 * Builder class for {@link AppUriAuthenticationPolicy} objects. 78 */ 79 public static final class Builder { 80 private Map<String, UrisToAliases> mPackageNameToUris; 81 82 /** 83 * Initialize a new Builder to construct an {@link AppUriAuthenticationPolicy}. 84 */ Builder()85 public Builder() { 86 mPackageNameToUris = new HashMap<>(); 87 } 88 89 /** 90 * Adds mappings from an app and URI to an alias, which will be used for authentication. 91 * <p> 92 * If this method is called with a package name and URI that was previously added, the 93 * previous alias will be overwritten. 94 * <p> 95 * When the system tries to determine which alias to return to a requesting app calling 96 * {@code KeyChain.choosePrivateKeyAlias}, it will choose the alias whose associated URI 97 * exactly matches the URI provided in {@link KeyChain#choosePrivateKeyAlias( 98 * Activity, KeyChainAliasCallback, String[], Principal[], Uri, String)} or the URI 99 * built from the host and port provided in {@link KeyChain#choosePrivateKeyAlias( 100 * Activity, KeyChainAliasCallback, String[], Principal[], String, int, String)}. 101 * 102 * @param appPackageName The app's package name to authenticate the user to. 103 * @param uri The URI to authenticate the user to. 104 * @param alias The alias which will be used for authentication. 105 * 106 * @return the same Builder instance. 107 */ 108 @NonNull addAppAndUriMapping(@onNull String appPackageName, @NonNull Uri uri, @NonNull String alias)109 public Builder addAppAndUriMapping(@NonNull String appPackageName, @NonNull Uri uri, 110 @NonNull String alias) { 111 Objects.requireNonNull(appPackageName); 112 Objects.requireNonNull(uri); 113 Objects.requireNonNull(alias); 114 UrisToAliases urisToAliases = 115 mPackageNameToUris.getOrDefault(appPackageName, new UrisToAliases()); 116 urisToAliases.addUriToAlias(uri, alias); 117 mPackageNameToUris.put(appPackageName, urisToAliases); 118 return this; 119 } 120 121 /** 122 * Adds mappings from an app and list of URIs to a list of aliases, which will be used for 123 * authentication. 124 * <p> 125 * appPackageName -> uri -> alias 126 * 127 * @hide 128 */ 129 @NonNull addAppAndUriMapping(@onNull String appPackageName, @NonNull UrisToAliases urisToAliases)130 public Builder addAppAndUriMapping(@NonNull String appPackageName, 131 @NonNull UrisToAliases urisToAliases) { 132 Objects.requireNonNull(appPackageName); 133 Objects.requireNonNull(urisToAliases); 134 mPackageNameToUris.put(appPackageName, urisToAliases); 135 return this; 136 } 137 138 /** 139 * Combines all of the attributes that have been set on the {@link Builder} 140 * 141 * @return a new {@link AppUriAuthenticationPolicy} object. 142 */ 143 @NonNull build()144 public AppUriAuthenticationPolicy build() { 145 return new AppUriAuthenticationPolicy(mPackageNameToUris); 146 } 147 } 148 149 @Override describeContents()150 public int describeContents() { 151 return 0; 152 } 153 154 @Override writeToParcel(@onNull Parcel dest, int flags)155 public void writeToParcel(@NonNull Parcel dest, int flags) { 156 dest.writeMap(mAppToUris); 157 } 158 159 @NonNull 160 public static final Parcelable.Creator<AppUriAuthenticationPolicy> CREATOR = 161 new Parcelable.Creator<AppUriAuthenticationPolicy>() { 162 @Override 163 public AppUriAuthenticationPolicy createFromParcel(Parcel in) { 164 Map<String, UrisToAliases> appToUris = new HashMap<>(); 165 in.readMap(appToUris, UrisToAliases.class.getClassLoader()); 166 return new AppUriAuthenticationPolicy(appToUris); 167 } 168 169 @Override 170 public AppUriAuthenticationPolicy[] newArray(int size) { 171 return new AppUriAuthenticationPolicy[size]; 172 } 173 }; 174 175 @Override toString()176 public String toString() { 177 return "AppUriAuthenticationPolicy{" 178 + "mPackageNameToUris=" + mAppToUris 179 + '}'; 180 } 181 182 /** 183 * Return the authentication policy mapping, which determines which alias for a private key 184 * and certificate pair should be used for authentication. 185 * <p> 186 * appPackageName -> uri -> alias 187 */ 188 @NonNull getAppAndUriMappings()189 public Map<String, Map<Uri, String>> getAppAndUriMappings() { 190 Map<String, Map<Uri, String>> appAndUris = new HashMap<>(); 191 for (Map.Entry<String, UrisToAliases> entry : mAppToUris.entrySet()) { 192 appAndUris.put(entry.getKey(), entry.getValue().getUrisToAliases()); 193 } 194 return appAndUris; 195 } 196 197 /** 198 * Restore a previously saved {@link AppUriAuthenticationPolicy} from XML. 199 * 200 * @hide 201 */ 202 @Nullable readFromXml(@onNull XmlPullParser parser)203 public static AppUriAuthenticationPolicy readFromXml(@NonNull XmlPullParser parser) 204 throws IOException, XmlPullParserException { 205 AppUriAuthenticationPolicy.Builder builder = new AppUriAuthenticationPolicy.Builder(); 206 int outerDepth = parser.getDepth(); 207 int type; 208 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 209 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 210 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 211 continue; 212 } 213 if (!parser.getName().equals(KEY_AUTHENTICATION_POLICY_APP_TO_URIS)) { 214 continue; 215 } 216 String app = parser.getAttributeValue(null, KEY_AUTHENTICATION_POLICY_APP); 217 UrisToAliases urisToAliases = UrisToAliases.readFromXml(parser); 218 builder.addAppAndUriMapping(app, urisToAliases); 219 } 220 return builder.build(); 221 } 222 223 /** 224 * Save the {@link AppUriAuthenticationPolicy} to XML. 225 * 226 * @hide 227 */ writeToXml(@onNull XmlSerializer out)228 public void writeToXml(@NonNull XmlSerializer out) throws IOException { 229 for (Map.Entry<String, UrisToAliases> appsToUris : mAppToUris.entrySet()) { 230 out.startTag(null, KEY_AUTHENTICATION_POLICY_APP_TO_URIS); 231 out.attribute(null, KEY_AUTHENTICATION_POLICY_APP, appsToUris.getKey()); 232 appsToUris.getValue().writeToXml(out); 233 out.endTag(null, KEY_AUTHENTICATION_POLICY_APP_TO_URIS); 234 } 235 } 236 237 /** 238 * Get the set of aliases found in the policy. 239 * 240 * @hide 241 */ getAliases()242 public Set<String> getAliases() { 243 Set<String> aliases = new HashSet<>(); 244 for (UrisToAliases appsToUris : mAppToUris.values()) { 245 aliases.addAll(appsToUris.getUrisToAliases().values()); 246 } 247 return aliases; 248 } 249 250 @Override equals(Object obj)251 public boolean equals(Object obj) { 252 if (this == obj) { 253 return true; 254 } 255 if (!(obj instanceof AppUriAuthenticationPolicy)) { 256 return false; 257 } 258 AppUriAuthenticationPolicy other = (AppUriAuthenticationPolicy) obj; 259 return Objects.equals(mAppToUris, other.mAppToUris); 260 } 261 262 @Override hashCode()263 public int hashCode() { 264 return mAppToUris.hashCode(); 265 } 266 267 } 268