1 /*
2  * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.security.jca;
27 
28 import java.util.*;
29 
30 import java.security.*;
31 import java.security.Provider.Service;
32 
33 /**
34  * List of Providers. Used to represent the provider preferences.
35  *
36  * The system starts out with a ProviderList that only has the classNames
37  * of the Providers. Providers are loaded on demand only when needed.
38  *
39  * For compatibility reasons, Providers that could not be loaded are ignored
40  * and internally presented as the instance EMPTY_PROVIDER. However, those
41  * objects cannot be presented to applications. Call the convert() method
42  * to force all Providers to be loaded and to obtain a ProviderList with
43  * invalid entries removed. All this is handled by the Security class.
44  *
45  * Note that all indices used by this class are 0-based per general Java
46  * convention. These must be converted to the 1-based indices used by the
47  * Security class externally when needed.
48  *
49  * Instances of this class are immutable. This eliminates the need for
50  * cloning and synchronization in consumers. The add() and remove() style
51  * methods are static in order to avoid confusion about the immutability.
52  *
53  * @author  Andreas Sterbenz
54  * @since   1.5
55  */
56 public final class ProviderList {
57 
58     final static sun.security.util.Debug debug =
59         sun.security.util.Debug.getInstance("jca", "ProviderList");
60 
61     private final static ProviderConfig[] PC0 = new ProviderConfig[0];
62 
63     private final static Provider[] P0 = new Provider[0];
64 
65     // constant for an ProviderList with no elements
66     static final ProviderList EMPTY = new ProviderList(PC0, true);
67 
68     // dummy provider object to use during initialization
69     // used to avoid explicit null checks in various places
70     private static final Provider EMPTY_PROVIDER =
71         new Provider("##Empty##", 1.0d, "initialization in progress") {
72             // BEGIN Android-added
73             // TODO(33172161): the computation of the default in Android yields
74             // -2591074641286775682L . Check why there's a difference and possibly change number
75             // accordingly.
76             // END Android-added
77             private static final long serialVersionUID = 1151354171352296389L;
78             // END Android-changed
79             // override getService() to return null slightly faster
80             public Service getService(String type, String algorithm) {
81                 return null;
82             }
83         };
84 
85     // construct a ProviderList from the security properties
86     // (static provider configuration in the java.security file)
fromSecurityProperties()87     static ProviderList fromSecurityProperties() {
88         // doPrivileged() because of Security.getProperty()
89         return AccessController.doPrivileged(
90                         new PrivilegedAction<ProviderList>() {
91             public ProviderList run() {
92                 return new ProviderList();
93             }
94         });
95     }
96 
97     public static ProviderList add(ProviderList providerList, Provider p) {
98         return insertAt(providerList, p, -1);
99     }
100 
101     public static ProviderList insertAt(ProviderList providerList, Provider p,
102             int position) {
103         if (providerList.getProvider(p.getName()) != null) {
104             return providerList;
105         }
106         List<ProviderConfig> list = new ArrayList<>
107                                     (Arrays.asList(providerList.configs));
108         int n = list.size();
109         if ((position < 0) || (position > n)) {
110             position = n;
111         }
112         list.add(position, new ProviderConfig(p));
113         return new ProviderList(list.toArray(PC0), true);
114     }
115 
116     public static ProviderList remove(ProviderList providerList, String name) {
117         // make sure provider exists
118         if (providerList.getProvider(name) == null) {
119             return providerList;
120         }
121         // copy all except matching to new list
122         ProviderConfig[] configs = new ProviderConfig[providerList.size() - 1];
123         int j = 0;
124         for (ProviderConfig config : providerList.configs) {
125             if (config.getProvider().getName().equals(name) == false) {
126                 configs[j++] = config;
127             }
128         }
129         return new ProviderList(configs, true);
130     }
131 
132     // Create a new ProviderList from the specified Providers.
133     // This method is for use by SunJSSE.
134     public static ProviderList newList(Provider ... providers) {
135         ProviderConfig[] configs = new ProviderConfig[providers.length];
136         for (int i = 0; i < providers.length; i++) {
137             configs[i] = new ProviderConfig(providers[i]);
138         }
139         return new ProviderList(configs, true);
140     }
141 
142     // configuration of the providers
143     private final ProviderConfig[] configs;
144 
145     // flag indicating whether all configs have been loaded successfully
146     private volatile boolean allLoaded;
147 
148     // List returned by providers()
149     private final List<Provider> userList = new AbstractList<Provider>() {
150         public int size() {
151             return configs.length;
152         }
153         public Provider get(int index) {
154             return getProvider(index);
155         }
156     };
157 
158     /**
159      * Create a new ProviderList from an array of configs
160      */
161     private ProviderList(ProviderConfig[] configs, boolean allLoaded) {
162         this.configs = configs;
163         this.allLoaded = allLoaded;
164     }
165 
166     /**
167      * Return a new ProviderList parsed from the java.security Properties.
168      */
169     private ProviderList() {
170         List<ProviderConfig> configList = new ArrayList<>();
171         for (int i = 1; true; i++) {
172             String entry = Security.getProperty("security.provider." + i);
173             if (entry == null) {
174                 break;
175             }
176             entry = entry.trim();
177             if (entry.length() == 0) {
178                 System.err.println("invalid entry for " +
179                                    "security.provider." + i);
180                 break;
181             }
182             int k = entry.indexOf(' ');
183             ProviderConfig config;
184             if (k == -1) {
185                 config = new ProviderConfig(entry);
186             } else {
187                 String className = entry.substring(0, k);
188                 String argument = entry.substring(k + 1).trim();
189                 config = new ProviderConfig(className, argument);
190             }
191 
192             // Get rid of duplicate providers.
193             if (configList.contains(config) == false) {
194                 configList.add(config);
195             }
196         }
197         configs = configList.toArray(PC0);
198         if (debug != null) {
199             debug.println("provider configuration: " + configList);
200         }
201     }
202 
203     /**
204      * Construct a special ProviderList for JAR verification. It consists
205      * of the providers specified via jarClassNames, which must be on the
206      * bootclasspath and cannot be in signed JAR files. This is to avoid
207      * possible recursion and deadlock during verification.
208      */
209     ProviderList getJarList(String[] jarClassNames) {
210         List<ProviderConfig> newConfigs = new ArrayList<>();
211         for (String className : jarClassNames) {
212             ProviderConfig newConfig = new ProviderConfig(className);
213             for (ProviderConfig config : configs) {
214                 // if the equivalent object is present in this provider list,
215                 // use the old object rather than the new object.
216                 // this ensures that when the provider is loaded in the
217                 // new thread local list, it will also become available
218                 // in this provider list
219                 if (config.equals(newConfig)) {
220                     newConfig = config;
221                     break;
222                 }
223             }
224             newConfigs.add(newConfig);
225         }
226         ProviderConfig[] configArray = newConfigs.toArray(PC0);
227         return new ProviderList(configArray, false);
228     }
229 
230     public int size() {
231         return configs.length;
232     }
233 
234     /**
235      * Return the Provider at the specified index. Returns EMPTY_PROVIDER
236      * if the provider could not be loaded at this time.
237      */
238     Provider getProvider(int index) {
239         Provider p = configs[index].getProvider();
240         return (p != null) ? p : EMPTY_PROVIDER;
241     }
242 
243     /**
244      * Return an unmodifiable List of all Providers in this List. The
245      * individual Providers are loaded on demand. Elements that could not
246      * be initialized are replaced with EMPTY_PROVIDER.
247      */
248     public List<Provider> providers() {
249         return userList;
250     }
251 
252     private ProviderConfig getProviderConfig(String name) {
253         int index = getIndex(name);
254         return (index != -1) ? configs[index] : null;
255     }
256 
257     // return the Provider with the specified name or null
258     public Provider getProvider(String name) {
259         ProviderConfig config = getProviderConfig(name);
260         return (config == null) ? null : config.getProvider();
261     }
262 
263     /**
264      * Return the index at which the provider with the specified name is
265      * installed or -1 if it is not present in this ProviderList.
266      */
267     public int getIndex(String name) {
268         for (int i = 0; i < configs.length; i++) {
269             Provider p = getProvider(i);
270             if (p.getName().equals(name)) {
271                 return i;
272             }
273         }
274         return -1;
275     }
276 
277     // attempt to load all Providers not already loaded
278     private int loadAll() {
279         if (allLoaded) {
280             return configs.length;
281         }
282         if (debug != null) {
283             debug.println("Loading all providers");
284             new Exception("Call trace").printStackTrace();
285         }
286         int n = 0;
287         for (int i = 0; i < configs.length; i++) {
288             Provider p = configs[i].getProvider();
289             if (p != null) {
290                 n++;
291             }
292         }
293         if (n == configs.length) {
294             allLoaded = true;
295         }
296         return n;
297     }
298 
299     /**
300      * Try to load all Providers and return the ProviderList. If one or
301      * more Providers could not be loaded, a new ProviderList with those
302      * entries removed is returned. Otherwise, the method returns this.
303      */
304     ProviderList removeInvalid() {
305         int n = loadAll();
306         if (n == configs.length) {
307             return this;
308         }
309         ProviderConfig[] newConfigs = new ProviderConfig[n];
310         for (int i = 0, j = 0; i < configs.length; i++) {
311             ProviderConfig config = configs[i];
312             if (config.isLoaded()) {
313                 newConfigs[j++] = config;
314             }
315         }
316         return new ProviderList(newConfigs, true);
317     }
318 
319     // return the providers as an array
320     public Provider[] toArray() {
321         return providers().toArray(P0);
322     }
323 
324     // return a String representation of this ProviderList
325     public String toString() {
326         return Arrays.asList(configs).toString();
327     }
328 
329     /**
330      * Return a Service describing an implementation of the specified
331      * algorithm from the Provider with the highest precedence that
332      * supports that algorithm. Return null if no Provider supports this
333      * algorithm.
334      */
335     public Service getService(String type, String name) {
336         for (int i = 0; i < configs.length; i++) {
337             Provider p = getProvider(i);
338             Service s = p.getService(type, name);
339             if (s != null) {
340                 return s;
341             }
342         }
343         return null;
344     }
345 
346     /**
347      * Return a List containing all the Services describing implementations
348      * of the specified algorithms in precedence order. If no implementation
349      * exists, this method returns an empty List.
350      *
351      * The elements of this list are determined lazily on demand.
352      *
353      * The List returned is NOT thread safe.
354      */
355     public List<Service> getServices(String type, String algorithm) {
356         return new ServiceList(type, algorithm);
357     }
358 
359     /**
360      * This method exists for compatibility with JCE only. It will be removed
361      * once JCE has been changed to use the replacement method.
362      * @deprecated use getServices(List<ServiceId>) instead
363      */
364     @Deprecated
365     public List<Service> getServices(String type, List<String> algorithms) {
366         List<ServiceId> ids = new ArrayList<>();
367         for (String alg : algorithms) {
368             ids.add(new ServiceId(type, alg));
369         }
370         return getServices(ids);
371     }
372 
373     public List<Service> getServices(List<ServiceId> ids) {
374         return new ServiceList(ids);
375     }
376 
377     /**
378      * Inner class for a List of Services. Custom List implementation in
379      * order to delay Provider initialization and lookup.
380      * Not thread safe.
381      */
382     private final class ServiceList extends AbstractList<Service> {
383 
384         // type and algorithm for simple lookup
385         // avoid allocating/traversing the ServiceId list for these lookups
386         private final String type;
387         private final String algorithm;
388 
389         // list of ids for parallel lookup
390         // if ids is non-null, type and algorithm are null
391         private final List<ServiceId> ids;
392 
393         // first service we have found
394         // it is stored in a separate variable so that we can avoid
395         // allocating the services list if we do not need the second service.
396         // this is the case if we don't failover (failovers are typically rare)
397         private Service firstService;
398 
399         // list of the services we have found so far
400         private List<Service> services;
401 
402         // index into config[] of the next provider we need to query
403         private int providerIndex;
404 
405         ServiceList(String type, String algorithm) {
406             this.type = type;
407             this.algorithm = algorithm;
408             this.ids = null;
409         }
410 
411         ServiceList(List<ServiceId> ids) {
412             this.type = null;
413             this.algorithm = null;
414             this.ids = ids;
415         }
416 
417         private void addService(Service s) {
418             if (firstService == null) {
419                 firstService = s;
420             } else {
421                 if (services == null) {
422                     services = new ArrayList<Service>(4);
423                     services.add(firstService);
424                 }
425                 services.add(s);
426             }
427         }
428 
429         private Service tryGet(int index) {
430             while (true) {
431                 if ((index == 0) && (firstService != null)) {
432                     return firstService;
433                 } else if ((services != null) && (services.size() > index)) {
434                     return services.get(index);
435                 }
436                 if (providerIndex >= configs.length) {
437                     return null;
438                 }
439                 // check all algorithms in this provider before moving on
440                 Provider p = getProvider(providerIndex++);
441                 if (type != null) {
442                     // simple lookup
443                     Service s = p.getService(type, algorithm);
444                     if (s != null) {
445                         addService(s);
446                     }
447                 } else {
448                     // parallel lookup
449                     for (ServiceId id : ids) {
450                         Service s = p.getService(id.type, id.algorithm);
451                         if (s != null) {
452                             addService(s);
453                         }
454                     }
455                 }
456             }
457         }
458 
459         public Service get(int index) {
460             Service s = tryGet(index);
461             if (s == null) {
462                 throw new IndexOutOfBoundsException();
463             }
464             return s;
465         }
466 
467         public int size() {
468             int n;
469             if (services != null) {
470                 n = services.size();
471             } else {
472                 n = (firstService != null) ? 1 : 0;
473             }
474             while (tryGet(n) != null) {
475                 n++;
476             }
477             return n;
478         }
479 
480         // override isEmpty() and iterator() to not call size()
481         // this avoids loading + checking all Providers
482 
483         public boolean isEmpty() {
484             return (tryGet(0) == null);
485         }
486 
487         public Iterator<Service> iterator() {
488             return new Iterator<Service>() {
489                 int index;
490 
491                 public boolean hasNext() {
492                     return tryGet(index) != null;
493                 }
494 
495                 public Service next() {
496                     Service s = tryGet(index);
497                     if (s == null) {
498                         throw new NoSuchElementException();
499                     }
500                     index++;
501                     return s;
502                 }
503 
504                 public void remove() {
505                     throw new UnsupportedOperationException();
506                 }
507             };
508         }
509     }
510 
511 }
512