/* * 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 2008-2009 Sun Microsystems, Inc. * Portions Copyright 2009 Parametric Technology Corporation (PTC) */ package com.sun.opends.sdk.tools; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; 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.DN; import org.opends.sdk.schema.Schema; import com.sun.opends.sdk.util.Validator; /** * This class is in charge of checking whether the certificates that are * presented are trusted or not. This implementation tries to check also * that the subject DN of the certificate corresponds to the host passed * using the setHostName method. This implementation also checks to make * sure the certificate is in the validity period. The constructor tries * to use a default TrustManager from the system and if it cannot be * retrieved this class will only accept the certificates explicitly * accepted by the user (and specified by calling acceptCertificate). */ class TrustStoreTrustManager implements X509TrustManager { static private final Logger LOG = Logger .getLogger(TrustStoreTrustManager.class.getName()); private final X509TrustManager trustManager; private final KeyStore truststore; private final File truststoreFile; private final char[] truststorePassword; private final String hostname; /** * The default constructor. */ TrustStoreTrustManager(String truststorePath, String truststorePassword, String hostname, boolean checkValidityDates) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { Validator.ensureNotNull(truststorePath); this.truststoreFile = new File(truststorePath); if (truststorePassword != null) { this.truststorePassword = truststorePassword.toCharArray(); } else { this.truststorePassword = null; } truststore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream fos = new FileInputStream(truststoreFile); truststore.load(fos, this.truststorePassword); TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(truststore); X509TrustManager x509tm = null; for (TrustManager tm : tmf.getTrustManagers()) { if (tm instanceof X509TrustManager) { x509tm = (X509TrustManager) tm; break; } } if (x509tm == null) { throw new NoSuchAlgorithmException(); } this.trustManager = x509tm; this.hostname = hostname; // this.checkValidityDates = checkValidityDates; } /** * {@inheritDoc} */ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { verifyExpiration(chain); verifyHostName(chain); trustManager.checkClientTrusted(chain, authType); } /** * {@inheritDoc} */ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { verifyExpiration(chain); verifyHostName(chain); trustManager.checkClientTrusted(chain, authType); } /** * {@inheritDoc} */ public X509Certificate[] getAcceptedIssuers() { if (trustManager != null) { return trustManager.getAcceptedIssuers(); } else { return new X509Certificate[0]; } } private void verifyExpiration(X509Certificate[] chain) throws CertificateException { Date currentDate = new Date(); for (X509Certificate c : chain) { try { c.checkValidity(currentDate); } catch (CertificateExpiredException cee) { LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \"" + c.getSubjectDN().getName() + "\" because it" + " expired on " + String.valueOf(c.getNotAfter())); throw cee; } catch (CertificateNotYetValidException cnyve) { LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \"" + c.getSubjectDN().getName() + "\" because it" + " is not valid until " + String.valueOf(c.getNotBefore())); throw cnyve; } } } /** * Verifies that the provided certificate chains subject DN * corresponds to the host name specified with the setHost method. * * @param chain * the certificate chain to analyze. * @throws HostnameMismatchCertificateException * if the subject DN of the certificate does not match with * the host name specified with the method setHost. */ private void verifyHostName(X509Certificate[] chain) throws HostnameMismatchCertificateException { if (hostname != null) { try { DN dn = DN.valueOf( chain[0].getSubjectX500Principal().getName(), Schema .getCoreSchema()); String value = dn.iterator().next().iterator().next() .getAttributeValue().toString(); if (!hostMatch(value, hostname)) { throw new HostnameMismatchCertificateException( "Hostname mismatch between host name " + hostname + " and subject DN: " + chain[0].getSubjectX500Principal(), hostname, chain[0].getSubjectX500Principal().getName()); } } catch (Throwable t) { LOG.log(Level.WARNING, "Error parsing subject dn: " + chain[0].getSubjectX500Principal(), t); } } } /** * Checks whether two host names match. It accepts the use of wildcard * in the host name. * * @param host1 * the first host name. * @param host2 * the second host name. * @return true if the host match and false * otherwise. */ private boolean hostMatch(String host1, String host2) { if (host1 == null) { throw new IllegalArgumentException( "The host1 parameter cannot be null"); } if (host2 == null) { throw new IllegalArgumentException( "The host2 parameter cannot be null"); } String[] h1 = host1.split("\\."); String[] h2 = host2.split("\\."); boolean hostMatch = h1.length == h2.length; for (int i = 0; i < h1.length && hostMatch; i++) { if (!h1[i].equals("*") && !h2[i].equals("*")) { hostMatch = h1[i].equalsIgnoreCase(h2[i]); } } return hostMatch; } }