1 /*
2  * Copyright (c) 2018, 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.
TrustStoreManager()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      */
getTrustedCerts()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      */
getTrustedKeyStore()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 
TrustStoreDescriptor(String storeName, String storeType, String storeProvider, String storePassword, File storeFile, long lastModified)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")
createInstance()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
equals(Object obj)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
hashCode()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 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 }
394