package org.apereo.cas.adaptors.x509.authentication.ldap; import org.apereo.cas.adaptors.x509.authentication.ResourceCRLFetcher; import org.apereo.cas.util.EncodingUtils; import org.apereo.cas.util.LdapUtils; import org.ldaptive.ConnectionConfig; import org.ldaptive.ConnectionFactory; import org.ldaptive.DefaultConnectionFactory; import org.ldaptive.LdapAttribute; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.Response; import org.ldaptive.ResultCode; import org.ldaptive.SearchExecutor; import org.ldaptive.SearchResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import java.io.IOException; import java.net.URI; import java.net.URL; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.X509CRL; /** * Fetches a CRL from an LDAP instance. * * @author Daniel Fisher * @since 4.1 */ public class LdaptiveResourceCRLFetcher extends ResourceCRLFetcher { private static final Logger LOGGER = LoggerFactory.getLogger(LdaptiveResourceCRLFetcher.class); /** * Search exec that looks for the attribute. */ private final SearchExecutor searchExecutor; /** * The connection config to prep for connections. **/ private final ConnectionConfig connectionConfig; private final String certificateAttribute; /** * Instantiates a new Ldap resource cRL fetcher. * * @param connectionConfig the connection configuration * @param searchExecutor the search executor * @param attributeName the attribute name */ public LdaptiveResourceCRLFetcher(final ConnectionConfig connectionConfig, final SearchExecutor searchExecutor, final String attributeName) { this.connectionConfig = connectionConfig; this.searchExecutor = searchExecutor; this.certificateAttribute = attributeName; } @Override public X509CRL fetch(final Resource crl) throws IOException, CRLException, CertificateException { if (LdapUtils.isLdapConnectionUrl(crl.toString())) { return fetchCRLFromLdap(crl); } return super.fetch(crl); } @Override public X509CRL fetch(final URI crl) throws IOException, CRLException, CertificateException { if (LdapUtils.isLdapConnectionUrl(crl)) { return fetchCRLFromLdap(crl); } return super.fetch(crl); } @Override public X509CRL fetch(final URL crl) throws IOException, CRLException, CertificateException { if (LdapUtils.isLdapConnectionUrl(crl)) { return fetchCRLFromLdap(crl); } return super.fetch(crl); } @Override public X509CRL fetch(final String crl) throws IOException, CRLException, CertificateException { if (LdapUtils.isLdapConnectionUrl(crl)) { return fetchCRLFromLdap(crl); } return super.fetch(crl); } /** * Downloads a CRL from given LDAP url. * * @param r the resource that is the ldap url. * @return the x 509 cRL * @throws IOException the exception thrown if resources cant be fetched * @throws CRLException the exception thrown if resources cant be fetched * @throws CertificateException if connection to ldap fails, or attribute to get the revocation list is unavailable */ protected X509CRL fetchCRLFromLdap(final Object r) throws CertificateException, IOException, CRLException { try { final String ldapURL = r.toString(); LOGGER.debug("Fetching CRL from ldap [{}]", ldapURL); final Response<SearchResult> result = performLdapSearch(ldapURL); if (result.getResultCode() == ResultCode.SUCCESS) { final LdapEntry entry = result.getResult().getEntry(); final LdapAttribute attribute = entry.getAttribute(this.certificateAttribute); if (attribute.isBinary()) { LOGGER.debug("Located entry [{}]. Retrieving first attribute [{}]", entry, attribute); return fetchX509CRLFromAttribute(attribute); } LOGGER.warn("Found certificate attribute [{}] but it is not marked as a binary attribute", this.certificateAttribute); } LOGGER.debug("Failed to execute the search [{}]", result); throw new CertificateException("Failed to establish a connection ldap and search."); } catch (final LdapException e) { LOGGER.error(e.getMessage(), e); throw new CertificateException(e.getMessage()); } } /** * Gets x509 cRL from attribute. Retrieves the binary attribute value, * decodes it to base64, and fetches it as a byte-array resource. * * @param aval the attribute, which may be null if it's not found * @return the x 509 cRL from attribute * @throws IOException the exception thrown if resources cant be fetched * @throws CRLException the exception thrown if resources cant be fetched * @throws CertificateException if connection to ldap fails, or attribute to get the revocation list is unavailable */ protected X509CRL fetchX509CRLFromAttribute(final LdapAttribute aval) throws CertificateException, IOException, CRLException { if (aval != null && aval.isBinary()) { final byte[] val = aval.getBinaryValue(); if (val == null || val.length == 0) { throw new CertificateException("Empty attribute. Can not download CRL from ldap"); } final byte[] decoded64 = EncodingUtils.decodeBase64(val); if (decoded64 == null) { throw new CertificateException("Could not decode the attribute value to base64"); } LOGGER.debug("Retrieved CRL from ldap as byte array decoded in base64. Fetching..."); return super.fetch(new ByteArrayResource(decoded64)); } throw new CertificateException("Attribute not found. Can not retrieve CRL"); } /** * Executes an LDAP search against the supplied URL. * * @param ldapURL to search * @return search result * @throws LdapException if an error occurs performing the search */ protected Response<SearchResult> performLdapSearch(final String ldapURL) throws LdapException { final ConnectionFactory connectionFactory = prepareConnectionFactory(ldapURL); return this.searchExecutor.search(connectionFactory); } /** * Prepare a new LDAP connection. * * @param ldapURL the ldap uRL * @return connection factory */ protected ConnectionFactory prepareConnectionFactory(final String ldapURL) { final ConnectionConfig cc = ConnectionConfig.newConnectionConfig(this.connectionConfig); cc.setLdapUrl(ldapURL); return new DefaultConnectionFactory(cc); } }