ProviderConfig.java revision 12745:f068a4ffddd2
1/*
2 * Copyright (c) 2003, 2015, 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    synchronized Provider getProvider() {
163        // volatile variable load
164        Provider p = provider;
165        if (p != null) {
166            return p;
167        }
168        if (shouldLoad() == false) {
169            return null;
170        }
171
172        // Create providers which are in java.base directly
173        if (provName.equals("SUN") || provName.equals("sun.security.provider.Sun")) {
174            p = new sun.security.provider.Sun();
175        } else if (provName.equals("SunRsaSign") || provName.equals("sun.security.rsa.SunRsaSign")) {
176            p = new sun.security.rsa.SunRsaSign();
177        } else if (provName.equals("SunJCE") || provName.equals("com.sun.crypto.provider.SunJCE")) {
178            p = new com.sun.crypto.provider.SunJCE();
179        } else if (provName.equals("SunJSSE") || provName.equals("com.sun.net.ssl.internal.ssl.Provider")) {
180            p = new com.sun.net.ssl.internal.ssl.Provider();
181        } else if (provName.equals("Apple") || provName.equals("apple.security.AppleProvider")) {
182            // need to use reflection since this class only exists on MacOsx
183            p = AccessController.doPrivileged(new PrivilegedAction<Provider>() {
184                public Provider run() {
185                    try {
186                        Class<?> c = Class.forName("apple.security.AppleProvider");
187                        if (Provider.class.isAssignableFrom(c)) {
188                            return (Provider) c.newInstance();
189                        } else {
190                            return null;
191                        }
192                    } catch (Exception ex) {
193                        if (debug != null) {
194                        debug.println("Error loading provider Apple");
195                        ex.printStackTrace();
196                    }
197                    return null;
198                }
199             }
200             });
201        } else {
202            if (isLoading) {
203                // because this method is synchronized, this can only
204                // happen if there is recursion.
205                if (debug != null) {
206                    debug.println("Recursion loading provider: " + this);
207                    new Exception("Call trace").printStackTrace();
208                }
209                return null;
210            }
211            try {
212                isLoading = true;
213                tries++;
214                p = doLoadProvider();
215            } finally {
216                isLoading = false;
217            }
218        }
219        provider = p;
220        return p;
221    }
222
223    /**
224     * Load and instantiate the Provider described by this class.
225     *
226     * NOTE use of doPrivileged().
227     *
228     * @return null if the Provider could not be loaded
229     *
230     * @throws ProviderException if executing the Provider's constructor
231     * throws a ProviderException. All other Exceptions are ignored.
232     */
233    private Provider doLoadProvider() {
234        return AccessController.doPrivileged(new PrivilegedAction<Provider>() {
235            public Provider run() {
236                if (debug != null) {
237                    debug.println("Loading provider " + ProviderConfig.this);
238                }
239                ProviderLoader pl = new ProviderLoader();
240                try {
241                    Provider p = pl.load(provName);
242                    if (p != null) {
243                        if (hasArgument()) {
244                            p = p.configure(argument);
245                        }
246                        if (debug != null) {
247                            debug.println("Loaded provider " + p.getName());
248                        }
249                    } else {
250                        if (debug != null) {
251                            debug.println("Error loading provider " +
252                                ProviderConfig.this);
253                        }
254                        disableLoad();
255                    }
256                    return p;
257                } catch (Exception e) {
258                    if (e instanceof ProviderException) {
259                        // pass up
260                        throw e;
261                    } else {
262                        if (debug != null) {
263                            debug.println("Error loading provider " +
264                                ProviderConfig.this);
265                            e.printStackTrace();
266                        }
267                        disableLoad();
268                        return null;
269                    }
270                } catch (ExceptionInInitializerError err) {
271                    // no sufficient permission to initialize provider class
272                    if (debug != null) {
273                        debug.println("Error loading provider " + ProviderConfig.this);
274                        err.printStackTrace();
275                    }
276                    disableLoad();
277                    return null;
278                }
279            }
280        });
281    }
282
283    /**
284     * Perform property expansion of the provider value.
285     *
286     * NOTE use of doPrivileged().
287     */
288    private static String expand(final String value) {
289        // shortcut if value does not contain any properties
290        if (value.contains("${") == false) {
291            return value;
292        }
293        return AccessController.doPrivileged(new PrivilegedAction<String>() {
294            public String run() {
295                try {
296                    return PropertyExpander.expand(value);
297                } catch (GeneralSecurityException e) {
298                    throw new ProviderException(e);
299                }
300            }
301        });
302    }
303
304    // Inner class for loading security providers listed in java.security file
305    private static final class ProviderLoader {
306        private final ServiceLoader<Provider> services;
307
308        ProviderLoader() {
309            // VM should already been booted at this point, if not
310            // - Only providers in java.base should be loaded, don't use
311            //   ServiceLoader
312            // - ClassLoader.getSystemClassLoader() will throw InternalError
313            services = ServiceLoader.load(java.security.Provider.class,
314                                          ClassLoader.getSystemClassLoader());
315        }
316
317        /**
318         * Loads the provider with the specified class name.
319         *
320         * @param name the name of the provider
321         * @return the Provider, or null if it cannot be found or loaded
322         * @throws ProviderException all other exceptions are ignored
323         */
324        public Provider load(String pn) {
325            if (debug != null) {
326                debug.println("Attempt to load " + pn + " using SL");
327            }
328            Iterator<Provider> iter = services.iterator();
329            while (iter.hasNext()) {
330                try {
331                    Provider p = iter.next();
332                    String pName = p.getName();
333                    if (debug != null) {
334                        debug.println("Found SL Provider named " + pName);
335                    }
336                    if (pName.equals(pn)) {
337                        return p;
338                    }
339                } catch (SecurityException | ServiceConfigurationError |
340                         InvalidParameterException ex) {
341                    // if provider loading fail due to security permission,
342                    // log it and move on to next provider
343                    if (debug != null) {
344                        debug.println("Encountered " + ex +
345                            " while iterating through SL, ignore and move on");
346                            ex.printStackTrace();
347                    }
348                }
349            }
350            // No success with ServiceLoader. Try loading provider the legacy,
351            // i.e. pre-module, way via reflection
352            try {
353                return legacyLoad(pn);
354            } catch (ProviderException pe) {
355                // pass through
356                throw pe;
357            } catch (Exception ex) {
358                // logged and ignored
359                if (debug != null) {
360                    debug.println("Encountered " + ex +
361                        " during legacy load of " + pn);
362                        ex.printStackTrace();
363                }
364                return null;
365            }
366        }
367
368        private Provider legacyLoad(String classname) {
369
370            if (debug != null) {
371                debug.println("Loading legacy provider: " + classname);
372            }
373
374            try {
375                Class<?> provClass =
376                    ClassLoader.getSystemClassLoader().loadClass(classname);
377
378                // only continue if the specified class extends Provider
379                if (!Provider.class.isAssignableFrom(provClass)) {
380                    if (debug != null) {
381                        debug.println(classname + " is not a provider");
382                    }
383                    return null;
384                }
385
386                Provider p = AccessController.doPrivileged
387                    (new PrivilegedExceptionAction<Provider>() {
388                    public Provider run() throws Exception {
389                        return (Provider) provClass.newInstance();
390                    }
391                });
392                return p;
393            } catch (Exception e) {
394                Throwable t;
395                if (e instanceof InvocationTargetException) {
396                    t = ((InvocationTargetException)e).getCause();
397                } else {
398                    t = e;
399                }
400                if (debug != null) {
401                    debug.println("Error loading legacy provider " + classname);
402                    t.printStackTrace();
403                }
404                // provider indicates fatal error, pass through exception
405                if (t instanceof ProviderException) {
406                    throw (ProviderException) t;
407                }
408                return null;
409            } catch (ExceptionInInitializerError | NoClassDefFoundError err) {
410                // no sufficient permission to access/initialize provider class
411                if (debug != null) {
412                    debug.println("Error loading legacy provider " + classname);
413                    err.printStackTrace();
414                }
415                return null;
416            }
417        }
418    }
419}
420