/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2010 Sun Microsystems, Inc. */ package org.opends.sdk; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.opends.sdk.schema.Schema; import com.sun.opends.sdk.util.Validator; /** * This class contains methods for creating common types of trust manager. */ public final class TrustManagers { /** * An X509TrustManager which rejects certificate chains whose subject DN does * not match a specified host name. */ private static final class CheckHostName implements X509TrustManager { private final X509TrustManager trustManager; private final String hostNamePattern; private CheckHostName(final X509TrustManager trustManager, final String hostNamePattern) { this.trustManager = trustManager; this.hostNamePattern = hostNamePattern; } /** * {@inheritDoc} */ public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { verifyHostName(chain); trustManager.checkClientTrusted(chain, authType); } /** * {@inheritDoc} */ public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { verifyHostName(chain); trustManager.checkServerTrusted(chain, authType); } /** * {@inheritDoc} */ public X509Certificate[] getAcceptedIssuers() { return trustManager.getAcceptedIssuers(); } /** * Checks whether a host name matches the provided pattern. It accepts the * use of wildcards in the pattern, e.g. {@code *.example.com}. * * @param hostName * The host name. * @param pattern * The host name pattern, which may contain wild cards. * @return {@code true} if the host name matched the pattern, otherwise * {@code false}. */ private boolean hostNameMatchesPattern(final String hostName, final String pattern) { final String[] nameElements = hostName.split("\\."); final String[] patternElements = pattern.split("\\."); boolean hostMatch = nameElements.length == patternElements.length; for (int i = 0; i < nameElements.length && hostMatch; i++) { final String ne = nameElements[i]; final String pe = patternElements[i]; if (!pe.equals("*")) { hostMatch = ne.equalsIgnoreCase(pe); } } return hostMatch; } private void verifyHostName(final X509Certificate[] chain) throws CertificateException { try { // TODO: NPE if root DN. final DN dn = DN.valueOf(chain[0].getSubjectX500Principal().getName(), Schema.getCoreSchema()); final String value = dn.iterator().next().iterator().next() .getAttributeValue().toString(); if (!hostNameMatchesPattern(value, hostNamePattern)) { throw new CertificateException( "The host name contained in the certificate chain subject DN \'" + chain[0].getSubjectX500Principal() + "' does not match the host name \'" + hostNamePattern + "'"); } } catch (final Throwable t) { LOG.log(Level.WARNING, "Error parsing subject dn: " + chain[0].getSubjectX500Principal(), t); } } } /** * An X509TrustManager which rejects certificates which have expired or are * not yet valid. */ private static final class CheckValidatyDates implements X509TrustManager { private final X509TrustManager trustManager; private CheckValidatyDates(final X509TrustManager trustManager) { this.trustManager = trustManager; } /** * {@inheritDoc} */ public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { verifyExpiration(chain); trustManager.checkClientTrusted(chain, authType); } /** * {@inheritDoc} */ public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { verifyExpiration(chain); trustManager.checkServerTrusted(chain, authType); } /** * {@inheritDoc} */ public X509Certificate[] getAcceptedIssuers() { return trustManager.getAcceptedIssuers(); } private void verifyExpiration(final X509Certificate[] chain) throws CertificateException { final Date currentDate = new Date(); for (final X509Certificate c : chain) { try { c.checkValidity(currentDate); } catch (final CertificateExpiredException e) { LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \"" + c.getSubjectDN().getName() + "\" because it" + " expired on " + String.valueOf(c.getNotAfter())); throw e; } catch (final CertificateNotYetValidException e) { LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \"" + c.getSubjectDN().getName() + "\" because it" + " is not valid until " + String.valueOf(c.getNotBefore())); throw e; } } } } /** * An X509TrustManager which does not trust any certificates. */ private static final class DistrustAll implements X509TrustManager { // Single instance. private static final DistrustAll INSTANCE = new DistrustAll(); // Prevent instantiation. private DistrustAll() { // Nothing to do. } /** * {@inheritDoc} */ public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { throw new CertificateException(); } /** * {@inheritDoc} */ public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { throw new CertificateException(); } /** * {@inheritDoc} */ public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } /** * An X509TrustManager which trusts all certificates. */ private static final class TrustAll implements X509TrustManager { // Single instance. private static final TrustAll INSTANCE = new TrustAll(); // Prevent instantiation. private TrustAll() { // Nothing to do. } /** * {@inheritDoc} */ public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { } /** * {@inheritDoc} */ public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { } /** * {@inheritDoc} */ public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } private static final Logger LOG = Logger.getLogger(TrustManagers.class .getName()); /** * Wraps the provided {@code X509TrustManager} by adding additional validation * which rejects certificate chains whose subject DN does not match the * specified host name pattern. The pattern may contain wild-cards, for * example {@code *.example.com}. * * @param hostNamePattern * A host name pattern which the RDN value contained in certificate * subject DNs must match. * @param trustManager * The trust manager to be wrapped. * @return The wrapped trust manager. * @throws NullPointerException * If {@code trustManager} or {@code hostNamePattern} was {@code * null}. */ public static X509TrustManager checkHostName(final String hostNamePattern, final X509TrustManager trustManager) throws NullPointerException { Validator.ensureNotNull(trustManager, hostNamePattern); return new CheckHostName(trustManager, hostNamePattern); } /** * Creates a new {@code X509TrustManager} which will use the named trust store * file to determine whether to trust a certificate. It will use the default * trust store format for the JVM (e.g. {@code JKS}) and will not use a * password to open the trust store. * * @param file * The trust store file name. * @return A new {@code X509TrustManager} which will use the named trust store * file to determine whether to trust a certificate. * @throws GeneralSecurityException * If the trust store could not be loaded, perhaps due to incorrect * format, or missing algorithms. * @throws IOException * If the trust store file could not be found or could not be read. * @throws NullPointerException * If {@code file} was {@code null}. */ public static X509TrustManager checkUsingTrustStore(final String file) throws GeneralSecurityException, IOException, NullPointerException { return checkUsingTrustStore(file, null, null); } /** * Creates a new {@code X509TrustManager} which will use the named trust store * file to determine whether to trust a certificate. It will use the provided * trust store format and password. * * @param file * The trust store file name. * @param password * The trust store password, which may be {@code null}. * @param format * The trust store format, which may be {@code null} to indicate that * the default trust store format for the JVM (e.g. {@code JKS}) * should be used. * @return A new {@code X509TrustManager} which will use the named trust store * file to determine whether to trust a certificate. * @throws GeneralSecurityException * If the trust store could not be loaded, perhaps due to incorrect * format, or missing algorithms. * @throws IOException * If the trust store file could not be found or could not be read. * @throws NullPointerException * If {@code file} was {@code null}. */ public static X509TrustManager checkUsingTrustStore(final String file, final String password, final String format) throws GeneralSecurityException, IOException, NullPointerException { Validator.ensureNotNull(file); final File trustStoreFile = new File(file); final char[] trustStorePassword = password != null ? password.toCharArray() : null; final String trustStoreFormat = format != null ? format : KeyStore .getDefaultType(); final KeyStore keyStore = KeyStore.getInstance(trustStoreFormat); FileInputStream fos = null; try { fos = new FileInputStream(trustStoreFile); keyStore.load(fos, trustStorePassword); } finally { if (fos != null) { try { fos.close(); } catch (final IOException ignored) { // Ignore. } } } final TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); X509TrustManager x509tm = null; for (final TrustManager tm : tmf.getTrustManagers()) { if (tm instanceof X509TrustManager) { x509tm = (X509TrustManager) tm; break; } } if (x509tm == null) { throw new NoSuchAlgorithmException(); } return x509tm; } /** * Wraps the provided {@code X509TrustManager} by adding additional validation * which rejects certificate chains containing certificates which have expired * or are not yet valid. * * @param trustManager * The trust manager to be wrapped. * @return The wrapped trust manager. * @throws NullPointerException * If {@code trustManager} was {@code null}. */ public static X509TrustManager checkValidityDates( final X509TrustManager trustManager) throws NullPointerException { Validator.ensureNotNull(trustManager); return new CheckValidatyDates(trustManager); } /** * Returns an {@code X509TrustManager} which does not trust any certificates. * * @return An {@code X509TrustManager} which does not trust any certificates. */ public static X509TrustManager distrustAll() { return DistrustAll.INSTANCE; } /** * Returns an {@code X509TrustManager} which trusts all certificates. * * @return An {@code X509TrustManager} which trusts all certificates. */ public static X509TrustManager trustAll() { return TrustAll.INSTANCE; } // Prevent insantiation. private TrustManagers() { // Nothing to do. } }