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
26package sun.security.util;
27
28import java.io.*;
29import java.security.*;
30import java.security.cert.Certificate;
31import java.security.cert.CertificateFactory;
32import java.security.cert.CertificateException;
33import java.util.*;
34
35import sun.security.util.Debug;
36
37/**
38 * This class delegates to a primary or secondary keystore implementation.
39 *
40 * @since 9
41 */
42
43public 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
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
88    public Key engineGetKey(String alias, char[] password)
89        throws NoSuchAlgorithmException, UnrecoverableKeyException {
90        return keystore.engineGetKey(alias, password);
91    }
92
93    @Override
94    public Certificate[] engineGetCertificateChain(String alias) {
95        return keystore.engineGetCertificateChain(alias);
96    }
97
98    @Override
99    public Certificate engineGetCertificate(String alias) {
100        return keystore.engineGetCertificate(alias);
101    }
102
103    @Override
104    public Date engineGetCreationDate(String alias) {
105        return keystore.engineGetCreationDate(alias);
106    }
107
108    @Override
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
115    public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain)
116        throws KeyStoreException {
117        keystore.engineSetKeyEntry(alias, key, chain);
118    }
119
120    @Override
121    public void engineSetCertificateEntry(String alias, Certificate cert)
122        throws KeyStoreException {
123        keystore.engineSetCertificateEntry(alias, cert);
124    }
125
126    @Override
127    public void engineDeleteEntry(String alias) throws KeyStoreException {
128        keystore.engineDeleteEntry(alias);
129    }
130
131    @Override
132    public Enumeration<String> engineAliases() {
133        return keystore.engineAliases();
134    }
135
136    @Override
137    public boolean engineContainsAlias(String alias) {
138        return keystore.engineContainsAlias(alias);
139    }
140
141    @Override
142    public int engineSize() {
143        return keystore.engineSize();
144    }
145
146    @Override
147    public boolean engineIsKeyEntry(String alias) {
148        return keystore.engineIsKeyEntry(alias);
149    }
150
151    @Override
152    public boolean engineIsCertificateEntry(String alias) {
153        return keystore.engineIsCertificateEntry(alias);
154    }
155
156    @Override
157    public String engineGetCertificateAlias(Certificate cert) {
158        return keystore.engineGetCertificateAlias(cert);
159    }
160
161    @Override
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
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
177    public boolean engineEntryInstanceOf(String alias,
178        Class<? extends KeyStore.Entry> entryClass) {
179        return keystore.engineEntryInstanceOf(alias, entryClass);
180    }
181
182    @Override
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
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                keystore = tmp;
221                type = primaryType;
222                keystore.engineLoad(bufferedStream, password);
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                    keystore = tmp;
241                    type = secondaryType;
242                    bufferedStream.reset();
243                    keystore.engineLoad(bufferedStream, password);
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                    }
273                }
274            }
275
276            if (debug != null) {
277                debug.println("Loaded a keystore in " + type + " format");
278            }
279        }
280    }
281
282    /**
283     * Probe the first few bytes of the keystore data stream for a valid
284     * keystore encoding. Only the primary keystore implementation is probed.
285     */
286    @Override
287    public boolean engineProbe(InputStream stream) throws IOException {
288
289        boolean result = false;
290
291        try {
292            @SuppressWarnings("deprecation")
293            KeyStoreSpi tmp = primaryKeyStore.newInstance();
294            keystore = tmp;
295            type = primaryType;
296            result = keystore.engineProbe(stream);
297
298        } catch (Exception e) {
299            throw new IOException(e);
300
301        } finally {
302            // reset
303            if (result == false) {
304                type = null;
305                keystore = null;
306            }
307        }
308
309        return result;
310    }
311}
312