1 /*
2  * Copyright (c) 2014, 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.util;
27 
28 import java.io.*;
29 import java.security.*;
30 import java.security.cert.Certificate;
31 import java.security.cert.CertificateFactory;
32 import java.security.cert.CertificateException;
33 import java.util.*;
34 
35 import sun.security.util.Debug;
36 
37 /**
38  * This class delegates to a primary or secondary keystore implementation.
39  *
40  * @since 9
41  */
42 
43 public class KeyStoreDelegator extends KeyStoreSpi {
44 
45     private static final String KEYSTORE_TYPE_COMPAT = "keystore.type.compat";
46     private static final Debug debug = Debug.getInstance("keystore");
47 
48     private String primaryType;   // the primary keystore's type
49     private String secondaryType; // the secondary keystore's type
50     private Class<? extends KeyStoreSpi> primaryKeyStore;
51                                   // the primary keystore's class
52     private Class<? extends KeyStoreSpi> secondaryKeyStore;
53                                   // the secondary keystore's class
54     private String type; // the delegate's type
55     private KeyStoreSpi keystore; // the delegate
56     private boolean compatModeEnabled = true;
57 
KeyStoreDelegator( String primaryType, Class<? extends KeyStoreSpi> primaryKeyStore, String secondaryType, Class<? extends KeyStoreSpi> secondaryKeyStore)58     public KeyStoreDelegator(
59         String primaryType,
60         Class<? extends KeyStoreSpi> primaryKeyStore,
61         String secondaryType,
62         Class<? extends KeyStoreSpi> secondaryKeyStore) {
63 
64         // Check whether compatibility mode has been disabled
65         compatModeEnabled = "true".equalsIgnoreCase(
66             AccessController.doPrivileged((PrivilegedAction<String>) () ->
67                 Security.getProperty(KEYSTORE_TYPE_COMPAT)));
68 
69         if (compatModeEnabled) {
70             this.primaryType = primaryType;
71             this.secondaryType = secondaryType;
72             this.primaryKeyStore = primaryKeyStore;
73             this.secondaryKeyStore = secondaryKeyStore;
74         } else {
75             this.primaryType = primaryType;
76             this.secondaryType = null;
77             this.primaryKeyStore = primaryKeyStore;
78             this.secondaryKeyStore = null;
79 
80             if (debug != null) {
81                 debug.println("WARNING: compatibility mode disabled for " +
82                     primaryType + " and " + secondaryType + " keystore types");
83             }
84         }
85     }
86 
87     @Override
engineGetKey(String alias, char[] password)88     public Key engineGetKey(String alias, char[] password)
89         throws NoSuchAlgorithmException, UnrecoverableKeyException {
90         return keystore.engineGetKey(alias, password);
91     }
92 
93     @Override
engineGetCertificateChain(String alias)94     public Certificate[] engineGetCertificateChain(String alias) {
95         return keystore.engineGetCertificateChain(alias);
96     }
97 
98     @Override
engineGetCertificate(String alias)99     public Certificate engineGetCertificate(String alias) {
100         return keystore.engineGetCertificate(alias);
101     }
102 
103     @Override
engineGetCreationDate(String alias)104     public Date engineGetCreationDate(String alias) {
105         return keystore.engineGetCreationDate(alias);
106     }
107 
108     @Override
engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)109     public void engineSetKeyEntry(String alias, Key key, char[] password,
110         Certificate[] chain) throws KeyStoreException {
111         keystore.engineSetKeyEntry(alias, key, password, chain);
112     }
113 
114     @Override
engineSetKeyEntry(String alias, byte[] key, Certificate[] chain)115     public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain)
116         throws KeyStoreException {
117         keystore.engineSetKeyEntry(alias, key, chain);
118     }
119 
120     @Override
engineSetCertificateEntry(String alias, Certificate cert)121     public void engineSetCertificateEntry(String alias, Certificate cert)
122         throws KeyStoreException {
123         keystore.engineSetCertificateEntry(alias, cert);
124     }
125 
126     @Override
engineDeleteEntry(String alias)127     public void engineDeleteEntry(String alias) throws KeyStoreException {
128         keystore.engineDeleteEntry(alias);
129     }
130 
131     @Override
engineAliases()132     public Enumeration<String> engineAliases() {
133         return keystore.engineAliases();
134     }
135 
136     @Override
engineContainsAlias(String alias)137     public boolean engineContainsAlias(String alias) {
138         return keystore.engineContainsAlias(alias);
139     }
140 
141     @Override
engineSize()142     public int engineSize() {
143         return keystore.engineSize();
144     }
145 
146     @Override
engineIsKeyEntry(String alias)147     public boolean engineIsKeyEntry(String alias) {
148         return keystore.engineIsKeyEntry(alias);
149     }
150 
151     @Override
engineIsCertificateEntry(String alias)152     public boolean engineIsCertificateEntry(String alias) {
153         return keystore.engineIsCertificateEntry(alias);
154     }
155 
156     @Override
engineGetCertificateAlias(Certificate cert)157     public String engineGetCertificateAlias(Certificate cert) {
158         return keystore.engineGetCertificateAlias(cert);
159     }
160 
161     @Override
engineGetEntry(String alias, KeyStore.ProtectionParameter protParam)162     public KeyStore.Entry engineGetEntry(String alias,
163         KeyStore.ProtectionParameter protParam)
164             throws KeyStoreException, NoSuchAlgorithmException,
165                 UnrecoverableEntryException {
166         return keystore.engineGetEntry(alias, protParam);
167     }
168 
169     @Override
engineSetEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)170     public void engineSetEntry(String alias, KeyStore.Entry entry,
171         KeyStore.ProtectionParameter protParam)
172             throws KeyStoreException {
173         keystore.engineSetEntry(alias, entry, protParam);
174     }
175 
176     @Override
engineEntryInstanceOf(String alias, Class<? extends KeyStore.Entry> entryClass)177     public boolean engineEntryInstanceOf(String alias,
178         Class<? extends KeyStore.Entry> entryClass) {
179         return keystore.engineEntryInstanceOf(alias, entryClass);
180     }
181 
182     @Override
engineStore(OutputStream stream, char[] password)183     public void engineStore(OutputStream stream, char[] password)
184         throws IOException, NoSuchAlgorithmException, CertificateException {
185 
186         if (debug != null) {
187             debug.println("Storing keystore in " + type + " format");
188         }
189         keystore.engineStore(stream, password);
190     }
191 
192     @Override
engineLoad(InputStream stream, char[] password)193     public void engineLoad(InputStream stream, char[] password)
194         throws IOException, NoSuchAlgorithmException, CertificateException {
195 
196         // A new keystore is always created in the primary keystore format
197         if (stream == null) {
198             try {
199                 @SuppressWarnings("deprecation")
200                 KeyStoreSpi tmp = primaryKeyStore.newInstance();
201                 keystore = tmp;
202             } catch (InstantiationException | IllegalAccessException e) {
203                 // can safely ignore
204             }
205             type = primaryType;
206 
207             if (debug != null) {
208                 debug.println("Creating a new keystore in " + type + " format");
209             }
210             keystore.engineLoad(stream, password);
211 
212         } else {
213             // First try the primary keystore then try the secondary keystore
214             InputStream bufferedStream = new BufferedInputStream(stream);
215             bufferedStream.mark(Integer.MAX_VALUE);
216 
217             try {
218                 @SuppressWarnings("deprecation")
219                 KeyStoreSpi tmp = primaryKeyStore.newInstance();
220                 tmp.engineLoad(bufferedStream, password);
221                 keystore = tmp;
222                 type = primaryType;
223 
224             } catch (Exception e) {
225 
226                 // incorrect password
227                 if (e instanceof IOException &&
228                     e.getCause() instanceof UnrecoverableKeyException) {
229                     throw (IOException)e;
230                 }
231 
232                 try {
233                     // Ignore secondary keystore when no compatibility mode
234                     if (!compatModeEnabled) {
235                         throw e;
236                     }
237 
238                     @SuppressWarnings("deprecation")
239                     KeyStoreSpi tmp = secondaryKeyStore.newInstance();
240                     bufferedStream.reset();
241                     tmp.engineLoad(bufferedStream, password);
242                     keystore = tmp;
243                     type = secondaryType;
244 
245                     if (debug != null) {
246                         debug.println("WARNING: switching from " +
247                           primaryType + " to " + secondaryType +
248                           " keystore file format has altered the " +
249                           "keystore security level");
250                     }
251 
252                 } catch (InstantiationException |
253                     IllegalAccessException e2) {
254                     // can safely ignore
255 
256                 } catch (IOException |
257                     NoSuchAlgorithmException |
258                     CertificateException e3) {
259 
260                     // incorrect password
261                     if (e3 instanceof IOException &&
262                         e3.getCause() instanceof UnrecoverableKeyException) {
263                         throw (IOException)e3;
264                     }
265                     // rethrow the outer exception
266                     if (e instanceof IOException) {
267                         throw (IOException)e;
268                     } else if (e instanceof CertificateException) {
269                         throw (CertificateException)e;
270                     } else if (e instanceof NoSuchAlgorithmException) {
271                         throw (NoSuchAlgorithmException)e;
272                     } else if (e instanceof RuntimeException){
273                         throw (RuntimeException)e;
274                     }
275                 }
276             }
277 
278             if (debug != null) {
279                 debug.println("Loaded a keystore in " + type + " format");
280             }
281         }
282     }
283 
284     /**
285      * Probe the first few bytes of the keystore data stream for a valid
286      * keystore encoding. Only the primary keystore implementation is probed.
287      */
288     @Override
engineProbe(InputStream stream)289     public boolean engineProbe(InputStream stream) throws IOException {
290 
291         boolean result = false;
292 
293         try {
294             @SuppressWarnings("deprecation")
295             KeyStoreSpi tmp = primaryKeyStore.newInstance();
296             keystore = tmp;
297             type = primaryType;
298             result = keystore.engineProbe(stream);
299 
300         } catch (Exception e) {
301             throw new IOException(e);
302 
303         } finally {
304             // reset
305             if (result == false) {
306                 type = null;
307                 keystore = null;
308             }
309         }
310 
311         return result;
312     }
313 }
314