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