1/*
2 * Copyright (c) 2003, 2016, 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
26package sun.security.jca;
27
28import java.util.*;
29
30import java.security.AccessController;
31import java.security.PrivilegedAction;
32import java.security.Provider;
33import java.security.Provider.Service;
34import java.security.Security;
35
36/**
37 * List of Providers. Used to represent the provider preferences.
38 *
39 * The system starts out with a ProviderList that only has the names
40 * of the Providers.
41 * When using ServiceLoader to load the providers, Providers are created
42 * semi-eagerly as we iterate through them looking for a match.
43 *
44 * For compatibility reasons, Providers that could not be loaded are ignored
45 * and internally presented as the instance EMPTY_PROVIDER. However, those
46 * objects cannot be presented to applications. Call the convert() method
47 * to force all Providers to be loaded and to obtain a ProviderList with
48 * invalid entries removed. All this is handled by the Security class.
49 *
50 * Note that all indices used by this class are 0-based per general Java
51 * convention. These must be converted to the 1-based indices used by the
52 * Security class externally when needed.
53 *
54 * Instances of this class are immutable. This eliminates the need for
55 * cloning and synchronization in consumers. The add() and remove() style
56 * methods are static in order to avoid confusion about the immutability.
57 *
58 * @author  Andreas Sterbenz
59 * @since   1.5
60 */
61public final class ProviderList {
62
63    static final sun.security.util.Debug debug =
64        sun.security.util.Debug.getInstance("jca", "ProviderList");
65
66    private static final ProviderConfig[] PC0 = new ProviderConfig[0];
67
68    private static final Provider[] P0 = new Provider[0];
69
70    // constant for an ProviderList with no elements
71    static final ProviderList EMPTY = new ProviderList(PC0, true);
72
73    // list of all jdk.security.provider.preferred entries
74    static private PreferredList preferredPropList = null;
75
76    // dummy provider object to use during initialization
77    // used to avoid explicit null checks in various places
78    private static final Provider EMPTY_PROVIDER =
79        new Provider("##Empty##", "1.0", "initialization in progress") {
80            private static final long serialVersionUID = 1151354171352296389L;
81            // override getService() to return null slightly faster
82            public Service getService(String type, String algorithm) {
83                return null;
84            }
85        };
86
87    // construct a ProviderList from the security properties
88    // (static provider configuration in the java.security file)
89    static ProviderList fromSecurityProperties() {
90        // doPrivileged() because of Security.getProperty()
91        return AccessController.doPrivileged(
92                        new PrivilegedAction<ProviderList>() {
93            public ProviderList run() {
94                return new ProviderList();
95            }
96        });
97    }
98
99    public static ProviderList add(ProviderList providerList, Provider p) {
100        return insertAt(providerList, p, -1);
101    }
102
103    public static ProviderList insertAt(ProviderList providerList, Provider p,
104            int position) {
105        if (providerList.getProvider(p.getName()) != null) {
106            return providerList;
107        }
108        List<ProviderConfig> list = new ArrayList<>
109                                    (Arrays.asList(providerList.configs));
110        int n = list.size();
111        if ((position < 0) || (position > n)) {
112            position = n;
113        }
114        list.add(position, new ProviderConfig(p));
115        return new ProviderList(list.toArray(PC0), true);
116    }
117
118    public static ProviderList remove(ProviderList providerList, String name) {
119        // make sure provider exists
120        if (providerList.getProvider(name) == null) {
121            return providerList;
122        }
123        // copy all except matching to new list
124        ProviderConfig[] configs = new ProviderConfig[providerList.size() - 1];
125        int j = 0;
126        for (ProviderConfig config : providerList.configs) {
127            if (config.getProvider().getName().equals(name) == false) {
128                configs[j++] = config;
129            }
130        }
131        return new ProviderList(configs, true);
132    }
133
134    // Create a new ProviderList from the specified Providers.
135    // This method is for use by SunJSSE.
136    public static ProviderList newList(Provider ... providers) {
137        ProviderConfig[] configs = new ProviderConfig[providers.length];
138        for (int i = 0; i < providers.length; i++) {
139            configs[i] = new ProviderConfig(providers[i]);
140        }
141        return new ProviderList(configs, true);
142    }
143
144    // configuration of the providers
145    private final ProviderConfig[] configs;
146
147    // flag indicating whether all configs have been loaded successfully
148    private volatile boolean allLoaded;
149
150    // List returned by providers()
151    private final List<Provider> userList = new AbstractList<Provider>() {
152        public int size() {
153            return configs.length;
154        }
155        public Provider get(int index) {
156            return getProvider(index);
157        }
158    };
159
160    /**
161     * Create a new ProviderList from an array of configs
162     */
163    private ProviderList(ProviderConfig[] configs, boolean allLoaded) {
164        this.configs = configs;
165        this.allLoaded = allLoaded;
166    }
167
168    /**
169     * Return a new ProviderList parsed from the java.security Properties.
170     */
171    private ProviderList() {
172        List<ProviderConfig> configList = new ArrayList<>();
173        String entry;
174        int i = 1;
175
176        while ((entry = Security.getProperty("security.provider." + i)) != null) {
177            entry = entry.trim();
178            if (entry.length() == 0) {
179                System.err.println("invalid entry for " +
180                                   "security.provider." + i);
181                break;
182            }
183            int k = entry.indexOf(' ');
184            ProviderConfig config;
185            if (k == -1) {
186                config = new ProviderConfig(entry);
187            } else {
188                String provName = entry.substring(0, k);
189                String argument = entry.substring(k + 1).trim();
190                config = new ProviderConfig(provName, argument);
191            }
192
193            // Get rid of duplicate providers.
194            if (configList.contains(config) == false) {
195                configList.add(config);
196            }
197            i++;
198        }
199        configs = configList.toArray(PC0);
200
201        // Load config entries for use when getInstance is called
202        entry = Security.getProperty("jdk.security.provider.preferred");
203        if (entry != null && (entry = entry.trim()).length() > 0) {
204            String[] entries = entry.split(",");
205            if (ProviderList.preferredPropList == null) {
206                ProviderList.preferredPropList = new PreferredList();
207            }
208
209            for (String e : entries) {
210                i = e.indexOf(':');
211                if (i < 0) {
212                    if (debug != null) {
213                        debug.println("invalid preferred entry skipped.  " +
214                                "Missing colon delimiter \"" + e + "\"");
215                    }
216                    continue;
217                }
218                ProviderList.preferredPropList.add(new PreferredEntry(
219                        e.substring(0, i).trim(), e.substring(i + 1).trim()));
220            }
221        }
222
223        if (debug != null) {
224            debug.println("provider configuration: " + configList);
225            debug.println("config configuration: " +
226                    ProviderList.preferredPropList);
227        }
228    }
229
230    /**
231     * Construct a special ProviderList for JAR verification. It consists
232     * of the providers specified via jarClassNames, which must be on the
233     * bootclasspath and cannot be in signed JAR files. This is to avoid
234     * possible recursion and deadlock during verification.
235     */
236    ProviderList getJarList(String[] jarProvNames) {
237        List<ProviderConfig> newConfigs = new ArrayList<>();
238        for (String provName : jarProvNames) {
239            ProviderConfig newConfig = new ProviderConfig(provName);
240            for (ProviderConfig config : configs) {
241                // if the equivalent object is present in this provider list,
242                // use the old object rather than the new object.
243                // this ensures that when the provider is loaded in the
244                // new thread local list, it will also become available
245                // in this provider list
246                if (config.equals(newConfig)) {
247                    newConfig = config;
248                    break;
249                }
250            }
251            newConfigs.add(newConfig);
252        }
253        ProviderConfig[] configArray = newConfigs.toArray(PC0);
254        return new ProviderList(configArray, false);
255    }
256
257    public int size() {
258        return configs.length;
259    }
260
261    /**
262     * Return the Provider at the specified index. Returns EMPTY_PROVIDER
263     * if the provider could not be loaded at this time.
264     */
265    Provider getProvider(int index) {
266        Provider p = configs[index].getProvider();
267        return (p != null) ? p : EMPTY_PROVIDER;
268    }
269
270    /**
271     * Return an unmodifiable List of all Providers in this List. The
272     * individual Providers are loaded on demand. Elements that could not
273     * be initialized are replaced with EMPTY_PROVIDER.
274     */
275    public List<Provider> providers() {
276        return userList;
277    }
278
279    private ProviderConfig getProviderConfig(String name) {
280        int index = getIndex(name);
281        return (index != -1) ? configs[index] : null;
282    }
283
284    // return the Provider with the specified name or null
285    public Provider getProvider(String name) {
286        ProviderConfig config = getProviderConfig(name);
287        return (config == null) ? null : config.getProvider();
288    }
289
290    /**
291     * Return the index at which the provider with the specified name is
292     * installed or -1 if it is not present in this ProviderList.
293     */
294    public int getIndex(String name) {
295        for (int i = 0; i < configs.length; i++) {
296            Provider p = getProvider(i);
297            if (p.getName().equals(name)) {
298                return i;
299            }
300        }
301        return -1;
302    }
303
304    // attempt to load all Providers not already loaded
305    private int loadAll() {
306        if (allLoaded) {
307            return configs.length;
308        }
309        if (debug != null) {
310            debug.println("Loading all providers");
311            new Exception("Call trace").printStackTrace();
312        }
313        int n = 0;
314        for (int i = 0; i < configs.length; i++) {
315            Provider p = configs[i].getProvider();
316            if (p != null) {
317                n++;
318            }
319        }
320        if (n == configs.length) {
321            allLoaded = true;
322        }
323        return n;
324    }
325
326    /**
327     * Try to load all Providers and return the ProviderList. If one or
328     * more Providers could not be loaded, a new ProviderList with those
329     * entries removed is returned. Otherwise, the method returns this.
330     */
331    ProviderList removeInvalid() {
332        int n = loadAll();
333        if (n == configs.length) {
334            return this;
335        }
336        ProviderConfig[] newConfigs = new ProviderConfig[n];
337        for (int i = 0, j = 0; i < configs.length; i++) {
338            ProviderConfig config = configs[i];
339            if (config.isLoaded()) {
340                newConfigs[j++] = config;
341            }
342        }
343        return new ProviderList(newConfigs, true);
344    }
345
346    // return the providers as an array
347    public Provider[] toArray() {
348        return providers().toArray(P0);
349    }
350
351    // return a String representation of this ProviderList
352    public String toString() {
353        return Arrays.asList(configs).toString();
354    }
355
356    /**
357     * Return a Service describing an implementation of the specified
358     * algorithm from the Provider with the highest precedence that
359     * supports that algorithm. Return null if no Provider supports this
360     * algorithm.
361     */
362    public Service getService(String type, String name) {
363        ArrayList<PreferredEntry> pList = null;
364        int i;
365
366        // Preferred provider list
367        if (preferredPropList != null &&
368                (pList = preferredPropList.getAll(type, name)) != null) {
369            for (i = 0; i < pList.size(); i++) {
370                Provider p = getProvider(pList.get(i).provider);
371                Service s = p.getService(type, name);
372                if (s != null) {
373                    return s;
374                }
375            }
376        }
377
378        for (i = 0; i < configs.length; i++) {
379            Provider p = getProvider(i);
380            Service s = p.getService(type, name);
381            if (s != null) {
382                return s;
383            }
384        }
385        return null;
386    }
387
388    /**
389     * Return a List containing all the Services describing implementations
390     * of the specified algorithms in precedence order. If no implementation
391     * exists, this method returns an empty List.
392     *
393     * The elements of this list are determined lazily on demand.
394     *
395     * The List returned is NOT thread safe.
396     */
397    public List<Service> getServices(String type, String algorithm) {
398        return new ServiceList(type, algorithm);
399    }
400
401    /**
402     * This method exists for compatibility with JCE only. It will be removed
403     * once JCE has been changed to use the replacement method.
404     * @deprecated use {@code getServices(List<ServiceId>)} instead
405     */
406    @Deprecated
407    public List<Service> getServices(String type, List<String> algorithms) {
408        List<ServiceId> ids = new ArrayList<>();
409        for (String alg : algorithms) {
410            ids.add(new ServiceId(type, alg));
411        }
412        return getServices(ids);
413    }
414
415    public List<Service> getServices(List<ServiceId> ids) {
416        return new ServiceList(ids);
417    }
418
419    /**
420     * Inner class for a List of Services. Custom List implementation in
421     * order to delay Provider initialization and lookup.
422     * Not thread safe.
423     */
424    private final class ServiceList extends AbstractList<Service> {
425
426        // type and algorithm for simple lookup
427        // avoid allocating/traversing the ServiceId list for these lookups
428        private final String type;
429        private final String algorithm;
430
431        // list of ids for parallel lookup
432        // if ids is non-null, type and algorithm are null
433        private final List<ServiceId> ids;
434
435        // first service we have found
436        // it is stored in a separate variable so that we can avoid
437        // allocating the services list if we do not need the second service.
438        // this is the case if we don't failover (failovers are typically rare)
439        private Service firstService;
440
441        // list of the services we have found so far
442        private List<Service> services;
443
444        // index into config[] of the next provider we need to query
445        private int providerIndex = 0;
446
447        // Matching preferred provider list for this ServiceList
448        ArrayList<PreferredEntry> preferredList = null;
449        private int preferredIndex = 0;
450
451        ServiceList(String type, String algorithm) {
452            this.type = type;
453            this.algorithm = algorithm;
454            this.ids = null;
455        }
456
457        ServiceList(List<ServiceId> ids) {
458            this.type = null;
459            this.algorithm = null;
460            this.ids = ids;
461        }
462
463        private void addService(Service s) {
464            if (firstService == null) {
465                firstService = s;
466            } else {
467                if (services == null) {
468                    services = new ArrayList<Service>(4);
469                    services.add(firstService);
470                }
471                services.add(s);
472            }
473        }
474
475        private Service tryGet(int index) {
476            Provider p;
477
478            // If preferred providers are configured, check for matches with
479            // the requested service.
480            if (preferredPropList != null && preferredList == null) {
481                preferredList = preferredPropList.getAll(this);
482            }
483
484            while (true) {
485                if ((index == 0) && (firstService != null)) {
486                    return firstService;
487                } else if ((services != null) && (services.size() > index)) {
488                    return services.get(index);
489                }
490                if (providerIndex >= configs.length) {
491                    return null;
492                }
493
494                // If there were matches with a preferred provider, iterate
495                // through the list first before going through the
496                // ordered list (java.security.provider.#)
497                if (preferredList != null &&
498                        preferredIndex < preferredList.size()) {
499                    PreferredEntry entry = preferredList.get(preferredIndex++);
500                    // Look for the provider name in the PreferredEntry
501                    p = getProvider(entry.provider);
502                    if (p == null) {
503                        if (debug != null) {
504                            debug.println("No provider found with name: " +
505                                    entry.provider);
506                        }
507                        continue;
508                    }
509                } else {
510                    // check all algorithms in this provider before moving on
511                    p = getProvider(providerIndex++);
512                }
513
514                if (type != null) {
515                    // simple lookup
516                    Service s = p.getService(type, algorithm);
517                    if (s != null) {
518                        addService(s);
519                    }
520                } else {
521                    // parallel lookup
522                    for (ServiceId id : ids) {
523                        Service s = p.getService(id.type, id.algorithm);
524                        if (s != null) {
525                            addService(s);
526                        }
527                    }
528                }
529            }
530        }
531
532        public Service get(int index) {
533            Service s = tryGet(index);
534            if (s == null) {
535                throw new IndexOutOfBoundsException();
536            }
537            return s;
538        }
539
540        public int size() {
541            int n;
542            if (services != null) {
543                n = services.size();
544            } else {
545                n = (firstService != null) ? 1 : 0;
546            }
547            while (tryGet(n) != null) {
548                n++;
549            }
550            return n;
551        }
552
553        // override isEmpty() and iterator() to not call size()
554        // this avoids loading + checking all Providers
555
556        public boolean isEmpty() {
557            return (tryGet(0) == null);
558        }
559
560        public Iterator<Service> iterator() {
561            return new Iterator<Service>() {
562                int index;
563
564                public boolean hasNext() {
565                    return tryGet(index) != null;
566                }
567
568                public Service next() {
569                    Service s = tryGet(index);
570                    if (s == null) {
571                        throw new NoSuchElementException();
572                    }
573                    index++;
574                    return s;
575                }
576
577                public void remove() {
578                    throw new UnsupportedOperationException();
579                }
580            };
581        }
582    }
583
584    // Provider list defined by jdk.security.provider.preferred entry
585    static final class PreferredList {
586        ArrayList<PreferredEntry> list = new ArrayList<PreferredEntry>();
587
588        /*
589         * Return a list of all preferred entries that match the passed
590         * ServiceList.
591         */
592        ArrayList<PreferredEntry> getAll(ServiceList s) {
593            if (s.ids == null) {
594                return getAll(s.type, s.algorithm);
595
596            }
597
598            ArrayList<PreferredEntry> l = new ArrayList<PreferredEntry>();
599            for (ServiceId id : s.ids) {
600                implGetAll(l, id.type, id.algorithm);
601            }
602
603            return l;
604        }
605
606        /*
607         * Return a list of all preferred entries that match the passed
608         * type and algorithm.
609         */
610        ArrayList<PreferredEntry> getAll(String type, String algorithm) {
611            ArrayList<PreferredEntry> l = new ArrayList<PreferredEntry>();
612            implGetAll(l, type, algorithm);
613            return l;
614        }
615
616        /*
617         * Compare each preferred entry against the passed type and
618         * algorithm, putting any matches in the passed ArrayList.
619         */
620        private void implGetAll(ArrayList<PreferredEntry> l, String type,
621                String algorithm) {
622            PreferredEntry e;
623
624            for (int i = 0; i < size(); i++) {
625                e = list.get(i);
626                if (e.match(type, algorithm)) {
627                    l.add(e);
628                }
629            }
630        }
631
632        public PreferredEntry get(int i) {
633            return list.get(i);
634        }
635
636        public int size() {
637            return list.size();
638        }
639
640        public boolean add(PreferredEntry e) {
641            return list.add(e);
642        }
643
644        public String toString() {
645            String s = "";
646            for (PreferredEntry e: list) {
647                s += e.toString();
648            }
649            return s;
650        }
651    }
652
653    /* Defined Groups for jdk.security.provider.preferred */
654    private static final String SHA2Group[] = { "SHA-224", "SHA-256",
655            "SHA-384", "SHA-512", "SHA-512/224", "SHA-512/256" };
656    private static final String HmacSHA2Group[] = { "HmacSHA224",
657            "HmacSHA256", "HmacSHA384", "HmacSHA512"};
658    private static final String SHA2RSAGroup[] = { "SHA224withRSA",
659            "SHA256withRSA", "SHA384withRSA", "SHA512withRSA"};
660    private static final String SHA2DSAGroup[] = { "SHA224withDSA",
661            "SHA256withDSA", "SHA384withDSA", "SHA512withDSA"};
662    private static final String SHA2ECDSAGroup[] = { "SHA224withECDSA",
663            "SHA256withECDSA", "SHA384withECDSA", "SHA512withECDSA"};
664    private static final String SHA3Group[] = { "SHA3-224", "SHA3-256",
665            "SHA3-384", "SHA3-512" };
666    private static final String HmacSHA3Group[] = { "HmacSHA3-224",
667            "HmacSHA3-256", "HmacSHA3-384", "HmacSHA3-512"};
668
669    // Individual preferred property entry from jdk.security.provider.preferred
670    private static class PreferredEntry {
671        private String type = null;
672        private String algorithm;
673        private String provider;
674        private String alternateNames[] = null;
675        private boolean group = false;
676
677        PreferredEntry(String t, String p) {
678            int i = t.indexOf('.');
679            if (i > 0) {
680                type = t.substring(0, i);
681                algorithm = t.substring(i + 1);
682            } else {
683                algorithm = t;
684            }
685
686            provider = p;
687            // Group definitions
688            if (type != null && type.compareToIgnoreCase("Group") == 0) {
689                // Currently intrinsic algorithm groups
690                if (algorithm.compareToIgnoreCase("SHA2") == 0) {
691                    alternateNames = SHA2Group;
692                } else if (algorithm.compareToIgnoreCase("HmacSHA2") == 0) {
693                    alternateNames = HmacSHA2Group;
694                } else if (algorithm.compareToIgnoreCase("SHA2RSA") == 0) {
695                    alternateNames = SHA2RSAGroup;
696                } else if (algorithm.compareToIgnoreCase("SHA2DSA") == 0) {
697                    alternateNames = SHA2DSAGroup;
698                } else if (algorithm.compareToIgnoreCase("SHA2ECDSA") == 0) {
699                    alternateNames = SHA2ECDSAGroup;
700                } else if (algorithm.compareToIgnoreCase("SHA3") == 0) {
701                    alternateNames = SHA3Group;
702                } else if (algorithm.compareToIgnoreCase("HmacSHA3") == 0) {
703                    alternateNames = HmacSHA3Group;
704                }
705                if (alternateNames != null) {
706                    group = true;
707                }
708
709            // If the algorithm name given is SHA1
710            } else if (algorithm.compareToIgnoreCase("SHA1") == 0) {
711                alternateNames = new String[] { "SHA-1" };
712            } else if (algorithm.compareToIgnoreCase("SHA-1") == 0) {
713                alternateNames = new String[] { "SHA1" };
714            }
715        }
716
717        boolean match(String t, String a) {
718            if (debug != null) {
719                debug.println("Config check:  " + toString() + " == " +
720                        print(t, a, null));
721            }
722
723            // Compare service type if configured
724            if (type != null && !group && type.compareToIgnoreCase(t) != 0) {
725                return false;
726            }
727
728            // Compare the algorithm string.
729            if (!group && a.compareToIgnoreCase(algorithm) == 0) {
730                if (debug != null) {
731                    debug.println("Config entry matched:  " + toString());
732                }
733                return true;
734            }
735
736            if (alternateNames != null) {
737                for (String alt : alternateNames) {
738                    if (debug != null) {
739                        debug.println("AltName check:  " + print(type, alt,
740                                provider));
741                    }
742                    if (a.compareToIgnoreCase(alt) == 0) {
743                        if (debug != null) {
744                            debug.println("AltName entry matched:  " +
745                                    provider);
746                        }
747                        return true;
748                    }
749                }
750            }
751
752            // No match
753            return false;
754        }
755
756        // Print debugging output of PreferredEntry
757        private String print(String t, String a, String p) {
758            return "[" + ((t != null) ? t : "" ) + ", " + a +
759                    ((p != null) ? " : " + p : "" ) + "] ";
760        }
761
762        public String toString() {
763            return print(type, algorithm, provider);
764        }
765    }
766
767}
768