1 /*
   2  * Copyright (c) 1997, 2014, 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.security.*;
  33 
  34 import java.security.Provider.Service;
  35 
  36 import sun.security.jca.*;
  37 import sun.security.jca.GetInstance.Instance;
  38 
  39 /**
  40  * This class instantiates implementations of JCE engine classes from
  41  * providers registered with the java.security.Security object.
  42  *
  43  * @author Jan Luehe
  44  * @author Sharon Liu
  45  * @since 1.4
  46  */
  47 
  48 final class JceSecurity {
  49 
  50     static final SecureRandom RANDOM = new SecureRandom();
  51 
  52     // The defaultPolicy and exemptPolicy will be set up
  53     // in the static initializer.
  54     private static CryptoPermissions defaultPolicy = null;
  55     private static CryptoPermissions exemptPolicy = null;
  56 
  57     // Map<Provider,?> of the providers we already have verified
  58     // value == PROVIDER_VERIFIED is successfully verified
  59     // value is failure cause Exception in error case
  60     private final static Map<Provider, Object> verificationResults =
  61             new IdentityHashMap<>();
  62 
  63     // Map<Provider,?> of the providers currently being verified
  64     private final static Map<Provider, Object> verifyingProviders =
  65             new IdentityHashMap<>();
  66 
  67     // Set the default value. May be changed in the static initializer.
  68     private static boolean isRestricted = true;
  69 
  70     /*
  71      * Don't let anyone instantiate this.
  72      */
  73     private JceSecurity() {
  74     }
  75 
  76     static {
  77         try {
  78             AccessController.doPrivileged(
  79                     (PrivilegedExceptionAction<Object>) () -> {
  80                 setupJurisdictionPolicies();
  81                 return null;
  82             });
  83 
  84             isRestricted = defaultPolicy.implies(
  85                 CryptoAllPermission.INSTANCE) ? false : true;
  86         } catch (Exception e) {
  87             throw new SecurityException(
  88                     "Can not initialize cryptographic mechanism", e);
  89         }
  90     }
  91 
  92     static Instance getInstance(String type, Class<?> clazz, String algorithm,
  93             String provider) throws NoSuchAlgorithmException,
  94             NoSuchProviderException {
  95         Service s = GetInstance.getService(type, algorithm, provider);
  96         Exception ve = getVerificationResult(s.getProvider());
  97         if (ve != null) {
  98             String msg = "JCE cannot authenticate the provider " + provider;
  99             throw (NoSuchProviderException)
 100                                 new NoSuchProviderException(msg).initCause(ve);
 101         }
 102         return GetInstance.getInstance(s, clazz);
 103     }
 104 
 105     static Instance getInstance(String type, Class<?> clazz, String algorithm,
 106             Provider provider) throws NoSuchAlgorithmException {
 107         Service s = GetInstance.getService(type, algorithm, provider);
 108         Exception ve = JceSecurity.getVerificationResult(provider);
 109         if (ve != null) {
 110             String msg = "JCE cannot authenticate the provider "
 111                 + provider.getName();
 112             throw new SecurityException(msg, ve);
 113         }
 114         return GetInstance.getInstance(s, clazz);
 115     }
 116 
 117     static Instance getInstance(String type, Class<?> clazz, String algorithm)
 118             throws NoSuchAlgorithmException {
 119         List<Service> services = GetInstance.getServices(type, algorithm);
 120         NoSuchAlgorithmException failure = null;
 121         for (Service s : services) {
 122             if (canUseProvider(s.getProvider()) == false) {
 123                 // allow only signed providers
 124                 continue;
 125             }
 126             try {
 127                 Instance instance = GetInstance.getInstance(s, clazz);
 128                 return instance;
 129             } catch (NoSuchAlgorithmException e) {
 130                 failure = e;
 131             }
 132         }
 133         throw new NoSuchAlgorithmException("Algorithm " + algorithm
 134                 + " not available", failure);
 135     }
 136 
 137     /**
 138      * Verify if the JAR at URL codeBase is a signed exempt application
 139      * JAR file and returns the permissions bundled with the JAR.
 140      *
 141      * @throws Exception on error
 142      */
 143     static CryptoPermissions verifyExemptJar(URL codeBase) throws Exception {
 144         URLVerifier jv = new URLVerifier(codeBase, true);
 145         jv.verify();
 146         return jv.getPermissions();
 147     }
 148 
 149     /**
 150      * Verify if the JAR at URL codeBase is a signed provider JAR file.
 151      *
 152      * @throws Exception on error
 153      */
 154     static void verifyProviderJar(URL codeBase, Provider p) throws Exception {
 155         // Verify the provider JAR file and all
 156         // supporting JAR files if there are any.
 157         URLVerifier uv = new URLVerifier(codeBase, p, false);
 158         uv.verify();
 159     }
 160 
 161     private final static Object PROVIDER_VERIFIED = Boolean.TRUE;
 162 
 163     /*
 164      * Verify that the provider JAR files are signed properly, which
 165      * means the signer's certificate can be traced back to a
 166      * JCE trusted CA.
 167      * Return null if ok, failure Exception if verification failed.
 168      */
 169     static synchronized Exception getVerificationResult(Provider p) {
 170         Object o = verificationResults.get(p);
 171         if (o == PROVIDER_VERIFIED) {
 172             return null;
 173         } else if (o != null) {
 174             return (Exception)o;
 175         }
 176         if (verifyingProviders.get(p) != null) {
 177             // this method is static synchronized, must be recursion
 178             // return failure now but do not save the result
 179             return new NoSuchProviderException("Recursion during verification");
 180         }
 181         try {
 182             verifyingProviders.put(p, Boolean.FALSE);
 183             URL providerURL = getCodeBase(p.getClass());
 184             verifyProviderJar(providerURL, p);
 185             // Verified ok, cache result
 186             verificationResults.put(p, PROVIDER_VERIFIED);
 187             return null;
 188         } catch (Exception e) {
 189             verificationResults.put(p, e);
 190             return e;
 191         } finally {
 192             verifyingProviders.remove(p);
 193         }
 194     }
 195 
 196     // return whether this provider is properly signed and can be used by JCE
 197     static boolean canUseProvider(Provider p) {
 198         return getVerificationResult(p) == null;
 199     }
 200 
 201     // dummy object to represent null
 202     private static final URL NULL_URL;
 203 
 204     static {
 205         try {
 206             NULL_URL = new URL("http://null.sun.com/");
 207         } catch (Exception e) {
 208             throw new RuntimeException(e);
 209         }
 210     }
 211 
 212     // reference to a Map we use as a cache for codebases
 213     private static final Map<Class<?>, URL> codeBaseCacheRef =
 214             new WeakHashMap<>();
 215 
 216     /*
 217      * Returns the CodeBase for the given class.
 218      */
 219     static URL getCodeBase(final Class<?> clazz) {
 220         synchronized (codeBaseCacheRef) {
 221             URL url = codeBaseCacheRef.get(clazz);
 222             if (url == null) {
 223                 url = AccessController.doPrivileged(
 224                         (PrivilegedAction<URL>) () -> {
 225                     ProtectionDomain pd = clazz.getProtectionDomain();
 226                     if (pd != null) {
 227                         CodeSource cs = pd.getCodeSource();
 228                         if (cs != null) {
 229                             return cs.getLocation();
 230                         }
 231                     }
 232                     return NULL_URL;
 233                 });
 234                 codeBaseCacheRef.put(clazz, url);
 235             }
 236             return (url == NULL_URL) ? null : url;
 237         }
 238     }
 239 
 240     private static void setupJurisdictionPolicies() throws Exception {
 241         String javaHomeDir = System.getProperty("java.home");
 242         String sep = File.separator;
 243         String pathToPolicyJar = javaHomeDir + sep + "lib" + sep +
 244             "security" + sep;
 245 
 246         File exportJar = new File(pathToPolicyJar, "US_export_policy.jar");
 247         File importJar = new File(pathToPolicyJar, "local_policy.jar");
 248         URL jceCipherURL = ClassLoader.getSystemResource
 249                 ("javax/crypto/Cipher.class");
 250 
 251         if ((jceCipherURL == null) ||
 252                 !exportJar.exists() || !importJar.exists()) {
 253             throw new SecurityException
 254                                 ("Cannot locate policy or framework files!");
 255         }
 256 
 257         // Read jurisdiction policies.
 258         CryptoPermissions defaultExport = new CryptoPermissions();
 259         CryptoPermissions exemptExport = new CryptoPermissions();
 260         loadPolicies(exportJar, defaultExport, exemptExport);
 261 
 262         CryptoPermissions defaultImport = new CryptoPermissions();
 263         CryptoPermissions exemptImport = new CryptoPermissions();
 264         loadPolicies(importJar, defaultImport, exemptImport);
 265 
 266         // Merge the export and import policies for default applications.
 267         if (defaultExport.isEmpty() || defaultImport.isEmpty()) {
 268             throw new SecurityException("Missing mandatory jurisdiction " +
 269                                         "policy files");
 270         }
 271         defaultPolicy = defaultExport.getMinimum(defaultImport);
 272 
 273         // Merge the export and import policies for exempt applications.
 274         if (exemptExport.isEmpty())  {
 275             exemptPolicy = exemptImport.isEmpty() ? null : exemptImport;
 276         } else {
 277             exemptPolicy = exemptExport.getMinimum(exemptImport);
 278         }
 279     }
 280 
 281     /**
 282      * Load the policies from the specified file. Also checks that the
 283      * policies are correctly signed.
 284      */
 285     private static void loadPolicies(File jarPathName,
 286                                      CryptoPermissions defaultPolicy,
 287                                      CryptoPermissions exemptPolicy)
 288         throws Exception {
 289 
 290         JarFile jf = new JarFile(jarPathName);
 291 
 292         Enumeration<JarEntry> entries = jf.entries();
 293         while (entries.hasMoreElements()) {
 294             JarEntry je = entries.nextElement();
 295             InputStream is = null;
 296             try {
 297                 if (je.getName().startsWith("default_")) {
 298                     is = jf.getInputStream(je);
 299                     defaultPolicy.load(is);
 300                 } else if (je.getName().startsWith("exempt_")) {
 301                     is = jf.getInputStream(je);
 302                     exemptPolicy.load(is);
 303                 } else {
 304                     continue;
 305                 }
 306             } finally {
 307                 if (is != null) {
 308                     is.close();
 309                 }
 310             }
 311 
 312             // Enforce the signer restraint, i.e. signer of JCE framework
 313             // jar should also be the signer of the two jurisdiction policy
 314             // jar files.
 315             URLVerifier.verifyPolicySigned(je.getCertificates());
 316         }
 317         // Close and nullify the JarFile reference to help GC.
 318         jf.close();
 319         jf = null;
 320     }
 321 
 322     static CryptoPermissions getDefaultPolicy() {
 323         return defaultPolicy;
 324     }
 325 
 326     static CryptoPermissions getExemptPolicy() {
 327         return exemptPolicy;
 328     }
 329 
 330     static boolean isRestricted() {
 331         return isRestricted;
 332     }
 333 }