001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2010 Sun Microsystems, Inc.
015 * Portions copyright 2012-2016 ForgeRock AS.
016 */
017package org.forgerock.opendj.ldap;
018
019import java.io.FileInputStream;
020import java.io.IOException;
021import java.net.Socket;
022import java.security.GeneralSecurityException;
023import java.security.KeyStore;
024import java.security.NoSuchAlgorithmException;
025import java.security.Principal;
026import java.security.PrivateKey;
027import java.security.cert.X509Certificate;
028
029import javax.net.ssl.KeyManager;
030import javax.net.ssl.KeyManagerFactory;
031import javax.net.ssl.SSLEngine;
032import javax.net.ssl.X509ExtendedKeyManager;
033import javax.net.ssl.X509KeyManager;
034
035import org.forgerock.util.Reject;
036
037/** This class contains methods for creating common types of key manager. */
038public final class KeyManagers {
039
040    private static final String KEY_STORE_PROVIDER = "javax.net.ssl.keyStoreProvider";
041    private static final String KEY_STORE_TYPE = "javax.net.ssl.keyStoreType";
042    private static final String KEY_STORE_FILE = "javax.net.ssl.keyStore";
043    private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword";
044    private static volatile X509KeyManager jvmKeyManager;
045
046    /**
047     * This class implements an X.509 key manager that will be used to wrap an
048     * existing key manager and makes it possible to configure which
049     * certificate(s) should be used for client and/or server operations. The
050     * certificate selection will be based on the alias (also called the
051     * nickname) of the certificate.
052     */
053    private static final class SelectCertificate extends X509ExtendedKeyManager {
054        private final String alias;
055        private final X509KeyManager keyManager;
056
057        private SelectCertificate(final X509KeyManager keyManager, final String alias) {
058            this.keyManager = keyManager;
059            this.alias = alias;
060        }
061
062        @Override
063        public String chooseClientAlias(final String[] keyType, final Principal[] issuers,
064                final Socket socket) {
065            for (final String type : keyType) {
066                final String[] clientAliases = keyManager.getClientAliases(type, issuers);
067                if (clientAliases != null) {
068                    for (final String clientAlias : clientAliases) {
069                        if (clientAlias.equals(alias)) {
070                            return alias;
071                        }
072                    }
073                }
074            }
075
076            return null;
077        }
078
079        @Override
080        public String chooseEngineClientAlias(final String[] keyType, final Principal[] issuers,
081                final SSLEngine engine) {
082            for (final String type : keyType) {
083                final String[] clientAliases = keyManager.getClientAliases(type, issuers);
084                if (clientAliases != null) {
085                    for (final String clientAlias : clientAliases) {
086                        if (clientAlias.equals(alias)) {
087                            return alias;
088                        }
089                    }
090                }
091            }
092
093            return null;
094        }
095
096        @Override
097        public String chooseEngineServerAlias(final String keyType, final Principal[] issuers,
098                final SSLEngine engine) {
099            final String[] serverAliases = keyManager.getServerAliases(keyType, issuers);
100            if (serverAliases != null) {
101                for (final String serverAlias : serverAliases) {
102                    if (serverAlias.equalsIgnoreCase(alias)) {
103                        return serverAlias;
104                    }
105                }
106            }
107
108            return null;
109        }
110
111        @Override
112        public String chooseServerAlias(final String keyType, final Principal[] issuers,
113                final Socket socket) {
114            final String[] serverAliases = keyManager.getServerAliases(keyType, issuers);
115            if (serverAliases != null) {
116                for (final String serverAlias : serverAliases) {
117                    if (serverAlias.equals(alias)) {
118                        return alias;
119                    }
120                }
121            }
122
123            return null;
124        }
125
126        @Override
127        public X509Certificate[] getCertificateChain(final String alias) {
128            return keyManager.getCertificateChain(alias);
129        }
130
131        @Override
132        public String[] getClientAliases(final String keyType, final Principal[] issuers) {
133            return keyManager.getClientAliases(keyType, issuers);
134        }
135
136        @Override
137        public PrivateKey getPrivateKey(final String alias) {
138            return keyManager.getPrivateKey(alias);
139        }
140
141        @Override
142        public String[] getServerAliases(final String keyType, final Principal[] issuers) {
143            return keyManager.getServerAliases(keyType, issuers);
144        }
145    }
146
147    /**
148     * Creates a new {@code X509KeyManager} which will use the named key store
149     * file for retrieving certificates. It will use the default key store
150     * format for the JVM (e.g. {@code JKS}) and will not use a password to open
151     * the key store.
152     *
153     * @param file
154     *            The key store file name.
155     * @return A new {@code X509KeyManager} which will use the named key store
156     *         file for retrieving certificates.
157     * @throws GeneralSecurityException
158     *             If the key store could not be loaded, perhaps due to
159     *             incorrect format, or missing algorithms.
160     * @throws IOException
161     *             If the key store file could not be found or could not be
162     *             read.
163     * @throws NullPointerException
164     *             If {@code file} was {@code null}.
165     */
166    public static X509KeyManager useKeyStoreFile(final String file)
167            throws GeneralSecurityException, IOException {
168        return useKeyStoreFile(file, null, null);
169    }
170
171    /**
172     * Creates a new {@code X509KeyManager} which will use the named key store
173     * file for retrieving certificates. It will use the provided key store
174     * format and password.
175     *
176     * @param file
177     *            The key store file name.
178     * @param password
179     *            The key store password, which may be {@code null}.
180     * @param format
181     *            The key store format, which may be {@code null} to indicate
182     *            that the default key store format for the JVM (e.g.
183     *            {@code JKS}) should be used.
184     * @return A new {@code X509KeyManager} which will use the named key store
185     *         file for retrieving certificates.
186     * @throws GeneralSecurityException
187     *             If the key store could not be loaded, perhaps due to
188     *             incorrect format, or missing algorithms.
189     * @throws IOException
190     *             If the key store file could not be found or could not be
191     *             read.
192     * @throws NullPointerException
193     *             If {@code file} was {@code null}.
194     */
195    public static X509KeyManager useKeyStoreFile(final String file, final char[] password,
196            final String format) throws GeneralSecurityException, IOException {
197        return useKeyStoreFile(file, password, format, null);
198    }
199
200    /**
201     * Creates a new {@code X509KeyManager} which will use the named key store
202     * file for retrieving certificates. It will use the provided key store
203     * format and password.
204     *
205     * @param keyStoreFile
206     *            The key store file name.
207     * @param password
208     *            The key store password, which may be {@code null}.
209     * @param format
210     *            The key store format, which may be {@code null} to indicate that the default key store format for the
211     *            JVM (e.g. {@code JKS}) should be used.
212     * @param provider
213     *            The key store provider, which may be {@code null} to indicate that the default key store provider for
214     *            the JVM should be used.
215     * @return A new {@code X509KeyManager} which will use the named key store file for retrieving certificates.
216     * @throws GeneralSecurityException
217     *            If the key store could not be loaded, perhaps due to incorrect format, or missing algorithms.
218     * @throws IOException
219     *            If the key store file could not be found or could not be read.
220     * @throws NullPointerException
221     *            If {@code file} was {@code null}.
222     */
223    public static X509KeyManager useKeyStoreFile(final String keyStoreFile, final char[] password,
224            final String format, String provider) throws GeneralSecurityException, IOException {
225        Reject.ifNull(keyStoreFile);
226
227        final String keyStoreFormat = format != null ? format : KeyStore.getDefaultType();
228        final KeyStore keyStore = provider != null
229            ? KeyStore.getInstance(keyStoreFormat, provider)
230            : KeyStore.getInstance(keyStoreFormat);
231
232        try (FileInputStream fis = new FileInputStream(keyStoreFile)) {
233            keyStore.load(fis, password);
234        }
235
236        return getX509KeyManager(keyStore, password);
237    }
238
239    /**
240     * Creates a new {@code X509KeyManager} which will use a PKCS#11 token for
241     * retrieving certificates.
242     *
243     * @param password
244     *            The password to use for accessing the PKCS#11 token, which may
245     *            be {@code null} if no password is required.
246     * @return A new {@code X509KeyManager} which will use a PKCS#11 token for
247     *         retrieving certificates.
248     * @throws GeneralSecurityException
249     *             If the PKCS#11 token could not be accessed, perhaps due to
250     *             incorrect password, or missing algorithms.
251     * @throws IOException
252     *             If the PKCS#11 token could not be found or could not be read.
253     */
254    public static X509KeyManager usePKCS11Token(final char[] password)
255            throws GeneralSecurityException, IOException {
256        final KeyStore keyStore = KeyStore.getInstance("PKCS11");
257        keyStore.load(null, password);
258        return getX509KeyManager(keyStore, password);
259    }
260
261    private static X509KeyManager getX509KeyManager(final KeyStore keyStore, final char[] password)
262            throws GeneralSecurityException {
263        final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
264        kmf.init(keyStore, password);
265
266        for (final KeyManager km : kmf.getKeyManagers()) {
267            if (km instanceof X509KeyManager) {
268                return (X509KeyManager) km;
269            }
270        }
271        throw new NoSuchAlgorithmException();
272    }
273
274    /**
275     * Creates a new {@code X509KeyManager} which will use the JVM's default keystore for retrieving certificates.
276     *
277     * @return A new {@code X509KeyManager} which will use the JVM's default keystore for retrieving certificates or
278     *             {@code null} if the necessary JVM settings are missing.
279     * @throws GeneralSecurityException
280     *             If the key store could not be loaded, perhaps due to incorrect format, or missing algorithms.
281     * @throws IOException
282     *             If the key store file could not be found or could not be read.
283     */
284    public static X509KeyManager useJvmDefaultKeyStore() throws GeneralSecurityException, IOException {
285        if (jvmKeyManager == null) {
286            final String keyStoreFile = System.getProperty(KEY_STORE_FILE);
287            if (keyStoreFile != null) {
288                synchronized (KeyManagers.class) {
289                    if (jvmKeyManager == null) {
290                        final String keyStoreProvider = System.getProperty(KEY_STORE_PROVIDER);
291                        final String keyStoreType = System.getProperty(KEY_STORE_TYPE, KeyStore.getDefaultType());
292                        final String keyStorePassword = System.getProperty(KEY_STORE_PASSWORD);
293
294                        jvmKeyManager = useKeyStoreFile(keyStoreFile,
295                                keyStorePassword != null ? keyStorePassword.toCharArray() : null,
296                                keyStoreType, keyStoreProvider);
297                    }
298                }
299            }
300        }
301
302        return jvmKeyManager;
303    }
304
305    /**
306     * Returns a new {@code X509KeyManager} which selects the named certificate
307     * from the provided {@code X509KeyManager}.
308     *
309     * @param alias
310     *            The nickname of the certificate that should be selected for
311     *            operations involving this key manager.
312     * @param keyManager
313     *            The key manager to be filtered.
314     * @return The filtered key manager.
315     * @throws NullPointerException
316     *             If {@code keyManager} or {@code alias} was {@code null}.
317     */
318    public static X509KeyManager useSingleCertificate(final String alias,
319            final X509KeyManager keyManager) {
320        Reject.ifNull(alias, keyManager);
321        return new SelectCertificate(keyManager, alias);
322    }
323
324    /** Prevent instantiation. */
325    private KeyManagers() {
326        // Nothing to do.
327    }
328}