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.io.File;
29import java.lang.reflect.*;
30import java.util.*;
31
32import java.security.*;
33
34import sun.security.util.PropertyExpander;
35
36/**
37 * Class representing a configured provider which encapsulates configuration
38 * (provider name + optional argument), the provider loading logic, and
39 * the loaded Provider object itself.
40 *
41 * @author  Andreas Sterbenz
42 * @since   1.5
43 */
44final class ProviderConfig {
45
46    private static final sun.security.util.Debug debug =
47        sun.security.util.Debug.getInstance("jca", "ProviderConfig");
48
49    // suffix for identifying the SunPKCS11-Solaris provider
50    private static final String P11_SOL_NAME = "SunPKCS11";
51
52    // config file argument of the SunPKCS11-Solaris provider
53    private static final String P11_SOL_ARG  =
54        "${java.home}/conf/security/sunpkcs11-solaris.cfg";
55
56    // maximum number of times to try loading a provider before giving up
57    private static final int MAX_LOAD_TRIES = 30;
58
59    // could be provider name (module) or provider class name (legacy)
60    private final String provName;
61
62    // argument to the Provider.configure() call, never null
63    private final String argument;
64
65    // number of times we have already tried to load this provider
66    private int tries;
67
68    // Provider object, if loaded
69    private volatile Provider provider;
70
71    // flag indicating if we are currently trying to load the provider
72    // used to detect recursion
73    private boolean isLoading;
74
75    ProviderConfig(String provName, String argument) {
76        if (provName.endsWith(P11_SOL_NAME) && argument.equals(P11_SOL_ARG)) {
77            checkSunPKCS11Solaris();
78        }
79        this.provName = provName;
80        this.argument = expand(argument);
81    }
82
83    ProviderConfig(String provName) {
84        this(provName, "");
85    }
86
87    ProviderConfig(Provider provider) {
88        this.provName = provider.getName();
89        this.argument = "";
90        this.provider = provider;
91    }
92
93    // check if we should try to load the SunPKCS11-Solaris provider
94    // avoid if not available (pre Solaris 10) to reduce startup time
95    // or if disabled via system property
96    private void checkSunPKCS11Solaris() {
97        Boolean o = AccessController.doPrivileged(
98                                new PrivilegedAction<Boolean>() {
99            public Boolean run() {
100                File file = new File("/usr/lib/libpkcs11.so");
101                if (file.exists() == false) {
102                    return Boolean.FALSE;
103                }
104                if ("false".equalsIgnoreCase(System.getProperty
105                        ("sun.security.pkcs11.enable-solaris"))) {
106                    return Boolean.FALSE;
107                }
108                return Boolean.TRUE;
109            }
110        });
111        if (o == Boolean.FALSE) {
112            tries = MAX_LOAD_TRIES;
113        }
114    }
115
116    private boolean hasArgument() {
117        return argument.length() != 0;
118    }
119
120    // should we try to load this provider?
121    private boolean shouldLoad() {
122        return (tries < MAX_LOAD_TRIES);
123    }
124
125    // do not try to load this provider again
126    private void disableLoad() {
127        tries = MAX_LOAD_TRIES;
128    }
129
130    boolean isLoaded() {
131        return (provider != null);
132    }
133
134    public boolean equals(Object obj) {
135        if (this == obj) {
136            return true;
137        }
138        if (obj instanceof ProviderConfig == false) {
139            return false;
140        }
141        ProviderConfig other = (ProviderConfig)obj;
142        return this.provName.equals(other.provName)
143            && this.argument.equals(other.argument);
144
145    }
146
147    public int hashCode() {
148        return provName.hashCode() + argument.hashCode();
149    }
150
151    public String toString() {
152        if (hasArgument()) {
153            return provName + "('" + argument + "')";
154        } else {
155            return provName;
156        }
157    }
158
159    /**
160     * Get the provider object. Loads the provider if it is not already loaded.
161     */
162    // com.sun.net.ssl.internal.ssl.Provider has been deprecated since JDK 9
163    @SuppressWarnings("deprecation")
164    synchronized Provider getProvider() {
165        // volatile variable load
166        Provider p = provider;
167        if (p != null) {
168            return p;
169        }
170        if (shouldLoad() == false) {
171            return null;
172        }
173
174        // Create providers which are in java.base directly
175        if (provName.equals("SUN") || provName.equals("sun.security.provider.Sun")) {
176            p = new sun.security.provider.Sun();
177        } else if (provName.equals("SunRsaSign") || provName.equals("sun.security.rsa.SunRsaSign")) {
178            p = new sun.security.rsa.SunRsaSign();
179        } else if (provName.equals("SunJCE") || provName.equals("com.sun.crypto.provider.SunJCE")) {
180            p = new com.sun.crypto.provider.SunJCE();
181        } else if (provName.equals("SunJSSE") || provName.equals("com.sun.net.ssl.internal.ssl.Provider")) {
182            p = new com.sun.net.ssl.internal.ssl.Provider();
183        } else if (provName.equals("Apple") || provName.equals("apple.security.AppleProvider")) {
184            // need to use reflection since this class only exists on MacOsx
185            p = AccessController.doPrivileged(new PrivilegedAction<Provider>() {
186                public Provider run() {
187                    try {
188                        Class<?> c = Class.forName("apple.security.AppleProvider");
189                        if (Provider.class.isAssignableFrom(c)) {
190                            @SuppressWarnings("deprecation")
191                            Object tmp = c.newInstance();
192                            return (Provider) tmp;
193                        } else {
194                            return null;
195                        }
196                    } catch (Exception ex) {
197                        if (debug != null) {
198                        debug.println("Error loading provider Apple");
199                        ex.printStackTrace();
200                    }
201                    return null;
202                }
203             }
204             });
205        } else {
206            if (isLoading) {
207                // because this method is synchronized, this can only
208                // happen if there is recursion.
209                if (debug != null) {
210                    debug.println("Recursion loading provider: " + this);
211                    new Exception("Call trace").printStackTrace();
212                }
213                return null;
214            }
215            try {
216                isLoading = true;
217                tries++;
218                p = doLoadProvider();
219            } finally {
220                isLoading = false;
221            }
222        }
223        provider = p;
224        return p;
225    }
226
227    /**
228     * Load and instantiate the Provider described by this class.
229     *
230     * NOTE use of doPrivileged().
231     *
232     * @return null if the Provider could not be loaded
233     *
234     * @throws ProviderException if executing the Provider's constructor
235     * throws a ProviderException. All other Exceptions are ignored.
236     */
237    private Provider doLoadProvider() {
238        return AccessController.doPrivileged(new PrivilegedAction<Provider>() {
239            public Provider run() {
240                if (debug != null) {
241                    debug.println("Loading provider " + ProviderConfig.this);
242                }
243                try {
244                    Provider p = ProviderLoader.INSTANCE.load(provName);
245                    if (p != null) {
246                        if (hasArgument()) {
247                            p = p.configure(argument);
248                        }
249                        if (debug != null) {
250                            debug.println("Loaded provider " + p.getName());
251                        }
252                    } else {
253                        if (debug != null) {
254                            debug.println("Error loading provider " +
255                                ProviderConfig.this);
256                        }
257                        disableLoad();
258                    }
259                    return p;
260                } catch (Exception e) {
261                    if (e instanceof ProviderException) {
262                        // pass up
263                        throw e;
264                    } else {
265                        if (debug != null) {
266                            debug.println("Error loading provider " +
267                                ProviderConfig.this);
268                            e.printStackTrace();
269                        }
270                        disableLoad();
271                        return null;
272                    }
273                } catch (ExceptionInInitializerError err) {
274                    // no sufficient permission to initialize provider class
275                    if (debug != null) {
276                        debug.println("Error loading provider " + ProviderConfig.this);
277                        err.printStackTrace();
278                    }
279                    disableLoad();
280                    return null;
281                }
282            }
283        });
284    }
285
286    /**
287     * Perform property expansion of the provider value.
288     *
289     * NOTE use of doPrivileged().
290     */
291    private static String expand(final String value) {
292        // shortcut if value does not contain any properties
293        if (value.contains("${") == false) {
294            return value;
295        }
296        return AccessController.doPrivileged(new PrivilegedAction<String>() {
297            public String run() {
298                try {
299                    return PropertyExpander.expand(value);
300                } catch (GeneralSecurityException e) {
301                    throw new ProviderException(e);
302                }
303            }
304        });
305    }
306
307    // Inner class for loading security providers listed in java.security file
308    private static final class ProviderLoader {
309        static final ProviderLoader INSTANCE = new ProviderLoader();
310
311        private final ServiceLoader<Provider> services;
312
313        private ProviderLoader() {
314            // VM should already been booted at this point, if not
315            // - Only providers in java.base should be loaded, don't use
316            //   ServiceLoader
317            // - ClassLoader.getSystemClassLoader() will throw InternalError
318            services = ServiceLoader.load(java.security.Provider.class,
319                                          ClassLoader.getSystemClassLoader());
320        }
321
322        /**
323         * Loads the provider with the specified class name.
324         *
325         * @param name the name of the provider
326         * @return the Provider, or null if it cannot be found or loaded
327         * @throws ProviderException all other exceptions are ignored
328         */
329        public Provider load(String pn) {
330            if (debug != null) {
331                debug.println("Attempt to load " + pn + " using SL");
332            }
333            Iterator<Provider> iter = services.iterator();
334            while (iter.hasNext()) {
335                try {
336                    Provider p = iter.next();
337                    String pName = p.getName();
338                    if (debug != null) {
339                        debug.println("Found SL Provider named " + pName);
340                    }
341                    if (pName.equals(pn)) {
342                        return p;
343                    }
344                } catch (SecurityException | ServiceConfigurationError |
345                         InvalidParameterException ex) {
346                    // if provider loading fail due to security permission,
347                    // log it and move on to next provider
348                    if (debug != null) {
349                        debug.println("Encountered " + ex +
350                            " while iterating through SL, ignore and move on");
351                            ex.printStackTrace();
352                    }
353                }
354            }
355            // No success with ServiceLoader. Try loading provider the legacy,
356            // i.e. pre-module, way via reflection
357            try {
358                return legacyLoad(pn);
359            } catch (ProviderException pe) {
360                // pass through
361                throw pe;
362            } catch (Exception ex) {
363                // logged and ignored
364                if (debug != null) {
365                    debug.println("Encountered " + ex +
366                        " during legacy load of " + pn);
367                        ex.printStackTrace();
368                }
369                return null;
370            }
371        }
372
373        private Provider legacyLoad(String classname) {
374
375            if (debug != null) {
376                debug.println("Loading legacy provider: " + classname);
377            }
378
379            try {
380                Class<?> provClass =
381                    ClassLoader.getSystemClassLoader().loadClass(classname);
382
383                // only continue if the specified class extends Provider
384                if (!Provider.class.isAssignableFrom(provClass)) {
385                    if (debug != null) {
386                        debug.println(classname + " is not a provider");
387                    }
388                    return null;
389                }
390
391                Provider p = AccessController.doPrivileged
392                    (new PrivilegedExceptionAction<Provider>() {
393                    @SuppressWarnings("deprecation") // Class.newInstance
394                    public Provider run() throws Exception {
395                        return (Provider) provClass.newInstance();
396                    }
397                });
398                return p;
399            } catch (Exception e) {
400                Throwable t;
401                if (e instanceof InvocationTargetException) {
402                    t = ((InvocationTargetException)e).getCause();
403                } else {
404                    t = e;
405                }
406                if (debug != null) {
407                    debug.println("Error loading legacy provider " + classname);
408                    t.printStackTrace();
409                }
410                // provider indicates fatal error, pass through exception
411                if (t instanceof ProviderException) {
412                    throw (ProviderException) t;
413                }
414                return null;
415            } catch (ExceptionInInitializerError | NoClassDefFoundError err) {
416                // no sufficient permission to access/initialize provider class
417                if (debug != null) {
418                    debug.println("Error loading legacy provider " + classname);
419                    err.printStackTrace();
420                }
421                return null;
422            }
423        }
424    }
425}
426