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             private static final long serialVersionUID = 1151354171352296389L;
73             // override getService() to return null slightly faster
74             public Service getService(String type, String algorithm) {
75                 return null;
76             }
77         };
78 
79     // construct a ProviderList from the security properties
80     // (static provider configuration in the java.security file)
fromSecurityProperties()81     static ProviderList fromSecurityProperties() {
82         // doPrivileged() because of Security.getProperty()
83         return AccessController.doPrivileged(
84                         new PrivilegedAction<ProviderList>() {
85             public ProviderList run() {
86                 return new ProviderList();
87             }
88         });
89     }
90 
91     public static ProviderList add(ProviderList providerList, Provider p) {
92         return insertAt(providerList, p, -1);
93     }
94 
95     public static ProviderList insertAt(ProviderList providerList, Provider p,
96             int position) {
97         if (providerList.getProvider(p.getName()) != null) {
98             return providerList;
99         }
100         List<ProviderConfig> list = new ArrayList<>
101                                     (Arrays.asList(providerList.configs));
102         int n = list.size();
103         if ((position < 0) || (position > n)) {
104             position = n;
105         }
106         list.add(position, new ProviderConfig(p));
107         return new ProviderList(list.toArray(PC0), true);
108     }
109 
110     public static ProviderList remove(ProviderList providerList, String name) {
111         // make sure provider exists
112         if (providerList.getProvider(name) == null) {
113             return providerList;
114         }
115         // copy all except matching to new list
116         ProviderConfig[] configs = new ProviderConfig[providerList.size() - 1];
117         int j = 0;
118         for (ProviderConfig config : providerList.configs) {
119             if (config.getProvider().getName().equals(name) == false) {
120                 configs[j++] = config;
121             }
122         }
123         return new ProviderList(configs, true);
124     }
125 
126     // Create a new ProviderList from the specified Providers.
127     // This method is for use by SunJSSE.
128     public static ProviderList newList(Provider ... providers) {
129         ProviderConfig[] configs = new ProviderConfig[providers.length];
130         for (int i = 0; i < providers.length; i++) {
131             configs[i] = new ProviderConfig(providers[i]);
132         }
133         return new ProviderList(configs, true);
134     }
135 
136     // configuration of the providers
137     private final ProviderConfig[] configs;
138 
139     // flag indicating whether all configs have been loaded successfully
140     private volatile boolean allLoaded;
141 
142     // List returned by providers()
143     private final List<Provider> userList = new AbstractList<Provider>() {
144         public int size() {
145             return configs.length;
146         }
147         public Provider get(int index) {
148             return getProvider(index);
149         }
150     };
151 
152     /**
153      * Create a new ProviderList from an array of configs
154      */
155     private ProviderList(ProviderConfig[] configs, boolean allLoaded) {
156         this.configs = configs;
157         this.allLoaded = allLoaded;
158     }
159 
160     /**
161      * Return a new ProviderList parsed from the java.security Properties.
162      */
163     private ProviderList() {
164         List<ProviderConfig> configList = new ArrayList<>();
165         for (int i = 1; true; i++) {
166             String entry = Security.getProperty("security.provider." + i);
167             if (entry == null) {
168                 break;
169             }
170             entry = entry.trim();
171             if (entry.length() == 0) {
172                 System.err.println("invalid entry for " +
173                                    "security.provider." + i);
174                 break;
175             }
176             int k = entry.indexOf(' ');
177             ProviderConfig config;
178             if (k == -1) {
179                 config = new ProviderConfig(entry);
180             } else {
181                 String className = entry.substring(0, k);
182                 String argument = entry.substring(k + 1).trim();
183                 config = new ProviderConfig(className, argument);
184             }
185 
186             // Get rid of duplicate providers.
187             if (configList.contains(config) == false) {
188                 configList.add(config);
189             }
190         }
191         configs = configList.toArray(PC0);
192         if (debug != null) {
193             debug.println("provider configuration: " + configList);
194         }
195     }
196 
197     /**
198      * Construct a special ProviderList for JAR verification. It consists
199      * of the providers specified via jarClassNames, which must be on the
200      * bootclasspath and cannot be in signed JAR files. This is to avoid
201      * possible recursion and deadlock during verification.
202      */
203     ProviderList getJarList(String[] jarClassNames) {
204         List<ProviderConfig> newConfigs = new ArrayList<>();
205         for (String className : jarClassNames) {
206             ProviderConfig newConfig = new ProviderConfig(className);
207             for (ProviderConfig config : configs) {
208                 // if the equivalent object is present in this provider list,
209                 // use the old object rather than the new object.
210                 // this ensures that when the provider is loaded in the
211                 // new thread local list, it will also become available
212                 // in this provider list
213                 if (config.equals(newConfig)) {
214                     newConfig = config;
215                     break;
216                 }
217             }
218             newConfigs.add(newConfig);
219         }
220         ProviderConfig[] configArray = newConfigs.toArray(PC0);
221         return new ProviderList(configArray, false);
222     }
223 
224     public int size() {
225         return configs.length;
226     }
227 
228     /**
229      * Return the Provider at the specified index. Returns EMPTY_PROVIDER
230      * if the provider could not be loaded at this time.
231      */
232     Provider getProvider(int index) {
233         Provider p = configs[index].getProvider();
234         return (p != null) ? p : EMPTY_PROVIDER;
235     }
236 
237     /**
238      * Return an unmodifiable List of all Providers in this List. The
239      * individual Providers are loaded on demand. Elements that could not
240      * be initialized are replaced with EMPTY_PROVIDER.
241      */
242     public List<Provider> providers() {
243         return userList;
244     }
245 
246     private ProviderConfig getProviderConfig(String name) {
247         int index = getIndex(name);
248         return (index != -1) ? configs[index] : null;
249     }
250 
251     // return the Provider with the specified name or null
252     public Provider getProvider(String name) {
253         ProviderConfig config = getProviderConfig(name);
254         return (config == null) ? null : config.getProvider();
255     }
256 
257     /**
258      * Return the index at which the provider with the specified name is
259      * installed or -1 if it is not present in this ProviderList.
260      */
261     public int getIndex(String name) {
262         for (int i = 0; i < configs.length; i++) {
263             Provider p = getProvider(i);
264             if (p.getName().equals(name)) {
265                 return i;
266             }
267         }
268         return -1;
269     }
270 
271     // attempt to load all Providers not already loaded
272     private int loadAll() {
273         if (allLoaded) {
274             return configs.length;
275         }
276         if (debug != null) {
277             debug.println("Loading all providers");
278             new Exception("Call trace").printStackTrace();
279         }
280         int n = 0;
281         for (int i = 0; i < configs.length; i++) {
282             Provider p = configs[i].getProvider();
283             if (p != null) {
284                 n++;
285             }
286         }
287         if (n == configs.length) {
288             allLoaded = true;
289         }
290         return n;
291     }
292 
293     /**
294      * Try to load all Providers and return the ProviderList. If one or
295      * more Providers could not be loaded, a new ProviderList with those
296      * entries removed is returned. Otherwise, the method returns this.
297      */
298     ProviderList removeInvalid() {
299         int n = loadAll();
300         if (n == configs.length) {
301             return this;
302         }
303         ProviderConfig[] newConfigs = new ProviderConfig[n];
304         for (int i = 0, j = 0; i < configs.length; i++) {
305             ProviderConfig config = configs[i];
306             if (config.isLoaded()) {
307                 newConfigs[j++] = config;
308             }
309         }
310         return new ProviderList(newConfigs, true);
311     }
312 
313     // return the providers as an array
314     public Provider[] toArray() {
315         return providers().toArray(P0);
316     }
317 
318     // return a String representation of this ProviderList
319     public String toString() {
320         return Arrays.asList(configs).toString();
321     }
322 
323     /**
324      * Return a Service describing an implementation of the specified
325      * algorithm from the Provider with the highest precedence that
326      * supports that algorithm. Return null if no Provider supports this
327      * algorithm.
328      */
329     public Service getService(String type, String name) {
330         for (int i = 0; i < configs.length; i++) {
331             Provider p = getProvider(i);
332             Service s = p.getService(type, name);
333             if (s != null) {
334                 return s;
335             }
336         }
337         return null;
338     }
339 
340     /**
341      * Return a List containing all the Services describing implementations
342      * of the specified algorithms in precedence order. If no implementation
343      * exists, this method returns an empty List.
344      *
345      * The elements of this list are determined lazily on demand.
346      *
347      * The List returned is NOT thread safe.
348      */
349     public List<Service> getServices(String type, String algorithm) {
350         return new ServiceList(type, algorithm);
351     }
352 
353     /**
354      * This method exists for compatibility with JCE only. It will be removed
355      * once JCE has been changed to use the replacement method.
356      * @deprecated use getServices(List<ServiceId>) instead
357      */
358     @Deprecated
359     public List<Service> getServices(String type, List<String> algorithms) {
360         List<ServiceId> ids = new ArrayList<>();
361         for (String alg : algorithms) {
362             ids.add(new ServiceId(type, alg));
363         }
364         return getServices(ids);
365     }
366 
367     public List<Service> getServices(List<ServiceId> ids) {
368         return new ServiceList(ids);
369     }
370 
371     /**
372      * Inner class for a List of Services. Custom List implementation in
373      * order to delay Provider initialization and lookup.
374      * Not thread safe.
375      */
376     private final class ServiceList extends AbstractList<Service> {
377 
378         // type and algorithm for simple lookup
379         // avoid allocating/traversing the ServiceId list for these lookups
380         private final String type;
381         private final String algorithm;
382 
383         // list of ids for parallel lookup
384         // if ids is non-null, type and algorithm are null
385         private final List<ServiceId> ids;
386 
387         // first service we have found
388         // it is stored in a separate variable so that we can avoid
389         // allocating the services list if we do not need the second service.
390         // this is the case if we don't failover (failovers are typically rare)
391         private Service firstService;
392 
393         // list of the services we have found so far
394         private List<Service> services;
395 
396         // index into config[] of the next provider we need to query
397         private int providerIndex;
398 
399         ServiceList(String type, String algorithm) {
400             this.type = type;
401             this.algorithm = algorithm;
402             this.ids = null;
403         }
404 
405         ServiceList(List<ServiceId> ids) {
406             this.type = null;
407             this.algorithm = null;
408             this.ids = ids;
409         }
410 
411         private void addService(Service s) {
412             if (firstService == null) {
413                 firstService = s;
414             } else {
415                 if (services == null) {
416                     services = new ArrayList<Service>(4);
417                     services.add(firstService);
418                 }
419                 services.add(s);
420             }
421         }
422 
423         private Service tryGet(int index) {
424             while (true) {
425                 if ((index == 0) && (firstService != null)) {
426                     return firstService;
427                 } else if ((services != null) && (services.size() > index)) {
428                     return services.get(index);
429                 }
430                 if (providerIndex >= configs.length) {
431                     return null;
432                 }
433                 // check all algorithms in this provider before moving on
434                 Provider p = getProvider(providerIndex++);
435                 if (type != null) {
436                     // simple lookup
437                     Service s = p.getService(type, algorithm);
438                     if (s != null) {
439                         addService(s);
440                     }
441                 } else {
442                     // parallel lookup
443                     for (ServiceId id : ids) {
444                         Service s = p.getService(id.type, id.algorithm);
445                         if (s != null) {
446                             addService(s);
447                         }
448                     }
449                 }
450             }
451         }
452 
453         public Service get(int index) {
454             Service s = tryGet(index);
455             if (s == null) {
456                 throw new IndexOutOfBoundsException();
457             }
458             return s;
459         }
460 
461         public int size() {
462             int n;
463             if (services != null) {
464                 n = services.size();
465             } else {
466                 n = (firstService != null) ? 1 : 0;
467             }
468             while (tryGet(n) != null) {
469                 n++;
470             }
471             return n;
472         }
473 
474         // override isEmpty() and iterator() to not call size()
475         // this avoids loading + checking all Providers
476 
477         public boolean isEmpty() {
478             return (tryGet(0) == null);
479         }
480 
481         public Iterator<Service> iterator() {
482             return new Iterator<Service>() {
483                 int index;
484 
485                 public boolean hasNext() {
486                     return tryGet(index) != null;
487                 }
488 
489                 public Service next() {
490                     Service s = tryGet(index);
491                     if (s == null) {
492                         throw new NoSuchElementException();
493                     }
494                     index++;
495                     return s;
496                 }
497 
498                 public void remove() {
499                     throw new UnsupportedOperationException();
500                 }
501             };
502         }
503     }
504 
505 }
506