/* ********************************************************************************* * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.springframework.security.ldap.server; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.directory.server.core.DefaultDirectoryService; import org.apache.directory.server.core.authn.AuthenticationInterceptor; import org.apache.directory.server.core.interceptor.Interceptor; import org.apache.directory.server.core.partition.Partition; import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition; import org.apache.directory.server.core.referral.ReferralInterceptor; import org.apache.directory.server.ldap.LdapServer; import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler; import org.apache.directory.server.protocol.shared.store.LdifFileLoader; import org.apache.directory.server.protocol.shared.transport.TcpTransport; import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.Lifecycle; import org.springframework.core.io.Resource; import sun.security.x509.X500Name; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.List; import static org.cloudfoundry.identity.uaa.util.SocketUtils.getSelfCertificate; public class ApacheDsSSLContainer implements InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware { private static final Log logger = LogFactory.getLog(ApacheDsSSLContainer.class); final DefaultDirectoryService service; LdapServer server; private ApplicationContext ctxt; private File workingDir; private boolean running; private final Resource[] ldifResources; private final JdbmPartition partition; private final String root; private int port = 53389; private int sslPort = 53636; private String keystoreFile; private boolean useStartTLS = false; public boolean isUseStartTLS() { return useStartTLS; } public ApacheDsSSLContainer setUseStartTLS(boolean useStartTLS) { this.useStartTLS = useStartTLS; return this; } public String getKeystoreFile() { return keystoreFile; } public ApacheDsSSLContainer setKeystoreFile(String keystoreFile) { this.keystoreFile = keystoreFile; return this; } public ApacheDsSSLContainer(String root, Resource[] ldifs) throws Exception { this.ldifResources = ldifs; service = new DefaultDirectoryService(); List<Interceptor> list = new ArrayList<Interceptor>(); //list.add(new NormalizationInterceptor()); list.add(new AuthenticationInterceptor()); list.add(new ReferralInterceptor()); // list.add( new AciAuthorizationInterceptor() ); // list.add( new DefaultAuthorizationInterceptor() ); //list.add(new ExceptionInterceptor()); // list.add( new ChangeLogInterceptor() ); //list.add(new OperationalAttributeInterceptor()); // list.add( new SchemaInterceptor() ); //list.add(new SubentryInterceptor()); // list.add( new CollectiveAttributeInterceptor() ); // list.add( new EventInterceptor() ); // list.add( new TriggerInterceptor() ); // list.add( new JournalInterceptor() ); //service.setInterceptors(list); partition = new JdbmPartition(); partition.setId("rootPartition"); partition.setSuffix(root); this.root = root; service.addPartition(partition); service.setExitVmOnShutdown(false); service.setShutdownHookEnabled(false); service.getChangeLog().setEnabled(false); service.setDenormalizeOpAttrsEnabled(true); } public ApacheDsSSLContainer setWorkingDirectory(File workingDir) { this.workingDir = workingDir; if (!workingDir.mkdirs()) { throw new RuntimeException("Unable to create directory:" + workingDir); } service.setWorkingDirectory(workingDir); return this; } public File getWorkingDirectory() { return workingDir; } @Override public void afterPropertiesSet() throws Exception { afterPropertiesSet(getKeystore(getWorkingDirectory())); } public ApacheDsSSLContainer afterPropertiesSet(File keystore) throws Exception { server = new LdapServer(); server.setDirectoryService(service); TcpTransport sslTransport = new TcpTransport(sslPort); if (isUseStartTLS()) { server.addExtendedOperationHandler(new StartTlsHandler()); } else { sslTransport.setEnableSSL(true); } TcpTransport tcpTransport = new TcpTransport(port); server.setTransports(sslTransport, tcpTransport); assert server.isEnableLdaps(sslTransport); assert !server.isEnableLdaps(tcpTransport); server.setCertificatePassword("password"); server.setKeystoreFile(keystore.getAbsolutePath()); server.addExtendedOperationHandler(new StartTlsHandler()); start(); return this; } public ApacheDsSSLContainer setSslPort(int sslPort) { this.sslPort = sslPort; return this; } public ApacheDsSSLContainer setPort(int port) { this.port = port; return this; } private static final int keysize = 1024; private static final String commonName = "localhost"; private static final String organizationalUnit = "UAA"; private static final String organization = "Pivotal Software"; private static final String city = "San Francisco"; private static final String state = "CA"; private static final String country = "UA"; private static final long validity = 1096; // 3 years private static final String alias = "uaa-ldap"; private static final char[] keyPass = "password".toCharArray(); //mimic what the keytool does public File getKeystore(File directory) throws Exception { KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(null, null); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(keysize); KeyPair keyPair = keyPairGenerator.generateKeyPair(); X509Certificate[] chain = {getSelfCertificate(new X500Name(commonName, organizationalUnit, organization, city, state, country), new Date(), (long) validity * 24 * 60 * 60, keyPair, "SHA256withRSA")}; keyStore.setKeyEntry(alias, keyPair.getPrivate(), keyPass, chain); String keystoreName = "ldap.keystore"; File keystore = new File(directory, keystoreName); if (!keystore.createNewFile()) { throw new FileNotFoundException("Unable to create file:" + keystore); } keyStore.store(new FileOutputStream(keystore, false), keyPass); return keystore; } @Override public void start() { if (isRunning()) { return; } if (service.isStarted()) { throw new IllegalStateException("DirectoryService is already running."); } logger.info("Starting directory server..."); try { service.startup(); server.start(); } catch (Exception e) { throw new RuntimeException("Server startup failed", e); } try { service.getAdminSession().lookup(partition.getSuffixDn()); } catch (LdapNameNotFoundException e) { try { // LdapDN dn = new LdapDN(root); // Assert.isTrue(root.startsWith("dc=")); // String dc = root.substring(3, root.indexOf(',')); // ServerEntry entry = service.newEntry(dn); // entry.add("objectClass", "top", "domain", "extensibleObject"); // entry.add("dc", dc); // service.getAdminSession().add(entry); addPartition("testPartition", root); } catch (Exception e1) { logger.error("Failed to create dc entry", e1); } } catch (Exception e) { logger.error("Lookup failed", e); } running = true; try { importLdifs(); } catch (Exception e) { throw new RuntimeException("Failed to import LDIF file(s)", e); } } protected Partition addPartition(String partitionId, String partitionDn) throws Exception { Partition partition = new JdbmPartition(); partition.setId(partitionId); partition.setSuffix(partitionDn); service.addPartition(partition); return partition; } public void stop() { if (!isRunning()) { return; } logger.info("Shutting down directory server ..."); try { server.stop(); service.shutdown(); } catch (Exception e) { logger.error("Shutdown failed", e); return; } running = false; if (workingDir.exists()) { logger.info("Deleting working directory " + workingDir.getAbsolutePath()); deleteDir(workingDir); } } protected void importLdifs() throws Exception { // Import any ldif files Resource[] ldifs = ldifResources; // Note that we can't just import using the ServerContext returned // from starting Apache DS, apparently because of the long-running issue // DIRSERVER-169. // We need a standard context. // DirContext dirContext = contextSource.getReadWriteContext(); if (ldifs == null || ldifs.length == 0) { return; } for (Resource resource : ldifs) { String ldifFile; try { ldifFile = resource.getFile().getAbsolutePath(); } catch (IOException e) { ldifFile = resource.getURI().toString(); } logger.info("Loading LDIF file: " + ldifFile); new LdifFileLoader( service.getAdminSession(), new File(ldifFile), null, getClass().getClassLoader() ).execute(); } } protected String createTempDirectory(String prefix) throws IOException { String parentTempDir = System.getProperty("java.io.tmpdir"); String fileNamePrefix = prefix + System.nanoTime(); String fileName = fileNamePrefix; for (int i = 0; i < 1000; i++) { File tempDir = new File(parentTempDir, fileName); if (!tempDir.exists()) { return tempDir.getAbsolutePath(); } fileName = fileNamePrefix + "~" + i; } throw new IOException("Failed to create a temporary directory for file at " + new File(parentTempDir, fileNamePrefix)); } protected boolean deleteDir(File dir) { if (dir.isDirectory()) { String[] children = dir.list(); for (String child : children) { boolean success = deleteDir(new File(dir, child)); if (!success) { return false; } } } return dir.delete(); } public boolean isRunning() { return running; } public void destroy() throws Exception { stop(); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctxt = applicationContext; } }