1 /*
   2  * Copyright (c) 2018, 2019, 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.ssl;
  27 
  28 import java.io.*;
  29 import java.lang.ref.WeakReference;
  30 import java.security.*;
  31 import java.security.cert.*;
  32 import java.util.*;
  33 import sun.security.action.*;
  34 import sun.security.validator.TrustStoreUtil;
  35 
  36 /**
  37  * Collection of static utility methods to manage the default trusted KeyStores
  38  * effectively.
  39  */
  40 final class TrustStoreManager {
  41 
  42     // A singleton service to manage the default trusted KeyStores effectively.
  43     private static final TrustAnchorManager tam = new TrustAnchorManager();
  44 
  45     // Restrict instantiation of this class.
  46     private TrustStoreManager() {
  47         // empty
  48     }
  49 
  50     /**
  51      * Return an unmodifiable set of all trusted X509Certificates contained
  52      * in the default trusted KeyStore.
  53      */
  54     public static Set<X509Certificate> getTrustedCerts() throws Exception {
  55         return tam.getTrustedCerts(TrustStoreDescriptor.createInstance());
  56     }
  57 
  58     /**
  59      * Return an instance of the default trusted KeyStore.
  60      */
  61     public static KeyStore getTrustedKeyStore() throws Exception {
  62         return tam.getKeyStore(TrustStoreDescriptor.createInstance());
  63     }
  64 
  65     /**
  66      * A descriptor of the default trusted KeyStore.
  67      *
  68      * The preference of the default trusted KeyStore is:
  69      *    javax.net.ssl.trustStore
  70      *    jssecacerts
  71      *    cacerts
  72      */
  73     private static final class TrustStoreDescriptor {
  74         private static final String fileSep = File.separator;
  75         private static final String defaultStorePath =
  76                 GetPropertyAction.privilegedGetProperty("java.home") +
  77                 fileSep + "lib" + fileSep + "security";
  78         private static final String defaultStore =
  79                 defaultStorePath + fileSep + "cacerts";
  80         private static final String jsseDefaultStore =
  81                 defaultStorePath + fileSep + "jssecacerts";
  82 
  83         // the trust store name
  84         private final String storeName;
  85 
  86         // the trust store type, JKS/PKCS12
  87         private final String storeType;
  88 
  89         // the provider of the trust store
  90         private final String storeProvider;
  91 
  92         // the password used for the trust store
  93         private final String storePassword;
  94 
  95         // the File object of the trust store
  96         private final File storeFile;
  97 
  98         // the last modified time of the store
  99         private final long lastModified;
 100 
 101         private TrustStoreDescriptor(String storeName, String storeType,
 102                 String storeProvider, String storePassword,
 103                 File storeFile, long lastModified) {
 104             this.storeName = storeName;
 105             this.storeType = storeType;
 106             this.storeProvider = storeProvider;
 107             this.storePassword = storePassword;
 108             this.storeFile = storeFile;
 109             this.lastModified = lastModified;
 110 
 111             if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) {
 112                 SSLLogger.fine(
 113                     "trustStore is: " + storeName + "\n" +
 114                     "trustStore type is: " + storeType + "\n" +
 115                     "trustStore provider is: " + storeProvider + "\n" +
 116                     "the last modified time is: " + (new Date(lastModified)));
 117             }
 118         }
 119 
 120         /**
 121          * Create an instance of TrustStoreDescriptor for the default
 122          * trusted KeyStore.
 123          */
 124         @SuppressWarnings("Convert2Lambda")
 125         static TrustStoreDescriptor createInstance() {
 126              return AccessController.doPrivileged(
 127                     new PrivilegedAction<TrustStoreDescriptor>() {
 128 
 129                 @Override
 130                 public TrustStoreDescriptor run() {
 131                     // Get the system properties for trust store.
 132                     String storePropName = System.getProperty(
 133                             "javax.net.ssl.trustStore", jsseDefaultStore);
 134                     String storePropType = System.getProperty(
 135                             "javax.net.ssl.trustStoreType",
 136                             KeyStore.getDefaultType());
 137                     String storePropProvider = System.getProperty(
 138                             "javax.net.ssl.trustStoreProvider", "");
 139                     String storePropPassword = System.getProperty(
 140                             "javax.net.ssl.trustStorePassword", "");
 141 
 142                     String temporaryName = "";
 143                     File temporaryFile = null;
 144                     long temporaryTime = 0L;
 145                     if (!"NONE".equals(storePropName)) {
 146                         String[] fileNames =
 147                                 new String[] {storePropName, defaultStore};
 148                         for (String fileName : fileNames) {
 149                             File f = new File(fileName);
 150                             if (f.isFile() && f.canRead()) {
 151                                 temporaryName = fileName;;
 152                                 temporaryFile = f;
 153                                 temporaryTime = f.lastModified();
 154 
 155                                 break;
 156                             }
 157 
 158                             // Not break, the file is inaccessible.
 159                             if (SSLLogger.isOn &&
 160                                     SSLLogger.isOn("trustmanager")) {
 161                                 SSLLogger.fine(
 162                                         "Inaccessible trust store: " +
 163                                         storePropName);
 164                             }
 165                         }
 166                     } else {
 167                         temporaryName = storePropName;
 168                     }
 169 
 170                     return new TrustStoreDescriptor(
 171                             temporaryName, storePropType, storePropProvider,
 172                             storePropPassword, temporaryFile, temporaryTime);
 173                 }
 174             });
 175         }
 176 
 177         @Override
 178         public boolean equals(Object obj) {
 179             if (obj == this) {
 180                 return true;
 181             }
 182 
 183             if (obj instanceof TrustStoreDescriptor) {
 184                 TrustStoreDescriptor that = (TrustStoreDescriptor)obj;
 185                 return ((this.lastModified == that.lastModified) &&
 186                     Objects.equals(this.storeName, that.storeName) &&
 187                     Objects.equals(this.storeType, that.storeType) &&
 188                     Objects.equals(this.storeProvider, that.storeProvider));
 189             }
 190 
 191             return false;
 192         }
 193 
 194 
 195         // Please be careful if computing security-sensitive attributes'
 196         // hash code.  For example the storePassword should not be computed.
 197         @Override
 198         public int hashCode() {
 199             int result = 17;
 200 
 201             if (storeName != null && !storeName.isEmpty()) {
 202                 result = 31 * result + storeName.hashCode();
 203             }
 204 
 205             if (storeType != null && !storeType.isEmpty()) {
 206                 result = 31 * result + storeType.hashCode();
 207             }
 208 
 209             if (storeProvider != null && !storeProvider.isEmpty()) {
 210                 result = 31 * result + storeProvider.hashCode();
 211             }
 212 
 213             if (storeFile != null) {
 214                 result = 31 * result + storeFile.hashCode();
 215             }
 216 
 217             if (lastModified != 0L) {
 218                 result = (int)(31 * result + lastModified);
 219             }
 220 
 221             return result;
 222         }
 223     }
 224 
 225     /**
 226      * The trust anchors manager used to expedite the performance.
 227      *
 228      * This class can be used to provide singleton services to access default
 229      * trust KeyStore more effectively.
 230      */
 231     private static final class TrustAnchorManager {
 232         // Last trust store descriptor.
 233         private TrustStoreDescriptor descriptor;
 234 
 235         // The key store used for the trust anchors.
 236         //
 237         // Use weak reference so that the heavy loaded KeyStore object can
 238         // be atomically cleared, and reloaded if needed.
 239         private WeakReference<KeyStore> ksRef;
 240 
 241         // The trusted X.509 certificates in the key store.
 242         //
 243         // Use weak reference so that the heavy loaded certificates collection
 244         // objects can be atomically cleared, and reloaded if needed.
 245         private WeakReference<Set<X509Certificate>> csRef;
 246 
 247         private TrustAnchorManager() {
 248             this.descriptor = null;
 249             this.ksRef = new WeakReference<>(null);
 250             this.csRef = new WeakReference<>(null);
 251         }
 252 
 253         /**
 254          * Get the default trusted KeyStore with the specified descriptor.
 255          *
 256          * @return null if the underlying KeyStore is not available.
 257          */
 258         synchronized KeyStore getKeyStore(
 259                 TrustStoreDescriptor descriptor) throws Exception {
 260 
 261             TrustStoreDescriptor temporaryDesc = this.descriptor;
 262             KeyStore ks = ksRef.get();
 263             if ((ks != null) && descriptor.equals(temporaryDesc)) {
 264                 return ks;
 265             }
 266 
 267             // Reload a new key store.
 268             if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) {
 269                 SSLLogger.fine("Reload the trust store");
 270             }
 271 
 272             ks = loadKeyStore(descriptor);
 273             this.descriptor = descriptor;
 274             this.ksRef = new WeakReference<>(ks);
 275 
 276             return ks;
 277         }
 278 
 279         /**
 280          * Get trusted certificates in the default trusted KeyStore with
 281          * the specified descriptor.
 282          *
 283          * @return empty collection if the underlying KeyStore is not available.
 284          */
 285         synchronized Set<X509Certificate> getTrustedCerts(
 286                 TrustStoreDescriptor descriptor) throws Exception {
 287 
 288             KeyStore ks = null;
 289             TrustStoreDescriptor temporaryDesc = this.descriptor;
 290             Set<X509Certificate> certs = csRef.get();
 291             if (certs != null) {
 292                 if (descriptor.equals(temporaryDesc)) {
 293                     return certs;
 294                 } else {
 295                     // Use the new descriptor.
 296                     this.descriptor = descriptor;
 297                 }
 298             } else {
 299                 // Try to use the cached store at first.
 300                 if (descriptor.equals(temporaryDesc)) {
 301                     ks = ksRef.get();
 302                 } else {
 303                     // Use the new descriptor.
 304                     this.descriptor = descriptor;
 305                 }
 306             }
 307 
 308             // Reload the trust store if needed.
 309             if (ks == null) {
 310                 if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) {
 311                     SSLLogger.fine("Reload the trust store");
 312                 }
 313                 ks = loadKeyStore(descriptor);
 314             }
 315 
 316             // Reload trust certs from the key store.
 317             if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) {
 318                 SSLLogger.fine("Reload trust certs");
 319             }
 320 
 321             certs = loadTrustedCerts(ks);
 322             if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) {
 323                 SSLLogger.fine("Reloaded " + certs.size() + " trust certs");
 324             }
 325 
 326             // Note that as ks is a local variable, it is not
 327             // necessary to add it to the ksRef weak reference.
 328             this.csRef = new WeakReference<>(certs);
 329 
 330             return certs;
 331         }
 332 
 333         /**
 334          * Load the KeyStore as described in the specified descriptor.
 335          */
 336         private static KeyStore loadKeyStore(
 337                 TrustStoreDescriptor descriptor) throws Exception {
 338             if (!"NONE".equals(descriptor.storeName) &&
 339                     descriptor.storeFile == null) {
 340 
 341                 // No file available, no KeyStore available.
 342                 if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) {
 343                     SSLLogger.fine("No available key store");
 344                 }
 345 
 346                 return null;
 347             }
 348 
 349             KeyStore ks;
 350             if (descriptor.storeProvider.isEmpty()) {
 351                 ks = KeyStore.getInstance(descriptor.storeType);
 352             } else {
 353                 ks = KeyStore.getInstance(
 354                         descriptor.storeType, descriptor.storeProvider);
 355             }
 356 
 357             char[] password = null;
 358             if (!descriptor.storePassword.isEmpty()) {
 359                 password = descriptor.storePassword.toCharArray();
 360             }
 361 
 362             if (!"NONE".equals(descriptor.storeName)) {
 363                 try (FileInputStream fis = AccessController.doPrivileged(
 364                         new OpenFileInputStreamAction(descriptor.storeFile))) {
 365                     ks.load(fis, password);
 366                 } catch (FileNotFoundException fnfe) {
 367                     // No file available, no KeyStore available.
 368                     if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) {
 369                         SSLLogger.fine(
 370                             "Not available key store: " + descriptor.storeName);
 371                     }
 372 
 373                     return null;
 374                 }
 375             } else {
 376                 ks.load(null, password);
 377             }
 378 
 379             return ks;
 380         }
 381 
 382         /**
 383          * Load trusted certificates from the specified KeyStore.
 384          */
 385         private static Set<X509Certificate> loadTrustedCerts(KeyStore ks) {
 386             if (ks == null) {
 387                 return Collections.<X509Certificate>emptySet();
 388             }
 389 
 390             return TrustStoreUtil.getTrustedCerts(ks);
 391         }
 392     }
 393 }