Hello,

I made some progress with the code Kiran provided. However now everything is fine until I add my Interceptor.

When my interceptor extends BaseInterceptor I get the problem as desribed in http://markmail.org/message/pjqwd5hdqa2puqps#query:+page:1+mid:mybcrygmh6vg57po+state:results

Following Kiran's advice given in that thread to make my class extend from SchemaInterceptor I got the following stacktrace:

java.lang.IllegalStateException: Error when attempting to start the directory / LDAP service! at de.robotron.certificate.ldap.EmbeddedLdapServer.start(EmbeddedLdapServer.java:191) at de.robotron.certificate.ldap.EmbeddedLdapServer.<init>(EmbeddedLdapServer.java:101) at de.robotron.certificate.ldap.EmbeddedLdapServer.getInstance(EmbeddedLdapServer.java:80) at de.robotron.certificate.ldap.EmbeddedLdapServerTest.interceptorTest(EmbeddedLdapServerTest.java:33)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.NullPointerException
at org.apache.directory.server.core.schema.SchemaInterceptor.check(SchemaInterceptor.java:922) at org.apache.directory.server.core.schema.SchemaInterceptor.add(SchemaInterceptor.java:1051) at org.apache.directory.server.core.api.interceptor.BaseInterceptor.next(BaseInterceptor.java:422) at org.apache.directory.server.core.normalization.NormalizationInterceptor.add(NormalizationInterceptor.java:131) at org.apache.directory.server.core.DefaultOperationManager.add(DefaultOperationManager.java:394) at org.apache.directory.server.core.shared.DefaultCoreSession.add(DefaultCoreSession.java:215) at org.apache.directory.server.core.shared.DefaultCoreSession.add(DefaultCoreSession.java:192) at de.robotron.certificate.ldap.EmbeddedLdapServer.addEntry(EmbeddedLdapServer.java:228) at de.robotron.certificate.ldap.EmbeddedLdapServer.start(EmbeddedLdapServer.java:189)
    ... 27 more

I think that this is due to my interceptor as I use a self defined object class with self defined attribute types. However I'm unsure how to add these objectclass and attribute types to the schema. I'm adding the interceptor as described in the user guide by

private void addInterceptor(DirectoryService service, OperationsFactory opsFactory) {
        List<Interceptor> interceptors = service.getInterceptors();

        // Find Normalization interceptor in chain
        int insertionPosition = -1;
        for (int pos = 0; pos < interceptors.size(); ++pos) {
            Interceptor interceptor = interceptors.get(pos);
            if (interceptor instanceof NormalizationInterceptor) {
                insertionPosition = pos;
                break;
            }
        }

interceptors.add(++insertionPosition, new CertSearchInterceptor(opsFactory));
        service.setInterceptors(interceptors);
    }

which is called after instantiating the DirectoryService but before it has been started (or the partition has been added). My SchemaInterceptor's code is

import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.filter.BranchNode;
import org.apache.directory.api.ldap.model.filter.EqualityNode;
import org.apache.directory.api.ldap.model.filter.ExprNode;
import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
import org.apache.directory.server.core.schema.SchemaInterceptor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import de.robotron.certificate.database.CertificateOperations;
import de.robotron.certificate.database.OperationsFactory;

/**
* The <code>CertSearchInterceptor</code> class represents an interceptor which
 * requests certificates from the database instead of a LDAP server.
 * <p>
 * This class is immutable. It is thread safe as far as the
 * {@link OperationsFactory} provided to the constructor is thread safe.
 *
 * @author Sebastian Oerding
 */
final class CertSearchInterceptor extends SchemaInterceptor {
    /**
     * The logger for this class.
     */
private static final Logger LOG = LogManager.getLogger(CertSearchInterceptor.class);

    /**
* The factory especially giving access to {@link CertificateOperations}.
     */
    private final OperationsFactory dbFactory;

    /**
     * Initializes a new instance with the argument factory.
     *
     * @param application
* the factory providing the required operations to access the
     *            database, must not be <code>null</code>
     * @throws IllegalArgumentException
     *             if the argument factory is <code>null</code>
     */
    CertSearchInterceptor(OperationsFactory opsFactory) {
        if (opsFactory == null) {
throw new IllegalArgumentException("The OperationsFactory must not be null!");
        }
        this.dbFactory = opsFactory;
    }

    /**
     * Searches for a certificate in the database.
     * <p>
* Note that this method will always return <code>null</code>. It extracts * the common name from the search filter of the argument search context and * uses the extracted common name to read a certificate from the database. * To do so the common must be the value of the SubjectKeyIdentifier X.509 * extension of the required certificate but with the colons normally used * for pretty printing omitted. If such a certificate is found an entry is
     * created and added to the search context.
     * <p>
* If any problem occurs getting the certificate this is written to the log * file. However as simply finding no matching certificate not necessarily * indicates an error this is not logged as error. Other problems especially
     * the ones indicating a bug are logged as errors.
     * <p>
* An entry is added if and only if a certificate matching the extracted
     * common name is found in the database.
     *
     * @return <code>null</code>
     */
    @Override
public EntryFilteringCursor search(SearchOperationContext searchContext) { // System.out.println(searchContext.getRequestControl("2.16.840.1.113730.3.4.2"));
        // searchContext.setRequestControls(Collections.<String, Control>
        // emptyMap());
        ExprNode searchNode = searchContext.getFilter();
        String commonName = extractCommonName(searchNode);
        if (commonName == null) {
            LOG.error("Common name not found in search filter!");
            return null;
        }
// We need some kind of wrapper as we also have to get the revocation
        // state from the database.
        byte[] userCertificate = readUserCertificate(commonName);
        boolean revocation = false;
        if (userCertificate == null) {
LOG.info("No certificate for SubjectKeyIdentifier {} found!", commonName);
            return null;
        }
createSearchEntry(searchContext, commonName, userCertificate, revocation);
        return null;
    }

    /**
* Creates an entry using the provided values and adds it to the provided
     * search context.
     *
     * @param searchContext
* the search context from which to get the distinguished name
     *            (DN) and to which to add the created entry
     * @param commonName
* the value of the SubjectKeyIdentifier X.509 extension of the
     *            certificate (but without colons)
     * @param userCertificate
     *            the DER encoded X.509 certificate
     * @param revocation
* <code>true</code> if the certificate has already been revoked,
     *            <code>false</code> otherwise
     */
private void createSearchEntry(SearchOperationContext searchContext, String commonName, byte[] userCertificate,
        boolean revocation) {
        Entry entry = new DefaultEntry(searchContext.getDn());
        try {
            entry.add("2.5.4.0", "extensibleObject", "top", "smpki");
entry.add("1.3.6.1.2.1.2.2.1.8", String.valueOf(revocation).toUpperCase());
            entry.add("2.5.29.14", commonName);
            entry.add("2.5.4.36", userCertificate);
            entry.add("2.5.4.3", commonName);
            entry.add("0.9.2342.19200300.100.1.25", "tr03109");
            entry.add("2.5.4.11", "tr03109");
            searchContext.setEntry(entry);
LOG.trace("Added entry with common name {} to search context.", commonName);
        } catch (LdapException e) {
LOG.error("Error creating an entry for common name {}!", commonName);
        }
    }

    /**
     * Returns the user certificate whose value of the SubjectKeyIdentifier
     * x.509 extension matches the argument name from the database.
     *
     * @param commonName
* the SubjectKeyIdentifier of the needed certificate with colons
     *            omitted
     * @return the certificate which matches the argument name,
* <code>null</code> if no such certificate exists in the database
     */
    private byte[] readUserCertificate(String commonName) {
        return null;
    }

    /**
     * Extracts the common name from the search filter.
     *
     * @param searchNode
* the node which contains the common name attribute or which has
     *            a child node containing the common name attribute
* @return the common name used in the search filter, <code>null</code> if
     *         no node containing this attribute is found
     */
    // TODO this is a very specific implementation only matching the search
    // requests expected by us. It may not be generalized!
    private String extractCommonName(ExprNode searchNode) {
        if (searchNode instanceof BranchNode) {
            BranchNode bNode = (BranchNode) searchNode;
            for (ExprNode child : bNode.getChildren()) {
                if (child instanceof EqualityNode<?>) {
                    EqualityNode<?> eNode = (EqualityNode<?>) child;
if (eNode.getAttributeType().getOid().equals("2.5.4.3")) {
                        return eNode.getValue().toString();
                    }
                }
            }
        } else if (searchNode instanceof EqualityNode<?>) {
            EqualityNode<?> eNode = (EqualityNode<?>) searchNode;
            if (eNode.getAttributeType().getOid().equals("2.5.4.3")) {
                return eNode.getValue().toString();
            }
        }
        return null;
    }
}

Furthermore there are a two more questions remaining:
1) When I have added the partition and the embedded server is shutdown (closing the JVM) and started again in a new JVM. Does I have to / may add the schema again or does that depend on whether the tmp file has been deleted? Should I take take by removing the partition on shutdown? 2) When do I have to add the interceptor? As I have done it or after startup or before startup but after adding the partition, ...?

Btw. sometimes I feel like I'm close to speaking ASN1 as well as English (not sure whether my English is bad or my ASN1 is well). As I'm sometimes dealing with ASN1 data structures I'm already used to it but I still prefer to use the Apache DS :-) .

With regards Sebastian

Reply via email to