/* * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.crypto; import java.util.*; import java.util.jar.*; import java.io.*; import java.net.URL; import java.nio.file.*; import java.security.*; import java.security.Provider.Service; import sun.security.jca.*; import sun.security.jca.GetInstance.Instance; import sun.security.util.Debug; /** * This class instantiates implementations of JCE engine classes from * providers registered with the java.security.Security object. * * @author Jan Luehe * @author Sharon Liu * @since 1.4 */ final class JceSecurity { static final SecureRandom RANDOM = new SecureRandom(); // The defaultPolicy and exemptPolicy will be set up // in the static initializer. private static CryptoPermissions defaultPolicy = null; private static CryptoPermissions exemptPolicy = null; // Map of the providers we already have verified // value == PROVIDER_VERIFIED is successfully verified // value is failure cause Exception in error case private final static Map verificationResults = new IdentityHashMap<>(); // Map of the providers currently being verified private final static Map verifyingProviders = new IdentityHashMap<>(); private static final boolean isRestricted; private static final Debug debug = Debug.getInstance("jca", "Cipher"); /* * Don't let anyone instantiate this. */ private JceSecurity() { } static { try { AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws Exception { setupJurisdictionPolicies(); return null; } }); isRestricted = defaultPolicy.implies( CryptoAllPermission.INSTANCE) ? false : true; } catch (Exception e) { throw new SecurityException( "Can not initialize cryptographic mechanism", e); } } static Instance getInstance(String type, Class clazz, String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException { Service s = GetInstance.getService(type, algorithm, provider); Exception ve = getVerificationResult(s.getProvider()); if (ve != null) { String msg = "JCE cannot authenticate the provider " + provider; throw (NoSuchProviderException) new NoSuchProviderException(msg).initCause(ve); } return GetInstance.getInstance(s, clazz); } static Instance getInstance(String type, Class clazz, String algorithm, Provider provider) throws NoSuchAlgorithmException { Service s = GetInstance.getService(type, algorithm, provider); Exception ve = JceSecurity.getVerificationResult(provider); if (ve != null) { String msg = "JCE cannot authenticate the provider " + provider.getName(); throw new SecurityException(msg, ve); } return GetInstance.getInstance(s, clazz); } static Instance getInstance(String type, Class clazz, String algorithm) throws NoSuchAlgorithmException { List services = GetInstance.getServices(type, algorithm); NoSuchAlgorithmException failure = null; for (Service s : services) { if (canUseProvider(s.getProvider()) == false) { // allow only signed providers continue; } try { Instance instance = GetInstance.getInstance(s, clazz); return instance; } catch (NoSuchAlgorithmException e) { failure = e; } } throw new NoSuchAlgorithmException("Algorithm " + algorithm + " not available", failure); } /** * Verify if the JAR at URL codeBase is a signed exempt application * JAR file and returns the permissions bundled with the JAR. * * @throws Exception on error */ static CryptoPermissions verifyExemptJar(URL codeBase) throws Exception { JarVerifier jv = new JarVerifier(codeBase, true); jv.verify(); return jv.getPermissions(); } /** * Verify if the JAR at URL codeBase is a signed provider JAR file. * * @throws Exception on error */ static void verifyProviderJar(URL codeBase) throws Exception { // Verify the provider JAR file and all // supporting JAR files if there are any. JarVerifier jv = new JarVerifier(codeBase, false); jv.verify(); } private final static Object PROVIDER_VERIFIED = Boolean.TRUE; /* * Verify that the provider JAR files are signed properly, which * means the signer's certificate can be traced back to a * JCE trusted CA. * Return null if ok, failure Exception if verification failed. */ static synchronized Exception getVerificationResult(Provider p) { Object o = verificationResults.get(p); if (o == PROVIDER_VERIFIED) { return null; } else if (o != null) { return (Exception)o; } if (verifyingProviders.get(p) != null) { // this method is static synchronized, must be recursion // return failure now but do not save the result return new NoSuchProviderException("Recursion during verification"); } try { verifyingProviders.put(p, Boolean.FALSE); URL providerURL = getCodeBase(p.getClass()); verifyProviderJar(providerURL); // Verified ok, cache result verificationResults.put(p, PROVIDER_VERIFIED); return null; } catch (Exception e) { verificationResults.put(p, e); return e; } finally { verifyingProviders.remove(p); } } // return whether this provider is properly signed and can be used by JCE static boolean canUseProvider(Provider p) { return getVerificationResult(p) == null; } // dummy object to represent null private static final URL NULL_URL; static { try { NULL_URL = new URL("http://null.oracle.com/"); } catch (Exception e) { throw new RuntimeException(e); } } // reference to a Map we use as a cache for codebases private static final Map, URL> codeBaseCacheRef = new WeakHashMap<>(); /* * Returns the CodeBase for the given class. */ static URL getCodeBase(final Class clazz) { synchronized (codeBaseCacheRef) { URL url = codeBaseCacheRef.get(clazz); if (url == null) { url = AccessController.doPrivileged(new PrivilegedAction() { public URL run() { ProtectionDomain pd = clazz.getProtectionDomain(); if (pd != null) { CodeSource cs = pd.getCodeSource(); if (cs != null) { return cs.getLocation(); } } return NULL_URL; } }); codeBaseCacheRef.put(clazz, url); } return (url == NULL_URL) ? null : url; } } /* * This is called from within an doPrivileged block. * * Following logic is used to decide what policy files are selected. * * If the new Security property (crypto.policy) is set in the * java.security file, or has been set dynamically using the * Security.setProperty() call before the JCE framework has * been initialized, that setting will be used. * Remember - this property is not defined by default. A conscious * user edit or an application call is required. * * Otherwise, if user has policy jar files installed in the legacy * jre/lib/security/ directory, the JDK will honor whatever * setting is set by those policy files. (legacy/current behavior) * * If none of the above 2 conditions are met, the JDK will default * to using the limited crypto policy files found in the * jre/lib/security/policy/limited/ directory */ private static void setupJurisdictionPolicies() throws Exception { // Sanity check the crypto.policy Security property. Single // directory entry, no pseudo-directories (".", "..", leading/trailing // path separators). normalize()/getParent() will help later. String javaHomeProperty = System.getProperty("java.home"); String cryptoPolicyProperty = Security.getProperty("crypto.policy"); Path cpPath = (cryptoPolicyProperty == null) ? null : Paths.get(cryptoPolicyProperty); if ((cpPath != null) && ((cpPath.getNameCount() != 1) || (cpPath.compareTo(cpPath.getFileName())) != 0)) { throw new SecurityException( "Invalid policy directory name format: " + cryptoPolicyProperty); } if (cpPath == null) { // Security property is not set, use default path cpPath = Paths.get(javaHomeProperty, "lib", "security"); } else { // populate with java.home cpPath = Paths.get(javaHomeProperty, "lib", "security", "policy", cryptoPolicyProperty); } if (debug != null) { debug.println("crypto policy directory: " + cpPath); } File exportJar = new File(cpPath.toFile(),"US_export_policy.jar"); File importJar = new File(cpPath.toFile(),"local_policy.jar"); if (cryptoPolicyProperty == null && (!exportJar.exists() || !importJar.exists())) { // Compatibility set up. If crypto.policy is not defined. // check to see if legacy jars exist in lib directory. If // they don't exist, we default to limited policy mode. cpPath = Paths.get( javaHomeProperty, "lib", "security", "policy", "limited"); // point to the new jar files in limited directory exportJar = new File(cpPath.toFile(),"US_export_policy.jar"); importJar = new File(cpPath.toFile(),"local_policy.jar"); } URL jceCipherURL = ClassLoader.getSystemResource ("javax/crypto/Cipher.class"); if ((jceCipherURL == null) || !exportJar.exists() || !importJar.exists()) { throw new SecurityException ("Cannot locate policy or framework files!"); } // Read jurisdiction policies. CryptoPermissions defaultExport = new CryptoPermissions(); CryptoPermissions exemptExport = new CryptoPermissions(); loadPolicies(exportJar, defaultExport, exemptExport); CryptoPermissions defaultImport = new CryptoPermissions(); CryptoPermissions exemptImport = new CryptoPermissions(); loadPolicies(importJar, defaultImport, exemptImport); // Merge the export and import policies for default applications. if (defaultExport.isEmpty() || defaultImport.isEmpty()) { throw new SecurityException("Missing mandatory jurisdiction " + "policy files"); } defaultPolicy = defaultExport.getMinimum(defaultImport); // Merge the export and import policies for exempt applications. if (exemptExport.isEmpty()) { exemptPolicy = exemptImport.isEmpty() ? null : exemptImport; } else { exemptPolicy = exemptExport.getMinimum(exemptImport); } } /** * Load the policies from the specified file. Also checks that the * policies are correctly signed. */ private static void loadPolicies(File jarPathName, CryptoPermissions defaultPolicy, CryptoPermissions exemptPolicy) throws Exception { JarFile jf = new JarFile(jarPathName); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry je = entries.nextElement(); InputStream is = null; try { if (je.getName().startsWith("default_")) { is = jf.getInputStream(je); defaultPolicy.load(is); } else if (je.getName().startsWith("exempt_")) { is = jf.getInputStream(je); exemptPolicy.load(is); } else { continue; } } finally { if (is != null) { is.close(); } } // Enforce the signer restraint, i.e. signer of JCE framework // jar should also be the signer of the two jurisdiction policy // jar files. JarVerifier.verifyPolicySigned(je.getCertificates()); } // Close and nullify the JarFile reference to help GC. jf.close(); jf = null; } static CryptoPermissions getDefaultPolicy() { return defaultPolicy; } static CryptoPermissions getExemptPolicy() { return exemptPolicy; } static boolean isRestricted() { return isRestricted; } }