/*
|
* 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.
|
}
|
|
}
|