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 
  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  * (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  */
  44 final 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                 try {
 240                     Provider p = ProviderLoader.INSTANCE.load(provName);
 241                     if (p != null) {
 242                         if (hasArgument()) {
 243                             p = p.configure(argument);
 244                         }
 245                         if (debug != null) {
 246                             debug.println("Loaded provider " + p.getName());
 247                         }
 248                     } else {
 249                         if (debug != null) {
 250                             debug.println("Error loading provider " +
 251                                 ProviderConfig.this);
 252                         }
 253                         disableLoad();
 254                     }
 255                     return p;
 256                 } catch (Exception e) {
 257                     if (e instanceof ProviderException) {
 258                         // pass up
 259                         throw e;
 260                     } else {
 261                         if (debug != null) {
 262                             debug.println("Error loading provider " +
 263                                 ProviderConfig.this);
 264                             e.printStackTrace();
 265                         }
 266                         disableLoad();
 267                         return null;
 268                     }
 269                 } catch (ExceptionInInitializerError err) {
 270                     // no sufficient permission to initialize provider class
 271                     if (debug != null) {
 272                         debug.println("Error loading provider " + ProviderConfig.this);
 273                         err.printStackTrace();
 274                     }
 275                     disableLoad();
 276                     return null;
 277                 }
 278             }
 279         });
 280     }
 281 
 282     /**
 283      * Perform property expansion of the provider value.
 284      *
 285      * NOTE use of doPrivileged().
 286      */
 287     private static String expand(final String value) {
 288         // shortcut if value does not contain any properties
 289         if (value.contains("${") == false) {
 290             return value;
 291         }
 292         return AccessController.doPrivileged(new PrivilegedAction<String>() {
 293             public String run() {
 294                 try {
 295                     return PropertyExpander.expand(value);
 296                 } catch (GeneralSecurityException e) {
 297                     throw new ProviderException(e);
 298                 }
 299             }
 300         });
 301     }
 302 
 303     // Inner class for loading security providers listed in java.security file
 304     private static final class ProviderLoader {
 305         static final ProviderLoader INSTANCE = new ProviderLoader();
 306 
 307         private final ServiceLoader<Provider> services;
 308 
 309         private ProviderLoader() {
 310             // VM should already been booted at this point, if not
 311             // - Only providers in java.base should be loaded, don't use
 312             //   ServiceLoader
 313             // - ClassLoader.getSystemClassLoader() will throw InternalError
 314             services = ServiceLoader.load(java.security.Provider.class,
 315                                           ClassLoader.getSystemClassLoader());
 316         }
 317 
 318         /**
 319          * Loads the provider with the specified class name.
 320          *
 321          * @param name the name of the provider
 322          * @return the Provider, or null if it cannot be found or loaded
 323          * @throws ProviderException all other exceptions are ignored
 324          */
 325         public Provider load(String pn) {
 326             if (debug != null) {
 327                 debug.println("Attempt to load " + pn + " using SL");
 328             }
 329             Iterator<Provider> iter = services.iterator();
 330             while (iter.hasNext()) {
 331                 try {
 332                     Provider p = iter.next();
 333                     String pName = p.getName();
 334                     if (debug != null) {
 335                         debug.println("Found SL Provider named " + pName);
 336                     }
 337                     if (pName.equals(pn)) {
 338                         return p;
 339                     }
 340                 } catch (SecurityException | ServiceConfigurationError |
 341                          InvalidParameterException ex) {
 342                     // if provider loading fail due to security permission,
 343                     // log it and move on to next provider
 344                     if (debug != null) {
 345                         debug.println("Encountered " + ex +
 346                             " while iterating through SL, ignore and move on");
 347                             ex.printStackTrace();
 348                     }
 349                 }
 350             }
 351             // No success with ServiceLoader. Try loading provider the legacy,
 352             // i.e. pre-module, way via reflection
 353             try {
 354                 return legacyLoad(pn);
 355             } catch (ProviderException pe) {
 356                 // pass through
 357                 throw pe;
 358             } catch (Exception ex) {
 359                 // logged and ignored
 360                 if (debug != null) {
 361                     debug.println("Encountered " + ex +
 362                         " during legacy load of " + pn);
 363                         ex.printStackTrace();
 364                 }
 365                 return null;
 366             }
 367         }
 368 
 369         private Provider legacyLoad(String classname) {
 370 
 371             if (debug != null) {
 372                 debug.println("Loading legacy provider: " + classname);
 373             }
 374 
 375             try {
 376                 Class<?> provClass =
 377                     ClassLoader.getSystemClassLoader().loadClass(classname);
 378 
 379                 // only continue if the specified class extends Provider
 380                 if (!Provider.class.isAssignableFrom(provClass)) {
 381                     if (debug != null) {
 382                         debug.println(classname + " is not a provider");
 383                     }
 384                     return null;
 385                 }
 386 
 387                 Provider p = AccessController.doPrivileged
 388                     (new PrivilegedExceptionAction<Provider>() {
 389                     public Provider run() throws Exception {
 390                         return (Provider) provClass.newInstance();
 391                     }
 392                 });
 393                 return p;
 394             } catch (Exception e) {
 395                 Throwable t;
 396                 if (e instanceof InvocationTargetException) {
 397                     t = ((InvocationTargetException)e).getCause();
 398                 } else {
 399                     t = e;
 400                 }
 401                 if (debug != null) {
 402                     debug.println("Error loading legacy provider " + classname);
 403                     t.printStackTrace();
 404                 }
 405                 // provider indicates fatal error, pass through exception
 406                 if (t instanceof ProviderException) {
 407                     throw (ProviderException) t;
 408                 }
 409                 return null;
 410             } catch (ExceptionInInitializerError | NoClassDefFoundError err) {
 411                 // no sufficient permission to access/initialize provider class
 412                 if (debug != null) {
 413                     debug.println("Error loading legacy provider " + classname);
 414                     err.printStackTrace();
 415                 }
 416                 return null;
 417             }
 418         }
 419     }
 420 }