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