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