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