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
26package sun.security.ssl;
27
28import java.lang.ref.WeakReference;
29import java.io.*;
30import java.util.*;
31
32import java.security.*;
33import java.security.cert.*;
34import java.security.cert.Certificate;
35
36import sun.security.action.*;
37import sun.security.validator.TrustStoreUtil;
38
39/**
40 * Collection of static utility methods to manage the default trusted KeyStores
41 * effectively.
42 */
43final 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 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}
396