/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.security.sts.claimsHandler; import java.io.FileInputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.cxf.sts.claims.ClaimsHandler; import org.codice.ddf.configuration.PropertyResolver; import org.forgerock.opendj.ldap.LDAPConnectionFactory; import org.forgerock.opendj.ldap.LdapException; import org.forgerock.util.Options; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.security.encryption.EncryptionService; /** * Creates and registers LDAP and Role claims handlers. */ public class ClaimsHandlerManager { public static final String URL = "url"; public static final String START_TLS = "startTls"; public static final String OVERRIDE_CERT_DN = "overrideCertDn"; public static final String LDAP_BIND_USER_DN = "ldapBindUserDn"; public static final String BIND_METHOD = "bindMethod"; public static final String REALM = "realm"; public static final String KDC_ADDRESS = "kdcAddress"; public static final String PASSWORD = "password"; public static final String LOGIN_USER_ATTRIBUTE = "loginUserAttribute"; public static final String MEMBER_USER_ATTRIBUTE = "membershipUserAttribute"; public static final String USER_BASE_DN = "userBaseDn"; public static final String OBJECT_CLASS = "objectClass"; public static final String MEMBER_NAME_ATTRIBUTE = "memberNameAttribute"; public static final String GROUP_BASE_DN = "groupBaseDn"; public static final String PROPERTY_FILE_LOCATION = "propertyFileLocation"; private static final Logger LOGGER = LoggerFactory.getLogger(ClaimsHandlerManager.class); private static final String PROTOCOL = "TLS"; private EncryptionService encryptService; private ServiceRegistration<ClaimsHandler> roleHandlerRegistration = null; private ServiceRegistration<ClaimsHandler> ldapHandlerRegistration = null; private Map<String, Object> ldapProperties = new HashMap<>(); /** * Creates a new instance of the ClaimsHandlerManager. * * @param encryptService Encryption service used to decrypt passwords from the configurations. */ public ClaimsHandlerManager(EncryptionService encryptService) { this.encryptService = encryptService; } /** * Callback method that is called when configuration is updated. Also called by the * blueprint init-method when all properties have been set. * * @param props Map of properties. */ public void update(Map<String, Object> props) { if (props == null) { return; } LOGGER.debug( "Received an updated set of configurations for the LDAP/Role Claims Handlers."); String url = new PropertyResolver((String) props.get(ClaimsHandlerManager.URL)).toString(); Boolean startTls; if (props.get(ClaimsHandlerManager.START_TLS) instanceof String) { startTls = Boolean.valueOf((String) props.get(ClaimsHandlerManager.START_TLS)); } else { startTls = (Boolean) props.get(ClaimsHandlerManager.START_TLS); } String userDn = (String) props.get(ClaimsHandlerManager.LDAP_BIND_USER_DN); String password = (String) props.get(ClaimsHandlerManager.PASSWORD); String userBaseDn = (String) props.get(ClaimsHandlerManager.USER_BASE_DN); String objectClass = (String) props.get(ClaimsHandlerManager.OBJECT_CLASS); String memberNameAttribute = (String) props.get(ClaimsHandlerManager.MEMBER_NAME_ATTRIBUTE); String groupBaseDn = (String) props.get(ClaimsHandlerManager.GROUP_BASE_DN); String loginUserAttribute = (String) props.get(ClaimsHandlerManager.LOGIN_USER_ATTRIBUTE); String membershipUserAttribute = (String) props.get( ClaimsHandlerManager.MEMBER_USER_ATTRIBUTE); String propertyFileLocation = (String) props.get( ClaimsHandlerManager.PROPERTY_FILE_LOCATION); String bindMethod = (String) props.get(ClaimsHandlerManager.BIND_METHOD); String realm = (props.get(ClaimsHandlerManager.REALM) != null) ? (String) props.get(ClaimsHandlerManager.REALM) : ""; String kdcAddress = (props.get(ClaimsHandlerManager.KDC_ADDRESS) != null) ? (String) props.get(ClaimsHandlerManager.KDC_ADDRESS) : ""; if ("GSSAPI SASL".equals(bindMethod) && (StringUtils.isEmpty(realm) || StringUtils.isEmpty( kdcAddress))) { LOGGER.warn( "LDAP connection will fail. GSSAPI SASL connection requires Kerberos Realm and KDC Address."); } Boolean overrideCertDn; if (props.get(ClaimsHandlerManager.OVERRIDE_CERT_DN) instanceof String) { overrideCertDn = Boolean.valueOf( (String) props.get(ClaimsHandlerManager.OVERRIDE_CERT_DN)); } else { overrideCertDn = (Boolean) props.get(ClaimsHandlerManager.OVERRIDE_CERT_DN); } if (startTls == null) { startTls = false; } if (overrideCertDn == null) { overrideCertDn = false; } try { if (encryptService != null) { password = encryptService.decryptValue(password); } LDAPConnectionFactory connection1 = createLdapConnectionFactory(url, startTls); LDAPConnectionFactory connection2 = createLdapConnectionFactory(url, startTls); registerRoleClaimsHandler(connection1, propertyFileLocation, userBaseDn, loginUserAttribute, membershipUserAttribute, objectClass, memberNameAttribute, groupBaseDn, userDn, password, overrideCertDn, bindMethod, realm, kdcAddress); registerLdapClaimsHandler(connection2, propertyFileLocation, userBaseDn, loginUserAttribute, userDn, password, overrideCertDn, bindMethod, realm, kdcAddress); } catch (Exception e) { LOGGER.warn( "Experienced error while configuring claims handlers. Handlers are NOT configured and claim retrieval will not work. Check LDAP configuration.", e); } } public void destroy() { } protected LDAPConnectionFactory createLdapConnectionFactory(String url, Boolean startTls) throws LdapException { boolean useSsl = url.startsWith("ldaps"); boolean useTls = !url.startsWith("ldaps") && startTls; Options lo = Options.defaultOptions(); try { if (useSsl || useTls) { lo.set(LDAPConnectionFactory.SSL_CONTEXT, SSLContext.getDefault()); } } catch (GeneralSecurityException e) { LOGGER.info("Error encountered while configuring SSL. Secure connection will fail.", e); } lo.set(LDAPConnectionFactory.SSL_USE_STARTTLS, useTls); lo.set(LDAPConnectionFactory.SSL_ENABLED_CIPHER_SUITES, Arrays.asList(System.getProperty("https.cipherSuites") .split(","))); lo.set(LDAPConnectionFactory.SSL_ENABLED_PROTOCOLS, Arrays.asList(System.getProperty("https.protocols") .split(","))); lo.set(LDAPConnectionFactory.TRANSPORT_PROVIDER_CLASS_LOADER, ClaimsHandlerManager.class.getClassLoader()); String host = url.substring(url.indexOf("://") + 3, url.lastIndexOf(":")); Integer port = useSsl ? 636 : 389; try { port = Integer.valueOf(url.substring(url.lastIndexOf(":") + 1)); } catch (NumberFormatException ignore) { } return new LDAPConnectionFactory(host, port, lo); } /** * Registers a new Role-based ClaimsHandler. * * @param connection LdapTemplate used to query ldap for the roles. * @param propertyFileLoc File location of the property file. * @param userBaseDn Base DN to determine the roles. * @param loginUserAttribute Identifier that defines the user. * @param groupBaseDn Base DN of the group. */ private void registerRoleClaimsHandler(LDAPConnectionFactory connection, String propertyFileLoc, String userBaseDn, String loginUserAttribute, String membershipUserAttribute, String objectClass, String memberNameAttribute, String groupBaseDn, String userDn, String password, boolean overrideCertDn, String bindMethod, String realm, String kdcAddress) { RoleClaimsHandler roleHandler = new RoleClaimsHandler(); roleHandler.setLdapConnectionFactory(connection); roleHandler.setPropertyFileLocation(propertyFileLoc); roleHandler.setUserBaseDn(userBaseDn); roleHandler.setLoginUserAttribute(loginUserAttribute); roleHandler.setMembershipUserAttribute(membershipUserAttribute); roleHandler.setObjectClass(objectClass); roleHandler.setMemberNameAttribute(memberNameAttribute); roleHandler.setGroupBaseDn(groupBaseDn); roleHandler.setBindUserDN(userDn); roleHandler.setBindUserCredentials(password); roleHandler.setOverrideCertDn(overrideCertDn); roleHandler.setBindMethod(bindMethod); roleHandler.setKerberosRealm(realm); roleHandler.setKdcAddress(kdcAddress); LOGGER.debug("Registering new role claims handler."); roleHandlerRegistration = registerClaimsHandler(roleHandler, roleHandlerRegistration); } /** * Registers a new Ldap-based Claims Handler. * * @param connection LdapTemplate used to query ldap for the roles. * @param propertyFileLoc File location of the property file. * @param userBaseDn Base DN to determine the roles. * @param userNameAttr Identifier that defines the user. */ private void registerLdapClaimsHandler(LDAPConnectionFactory connection, String propertyFileLoc, String userBaseDn, String userNameAttr, String userDn, String password, boolean overrideCertDn, String bindMethod, String realm, String kdcAddress) { LdapClaimsHandler ldapHandler = new LdapClaimsHandler(); ldapHandler.setLdapConnectionFactory(connection); ldapHandler.setPropertyFileLocation(propertyFileLoc); ldapHandler.setUserBaseDN(userBaseDn); ldapHandler.setUserNameAttribute(userNameAttr); ldapHandler.setBindUserDN(userDn); ldapHandler.setBindUserCredentials(password); ldapHandler.setOverrideCertDn(overrideCertDn); ldapHandler.setBindMethod(bindMethod); ldapHandler.setKerberosRealm(realm); ldapHandler.setKdcAddress(kdcAddress); LOGGER.debug("Registering new ldap claims handler."); ldapHandlerRegistration = registerClaimsHandler(ldapHandler, ldapHandlerRegistration); } /** * Utility method that registers a ClaimsHandler and returns the service * registration. * * @param handler Handler that should be registered. * @param registration Previous registration, will be used to unregister if not null. * @return new registration for the service. */ private ServiceRegistration<ClaimsHandler> registerClaimsHandler(ClaimsHandler handler, ServiceRegistration<ClaimsHandler> registration) { BundleContext context = getContext(); if (null != context) { if (registration != null) { ClaimsHandler oldClaimsHandler = context.getService(registration.getReference()); if (oldClaimsHandler instanceof RoleClaimsHandler) { ((RoleClaimsHandler) oldClaimsHandler).disconnect(); } else if (oldClaimsHandler instanceof LdapClaimsHandler) { ((LdapClaimsHandler) oldClaimsHandler).disconnect(); } registration.unregister(); } return context.registerService(ClaimsHandler.class, handler, null); } return null; } protected BundleContext getContext() { Bundle cxfBundle = FrameworkUtil.getBundle(ClaimsHandlerManager.class); if (cxfBundle != null) { return cxfBundle.getBundleContext(); } return null; } public void setUrl(String url) { LOGGER.trace("Setting url: {}", url); ldapProperties.put(URL, url); } public void setStartTls(boolean startTls) { LOGGER.trace("Setting startTls: {}", startTls); ldapProperties.put(START_TLS, startTls); } public void setStartTls(String startTls) { LOGGER.trace("Setting startTls: {}", startTls); ldapProperties.put(START_TLS, startTls); } public void setLdapBindUserDn(String bindUserDn) { LOGGER.trace("Setting bindUserDn: {}", bindUserDn); ldapProperties.put(LDAP_BIND_USER_DN, bindUserDn); } public void setPassword(String password) { LOGGER.trace("Setting password: [HIDDEN]"); ldapProperties.put(PASSWORD, password); } public void setLoginUserAttribute(String loginUserAttribute) { LOGGER.trace("Setting userNameAttribute: {}", loginUserAttribute); ldapProperties.put(LOGIN_USER_ATTRIBUTE, loginUserAttribute); } public void setMembershipUserAttribute(String membershipUserAttribute) { LOGGER.trace("Setting userNameAttribute: {}", membershipUserAttribute); ldapProperties.put(MEMBER_USER_ATTRIBUTE, membershipUserAttribute); } public void setUserBaseDn(String userBaseDn) { LOGGER.trace("Setting userBaseDn: {}", userBaseDn); ldapProperties.put(USER_BASE_DN, userBaseDn); } public void setObjectClass(String objectClass) { LOGGER.trace("Setting objectClass: {}", objectClass); ldapProperties.put(OBJECT_CLASS, objectClass); } public void setMemberNameAttribute(String memberNameAttribute) { LOGGER.trace("Setting memberNameAttribute: {}", memberNameAttribute); ldapProperties.put(MEMBER_NAME_ATTRIBUTE, memberNameAttribute); } public void setGroupBaseDn(String groupBaseDn) { LOGGER.trace("Setting groupBaseDn: {}", groupBaseDn); ldapProperties.put(GROUP_BASE_DN, groupBaseDn); } public void setPropertyFileLocation(String propertyFileLocation) { LOGGER.trace("Setting propertyFileLocation: {}", propertyFileLocation); ldapProperties.put(PROPERTY_FILE_LOCATION, propertyFileLocation); } public void setBindMethod(String bindMethod) { LOGGER.trace("Setting bindMethod: {}", bindMethod); ldapProperties.put(BIND_METHOD, bindMethod); } public void setRealm(String realm) { LOGGER.trace("Setting realm: {}", realm); ldapProperties.put(REALM, realm); } public void setKdcAddress(String kdcAddress) { LOGGER.trace("Setting kdcAddress: {}", kdcAddress); ldapProperties.put(KDC_ADDRESS, kdcAddress); } public void setOverrideCertDn(boolean overrideCertDn) { LOGGER.trace("Setting propertyFileLocation: {}", overrideCertDn); ldapProperties.put(OVERRIDE_CERT_DN, overrideCertDn); } public void configure() { LOGGER.trace("configure method called - calling update"); update(ldapProperties); } public static KeyManagerFactory createKeyManagerFactory(String keyStoreLoc, String keyStorePass) throws IOException { KeyManagerFactory kmf; try { // keystore stuff KeyStore keyStore = KeyStore.getInstance( System.getProperty("javax.net.ssl.keyStoreType")); LOGGER.debug("keyStoreLoc = {}", keyStoreLoc); FileInputStream keyFIS = new FileInputStream(keyStoreLoc); try { LOGGER.debug("Loading keyStore"); keyStore.load(keyFIS, keyStorePass.toCharArray()); } catch (CertificateException e) { throw new IOException("Unable to load certificates from keystore. " + keyStoreLoc, e); } finally { IOUtils.closeQuietly(keyFIS); } kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, keyStorePass.toCharArray()); LOGGER.debug("key manager factory initialized"); } catch (NoSuchAlgorithmException e) { throw new IOException("Problems creating SSL socket. Usually this is " + "referring to the certificate sent by the server not being trusted by the client.", e); } catch (UnrecoverableKeyException e) { throw new IOException("Unable to load keystore. " + keyStoreLoc, e); } catch (KeyStoreException e) { throw new IOException("Unable to read keystore. " + keyStoreLoc, e); } return kmf; } public static TrustManagerFactory createTrustManagerFactory(String trustStoreLoc, String trustStorePass) throws IOException { TrustManagerFactory tmf; try { // truststore stuff KeyStore trustStore = KeyStore.getInstance( System.getProperty("javax.net.ssl.keyStoreType")); LOGGER.debug("trustStoreLoc = {}", trustStoreLoc); FileInputStream trustFIS = new FileInputStream(trustStoreLoc); try { LOGGER.debug("Loading trustStore"); trustStore.load(trustFIS, trustStorePass.toCharArray()); } catch (CertificateException e) { throw new IOException( "Unable to load certificates from truststore. " + trustStoreLoc, e); } finally { IOUtils.closeQuietly(trustFIS); } tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); LOGGER.debug("trust manager factory initialized"); } catch (NoSuchAlgorithmException e) { throw new IOException("Problems creating SSL socket. Usually this is " + "referring to the certificate sent by the server not being trusted by the client.", e); } catch (KeyStoreException e) { throw new IOException("Unable to read keystore. " + trustStoreLoc, e); } return tmf; } }