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.*;
30
31import java.security.*;
32
33import sun.security.util.PropertyExpander;
34
35/**
36 * Class representing a configured provider. Encapsulates configuration
37 * (className plus optional argument), the provider loading logic, and
38 * the loaded Provider object itself.
39 *
40 * @author  Andreas Sterbenz
41 * @since   1.5
42 */
43final class ProviderConfig {
44
45    private final static sun.security.util.Debug debug =
46        sun.security.util.Debug.getInstance("jca", "ProviderConfig");
47
48    // classname of the SunPKCS11-Solaris provider
49    private static final String P11_SOL_NAME =
50        "sun.security.pkcs11.SunPKCS11";
51
52    // config file argument of the SunPKCS11-Solaris provider
53    private static final String P11_SOL_ARG  =
54        "${java.home}/lib/security/sunpkcs11-solaris.cfg";
55
56    // maximum number of times to try loading a provider before giving up
57    private final static int MAX_LOAD_TRIES = 30;
58
59    // parameters for the Provider(String) constructor,
60    // use by doLoadProvider()
61    private final static Class[] CL_STRING = { String.class };
62
63    // name of the provider class
64    private final String className;
65
66    // argument to the provider constructor,
67    // empty string indicates no-arg constructor
68    private final String argument;
69
70    // number of times we have already tried to load this provider
71    private int tries;
72
73    // Provider object, if loaded
74    private volatile Provider provider;
75
76    // flag indicating if we are currently trying to load the provider
77    // used to detect recursion
78    private boolean isLoading;
79
80    ProviderConfig(String className, String argument) {
81        if (className.equals(P11_SOL_NAME) && argument.equals(P11_SOL_ARG)) {
82            checkSunPKCS11Solaris();
83        }
84        this.className = className;
85        this.argument = expand(argument);
86    }
87
88    ProviderConfig(String className) {
89        this(className, "");
90    }
91
92    ProviderConfig(Provider provider) {
93        this.className = provider.getClass().getName();
94        this.argument = "";
95        this.provider = provider;
96    }
97
98    // check if we should try to load the SunPKCS11-Solaris provider
99    // avoid if not available (pre Solaris 10) to reduce startup time
100    // or if disabled via system property
101    private void checkSunPKCS11Solaris() {
102        Boolean o = AccessController.doPrivileged(
103                                new PrivilegedAction<Boolean>() {
104            public Boolean run() {
105                File file = new File("/usr/lib/libpkcs11.so");
106                if (file.exists() == false) {
107                    return Boolean.FALSE;
108                }
109                if ("false".equalsIgnoreCase(System.getProperty
110                        ("sun.security.pkcs11.enable-solaris"))) {
111                    return Boolean.FALSE;
112                }
113                return Boolean.TRUE;
114            }
115        });
116        if (o == Boolean.FALSE) {
117            tries = MAX_LOAD_TRIES;
118        }
119    }
120
121    private boolean hasArgument() {
122        return argument.length() != 0;
123    }
124
125    // should we try to load this provider?
126    private boolean shouldLoad() {
127        return (tries < MAX_LOAD_TRIES);
128    }
129
130    // do not try to load this provider again
131    private void disableLoad() {
132        tries = MAX_LOAD_TRIES;
133    }
134
135    boolean isLoaded() {
136        return (provider != null);
137    }
138
139    public boolean equals(Object obj) {
140        if (this == obj) {
141            return true;
142        }
143        if (obj instanceof ProviderConfig == false) {
144            return false;
145        }
146        ProviderConfig other = (ProviderConfig)obj;
147        return this.className.equals(other.className)
148            && this.argument.equals(other.argument);
149    }
150
151    public int hashCode() {
152        return className.hashCode() + argument.hashCode();
153    }
154
155    public String toString() {
156        if (hasArgument()) {
157            return className + "('" + argument + "')";
158        } else {
159            return className;
160        }
161    }
162
163    /**
164     * Get the provider object. Loads the provider if it is not already loaded.
165     */
166    synchronized Provider getProvider() {
167        // volatile variable load
168        Provider p = provider;
169        if (p != null) {
170            return p;
171        }
172        if (shouldLoad() == false) {
173            return null;
174        }
175        if (isLoading) {
176            // because this method is synchronized, this can only
177            // happen if there is recursion.
178            if (debug != null) {
179                debug.println("Recursion loading provider: " + this);
180                new Exception("Call trace").printStackTrace();
181            }
182            return null;
183        }
184        try {
185            isLoading = true;
186            tries++;
187            p = doLoadProvider();
188        } finally {
189            isLoading = false;
190        }
191        provider = p;
192        return p;
193    }
194
195    /**
196     * Load and instantiate the Provider described by this class.
197     *
198     * NOTE use of doPrivileged().
199     *
200     * @return null if the Provider could not be loaded
201     *
202     * @throws ProviderException if executing the Provider's constructor
203     * throws a ProviderException. All other Exceptions are ignored.
204     */
205    private Provider doLoadProvider() {
206        return AccessController.doPrivileged(new PrivilegedAction<Provider>() {
207            public Provider run() {
208                if (debug != null) {
209                    debug.println("Loading provider: " + ProviderConfig.this);
210                }
211
212// BEGIN Android-changed: Prefer the boot classloader to the system classloader.
213                try {
214                    // First try with the boot classloader.
215                    return initProvider(className, Object.class.getClassLoader());
216                } catch (Exception e1) {
217                    // If that fails, try with the system classloader.
218                    try {
219                        return initProvider(className, ClassLoader.getSystemClassLoader());
220                    } catch (Exception e) {
221                        Throwable t;
222                        if (e instanceof InvocationTargetException) {
223                            t = ((InvocationTargetException)e).getCause();
224                        } else {
225                            t = e;
226                        }
227                        if (debug != null) {
228                            debug.println("Error loading provider " + ProviderConfig.this);
229                            t.printStackTrace();
230                        }
231                        // provider indicates fatal error, pass through exception
232                        if (t instanceof ProviderException) {
233                            throw (ProviderException)t;
234                        }
235                        // provider indicates that loading should not be retried
236                        if (t instanceof UnsupportedOperationException) {
237                            disableLoad();
238                        }
239                        return null;
240                    }
241                }
242            }
243        });
244    }
245
246    private Provider initProvider(String className, ClassLoader cl) throws Exception {
247        Class<?> provClass;
248        if (cl != null) {
249            provClass = cl.loadClass(className);
250        } else {
251            provClass = Class.forName(className);
252        }
253        Object obj;
254        if (hasArgument() == false) {
255            obj = provClass.newInstance();
256        } else {
257            Constructor<?> cons = provClass.getConstructor(CL_STRING);
258            obj = cons.newInstance(argument);
259        }
260        if (obj instanceof Provider) {
261            if (debug != null) {
262                debug.println("Loaded provider " + obj);
263            }
264            return (Provider)obj;
265        } else {
266            if (debug != null) {
267                debug.println(className + " is not a provider");
268            }
269            disableLoad();
270            return null;
271        }
272    }
273// END Android-changed: Prefer the boot classloader to the system classloader.
274
275    /**
276     * Perform property expansion of the provider value.
277     *
278     * NOTE use of doPrivileged().
279     */
280    private static String expand(final String value) {
281        // shortcut if value does not contain any properties
282        if (value.contains("${") == false) {
283            return value;
284        }
285        return AccessController.doPrivileged(new PrivilegedAction<String>() {
286            public String run() {
287                try {
288                    return PropertyExpander.expand(value);
289                } catch (GeneralSecurityException e) {
290                    throw new ProviderException(e);
291                }
292            }
293        });
294    }
295
296}
297