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                 new PrivilegedExceptionAction<> () {
  80                     @Override
  81                     public Void run() throws Exception {
  82                         setupJurisdictionPolicies();
  83                         return null;
  84                     }
  85                 }
  86             );
  87 
  88             isRestricted = defaultPolicy.implies(
  89                 CryptoAllPermission.INSTANCE) ? false : true;
  90         } catch (Exception e) {
  91             throw new SecurityException(
  92                     "Can not initialize cryptographic mechanism", e);
  93         }
  94     }
  95 
  96     static Instance getInstance(String type, Class<?> clazz, String algorithm,
  97             String provider) throws NoSuchAlgorithmException,
  98             NoSuchProviderException {
  99         Service s = GetInstance.getService(type, algorithm, provider);
 100         Exception ve = getVerificationResult(s.getProvider());
 101         if (ve != null) {
 102             String msg = "JCE cannot authenticate the provider " + provider;
 103             throw (NoSuchProviderException)
 104                                 new NoSuchProviderException(msg).initCause(ve);
 105         }
 106         return GetInstance.getInstance(s, clazz);
 107     }
 108 
 109     static Instance getInstance(String type, Class<?> clazz, String algorithm,
 110             Provider provider) throws NoSuchAlgorithmException {
 111         Service s = GetInstance.getService(type, algorithm, provider);
 112         Exception ve = JceSecurity.getVerificationResult(provider);
 113         if (ve != null) {
 114             String msg = "JCE cannot authenticate the provider "
 115                 + provider.getName();
 116             throw new SecurityException(msg, ve);
 117         }
 118         return GetInstance.getInstance(s, clazz);
 119     }
 120 
 121     static Instance getInstance(String type, Class<?> clazz, String algorithm)
 122             throws NoSuchAlgorithmException {
 123         List<Service> services = GetInstance.getServices(type, algorithm);
 124         NoSuchAlgorithmException failure = null;
 125         for (Service s : services) {
 126             if (canUseProvider(s.getProvider()) == false) {
 127                 // allow only signed providers
 128                 continue;
 129             }
 130             try {
 131                 Instance instance = GetInstance.getInstance(s, clazz);
 132                 return instance;
 133             } catch (NoSuchAlgorithmException e) {
 134                 failure = e;
 135             }
 136         }
 137         throw new NoSuchAlgorithmException("Algorithm " + algorithm
 138                 + " not available", failure);
 139     }
 140 
 141     /**
 142      * Verify if the JAR at URL codeBase is a signed exempt application
 143      * JAR file and returns the permissions bundled with the JAR.
 144      *
 145      * @throws Exception on error
 146      */
 147     static CryptoPermissions verifyExemptJar(URL codeBase) throws Exception {
 148         ProviderVerifier pv = new ProviderVerifier(codeBase, true);
 149         pv.verify();
 150         return pv.getPermissions();
 151     }
 152 
 153     /**
 154      * Verify if the JAR at URL codeBase is a signed provider JAR file.
 155      *
 156      * @throws Exception on error
 157      */
 158     static void verifyProvider(URL codeBase, Provider p) throws Exception {
 159         // Verify the provider JAR file and all
 160         // supporting JAR files if there are any.
 161         ProviderVerifier pv = new ProviderVerifier(codeBase, p, false);
 162         pv.verify();
 163     }
 164 
 165     private final static Object PROVIDER_VERIFIED = Boolean.TRUE;
 166 
 167     /*
 168      * Verify that the provider JAR files are signed properly, which
 169      * means the signer's certificate can be traced back to a
 170      * JCE trusted CA.
 171      * Return null if ok, failure Exception if verification failed.
 172      */
 173     static synchronized Exception getVerificationResult(Provider p) {
 174         Object o = verificationResults.get(p);
 175         if (o == PROVIDER_VERIFIED) {
 176             return null;
 177         } else if (o != null) {
 178             return (Exception)o;
 179         }
 180         if (verifyingProviders.get(p) != null) {
 181             // this method is static synchronized, must be recursion
 182             // return failure now but do not save the result
 183             return new NoSuchProviderException("Recursion during verification");
 184         }
 185         try {
 186             verifyingProviders.put(p, Boolean.FALSE);
 187             URL providerURL = getCodeBase(p.getClass());
 188             verifyProvider(providerURL, p);
 189             // Verified ok, cache result
 190             verificationResults.put(p, PROVIDER_VERIFIED);
 191             return null;
 192         } catch (Exception e) {
 193             verificationResults.put(p, e);
 194             return e;
 195         } finally {
 196             verifyingProviders.remove(p);
 197         }
 198     }
 199 
 200     // return whether this provider is properly signed and can be used by JCE
 201     static boolean canUseProvider(Provider p) {
 202         return getVerificationResult(p) == null;
 203     }
 204 
 205     // dummy object to represent null
 206     private static final URL NULL_URL;
 207 
 208     static {
 209         try {
 210             NULL_URL = new URL("http://null.sun.com/");
 211         } catch (Exception e) {
 212             throw new RuntimeException(e);
 213         }
 214     }
 215 
 216     // reference to a Map we use as a cache for codebases
 217     private static final Map<Class<?>, URL> codeBaseCacheRef =
 218             new WeakHashMap<>();
 219 
 220     /*
 221      * Returns the CodeBase for the given class.
 222      */
 223     static URL getCodeBase(final Class<?> clazz) {
 224         synchronized (codeBaseCacheRef) {
 225             URL url = codeBaseCacheRef.get(clazz);
 226             if (url == null) {
 227                 url = AccessController.doPrivileged(
 228                     new PrivilegedAction<>() {
 229                         @Override
 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     private static void setupJurisdictionPolicies() throws Exception {
 248         String javaHomeDir = System.getProperty("java.home");
 249         String sep = File.separator;
 250         String pathToPolicyJar = javaHomeDir + sep + "lib" + sep +
 251             "security" + sep;
 252 
 253         File exportJar = new File(pathToPolicyJar, "US_export_policy.jar");
 254         File importJar = new File(pathToPolicyJar, "local_policy.jar");
 255         URL jceCipherURL = ClassLoader.getSystemResource
 256                 ("javax/crypto/Cipher.class");
 257 
 258         if ((jceCipherURL == null) ||
 259                 !exportJar.exists() || !importJar.exists()) {
 260             throw new SecurityException
 261                                 ("Cannot locate policy or framework files!");
 262         }
 263 
 264         // Read jurisdiction policies.
 265         CryptoPermissions defaultExport = new CryptoPermissions();
 266         CryptoPermissions exemptExport = new CryptoPermissions();
 267         loadPolicies(exportJar, defaultExport, exemptExport);
 268 
 269         CryptoPermissions defaultImport = new CryptoPermissions();
 270         CryptoPermissions exemptImport = new CryptoPermissions();
 271         loadPolicies(importJar, defaultImport, exemptImport);
 272 
 273         // Merge the export and import policies for default applications.
 274         if (defaultExport.isEmpty() || defaultImport.isEmpty()) {
 275             throw new SecurityException("Missing mandatory jurisdiction " +
 276                                         "policy files");
 277         }
 278         defaultPolicy = defaultExport.getMinimum(defaultImport);
 279 
 280         // Merge the export and import policies for exempt applications.
 281         if (exemptExport.isEmpty())  {
 282             exemptPolicy = exemptImport.isEmpty() ? null : exemptImport;
 283         } else {
 284             exemptPolicy = exemptExport.getMinimum(exemptImport);
 285         }
 286     }
 287 
 288     /**
 289      * Load the policies from the specified file. Also checks that the
 290      * policies are correctly signed.
 291      */
 292     private static void loadPolicies(File jarPathName,
 293                                      CryptoPermissions defaultPolicy,
 294                                      CryptoPermissions exemptPolicy)
 295         throws Exception {
 296 
 297         JarFile jf = new JarFile(jarPathName);
 298 
 299         Enumeration<JarEntry> entries = jf.entries();
 300         while (entries.hasMoreElements()) {
 301             JarEntry je = entries.nextElement();
 302             InputStream is = null;
 303             try {
 304                 if (je.getName().startsWith("default_")) {
 305                     is = jf.getInputStream(je);
 306                     defaultPolicy.load(is);
 307                 } else if (je.getName().startsWith("exempt_")) {
 308                     is = jf.getInputStream(je);
 309                     exemptPolicy.load(is);
 310                 } else {
 311                     continue;
 312                 }
 313             } finally {
 314                 if (is != null) {
 315                     is.close();
 316                 }
 317             }
 318 
 319             // Enforce the signer restraint, i.e. signer of JCE framework
 320             // jar should also be the signer of the two jurisdiction policy
 321             // jar files.
 322             ProviderVerifier.verifyPolicySigned(je.getCertificates());
 323         }
 324         // Close and nullify the JarFile reference to help GC.
 325         jf.close();
 326         jf = null;
 327     }
 328 
 329     static CryptoPermissions getDefaultPolicy() {
 330         return defaultPolicy;
 331     }
 332 
 333     static CryptoPermissions getExemptPolicy() {
 334         return exemptPolicy;
 335     }
 336 
 337     static boolean isRestricted() {
 338         return isRestricted;
 339     }
 340 }