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