/** * 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.cxf.xkms.x509.repo.ldap; import java.io.ByteArrayInputStream; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.directory.BasicAttributes; import javax.naming.directory.SearchResult; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.xkms.handlers.Applications; import org.apache.cxf.xkms.model.xkms.UseKeyWithType; import org.apache.cxf.xkms.x509.repo.CertificateRepo; public class LdapCertificateRepo implements CertificateRepo { private static final Logger LOG = LogUtils.getL7dLogger(LdapCertificateRepo.class); private static final String ATTR_OBJECT_CLASS = "objectClass"; private LdapSearch ldapSearch; private String rootDN; private CertificateFactory certificateFactory; private final LdapSchemaConfig ldapConfig; private final String filterUIDTemplate; private final String filterIssuerSerialTemplate; /** * * @param ldapSearch * @param rootDN rootDN of the LDAP tree * @param trustedAuthorityFilter * @param intermediateFilter * @param attrName */ public LdapCertificateRepo(LdapSearch ldapSearch, LdapSchemaConfig ldapConfig, String rootDN) { this.ldapSearch = ldapSearch; this.ldapSearch = ldapSearch; this.ldapConfig = ldapConfig; this.rootDN = rootDN; try { this.certificateFactory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { LOG.log(Level.SEVERE, e.getMessage(), e); } filterUIDTemplate = "(" + ldapConfig.getAttrUID() + "=%s)"; filterIssuerSerialTemplate = "(&(" + ldapConfig.getAttrIssuerID() + "=%s)(" + ldapConfig.getAttrSerialNumber() + "=%s))"; } @Override public List<X509Certificate> getTrustedCaCerts() { return getCertificatesFromLdap(rootDN, ldapConfig.getTrustedAuthorityFilter(), ldapConfig.getAttrCrtBinary()); } @Override public List<X509Certificate> getCaCerts() { return getCertificatesFromLdap(rootDN, ldapConfig.getIntermediateFilter(), ldapConfig.getAttrCrtBinary()); } @Override public List<X509CRL> getCRLs() { return getCRLsFromLdap(rootDN, ldapConfig.getCrlFilter(), ldapConfig.getAttrCrlBinary()); } private List<X509Certificate> getCertificatesFromLdap(String tmpRootDN, String tmpFilter, String tmpAttrName) { try { List<X509Certificate> certificates = new ArrayList<>(); NamingEnumeration<SearchResult> answer = ldapSearch.searchSubTree(tmpRootDN, tmpFilter); while (answer.hasMore()) { SearchResult sr = answer.next(); Attributes attrs = sr.getAttributes(); Attribute attribute = attrs.get(tmpAttrName); if (attribute != null) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream( (byte[]) attribute.get())); certificates.add(certificate); } } return certificates; } catch (CertificateException e) { throw new RuntimeException(e.getMessage(), e); } catch (NamingException e) { throw new RuntimeException(e.getMessage(), e); } } private List<X509CRL> getCRLsFromLdap(String tmpRootDN, String tmpFilter, String tmpAttrName) { try { List<X509CRL> crls = new ArrayList<>(); NamingEnumeration<SearchResult> answer = ldapSearch.searchSubTree(tmpRootDN, tmpFilter); while (answer.hasMore()) { SearchResult sr = answer.next(); Attributes attrs = sr.getAttributes(); Attribute attribute = attrs.get(tmpAttrName); if (attribute != null) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509CRL crl = (X509CRL) cf.generateCRL(new ByteArrayInputStream( (byte[]) attribute.get())); crls.add(crl); } } return crls; } catch (CertificateException e) { throw new RuntimeException(e.getMessage(), e); } catch (NamingException e) { throw new RuntimeException(e.getMessage(), e); } catch (CRLException e) { throw new RuntimeException(e.getMessage(), e); } } private void saveCertificate(X509Certificate cert, String dn, Map<String, String> appAttrs) { Attributes attribs = new BasicAttributes(); attribs.put(new BasicAttribute(ATTR_OBJECT_CLASS, ldapConfig.getCertObjectClass())); attribs.put(new BasicAttribute(ldapConfig.getAttrUID(), cert.getSubjectX500Principal().getName())); attribs.put(new BasicAttribute(ldapConfig.getAttrIssuerID(), cert.getIssuerX500Principal().getName())); attribs.put(new BasicAttribute(ldapConfig.getAttrSerialNumber(), cert.getSerialNumber().toString(16))); addConstantAttributes(ldapConfig.getConstAttrNamesCSV(), ldapConfig.getConstAttrValuesCSV(), attribs); if (appAttrs != null && !appAttrs.isEmpty()) { for (Map.Entry<String, String> entry : appAttrs.entrySet()) { attribs.put(new BasicAttribute(entry.getKey(), entry.getValue())); } } try { attribs.put(new BasicAttribute(ldapConfig.getAttrCrtBinary(), cert.getEncoded())); ldapSearch.bind(dn, attribs); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } private void addConstantAttributes(String names, String values, Attributes attribs) { String[] arrNames = names.split(","); String[] arrValues = values.split(","); if (arrNames.length != arrValues.length) { throw new IllegalArgumentException( String.format("Inconsintent constant attributes: %s; %s", names, values)); } for (int i = 0; i < arrNames.length; i++) { attribs.put(new BasicAttribute(arrNames[i], arrValues[i])); } } @Override public X509Certificate findBySubjectDn(String id) { X509Certificate cert = null; try { String dn = id; if (rootDN != null && !rootDN.isEmpty()) { dn = dn + "," + rootDN; } cert = getCertificateForDn(dn); } catch (NamingException e) { // Not found } // Try to find certificate by search for uid attribute try { cert = getCertificateForUIDAttr(id); } catch (NamingException e) { // Not found } return cert; } @Override public X509Certificate findByServiceName(String serviceName) { X509Certificate cert = null; try { String dn = getDnForIdentifier(serviceName); cert = getCertificateForDn(dn); } catch (NamingException e) { // Not found } // Try to find certificate by search for uid attribute try { String uidAttr = String.format(ldapConfig.getServiceCertUIDTemplate(), serviceName); cert = getCertificateForUIDAttr(uidAttr); } catch (NamingException e) { // Not found } return cert; } @Override public X509Certificate findByEndpoint(String endpoint) { X509Certificate cert = null; String filter = String.format("(%s=%s)", ldapConfig.getAttrEndpoint(), endpoint); try { Attribute attr = ldapSearch.findAttribute(rootDN, filter, ldapConfig.getAttrCrtBinary()); cert = getCert(attr); } catch (NamingException e) { // Not found } return cert; } private String getDnForIdentifier(String id) { String escapedIdentifier = id.replaceAll("\\/", Matcher.quoteReplacement("\\/")); return String.format(ldapConfig.getServiceCertRDNTemplate(), escapedIdentifier) + "," + rootDN; } private X509Certificate getCertificateForDn(String dn) throws NamingException { Attribute attr = ldapSearch.getAttribute(dn, ldapConfig.getAttrCrtBinary()); return getCert(attr); } private X509Certificate getCertificateForUIDAttr(String uid) throws NamingException { String filter = String.format(filterUIDTemplate, uid); Attribute attr = ldapSearch.findAttribute(rootDN, filter, ldapConfig.getAttrCrtBinary()); return getCert(attr); } @Override public X509Certificate findByIssuerSerial(String issuer, String serial) { if (issuer == null || serial == null) { throw new IllegalArgumentException("Issuer and serial applications are expected in request"); } String filter = String.format(filterIssuerSerialTemplate, issuer, serial); try { Attribute attr = ldapSearch.findAttribute(rootDN, filter, ldapConfig.getAttrCrtBinary()); return getCert(attr); } catch (NamingException e) { throw new RuntimeException(e.getMessage(), e); } } private X509Certificate getCert(Attribute attr) { if (attr == null) { return null; } byte[] data; try { data = (byte[]) attr.get(); } catch (NamingException e) { throw new RuntimeException(e.getMessage(), e); } if (data == null) { return null; } try { return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(data)); } catch (CertificateException e) { throw new RuntimeException("Error deserializing certificate: " + e.getMessage(), e); } } @Override public void saveCertificate(X509Certificate cert, UseKeyWithType key) { Applications application = Applications.fromUri(key.getApplication()); String dn = null; Map<String, String> attrs = new HashMap<>(); if (application == Applications.PKIX) { dn = key.getIdentifier() + "," + rootDN; } else if (application == Applications.SERVICE_NAME) { dn = getDnForIdentifier(key.getIdentifier()); } else if (application == Applications.SERVICE_ENDPOINT) { attrs.put(ldapConfig.getAttrEndpoint(), key.getIdentifier()); dn = getDnForIdentifier(key.getIdentifier()); } else { throw new IllegalArgumentException("Unsupported Application " + application); } saveCertificate(cert, dn, attrs); } }