1 /*
   2  * Copyright (c) 1997, 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 javax.crypto;
  27 
  28 import java.util.*;
  29 import java.util.jar.*;
  30 import java.io.*;
  31 import java.net.URL;
  32 import java.nio.file.*;
  33 import java.security.*;
  34 
  35 import java.security.Provider.Service;
  36 
  37 import sun.security.jca.*;
  38 import sun.security.jca.GetInstance.Instance;
  39 import sun.security.util.Debug;
  40 
  41 /**
  42  * This class instantiates implementations of JCE engine classes from
  43  * providers registered with the java.security.Security object.
  44  *
  45  * @author Jan Luehe
  46  * @author Sharon Liu
  47  * @since 1.4
  48  */
  49 
  50 final class JceSecurity {
  51 
  52     static final SecureRandom RANDOM = new SecureRandom();
  53 
  54     // The defaultPolicy and exemptPolicy will be set up
  55     // in the static initializer.
  56     private static CryptoPermissions defaultPolicy = null;
  57     private static CryptoPermissions exemptPolicy = null;
  58 
  59     // Map<Provider,?> of the providers we already have verified
  60     // value == PROVIDER_VERIFIED is successfully verified
  61     // value is failure cause Exception in error case
  62     private final static Map<Provider, Object> verificationResults =
  63             new IdentityHashMap<>();
  64 
  65     // Map<Provider,?> of the providers currently being verified
  66     private final static Map<Provider, Object> verifyingProviders =
  67             new IdentityHashMap<>();
  68 
  69     private static final boolean isRestricted;
  70 
  71     private static final Debug debug =
  72                         Debug.getInstance("jca", "Cipher");
  73 
  74     /*
  75      * Don't let anyone instantiate this.
  76      */
  77     private JceSecurity() {
  78     }
  79 
  80     static {
  81         try {
  82             AccessController.doPrivileged(
  83                 new PrivilegedExceptionAction<Object>() {
  84                     public Object run() throws Exception {
  85                         setupJurisdictionPolicies();
  86                         return null;
  87                     }
  88                 });
  89 
  90             isRestricted = defaultPolicy.implies(
  91                 CryptoAllPermission.INSTANCE) ? false : true;
  92         } catch (Exception e) {
  93             throw new SecurityException(
  94                     "Can not initialize cryptographic mechanism", e);
  95         }
  96     }
  97 
  98     static Instance getInstance(String type, Class<?> clazz, String algorithm,
  99             String provider) throws NoSuchAlgorithmException,
 100             NoSuchProviderException {
 101         Service s = GetInstance.getService(type, algorithm, provider);
 102         Exception ve = getVerificationResult(s.getProvider());
 103         if (ve != null) {
 104             String msg = "JCE cannot authenticate the provider " + provider;
 105             throw (NoSuchProviderException)
 106                                 new NoSuchProviderException(msg).initCause(ve);
 107         }
 108         return GetInstance.getInstance(s, clazz);
 109     }
 110 
 111     static Instance getInstance(String type, Class<?> clazz, String algorithm,
 112             Provider provider) throws NoSuchAlgorithmException {
 113         Service s = GetInstance.getService(type, algorithm, provider);
 114         Exception ve = JceSecurity.getVerificationResult(provider);
 115         if (ve != null) {
 116             String msg = "JCE cannot authenticate the provider "
 117                 + provider.getName();
 118             throw new SecurityException(msg, ve);
 119         }
 120         return GetInstance.getInstance(s, clazz);
 121     }
 122 
 123     static Instance getInstance(String type, Class<?> clazz, String algorithm)
 124             throws NoSuchAlgorithmException {
 125         List<Service> services = GetInstance.getServices(type, algorithm);
 126         NoSuchAlgorithmException failure = null;
 127         for (Service s : services) {
 128             if (canUseProvider(s.getProvider()) == false) {
 129                 // allow only signed providers
 130                 continue;
 131             }
 132             try {
 133                 Instance instance = GetInstance.getInstance(s, clazz);
 134                 return instance;
 135             } catch (NoSuchAlgorithmException e) {
 136                 failure = e;
 137             }
 138         }
 139         throw new NoSuchAlgorithmException("Algorithm " + algorithm
 140                 + " not available", failure);
 141     }
 142 
 143     /**
 144      * Verify if the JAR at URL codeBase is a signed exempt application
 145      * JAR file and returns the permissions bundled with the JAR.
 146      *
 147      * @throws Exception on error
 148      */
 149     static CryptoPermissions verifyExemptJar(URL codeBase) throws Exception {
 150         JarVerifier jv = new JarVerifier(codeBase, true);
 151         jv.verify();
 152         return jv.getPermissions();
 153     }
 154 
 155     /**
 156      * Verify if the JAR at URL codeBase is a signed provider JAR file.
 157      *
 158      * @throws Exception on error
 159      */
 160     static void verifyProviderJar(URL codeBase) throws Exception {
 161         // Verify the provider JAR file and all
 162         // supporting JAR files if there are any.
 163         JarVerifier jv = new JarVerifier(codeBase, false);
 164         jv.verify();
 165     }
 166 
 167     private final static Object PROVIDER_VERIFIED = Boolean.TRUE;
 168 
 169     /*
 170      * Verify that the provider JAR files are signed properly, which
 171      * means the signer's certificate can be traced back to a
 172      * JCE trusted CA.
 173      * Return null if ok, failure Exception if verification failed.
 174      */
 175     static synchronized Exception getVerificationResult(Provider p) {
 176         Object o = verificationResults.get(p);
 177         if (o == PROVIDER_VERIFIED) {
 178             return null;
 179         } else if (o != null) {
 180             return (Exception)o;
 181         }
 182         if (verifyingProviders.get(p) != null) {
 183             // this method is static synchronized, must be recursion
 184             // return failure now but do not save the result
 185             return new NoSuchProviderException("Recursion during verification");
 186         }
 187         try {
 188             verifyingProviders.put(p, Boolean.FALSE);
 189             URL providerURL = getCodeBase(p.getClass());
 190             verifyProviderJar(providerURL);
 191             // Verified ok, cache result
 192             verificationResults.put(p, PROVIDER_VERIFIED);
 193             return null;
 194         } catch (Exception e) {
 195             verificationResults.put(p, e);
 196             return e;
 197         } finally {
 198             verifyingProviders.remove(p);
 199         }
 200     }
 201 
 202     // return whether this provider is properly signed and can be used by JCE
 203     static boolean canUseProvider(Provider p) {
 204         return getVerificationResult(p) == null;
 205     }
 206 
 207     // dummy object to represent null
 208     private static final URL NULL_URL;
 209 
 210     static {
 211         try {
 212             NULL_URL = new URL("http://null.oracle.com/");
 213         } catch (Exception e) {
 214             throw new RuntimeException(e);
 215         }
 216     }
 217 
 218     // reference to a Map we use as a cache for codebases
 219     private static final Map<Class<?>, URL> codeBaseCacheRef =
 220             new WeakHashMap<>();
 221 
 222     /*
 223      * Returns the CodeBase for the given class.
 224      */
 225     static URL getCodeBase(final Class<?> clazz) {
 226         synchronized (codeBaseCacheRef) {
 227             URL url = codeBaseCacheRef.get(clazz);
 228             if (url == null) {
 229                 url = AccessController.doPrivileged(new PrivilegedAction<URL>() {
 230                     public URL run() {
 231                         ProtectionDomain pd = clazz.getProtectionDomain();
 232                         if (pd != null) {
 233                             CodeSource cs = pd.getCodeSource();
 234                             if (cs != null) {
 235                                 return cs.getLocation();
 236                             }
 237                         }
 238                         return NULL_URL;
 239                     }
 240                 });
 241                 codeBaseCacheRef.put(clazz, url);
 242             }
 243             return (url == NULL_URL) ? null : url;
 244         }
 245     }
 246 
 247     /*
 248      * This is called from within an doPrivileged block.
 249      *
 250      * Following logic is used to decide what policy files are selected.
 251      *
 252      * If the new Security property (crypto.policy) is set in the
 253      * java.security file, or has been set dynamically using the
 254      * Security.setProperty() call before the JCE framework has
 255      * been initialized, that setting will be used.
 256      * Remember - this property is not defined by default. A conscious
 257      * user edit or an application call is required.
 258      *
 259      * Otherwise, if user has policy jar files installed in the legacy
 260      * jre/lib/security/ directory, the JDK will honor whatever
 261      * setting is set by those policy files. (legacy/current behavior)
 262      *
 263      * If none of the above 2 conditions are met, the JDK will default
 264      * to using the limited crypto policy files found in the
 265      * jre/lib/security/policy/limited/ directory
 266      */
 267     private static void setupJurisdictionPolicies() throws Exception {
 268         // Sanity check the crypto.policy Security property.  Single
 269         // directory entry, no pseudo-directories (".", "..", leading/trailing
 270         // path separators). normalize()/getParent() will help later.
 271         String javaHomeProperty = System.getProperty("java.home");
 272         String cryptoPolicyProperty = Security.getProperty("crypto.policy");
 273         Path cpPath = (cryptoPolicyProperty == null) ? null :
 274                 Paths.get(cryptoPolicyProperty);
 275 
 276         if ((cpPath != null) && ((cpPath.getNameCount() != 1) ||
 277                 (cpPath.compareTo(cpPath.getFileName())) != 0)) {
 278             throw new SecurityException(
 279                     "Invalid policy directory name format: " +
 280                             cryptoPolicyProperty);
 281         }
 282 
 283         if (cpPath == null) {
 284             // Security property is not set, use default path
 285             cpPath = Paths.get(javaHomeProperty, "lib", "security");
 286         } else {
 287             // populate with java.home
 288             cpPath = Paths.get(javaHomeProperty, "lib", "security",
 289                     "policy", cryptoPolicyProperty);
 290         }
 291 
 292         if (debug != null) {
 293             debug.println("crypto policy directory: " + cpPath);
 294         }
 295 
 296         File exportJar = new File(cpPath.toFile(),"US_export_policy.jar");
 297         File importJar = new File(cpPath.toFile(),"local_policy.jar");
 298 
 299         if (cryptoPolicyProperty == null && (!exportJar.exists() ||
 300                 !importJar.exists())) {
 301             // Compatibility set up. If crypto.policy is not defined.
 302             // check to see if legacy jars exist in lib directory. If
 303             // they don't exist, we default to limited policy mode.
 304             cpPath = Paths.get(
 305                     javaHomeProperty, "lib", "security", "policy", "limited");
 306             // point to the new jar files in limited directory
 307             exportJar = new File(cpPath.toFile(),"US_export_policy.jar");
 308             importJar = new File(cpPath.toFile(),"local_policy.jar");
 309         }
 310 
 311         URL jceCipherURL = ClassLoader.getSystemResource
 312                 ("javax/crypto/Cipher.class");
 313 
 314         if ((jceCipherURL == null) ||
 315                 !exportJar.exists() || !importJar.exists()) {
 316             throw new SecurityException
 317                                 ("Cannot locate policy or framework files!");
 318         }
 319 
 320         // Read jurisdiction policies.
 321         CryptoPermissions defaultExport = new CryptoPermissions();
 322         CryptoPermissions exemptExport = new CryptoPermissions();
 323         loadPolicies(exportJar, defaultExport, exemptExport);
 324 
 325         CryptoPermissions defaultImport = new CryptoPermissions();
 326         CryptoPermissions exemptImport = new CryptoPermissions();
 327         loadPolicies(importJar, defaultImport, exemptImport);
 328 
 329         // Merge the export and import policies for default applications.
 330         if (defaultExport.isEmpty() || defaultImport.isEmpty()) {
 331             throw new SecurityException("Missing mandatory jurisdiction " +
 332                                         "policy files");
 333         }
 334         defaultPolicy = defaultExport.getMinimum(defaultImport);
 335 
 336         // Merge the export and import policies for exempt applications.
 337         if (exemptExport.isEmpty())  {
 338             exemptPolicy = exemptImport.isEmpty() ? null : exemptImport;
 339         } else {
 340             exemptPolicy = exemptExport.getMinimum(exemptImport);
 341         }
 342     }
 343 
 344     /**
 345      * Load the policies from the specified file. Also checks that the
 346      * policies are correctly signed.
 347      */
 348     private static void loadPolicies(File jarPathName,
 349                                      CryptoPermissions defaultPolicy,
 350                                      CryptoPermissions exemptPolicy)
 351         throws Exception {
 352 
 353         JarFile jf = new JarFile(jarPathName);
 354 
 355         Enumeration<JarEntry> entries = jf.entries();
 356         while (entries.hasMoreElements()) {
 357             JarEntry je = entries.nextElement();
 358             InputStream is = null;
 359             try {
 360                 if (je.getName().startsWith("default_")) {
 361                     is = jf.getInputStream(je);
 362                     defaultPolicy.load(is);
 363                 } else if (je.getName().startsWith("exempt_")) {
 364                     is = jf.getInputStream(je);
 365                     exemptPolicy.load(is);
 366                 } else {
 367                     continue;
 368                 }
 369             } finally {
 370                 if (is != null) {
 371                     is.close();
 372                 }
 373             }
 374 
 375             // Enforce the signer restraint, i.e. signer of JCE framework
 376             // jar should also be the signer of the two jurisdiction policy
 377             // jar files.
 378             JarVerifier.verifyPolicySigned(je.getCertificates());
 379         }
 380         // Close and nullify the JarFile reference to help GC.
 381         jf.close();
 382         jf = null;
 383     }
 384 
 385     static CryptoPermissions getDefaultPolicy() {
 386         return defaultPolicy;
 387     }
 388 
 389     static CryptoPermissions getExemptPolicy() {
 390         return exemptPolicy;
 391     }
 392 
 393     static boolean isRestricted() {
 394         return isRestricted;
 395     }
 396 }