blob: e6b7685493308b53186a9f40e2c5e88e0b0539ef [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.shared.client.api;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.CertPathValidatorException.Reason;
import java.security.cert.Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Date;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapTlsHandshakeException;
import org.apache.directory.api.ldap.model.exception.LdapTlsHandshakeFailCause.LdapApiReason;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.ldap.client.api.NoVerificationTrustManager;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.server.core.security.TlsKeyGenerator;
import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Test connections with SSL/StartTLS with various certificates.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
@RunWith(FrameworkRunner.class)
@CreateDS(allowAnonAccess = true, name = "KeyStoreIT-class")
public class CertificateValidationTest extends AbstractLdapTestUnit
{
private static final String KEYSTORE_PW = "changeit";
private static final String ROOT_CA_KEYSTORE_PATH = "target/test-classes/root-ca-keystore.ks";
private static KeyStore ROOT_CA_KEYSTORE;
private static final String VALID_KEYSTORE_PATH = "target/test-classes/valid-keystore.ks";
private static final String EXPIRED_KEYSTORE_PATH = "target/test-classes/expired-keystore.ks";
private static final String NOT_YET_VALID_KEYSTORE_PATH = "target/test-classes/not-yet-valid-keystore.ks";
private static final String SMALL_KEYSIZE_KEYSTORE_PATH = "target/test-classes/small-keysize-keystore.ks";
@BeforeClass
public static void installKeyStoreWithCertificate() throws Exception
{
String hostName = InetAddress.getLocalHost().getHostName();
String issuerDn = TlsKeyGenerator.CERTIFICATE_PRINCIPAL_DN;
String subjectDn = "CN=" + hostName;
Date startDate = new Date();
Date expiryDate = new Date( System.currentTimeMillis() + TlsKeyGenerator.YEAR_MILLIS );
String keyAlgo = "RSA";
int keySize = 1024;
// generate root CA, self-signed
String rootCaSubjectDn = issuerDn;
ROOT_CA_KEYSTORE = createKeyStore( rootCaSubjectDn, issuerDn, startDate, expiryDate, keyAlgo, keySize, null,
true, ROOT_CA_KEYSTORE_PATH );
PrivateKey rootCaPrivateKey = ( PrivateKey ) ROOT_CA_KEYSTORE.getKey( "apacheds", KEYSTORE_PW.toCharArray() );
// generate a valid certificate, signed by root CA
createKeyStore( subjectDn, issuerDn, startDate, expiryDate, keyAlgo, keySize, rootCaPrivateKey,
false, VALID_KEYSTORE_PATH );
// generate an expired certificate, signed by root CA
Date expiredStartDate = new Date( System.currentTimeMillis() - TlsKeyGenerator.YEAR_MILLIS );
Date expiredExpiryDate = new Date( System.currentTimeMillis() - TlsKeyGenerator.YEAR_MILLIS / 365 );
createKeyStore( subjectDn, issuerDn, expiredStartDate, expiredExpiryDate, keyAlgo, keySize,
rootCaPrivateKey, false, EXPIRED_KEYSTORE_PATH );
// generate a not yet valid certificate, signed by root CA
Date notYetValidStartDate = new Date( System.currentTimeMillis() + TlsKeyGenerator.YEAR_MILLIS / 365 );
Date notYetValidExpiryDate = new Date( System.currentTimeMillis() + TlsKeyGenerator.YEAR_MILLIS );
createKeyStore( subjectDn, issuerDn, notYetValidStartDate, notYetValidExpiryDate, keyAlgo, keySize,
rootCaPrivateKey, false, NOT_YET_VALID_KEYSTORE_PATH );
// generate a certificate with small key size, signed by root CA
int smallKeySize = 512;
createKeyStore( subjectDn, issuerDn, startDate, expiryDate, keyAlgo, smallKeySize,
rootCaPrivateKey, false, SMALL_KEYSIZE_KEYSTORE_PATH );
// TODO signature does not match if root private key is null
}
private static KeyStore createKeyStore( String subjectDn, String issuerDn, Date startDate, Date expiryDate,
String keyAlgo, int keySize, PrivateKey optionalSigningKey, boolean isCA, String keystorePath )
throws Exception
{
File goodKeyStoreFile = new File( keystorePath );
if ( goodKeyStoreFile.exists() )
{
goodKeyStoreFile.delete();
}
Entry entry = new DefaultEntry();
TlsKeyGenerator.addKeyPair( entry, issuerDn, subjectDn, startDate, expiryDate, keyAlgo, keySize,
optionalSigningKey, isCA );
KeyPair keyPair = TlsKeyGenerator.getKeyPair( entry );
X509Certificate cert = TlsKeyGenerator.getCertificate( entry );
//System.out.println( cert );
KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
keyStore.load( null, null );
keyStore.setCertificateEntry( "apacheds", cert );
keyStore.setKeyEntry( "apacheds", keyPair.getPrivate(), KEYSTORE_PW.toCharArray(), new Certificate[]
{ cert } );
try ( FileOutputStream out = new FileOutputStream( goodKeyStoreFile ) )
{
keyStore.store( out, KEYSTORE_PW.toCharArray() );
}
return keyStore;
}
@CreateLdapServer(keyStore = VALID_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW)
@Test
public void testLdaps_Valid_NoVerificationTrustManager() throws Exception
{
LdapConnectionConfig config = ldapsConnectionConfig();
config.setTrustManagers( noVerificationTrustManagers() );
connectOk( config );
}
@CreateLdapServer(keyStore = VALID_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW, extendedOpHandlers = StartTlsHandler.class)
@Test
public void testStartTls_Valid_NoVerificationTrustManager() throws Exception
{
LdapConnectionConfig config = startTlsConnectionConfig();
config.setTrustManagers( noVerificationTrustManagers() );
connectOk( config );
}
@CreateLdapServer(keyStore = ROOT_CA_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW)
@Test
public void testLdaps_SelfSigned_JvmDefaultTrustManager() throws Exception
{
LdapConnectionConfig config = ldapsConnectionConfig();
config.setTrustManagers( jvmDefaultTrustManagers() );
connectAndExpectTlsHandshakeException( config, CertPathBuilderException.class,
LdapApiReason.NO_VALID_CERTIFICATION_PATH, "Failed to build certification path",
"unable to find valid certification path to requested target" );
}
@CreateLdapServer(keyStore = ROOT_CA_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW, extendedOpHandlers = StartTlsHandler.class)
@Test
public void testStartTls_SelfSigned_JvmDefaultTrustManager() throws Exception
{
LdapConnectionConfig config = startTlsConnectionConfig();
config.setTrustManagers( jvmDefaultTrustManagers() );
connectAndExpectTlsHandshakeException( config, CertPathBuilderException.class,
LdapApiReason.NO_VALID_CERTIFICATION_PATH, "Failed to build certification path",
"unable to find valid certification path to requested target" );
}
@CreateLdapServer(keyStore = VALID_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW)
@Test
public void testLdaps_Valid_JvmDefaultTrustManager() throws Exception
{
LdapConnectionConfig config = ldapsConnectionConfig();
config.setTrustManagers( jvmDefaultTrustManagers() );
connectAndExpectTlsHandshakeException( config, CertPathBuilderException.class,
LdapApiReason.NO_VALID_CERTIFICATION_PATH, "Failed to build certification path",
"unable to find valid certification path to requested target" );
}
@CreateLdapServer(keyStore = VALID_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW, extendedOpHandlers = StartTlsHandler.class)
@Test
public void testStartTls_Valid_JvmDefaultTrustManager() throws Exception
{
LdapConnectionConfig config = startTlsConnectionConfig();
config.setTrustManagers( jvmDefaultTrustManagers() );
connectAndExpectTlsHandshakeException( config, CertPathBuilderException.class,
LdapApiReason.NO_VALID_CERTIFICATION_PATH, "Failed to build certification path",
"unable to find valid certification path to requested target" );
}
@CreateLdapServer(keyStore = VALID_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW)
@Test
public void testLdaps_Valid_RootCaTrustManager() throws Exception
{
LdapConnectionConfig config = ldapsConnectionConfig();
config.setTrustManagers( getCustomTrustManager( ROOT_CA_KEYSTORE ) );
connectOk( config );
}
@CreateLdapServer(keyStore = VALID_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW, extendedOpHandlers = StartTlsHandler.class)
@Test
public void testStartTls_Valid_RootCaTrustManager() throws Exception
{
LdapConnectionConfig config = startTlsConnectionConfig();
config.setTrustManagers( getCustomTrustManager( ROOT_CA_KEYSTORE ) );
connectOk( config );
}
@CreateLdapServer(keyStore = EXPIRED_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW)
@Test
public void testLdaps_Expired_RootCaTrustManager() throws Exception
{
LdapConnectionConfig config = ldapsConnectionConfig();
config.setTrustManagers( getCustomTrustManager( ROOT_CA_KEYSTORE ) );
connectAndExpectTlsHandshakeException( config, CertificateExpiredException.class, BasicReason.EXPIRED,
"Certificate expired", "NotAfter" );
}
@CreateLdapServer(keyStore = EXPIRED_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW, extendedOpHandlers = StartTlsHandler.class)
@Test
public void testStartTls_Expired_RootCaTrustManager() throws Exception
{
LdapConnectionConfig config = startTlsConnectionConfig();
config.setTrustManagers( getCustomTrustManager( ROOT_CA_KEYSTORE ) );
connectAndExpectTlsHandshakeException( config, CertificateExpiredException.class, BasicReason.EXPIRED,
"Certificate expired", "NotAfter" );
}
@CreateLdapServer(keyStore = NOT_YET_VALID_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW)
@Test
public void testLdaps_NotYetValid_RootCaTrustManager() throws Exception
{
LdapConnectionConfig config = ldapsConnectionConfig();
config.setTrustManagers( getCustomTrustManager( ROOT_CA_KEYSTORE ) );
connectAndExpectTlsHandshakeException( config, CertificateNotYetValidException.class, BasicReason.NOT_YET_VALID,
"Certificate not yet valid", "NotBefore" );
}
@CreateLdapServer(keyStore = NOT_YET_VALID_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW, extendedOpHandlers = StartTlsHandler.class)
@Test
public void testStartTls_NotYetValid_RootCaTrustManager() throws Exception
{
LdapConnectionConfig config = startTlsConnectionConfig();
config.setTrustManagers( getCustomTrustManager( ROOT_CA_KEYSTORE ) );
connectAndExpectTlsHandshakeException( config, CertificateNotYetValidException.class, BasicReason.NOT_YET_VALID,
"Certificate not yet valid", "NotBefore" );
}
@CreateLdapServer(keyStore = SMALL_KEYSIZE_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW)
@Test
public void testLdaps_SmallKeySize_RootCaTrustManager() throws Exception
{
LdapConnectionConfig config = ldapsConnectionConfig();
config.setTrustManagers( getCustomTrustManager( ROOT_CA_KEYSTORE ) );
connectAndExpectTlsHandshakeException( config, CertPathValidatorException.class,
BasicReason.ALGORITHM_CONSTRAINED, "Failed to verify certification path",
"Algorithm constraints check failed on keysize limits" );
}
@CreateLdapServer(keyStore = SMALL_KEYSIZE_KEYSTORE_PATH, certificatePassword = KEYSTORE_PW, extendedOpHandlers = StartTlsHandler.class)
@Test
public void testStartTls_SmallKeySize_RootCaTrustManager() throws Exception
{
LdapConnectionConfig config = startTlsConnectionConfig();
config.setTrustManagers( getCustomTrustManager( ROOT_CA_KEYSTORE ) );
connectAndExpectTlsHandshakeException( config, CertPathValidatorException.class,
BasicReason.ALGORITHM_CONSTRAINED, "Failed to verify certification path",
"Algorithm constraints check failed on keysize limits" );
}
private void connectOk( LdapConnectionConfig config ) throws LdapException, IOException
{
assertTrue( getLdapServer().isStarted() );
try ( LdapNetworkConnection conn = new LdapNetworkConnection( config ) )
{
if ( config.isUseTls() )
{
conn.startTls();
}
else if ( config.isUseSsl() )
{
conn.connect();
}
else
{
fail( "Either useTls or useSsl must be enabled" );
}
assertTrue( conn.isConnected() );
assertTrue( conn.isSecured() );
}
}
private void connectAndExpectTlsHandshakeException( LdapConnectionConfig config,
Class<? extends Throwable> rootCauseExceptionClass, Reason reason,
String reasonPhrase, String reasonMessage ) throws IOException
{
assertTrue( getLdapServer().isStarted() );
try ( LdapNetworkConnection conn = new LdapNetworkConnection( config ) )
{
try
{
if ( config.isUseTls() )
{
conn.startTls();
}
else if ( config.isUseSsl() )
{
conn.connect();
}
else
{
fail( "Either useTls or useSsl must be enabled" );
}
fail( "Expected exception" );
}
catch ( LdapException e )
{
assertThat( e, is( instanceOf( LdapTlsHandshakeException.class ) ) );
LdapTlsHandshakeException lthse = ( LdapTlsHandshakeException ) e;
assertThat( lthse.getFailCause().getRootCause(), instanceOf( rootCauseExceptionClass ) );
assertThat( lthse.getFailCause().getReason(), equalTo( reason ) );
assertThat( lthse.getFailCause().getReasonPhrase(), equalTo( reasonPhrase ) );
assertThat( lthse.getMessage(), containsString( "ERR_04120_TLS_HANDSHAKE_ERROR" ) );
assertThat( lthse.getMessage(), containsString( "The TLS handshake failed" ) );
assertThat( lthse.getMessage(), containsString( "reason: " + reasonPhrase ) );
assertThat( lthse.getMessage(), containsString( reasonMessage ) );
}
assertFalse( conn.isConnected() );
assertFalse( conn.isSecured() );
}
}
private LdapConnectionConfig startTlsConnectionConfig()
{
LdapConnectionConfig config = new LdapConnectionConfig();
config.setTimeout( 1000 );
config.setLdapHost( "localhost" );
config.setLdapPort( getLdapServer().getPort() );
config.setUseTls( true );
return config;
}
private LdapConnectionConfig ldapsConnectionConfig()
{
LdapConnectionConfig config = new LdapConnectionConfig();
config.setTimeout( 1000 );
config.setLdapHost( "localhost" );
config.setLdapPort( getLdapServer().getPortSSL() );
config.setUseSsl( true );
return config;
}
private X509TrustManager getCustomTrustManager( KeyStore trustStore )
throws NoSuchAlgorithmException, KeyStoreException
{
TrustManagerFactory factory = TrustManagerFactory.getInstance( TrustManagerFactory
.getDefaultAlgorithm() );
factory.init( trustStore );
TrustManager[] trustManagers = factory.getTrustManagers();
TrustManager trustManager = trustManagers[0];
return ( X509TrustManager ) trustManager;
}
private TrustManager[] jvmDefaultTrustManagers() throws NoSuchAlgorithmException, KeyStoreException
{
TrustManagerFactory factory = TrustManagerFactory.getInstance( TrustManagerFactory
.getDefaultAlgorithm() );
factory.init( ( KeyStore ) null );
TrustManager[] defaultTrustManagers = factory.getTrustManagers();
return defaultTrustManagers;
}
private TrustManager[] noVerificationTrustManagers()
{
return new X509TrustManager[]
{ new NoVerificationTrustManager() };
}
}