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-2014 ForgeRock AS.
026 */
027
028package org.forgerock.opendj.ldap;
029
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.IOException;
033import java.security.GeneralSecurityException;
034import java.security.KeyStore;
035import java.security.NoSuchAlgorithmException;
036import java.security.cert.CertificateException;
037import java.security.cert.CertificateExpiredException;
038import java.security.cert.CertificateNotYetValidException;
039import java.security.cert.X509Certificate;
040import java.util.Date;
041import java.util.logging.Level;
042import java.util.logging.Logger;
043
044import javax.net.ssl.TrustManager;
045import javax.net.ssl.TrustManagerFactory;
046import javax.net.ssl.X509TrustManager;
047
048import org.forgerock.opendj.ldap.schema.Schema;
049import org.forgerock.util.Reject;
050
051/**
052 * This class contains methods for creating common types of trust manager.
053 */
054public final class TrustManagers {
055
056    /**
057     * An X509TrustManager which rejects certificate chains whose subject DN
058     * does not match a specified host name.
059     */
060    private static final class CheckHostName implements X509TrustManager {
061
062        private final X509TrustManager trustManager;
063
064        private final String hostNamePattern;
065
066        private CheckHostName(final X509TrustManager trustManager, final String hostNamePattern) {
067            this.trustManager = trustManager;
068            this.hostNamePattern = hostNamePattern;
069        }
070
071        /** {@inheritDoc} */
072        public void checkClientTrusted(final X509Certificate[] chain, final String authType)
073                throws CertificateException {
074            verifyHostName(chain);
075            trustManager.checkClientTrusted(chain, authType);
076        }
077
078        /** {@inheritDoc} */
079        public void checkServerTrusted(final X509Certificate[] chain, final String authType)
080                throws CertificateException {
081            verifyHostName(chain);
082            trustManager.checkServerTrusted(chain, authType);
083        }
084
085        /** {@inheritDoc} */
086        public X509Certificate[] getAcceptedIssuers() {
087            return trustManager.getAcceptedIssuers();
088        }
089
090        /**
091         * Checks whether a host name matches the provided pattern. It accepts
092         * the use of wildcards in the pattern, e.g. {@code *.example.com}.
093         *
094         * @param hostName
095         *            The host name.
096         * @param pattern
097         *            The host name pattern, which may contain wild cards.
098         * @return {@code true} if the host name matched the pattern, otherwise
099         *         {@code false}.
100         */
101        private boolean hostNameMatchesPattern(final String hostName, final String pattern) {
102            final String[] nameElements = hostName.split("\\.");
103            final String[] patternElements = pattern.split("\\.");
104
105            boolean hostMatch = nameElements.length == patternElements.length;
106            for (int i = 0; i < nameElements.length && hostMatch; i++) {
107                final String ne = nameElements[i];
108                final String pe = patternElements[i];
109                if (!pe.equals("*")) {
110                    hostMatch = ne.equalsIgnoreCase(pe);
111                }
112            }
113            return hostMatch;
114        }
115
116        private void verifyHostName(final X509Certificate[] chain) {
117            try {
118                // TODO: NPE if root DN.
119                final DN dn =
120                        DN.valueOf(chain[0].getSubjectX500Principal().getName(), Schema
121                                .getCoreSchema());
122                final String value =
123                        dn.iterator().next().iterator().next().getAttributeValue().toString();
124                if (!hostNameMatchesPattern(value, hostNamePattern)) {
125                    throw new CertificateException(
126                            "The host name contained in the certificate chain subject DN \'"
127                                    + chain[0].getSubjectX500Principal()
128                                    + "' does not match the host name \'" + hostNamePattern + "'");
129                }
130            } catch (final Throwable t) {
131                LOG.log(Level.WARNING, "Error parsing subject dn: "
132                        + chain[0].getSubjectX500Principal(), t);
133            }
134        }
135    }
136
137    /**
138     * An X509TrustManager which rejects certificates which have expired or are
139     * not yet valid.
140     */
141    private static final class CheckValidityDates implements X509TrustManager {
142
143        private final X509TrustManager trustManager;
144
145        private CheckValidityDates(final X509TrustManager trustManager) {
146            this.trustManager = trustManager;
147        }
148
149        /** {@inheritDoc} */
150        public void checkClientTrusted(final X509Certificate[] chain, final String authType)
151                throws CertificateException {
152            verifyExpiration(chain);
153            trustManager.checkClientTrusted(chain, authType);
154        }
155
156        /** {@inheritDoc} */
157        public void checkServerTrusted(final X509Certificate[] chain, final String authType)
158                throws CertificateException {
159            verifyExpiration(chain);
160            trustManager.checkServerTrusted(chain, authType);
161        }
162
163        /** {@inheritDoc} */
164        public X509Certificate[] getAcceptedIssuers() {
165            return trustManager.getAcceptedIssuers();
166        }
167
168        private void verifyExpiration(final X509Certificate[] chain) throws CertificateException {
169            final Date currentDate = new Date();
170            for (final X509Certificate c : chain) {
171                try {
172                    c.checkValidity(currentDate);
173                } catch (final CertificateExpiredException e) {
174                    LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \""
175                            + c.getSubjectDN().getName() + "\" because it" + " expired on "
176                            + String.valueOf(c.getNotAfter()));
177
178                    throw e;
179                } catch (final CertificateNotYetValidException e) {
180                    LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \""
181                            + c.getSubjectDN().getName() + "\" because it" + " is not valid until "
182                            + String.valueOf(c.getNotBefore()));
183
184                    throw e;
185                }
186            }
187        }
188    }
189
190    /**
191     * An X509TrustManager which does not trust any certificates.
192     */
193    private static final class DistrustAll implements X509TrustManager {
194        /** Single instance. */
195        private static final DistrustAll INSTANCE = new DistrustAll();
196
197        /** Prevent instantiation. */
198        private DistrustAll() {
199            // Nothing to do.
200        }
201
202        /** {@inheritDoc} */
203        public void checkClientTrusted(final X509Certificate[] chain, final String authType)
204                throws CertificateException {
205            throw new CertificateException();
206        }
207
208        /** {@inheritDoc} */
209        public void checkServerTrusted(final X509Certificate[] chain, final String authType)
210                throws CertificateException {
211            throw new CertificateException();
212        }
213
214        /** {@inheritDoc} */
215        public X509Certificate[] getAcceptedIssuers() {
216            return new X509Certificate[0];
217        }
218    }
219
220    /**
221     * An X509TrustManager which trusts all certificates.
222     */
223    private static final class TrustAll implements X509TrustManager {
224
225        /** Single instance. */
226        private static final TrustAll INSTANCE = new TrustAll();
227
228        /** Prevent instantiation. */
229        private TrustAll() {
230            // Nothing to do.
231        }
232
233        /** {@inheritDoc} */
234        public void checkClientTrusted(final X509Certificate[] chain, final String authType)
235                throws CertificateException {
236        }
237
238        /** {@inheritDoc} */
239        public void checkServerTrusted(final X509Certificate[] chain, final String authType)
240                throws CertificateException {
241        }
242
243        /** {@inheritDoc} */
244        public X509Certificate[] getAcceptedIssuers() {
245            return new X509Certificate[0];
246        }
247    }
248
249    private static final Logger LOG = Logger.getLogger(TrustManagers.class.getName());
250
251    /**
252     * Wraps the provided {@code X509TrustManager} by adding additional
253     * validation which rejects certificate chains whose subject DN does not
254     * match the specified host name pattern. The pattern may contain
255     * wild-cards, for example {@code *.example.com}.
256     *
257     * @param hostNamePattern
258     *            A host name pattern which the RDN value contained in
259     *            certificate subject DNs must match.
260     * @param trustManager
261     *            The trust manager to be wrapped.
262     * @return The wrapped trust manager.
263     * @throws NullPointerException
264     *             If {@code trustManager} or {@code hostNamePattern} was
265     *             {@code null}.
266     */
267    public static X509TrustManager checkHostName(final String hostNamePattern,
268            final X509TrustManager trustManager) {
269        Reject.ifNull(trustManager, hostNamePattern);
270        return new CheckHostName(trustManager, hostNamePattern);
271    }
272
273    /**
274     * Creates a new {@code X509TrustManager} which will use the named trust
275     * store file to determine whether to trust a certificate. It will use the
276     * default trust store format for the JVM (e.g. {@code JKS}) and will not
277     * use a password to open the trust store.
278     *
279     * @param file
280     *            The trust store file name.
281     * @return A new {@code X509TrustManager} which will use the named trust
282     *         store file to determine whether to trust a certificate.
283     * @throws GeneralSecurityException
284     *             If the trust store could not be loaded, perhaps due to
285     *             incorrect format, or missing algorithms.
286     * @throws IOException
287     *             If the trust store file could not be found or could not be
288     *             read.
289     * @throws NullPointerException
290     *             If {@code file} was {@code null}.
291     */
292    public static X509TrustManager checkUsingTrustStore(final String file)
293            throws GeneralSecurityException, IOException {
294        return checkUsingTrustStore(file, null, null);
295    }
296
297    /**
298     * Creates a new {@code X509TrustManager} which will use the named trust
299     * store file to determine whether to trust a certificate. It will use the
300     * provided trust store format and password.
301     *
302     * @param file
303     *            The trust store file name.
304     * @param password
305     *            The trust store password, which may be {@code null}.
306     * @param format
307     *            The trust store format, which may be {@code null} to indicate
308     *            that the default trust store format for the JVM (e.g.
309     *            {@code JKS}) should be used.
310     * @return A new {@code X509TrustManager} which will use the named trust
311     *         store file to determine whether to trust a certificate.
312     * @throws GeneralSecurityException
313     *             If the trust store could not be loaded, perhaps due to
314     *             incorrect format, or missing algorithms.
315     * @throws IOException
316     *             If the trust store file could not be found or could not be
317     *             read.
318     * @throws NullPointerException
319     *             If {@code file} was {@code null}.
320     */
321    public static X509TrustManager checkUsingTrustStore(final String file, final char[] password,
322            final String format) throws GeneralSecurityException, IOException {
323        Reject.ifNull(file);
324
325        final File trustStoreFile = new File(file);
326        final String trustStoreFormat = format != null ? format : KeyStore.getDefaultType();
327
328        final KeyStore keyStore = KeyStore.getInstance(trustStoreFormat);
329
330        FileInputStream fos = null;
331        try {
332            fos = new FileInputStream(trustStoreFile);
333            keyStore.load(fos, password);
334        } finally {
335            if (fos != null) {
336                try {
337                    fos.close();
338                } catch (final IOException ignored) {
339                    // Ignore.
340                }
341            }
342        }
343
344        final TrustManagerFactory tmf =
345                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
346        tmf.init(keyStore);
347
348        X509TrustManager x509tm = null;
349        for (final TrustManager tm : tmf.getTrustManagers()) {
350            if (tm instanceof X509TrustManager) {
351                x509tm = (X509TrustManager) tm;
352                break;
353            }
354        }
355
356        if (x509tm == null) {
357            throw new NoSuchAlgorithmException();
358        }
359
360        return x509tm;
361    }
362
363    /**
364     * Wraps the provided {@code X509TrustManager} by adding additional
365     * validation which rejects certificate chains containing certificates which
366     * have expired or are not yet valid.
367     *
368     * @param trustManager
369     *            The trust manager to be wrapped.
370     * @return The wrapped trust manager.
371     * @throws NullPointerException
372     *             If {@code trustManager} was {@code null}.
373     */
374    public static X509TrustManager checkValidityDates(final X509TrustManager trustManager) {
375        Reject.ifNull(trustManager);
376        return new CheckValidityDates(trustManager);
377    }
378
379    /**
380     * Returns an {@code X509TrustManager} which does not trust any
381     * certificates.
382     *
383     * @return An {@code X509TrustManager} which does not trust any
384     *         certificates.
385     */
386    public static X509TrustManager distrustAll() {
387        return DistrustAll.INSTANCE;
388    }
389
390    /**
391     * Returns an {@code X509TrustManager} which trusts all certificates.
392     *
393     * @return An {@code X509TrustManager} which trusts all certificates.
394     */
395    public static X509TrustManager trustAll() {
396        return TrustAll.INSTANCE;
397    }
398
399    /** Prevent insantiation. */
400    private TrustManagers() {
401        // Nothing to do.
402    }
403
404}