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