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