cxf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cschnei...@apache.org
Subject [1/2] CXF-5734 Allow to plug in alternative certificateRepo using OSGi service
Date Wed, 07 May 2014 12:07:14 GMT
Repository: cxf
Updated Branches:
  refs/heads/master 148bbc8f4 -> 037abfeb7


http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-handlers/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPSearchTest.java
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-handlers/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPSearchTest.java
b/services/xkms/xkms-x509-handlers/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPSearchTest.java
deleted file mode 100644
index e029023..0000000
--- a/services/xkms/xkms-x509-handlers/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPSearchTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * 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.net.URISyntaxException;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchResult;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * Tests need a real ldap server
- */
-public class LDAPSearchTest {
-    @Test
-    @Ignore
-    public void testSearch() throws URISyntaxException, NamingException {
-        LdapSearch ldapSearch = new LdapSearch("ldap://localhost:2389",
-                                               "cn=Directory Manager,dc=example,dc=com",
"test", 2);
-        NamingEnumeration<SearchResult> answer = ldapSearch.searchSubTree("dc=example,
dc=com",
-                                                                          "(cn=Testuser)");
-        while (answer.hasMore()) {
-            SearchResult sr = answer.next();
-            Attributes attrs = sr.getAttributes();
-            Attribute cn = attrs.get("sn");
-            System.out.println(cn.get());
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-repo-ldap/pom.xml
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-repo-ldap/pom.xml b/services/xkms/xkms-x509-repo-ldap/pom.xml
new file mode 100644
index 0000000..ec2c417
--- /dev/null
+++ b/services/xkms/xkms-x509-repo-ldap/pom.xml
@@ -0,0 +1,35 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-parent</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+        <relativePath>../../../parent/pom.xml</relativePath>
+    </parent>
+    <groupId>org.apache.cxf.services.xkms</groupId>
+	<artifactId>cxf-services-xkms-x509-repo-ldap</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache CXF XKMS LDAP repository</name>
+    <url>http://cxf.apache.org</url>
+    
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.cxf.services.xkms</groupId>
+			<artifactId>cxf-services-xkms-common</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.easymock</groupId>
+			<artifactId>easymock</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapCertificateRepo.java
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapCertificateRepo.java
b/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapCertificateRepo.java
new file mode 100644
index 0000000..b28e6ba
--- /dev/null
+++ b/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapCertificateRepo.java
@@ -0,0 +1,299 @@
+/**
+ * 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<X509Certificate>();
+            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<X509CRL>();
+            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 (String attrName : appAttrs.keySet()) {
+                attribs.put(new BasicAttribute(attrName, appAttrs.get(attrName)));
+            }
+        }
+        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<String, String>();
+        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);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapSchemaConfig.java
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapSchemaConfig.java
b/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapSchemaConfig.java
new file mode 100644
index 0000000..6dfe653
--- /dev/null
+++ b/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapSchemaConfig.java
@@ -0,0 +1,149 @@
+/**
+ * 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;
+
+public class LdapSchemaConfig {
+    private String certObjectClass = "inetOrgPerson";
+    private String attrUID = "uid";
+    private String attrIssuerID = "manager";
+    private String attrSerialNumber = "employeeNumber";
+    private String attrEndpoint = "labeledURI";
+    private String attrCrtBinary = "userCertificate;binary";
+    private String attrCrlBinary = "certificateRevocationList;binary";
+    private String constAttrNamesCSV = "sn";
+    private String constAttrValuesCSV = "X509 certificate";
+    private String serviceCertRDNTemplate = "cn=%s,ou=services";
+    private String serviceCertUIDTemplate = "cn=%s";
+    private String trustedAuthorityFilter = "(&(objectClass=inetOrgPerson)(ou:dn:=CAs))";
+    private String intermediateFilter = "(objectClass=*)";
+    private String crlFilter = "(&(objectClass=inetOrgPerson)(ou:dn:=CAs))";
+
+    public String getCertObjectClass() {
+        return certObjectClass;
+    }
+
+    public void setCertObjectClass(String crtObjectClass) {
+        this.certObjectClass = crtObjectClass;
+    }
+
+    public String getAttrUID() {
+        return attrUID;
+    }
+
+    public void setAttrUID(String attrUID) {
+        this.attrUID = attrUID;
+    }
+
+    public String getAttrIssuerID() {
+        return attrIssuerID;
+    }
+
+    public void setAttrIssuerID(String attrIssuerID) {
+        this.attrIssuerID = attrIssuerID;
+    }
+
+    public String getAttrSerialNumber() {
+        return attrSerialNumber;
+    }
+
+    public void setAttrSerialNumber(String attrSerialNumber) {
+        this.attrSerialNumber = attrSerialNumber;
+    }
+
+    public String getAttrCrtBinary() {
+        return attrCrtBinary;
+    }
+
+    public void setAttrCrtBinary(String attrCrtBinary) {
+        this.attrCrtBinary = attrCrtBinary;
+    }
+
+    public String getConstAttrNamesCSV() {
+        return constAttrNamesCSV;
+    }
+
+    public void setConstAttrNamesCSV(String constAttrNamesCSV) {
+        this.constAttrNamesCSV = constAttrNamesCSV;
+    }
+
+    public String getConstAttrValuesCSV() {
+        return constAttrValuesCSV;
+    }
+
+    public void setConstAttrValuesCSV(String constAttrValuesCSV) {
+        this.constAttrValuesCSV = constAttrValuesCSV;
+    }
+
+    public String getServiceCertRDNTemplate() {
+        return serviceCertRDNTemplate;
+    }
+
+    public void setServiceCertRDNTemplate(String serviceCrtRDNTemplate) {
+        this.serviceCertRDNTemplate = serviceCrtRDNTemplate;
+    }
+
+    public String getServiceCertUIDTemplate() {
+        return serviceCertUIDTemplate;
+    }
+
+    public void setServiceCertUIDTemplate(String serviceCrtUIDTemplate) {
+        this.serviceCertUIDTemplate = serviceCrtUIDTemplate;
+    }
+
+    public String getTrustedAuthorityFilter() {
+        return trustedAuthorityFilter;
+    }
+
+    public void setTrustedAuthorityFilter(String trustedAuthorityFilter) {
+        this.trustedAuthorityFilter = trustedAuthorityFilter;
+    }
+
+    public String getIntermediateFilter() {
+        return intermediateFilter;
+    }
+
+    public void setIntermediateFilter(String intermediateFilter) {
+        this.intermediateFilter = intermediateFilter;
+    }
+
+    public String getCrlFilter() {
+        return crlFilter;
+    }
+
+    public void setCrlFilter(String crlFilter) {
+        this.crlFilter = crlFilter;
+    }
+
+    public String getAttrCrlBinary() {
+        return attrCrlBinary;
+    }
+
+    public void setAttrCrlBinary(String attrCrlBinary) {
+        this.attrCrlBinary = attrCrlBinary;
+    }
+
+    public String getAttrEndpoint() {
+        return attrEndpoint;
+    }
+
+    public void setAttrEndpoint(String attrEndpoint) {
+        this.attrEndpoint = attrEndpoint;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapSearch.java
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapSearch.java
b/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapSearch.java
new file mode 100644
index 0000000..7e42db3
--- /dev/null
+++ b/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapSearch.java
@@ -0,0 +1,165 @@
+/**
+ * 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.util.Hashtable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.naming.CommunicationException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.xkms.exception.XKMSException;
+import org.apache.cxf.xkms.model.xkms.ResultMajorEnum;
+import org.apache.cxf.xkms.model.xkms.ResultMinorEnum;
+
+public class LdapSearch {
+    private static final String SECURITY_AUTHENTICATION = "simple";
+    private static final Logger LOG = LogUtils.getL7dLogger(LdapSearch.class);
+    
+    private String ldapuri;
+    private String bindDN;
+    private String bindPassword;
+    private int numRetries;
+    
+    private InitialDirContext dirContext;
+
+    public LdapSearch(String ldapuri, String bindDN, String bindPassword, int numRetries)
{
+        this.ldapuri = ldapuri;
+        this.bindDN = bindDN;
+        this.bindPassword = bindPassword;
+        this.numRetries = numRetries;
+    }
+
+    //CHECKSTYLE:OFF
+    private InitialDirContext createInitialContext() throws NamingException {
+        Hashtable<String, String> env = new Hashtable<String, String>(5);
+        env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put(javax.naming.Context.PROVIDER_URL, ldapuri.toString());
+        env.put(javax.naming.Context.SECURITY_AUTHENTICATION, SECURITY_AUTHENTICATION);
+        env.put(javax.naming.Context.SECURITY_PRINCIPAL, bindDN);
+        env.put(javax.naming.Context.SECURITY_CREDENTIALS, bindPassword);
+        return new InitialLdapContext(env, null);
+    }
+    //CHECKSTYLE:ON
+
+    public NamingEnumeration<SearchResult> searchSubTree(String rootEntry, String filter)
throws NamingException {
+        int retry = 0;
+        while (true) {
+            try {
+                if (this.dirContext == null) {
+                    this.dirContext = createInitialContext();
+                }
+                SearchControls ctls = new SearchControls();
+                ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+                return dirContext.search(rootEntry, filter, ctls);
+            } catch (CommunicationException e) {
+                LOG.log(Level.WARNING, "Error in ldap search: " + e.getMessage(), e);
+                this.dirContext = null;
+                retry++;
+                if (retry >= numRetries) {
+                    throw new XKMSException(ResultMajorEnum.HTTP_WWW_W_3_ORG_2002_03_XKMS_RECEIVER,
+                                            ResultMinorEnum.HTTP_WWW_W_3_ORG_2002_03_XKMS_FAILURE,
"Backend failure");
+                }
+            }
+        }
+    }
+    
+    public Attributes getAttributes(String dn) throws NamingException {
+        int retry = 0;
+        while (true) {
+            try {
+                if (this.dirContext == null) {
+                    this.dirContext = createInitialContext();
+                }
+                return dirContext.getAttributes(dn);
+            } catch (CommunicationException e) {
+                LOG.log(Level.WARNING, "Error in ldap search: " + e.getMessage(), e);
+                this.dirContext = null;
+                retry++;
+                if (retry >= numRetries) {
+                    throw new XKMSException(ResultMajorEnum.HTTP_WWW_W_3_ORG_2002_03_XKMS_RECEIVER,
+                                            ResultMinorEnum.HTTP_WWW_W_3_ORG_2002_03_XKMS_FAILURE,
"Backend failure");
+                }
+            }
+        }
+    }
+    
+    public Attribute getAttribute(String dn, String attrName) throws NamingException {
+        Attribute attr = getAttributes(dn).get(attrName);
+        if (attr != null) {
+            return attr;
+        }
+        throw new RuntimeException("Did not find a matching attribute for dn: " + dn 
+                                   + " attributeName: " + attrName);
+    }
+    
+    public Attributes findAttributes(String rootDN, String filter) throws NamingException
{
+        NamingEnumeration<SearchResult> answer = searchSubTree(rootDN, filter);
+        if (answer.hasMore()) {
+            SearchResult sr = answer.next();
+            return sr.getAttributes();
+        } else {
+            return null;
+        }
+    }
+
+    public Attribute findAttribute(String rootDN, String filter, String attrName) throws
NamingException {
+        Attributes attrs = findAttributes(rootDN, filter);
+        if (attrs != null) {
+            Attribute attr = attrs.get(attrName);
+            if (attr == null) {
+                throw new RuntimeException("Did not find a matching attribute for root: "
+ rootDN 
+                                           + " filter: " + filter + " attributeName: " +
attrName);
+            }
+            return attr;
+        } 
+        return null;
+    }
+
+    public void bind(String dn, Attributes attribs) throws NamingException {
+        int retry = 0;
+        while (true) {
+            try {
+                if (this.dirContext == null) {
+                    this.dirContext = createInitialContext();
+                }
+                dirContext.bind(dn, null, attribs);
+                return;
+            } catch (CommunicationException e) {
+                LOG.log(Level.WARNING, "Error in ldap search: " + e.getMessage(), e);
+                this.dirContext = null;
+                retry++;
+                if (retry >= numRetries) {
+                    throw new XKMSException(ResultMajorEnum.HTTP_WWW_W_3_ORG_2002_03_XKMS_RECEIVER,
+                                            ResultMinorEnum.HTTP_WWW_W_3_ORG_2002_03_XKMS_FAILURE,
"Backend failure");
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-repo-ldap/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-repo-ldap/src/main/resources/OSGI-INF/blueprint/blueprint.xml
b/services/xkms/xkms-x509-repo-ldap/src/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 0000000..ec2d13c
--- /dev/null
+++ b/services/xkms/xkms-x509-repo-ldap/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!-- 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. -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cxf="http://cxf.apache.org/blueprint/core" xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0" xmlns:ext="http://www.osgi.org/xmlns/blueprint-ext/v1.1.0"
xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
                        http://www.osgi.org/xmlns/blueprint-ext/v1.1.0 https://svn.apache.org/repos/asf/aries/tags/blueprint-0.3.1/blueprint-core/src/main/resources/org/apache/aries/blueprint/ext/blueprint-ext.xsd
                          http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.0.0.xsd
                        http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd
                        http://cxf.apache.org/blue
 print/core http://cxf.apache.org/schemas/blueprint/core.xsd ">
+    <cm:property-placeholder persistent-id="org.apache.cxf.xkms.ldap" update-strategy="reload">
+        <cm:default-properties>
+            <cm:property name="xkms.ldap.url" value="tcp:localhost:389"/>
+            <cm:property name="xkms.ldap.user" value=""/>
+            <cm:property name="xkms.ldap.pwd" value=""/>
+            <cm:property name="xkms.ldap.retry" value="2"/>
+            <cm:property name="xkms.ldap.rootDN" value=""/>
+        </cm:default-properties>
+    </cm:property-placeholder>
+    <bean id="ldapSearch" class="org.apache.cxf.xkms.x509.repo.ldap.LdapSearch">
+        <argument value="${xkms.ldap.url}"/>
+        <argument value="${xkms.ldap.user}"/>
+        <argument value="${xkms.ldap.pwd}"/>
+        <argument value="${xkms.ldap.retry}"/>
+    </bean>
+    <bean id="ldapSchemaConfig" class="org.apache.cxf.xkms.x509.repo.ldap.LdapSchemaConfig">
+        <property name="certObjectClass" value="${xkms.ldap.schema.certObjectClass}"/>
+        <property name="attrUID" value="${xkms.ldap.schema.attrUID}"/>
+        <property name="attrIssuerID" value="${xkms.ldap.schema.attrIssuerID}"/>
+        <property name="attrSerialNumber" value="${xkms.ldap.schema.attrSerialNumber}"/>
+        <property name="attrEndpoint" value="${xkms.ldap.schema.attrEndpoint}"/>
+        <property name="attrCrtBinary" value="${xkms.ldap.schema.attrCrtBinary}"/>
+        <property name="attrCrlBinary" value="${xkms.ldap.schema.attrCrlBinary}"/>
+        <property name="constAttrNamesCSV" value="${xkms.ldap.schema.constAttrNamesCSV}"/>
+        <property name="constAttrValuesCSV" value="${xkms.ldap.schema.constAttrValuesCSV}"/>
+        <property name="serviceCertRDNTemplate" value="${xkms.ldap.schema.serviceCertRDNTemplate}"/>
+        <property name="serviceCertUIDTemplate" value="${xkms.ldap.schema.serviceCertUIDTemplate}"/>
+        <property name="trustedAuthorityFilter" value="${xkms.ldap.schema.trustedAuthorities}"/>
+        <property name="crlFilter" value="${xkms.ldap.schema.crls}"/>
+        <property name="intermediateFilter" value="${xkms.ldap.schema.intermediates}"/>
+    </bean>
+    <bean id="certificateRepo" class="org.apache.cxf.xkms.x509.repo.ldap.LdapCertificateRepo">
+        <argument ref="ldapSearch"/>
+        <argument ref="ldapSchemaConfig"/>
+        <argument value="${xkms.ldap.rootDN}"/>
+    </bean>
+    <service ref="certificateRepo" interface="org.apache.cxf.xkms.x509.repo.CertificateRepo">
+    	<service-properties>
+    		<entry key="name" value="ldap"/>
+    	</service-properties>
+    </service>
+</blueprint>

http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-repo-ldap/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPCertificateRepoTest.java
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-repo-ldap/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPCertificateRepoTest.java
b/services/xkms/xkms-x509-repo-ldap/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPCertificateRepoTest.java
new file mode 100644
index 0000000..167eb0c
--- /dev/null
+++ b/services/xkms/xkms-x509-repo-ldap/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPCertificateRepoTest.java
@@ -0,0 +1,146 @@
+/**
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.net.URISyntaxException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+
+import org.apache.cxf.xkms.handlers.Applications;
+import org.apache.cxf.xkms.model.xkms.UseKeyWithType;
+import org.apache.cxf.xkms.x509.repo.CertificateRepo;
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Tests need a real ldap server
+ */
+public class LDAPCertificateRepoTest {
+    private static final String EXPECTED_SUBJECT_DN = "CN=www.issuer.com, L=CGN, ST=NRW,
C=DE, O=Issuer";
+    private static final String ROOT_DN = "dc=example,dc=com";
+    private static final String EXPECTED_SUBJECT_DN2 = "CN=www.issuer.com,L=CGN,ST=NRW,C=DE,O=Issuer";
+    private static final String EXPECTED_SERVICE_URI = "http://myservice.apache.org/MyServiceName";
+    private static final String EXPECTED_DN_FOR_SERVICE = 
+            "cn=http:\\/\\/myservice.apache.org\\/MyServiceName,ou=services";
+    private static final LdapSchemaConfig LDAP_CERT_CONFIG = new LdapSchemaConfig();
+
+    @Test
+    @Ignore
+    public void testFindUserCert() throws URISyntaxException, NamingException, CertificateException
{
+        CertificateRepo persistenceManager = createLdapCertificateRepo();
+        testFindBySubjectDnInternal(persistenceManager);
+    }
+
+    @Test
+    @Ignore
+    public void testFindUserCertForNonExistantDn() throws URISyntaxException, NamingException,
CertificateException {
+        CertificateRepo persistenceManager = createLdapCertificateRepo();
+        X509Certificate cert = persistenceManager.findBySubjectDn("CN=wrong");
+        Assert.assertNull("Certifiacte should be null", cert);
+    }
+
+    @Test
+    @Ignore
+    public void testFindServiceCert() throws URISyntaxException, NamingException, CertificateException
{
+        CertificateRepo persistenceManager = createLdapCertificateRepo();
+        String serviceUri = "cn=http:\\/\\/myservice.apache.org\\/MyServiceName,ou=services";
+        X509Certificate cert = persistenceManager.findByServiceName(serviceUri);
+        Assert.assertEquals(EXPECTED_SUBJECT_DN, cert.getSubjectDN().toString());
+    }
+
+    @Test
+    @Ignore
+    public void testSave() throws Exception {
+        CertificateRepo persistenceManager = createLdapCertificateRepo();
+        File certFile = new File("src/test/java/cert1.cer");
+        Assert.assertTrue(certFile.exists());
+        FileInputStream fis = new FileInputStream(certFile);
+        CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        X509Certificate cert = (X509Certificate) factory.generateCertificate(fis);
+
+        UseKeyWithType key = new UseKeyWithType();
+        key.setApplication(Applications.PKIX.getUri());
+        key.setIdentifier(EXPECTED_SUBJECT_DN);
+        persistenceManager.saveCertificate(cert, key);
+        testFindBySubjectDnInternal(persistenceManager);
+    }
+
+    private CertificateRepo createLdapCertificateRepo() throws CertificateException {
+        LdapSearch ldapSearch = new LdapSearch("ldap://localhost:2389", 
+            "cn=Directory Manager,dc=example,dc=com", "test", 2);
+        return new LdapCertificateRepo(ldapSearch, LDAP_CERT_CONFIG, "dc=example,dc=com");
+    }
+
+    private void testFindBySubjectDnInternal(CertificateRepo persistenceManager) throws CertificateException
{
+        X509Certificate cert2 = persistenceManager.findBySubjectDn(EXPECTED_SUBJECT_DN);
+        Assert.assertEquals(EXPECTED_SUBJECT_DN, cert2.getSubjectDN().toString());
+    }
+    
+    @Test
+    public void testSaveUserCert() throws Exception {
+        IMocksControl c = EasyMock.createControl();
+        LdapSearch ldapSearch = c.createMock(LdapSearch.class);
+        ldapSearch.bind(EasyMock.eq(EXPECTED_SUBJECT_DN2 + "," + ROOT_DN), EasyMock.anyObject(Attributes.class));
+        EasyMock.expectLastCall().once();
+        LdapCertificateRepo ldapCertRepo = new LdapCertificateRepo(ldapSearch, LDAP_CERT_CONFIG,
ROOT_DN);
+        X509Certificate cert = getTestCert();
+
+        c.replay();
+        UseKeyWithType key = new UseKeyWithType();
+        key.setApplication(Applications.PKIX.getUri());
+        key.setIdentifier(EXPECTED_SUBJECT_DN2);
+        ldapCertRepo.saveCertificate(cert, key);
+        c.verify();
+    }
+
+    @Test
+    public void testSaveServiceCert() throws Exception {
+        IMocksControl c = EasyMock.createControl();
+        LdapSearch ldapSearch = c.createMock(LdapSearch.class);
+        ldapSearch.bind(EasyMock.eq(EXPECTED_DN_FOR_SERVICE + "," + ROOT_DN), EasyMock.anyObject(Attributes.class));
+        EasyMock.expectLastCall().once();
+        LdapCertificateRepo ldapCertRepo = new LdapCertificateRepo(ldapSearch, LDAP_CERT_CONFIG,
ROOT_DN);
+        X509Certificate cert = getTestCert();
+
+        c.replay();
+        UseKeyWithType key = new UseKeyWithType();
+        key.setApplication(Applications.SERVICE_NAME.getUri());
+        key.setIdentifier(EXPECTED_SERVICE_URI);
+        ldapCertRepo.saveCertificate(cert, key);
+        c.verify();
+    }
+
+    private X509Certificate getTestCert() throws FileNotFoundException, CertificateException
{
+        File certFile = new File("src/test/resources/cert1.cer");
+        Assert.assertTrue(certFile.exists());
+        FileInputStream fis = new FileInputStream(certFile);
+        CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        return (X509Certificate) factory.generateCertificate(fis);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-repo-ldap/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPSearchTest.java
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-repo-ldap/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPSearchTest.java
b/services/xkms/xkms-x509-repo-ldap/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPSearchTest.java
new file mode 100644
index 0000000..e029023
--- /dev/null
+++ b/services/xkms/xkms-x509-repo-ldap/src/test/java/org/apache/cxf/xkms/x509/repo/ldap/LDAPSearchTest.java
@@ -0,0 +1,51 @@
+/**
+ * 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.net.URISyntaxException;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchResult;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Tests need a real ldap server
+ */
+public class LDAPSearchTest {
+    @Test
+    @Ignore
+    public void testSearch() throws URISyntaxException, NamingException {
+        LdapSearch ldapSearch = new LdapSearch("ldap://localhost:2389",
+                                               "cn=Directory Manager,dc=example,dc=com",
"test", 2);
+        NamingEnumeration<SearchResult> answer = ldapSearch.searchSubTree("dc=example,
dc=com",
+                                                                          "(cn=Testuser)");
+        while (answer.hasMore()) {
+            SearchResult sr = answer.next();
+            Attributes attrs = sr.getAttributes();
+            Attribute cn = attrs.get("sn");
+            System.out.println(cn.get());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/037abfeb/services/xkms/xkms-x509-repo-ldap/src/test/resources/cert1.cer
----------------------------------------------------------------------
diff --git a/services/xkms/xkms-x509-repo-ldap/src/test/resources/cert1.cer b/services/xkms/xkms-x509-repo-ldap/src/test/resources/cert1.cer
new file mode 100644
index 0000000..df38fa2
--- /dev/null
+++ b/services/xkms/xkms-x509-repo-ldap/src/test/resources/cert1.cer
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+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
+p6TxIkB2cZQDT8eTSS/PTHC+muh5/365lRE=
+-----END CERTIFICATE-----


Mime
View raw message