1 /* 2 * Copyright (C) 2022 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 com.android.server.credentials; 18 19 import android.credentials.CredentialDescription; 20 import android.credentials.RegisterCredentialDescriptionRequest; 21 import android.credentials.UnregisterCredentialDescriptionRequest; 22 import android.service.credentials.CredentialEntry; 23 import android.util.SparseArray; 24 25 import com.android.internal.annotations.GuardedBy; 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import java.util.HashMap; 29 import java.util.HashSet; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.Set; 33 import java.util.concurrent.locks.ReentrantLock; 34 35 /** Contains information on what CredentialProvider has what provisioned Credential. */ 36 public class CredentialDescriptionRegistry { 37 38 private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128; 39 private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16; 40 @GuardedBy("sLock") 41 private static final SparseArray<CredentialDescriptionRegistry> 42 sCredentialDescriptionSessionPerUser; 43 private static final ReentrantLock sLock; 44 45 static { 46 sCredentialDescriptionSessionPerUser = new SparseArray<>(); 47 sLock = new ReentrantLock(); 48 } 49 50 /** Represents the results of a given query into the registry. */ 51 public static final class FilterResult { 52 final String mPackageName; 53 final Set<String> mElementKeys; 54 final List<CredentialEntry> mCredentialEntries; 55 56 @VisibleForTesting FilterResult(String packageName, Set<String> elementKeys, List<CredentialEntry> credentialEntries)57 FilterResult(String packageName, 58 Set<String> elementKeys, 59 List<CredentialEntry> credentialEntries) { 60 mPackageName = packageName; 61 mElementKeys = elementKeys; 62 mCredentialEntries = credentialEntries; 63 } 64 } 65 66 /** Get and/or create a {@link CredentialDescription} for the given user id. */ 67 @GuardedBy("sLock") forUser(int userId)68 public static CredentialDescriptionRegistry forUser(int userId) { 69 sLock.lock(); 70 try { 71 CredentialDescriptionRegistry session = 72 sCredentialDescriptionSessionPerUser.get(userId, null); 73 74 if (session == null) { 75 session = new CredentialDescriptionRegistry(); 76 sCredentialDescriptionSessionPerUser.put(userId, session); 77 } 78 return session; 79 } finally { 80 sLock.unlock(); 81 } 82 } 83 84 /** Clears an existing session for a given user identifier. */ 85 @GuardedBy("sLock") clearUserSession(int userId)86 public static void clearUserSession(int userId) { 87 sLock.lock(); 88 try { 89 sCredentialDescriptionSessionPerUser.remove(userId); 90 } finally { 91 sLock.unlock(); 92 } 93 } 94 95 /** Clears an existing session for a given user identifier. Used when testing only. */ 96 @GuardedBy("sLock") 97 @VisibleForTesting clearAllSessions()98 static void clearAllSessions() { 99 sLock.lock(); 100 try { 101 sCredentialDescriptionSessionPerUser.clear(); 102 } finally { 103 sLock.unlock(); 104 } 105 } 106 107 /** Sets an existing session for a given user identifier. Used when testing only. */ 108 @GuardedBy("sLock") 109 @VisibleForTesting setSession(int userId, CredentialDescriptionRegistry credentialDescriptionRegistry)110 static void setSession(int userId, CredentialDescriptionRegistry 111 credentialDescriptionRegistry) { 112 sLock.lock(); 113 try { 114 sCredentialDescriptionSessionPerUser.put(userId, credentialDescriptionRegistry); 115 } finally { 116 sLock.unlock(); 117 } 118 } 119 120 private Map<String, Set<CredentialDescription>> mCredentialDescriptions; 121 private int mTotalDescriptionCount; 122 CredentialDescriptionRegistry()123 private CredentialDescriptionRegistry() { 124 this.mCredentialDescriptions = new HashMap<>(); 125 this.mTotalDescriptionCount = 0; 126 } 127 128 /** Handle the given {@link RegisterCredentialDescriptionRequest} by creating 129 * the appropriate package name mapping. */ executeRegisterRequest(RegisterCredentialDescriptionRequest request, String callingPackageName)130 public void executeRegisterRequest(RegisterCredentialDescriptionRequest request, 131 String callingPackageName) { 132 133 if (!mCredentialDescriptions.containsKey(callingPackageName)) { 134 mCredentialDescriptions.put(callingPackageName, new HashSet<>()); 135 } 136 137 if (mTotalDescriptionCount <= MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS 138 && mCredentialDescriptions.get(callingPackageName).size() 139 <= MAX_ALLOWED_ENTRIES_PER_PROVIDER) { 140 Set<CredentialDescription> descriptions = request.getCredentialDescriptions(); 141 int size = mCredentialDescriptions.get(callingPackageName).size(); 142 mCredentialDescriptions.get(callingPackageName) 143 .addAll(descriptions); 144 mTotalDescriptionCount += mCredentialDescriptions.get(callingPackageName).size() - size; 145 } 146 147 } 148 149 /** Handle the given {@link UnregisterCredentialDescriptionRequest} by creating 150 * the appropriate package name mapping. */ executeUnregisterRequest( UnregisterCredentialDescriptionRequest request, String callingPackageName)151 public void executeUnregisterRequest( 152 UnregisterCredentialDescriptionRequest request, 153 String callingPackageName) { 154 155 if (mCredentialDescriptions.containsKey(callingPackageName)) { 156 int size = mCredentialDescriptions.get(callingPackageName).size(); 157 mCredentialDescriptions.get(callingPackageName) 158 .removeAll(request.getCredentialDescriptions()); 159 mTotalDescriptionCount -= size - mCredentialDescriptions.get(callingPackageName).size(); 160 } 161 } 162 163 /** Returns package names and entries of a CredentialProviders that can satisfy a given 164 * {@link CredentialDescription}. */ getFilteredResultForProvider(String packageName, Set<String> requestedKeyElements)165 public Set<FilterResult> getFilteredResultForProvider(String packageName, 166 Set<String> requestedKeyElements) { 167 Set<FilterResult> result = new HashSet<>(); 168 if (!mCredentialDescriptions.containsKey(packageName)) { 169 return result; 170 } 171 Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName); 172 for (CredentialDescription containedDescription: currentSet) { 173 if (checkForMatch(containedDescription.getSupportedElementKeys(), 174 requestedKeyElements)) { 175 result.add(new FilterResult(packageName, 176 containedDescription.getSupportedElementKeys(), containedDescription 177 .getCredentialEntries())); 178 } 179 } 180 return result; 181 } 182 183 /** Returns package names of CredentialProviders that can satisfy a given 184 * {@link CredentialDescription}. */ getMatchingProviders(Set<Set<String>> supportedElementKeys)185 public Set<FilterResult> getMatchingProviders(Set<Set<String>> supportedElementKeys) { 186 Set<FilterResult> result = new HashSet<>(); 187 for (String packageName: mCredentialDescriptions.keySet()) { 188 Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName); 189 for (CredentialDescription containedDescription : currentSet) { 190 if (canProviderSatisfyAny(containedDescription.getSupportedElementKeys(), 191 supportedElementKeys)) { 192 result.add(new FilterResult(packageName, 193 containedDescription.getSupportedElementKeys(), containedDescription 194 .getCredentialEntries())); 195 } 196 } 197 } 198 return result; 199 } 200 evictProviderWithPackageName(String packageName)201 void evictProviderWithPackageName(String packageName) { 202 if (mCredentialDescriptions.containsKey(packageName)) { 203 mCredentialDescriptions.remove(packageName); 204 } 205 } 206 canProviderSatisfyAny(Set<String> registeredElementKeys, Set<Set<String>> requestedElementKeys)207 private static boolean canProviderSatisfyAny(Set<String> registeredElementKeys, 208 Set<Set<String>> requestedElementKeys) { 209 for (Set<String> requestedUnflattenedString : requestedElementKeys) { 210 if (registeredElementKeys.containsAll(requestedUnflattenedString)) { 211 return true; 212 } 213 } 214 return false; 215 } 216 checkForMatch(Set<String> registeredElementKeys, Set<String> requestedElementKeys)217 static boolean checkForMatch(Set<String> registeredElementKeys, 218 Set<String> requestedElementKeys) { 219 return registeredElementKeys.containsAll(requestedElementKeys); 220 } 221 222 } 223