5/09/2013

CRUD for LDAP (UnboundID JDK, ApacheDS)

A raw implementation.
---Two ways to create, use CreateRequest or @LDAPObject
---Retrieve all sub entries or single entry based on DN. If want to retrieve all the entries including sub-entries under one dn, pass in the dn, and set filter as 'objectClass=*'.
---Support recursively delete all entries including sub entries.
     ApacheDS does not implement the tree delete function, so the SubtreeDeleteRequestControl in the jdk is not useful. So to support tree delete for ApacheDS, we need to implement recursively delete ourselves.
---ApacheDS seems does not support ModifyDSRequest with deleteOldRDN set to true.(Old RDN will still be left over, but as attribute instead of rdn, rdn is updated.)

TestService.java



package test.common.ldap.service;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.naming.NamingException;

import test.common.ldap.entity.User;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;

import com.unboundid.ldap.sdk.AddRequest;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.DeleteRequest;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.ModifyDNRequest;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl;
import com.unboundid.ldap.sdk.persist.LDAPPersistException;
import com.unboundid.ldap.sdk.persist.LDAPPersister;
import com.unboundid.ldif.LDIFException;

public class TestService {

   private String host;
   private String portString;
   private int port;
   private String psw;
   private int OPERATION_TIMEOUT_MILLIS = 1000;

   static {

       Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
   }

   public TestService() {

       // String confPath = "..\\conf";
       // String name = confPath + "\\conf.properties";

       String name = "C:\\Projects\\LDAPTestTool\\conf\\conf.properties";

       InputStream in = null;
       try {
           in = new BufferedInputStream(new FileInputStream(name));
       } catch (FileNotFoundException e) {
           e.printStackTrace();
       }
       Properties p = new Properties();
       try {
           p.load(in);
       } catch (IOException e) {
           e.printStackTrace();
       }

       host = p.getProperty("host");
       System.out.println("host read from propery file.");

       portString = p.getProperty("port");
       System.out.println("port read from propery file.");
       port = Integer.parseInt(portString);

       psw = p.getProperty("password");
       System.out.println("password read from propery file.");

   }

   public void create(String entryDN, String cn, byte[] p12Cert,
           byte[] publicKey, byte[] privateKey) {

       byte[] encodedPrivateCert = Base64.encodeBase64(privateKey);
       byte[] encodedP12Cert = Base64.encodeBase64(p12Cert);

       String[] ldifLines = { "dn: " + "cn=" + cn + "," + entryDN,
               "objectClass: top", "objectClass: tlsKeyInfo",
               "objectClass: person", "objectClass: organizationalPerson",
               "objectClass: inetOrgPerson", "changetype: add", "cn: " + cn,
               "sn: " + cn, "mail: " + cn, "keyAlgorithm: RSA",
               // "privateKey: " + new String(encodedp12Cert),
               "privateKeyFormat: PKCS#8",
               // "publicKey: " + new String(publicKey),
               "publicKeyFormat: X.509",
       // "userCertificate: " + new String(publicKey),
       };

       LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
       connectionOptions.setAbandonOnTimeout(true);
       connectionOptions.setConnectTimeoutMillis(OPERATION_TIMEOUT_MILLIS);

       int result;
       LDAPResult ldapResult = null;
       try {

           // Connect to the server.
           LDAPConnection ldapConnection = new LDAPConnection(
                   connectionOptions, host, port);
           try {

               // Create the AddRequest object using the LDIF lines.
               AddRequest addRequest = new AddRequest(ldifLines);

               addRequest.addAttribute("privateKey", encodedPrivateCert);
               addRequest.addAttribute("publicKey", publicKey);
               addRequest.addAttribute("userCertificate", publicKey);
               addRequest.addAttribute("userPKCS12", encodedP12Cert);

               // Transmit the AddRequest to the server.
               ldapResult = ldapConnection.add(addRequest);

               System.out.println(ldapResult);

           } catch (final LDIFException e) {
               System.err.println(e);
           } finally {
               ldapConnection.close();

               // Convert the result code to an integer for use in the exit
               // method.
               result = ldapResult == null ? 1 : ldapResult.getResultCode()
                       .intValue();
           }
       } catch (final LDAPException e) {
           System.err.println(e);
           result = 1;
       }

       // System.exit(result);

   }

   public void createWithPersistObject(String entryDN, String cn,
           byte[] p12Cert, byte[] publicKey, byte[] privateKey) {

       byte[] encodedPrivateCert = Base64.encodeBase64(privateKey);
       byte[] encodedP12Cert = Base64.encodeBase64(p12Cert);

       LDAPPersister<User> persister = null;
       try {
           persister = LDAPPersister.getInstance(User.class);
       } catch (LDAPPersistException e2) {
           // TODO Auto-generated catch block
           e2.printStackTrace();
       }

       // Create a new MyObject instance and add it to the directory. We can
       // use
       // a parent DN of null to indicate that it should use the default
       // defined
       // in the @LDAPObject annotation.
       LDAPConnection ldapConnection = null;
       try {
           ldapConnection = getLdapConnection();
       } catch (LDAPException e1) {
           // TODO Auto-generated catch block
           e1.printStackTrace();
       }

       User user = new User();
       user.setDn("cn=" + cn + "," + entryDN);
       user.setCn(cn);
       user.setSn(cn);
       user.setMail(cn);
       user.setPrivateKey(encodedPrivateCert);
       user.setPublicKey(publicKey);
       user.setUserCertificate(publicKey);
       user.setUserPKCS12(encodedP12Cert);

       LDAPResult ldapResult = null;
       int result;
       try {
           ldapResult = persister.add(user, ldapConnection, null);

           System.out.println(ldapResult);

       } catch (Exception e) {
           System.err.println(e);
       } finally {
           ldapConnection.close();

           // Convert the result code to an integer for use in the exit method.
           result = ldapResult == null ? 1 : ldapResult.getResultCode()
                   .intValue();
       }

       // System.exit(result);

   }

   @SuppressWarnings({ "unchecked", "rawtypes" })
   public void search(String dn, String psw, String filter)
           throws NamingException {

       LDAPConnection ldapConnection = null;
       try {
           ldapConnection = getAuthLdapConnection(psw);
       } catch (LDAPException e1) {
           // TODO Auto-generated catch block
           e1.printStackTrace();
       }

       if (StringUtils.isNotEmpty(filter)) {
           SearchRequest searchRequest = null;
           try {
               searchRequest = new SearchRequest(dn, SearchScope.SUB, filter);
           } catch (LDAPException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
           }

           System.out
                   .println("========================================================");
           System.out.println("Search Result: ");

           try {
               SearchResult searchResult = ldapConnection
                       .search(searchRequest);

               for (SearchResultEntry entry : searchResult.getSearchEntries()) {
                   // String name = entry.getAttributeValue("cn");
                   // String mail = entry.getAttributeValue("mail");
                   /*
                    * StringBuilder buffer = new StringBuilder();
                    * 
                    * entry.toString(buffer);
                    * System.out.println(buffer.toString());
                    */
                   System.out.println();
                   System.out.println("Entry DN: " + entry.getDN());
                   List<Attribute> attributes = new ArrayList(
                           entry.getAttributes());
                   System.out
                           .println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
                   System.out.println("Entry content: ");

                   for (Attribute att : attributes) {

                       System.out.print(att.getName() + ": ");

                       String[] values = att.getValues();

                       for (int i = 0; i < values.length - 1; i++) {
                           System.out.print(values[i]);
                           System.out.print(",");
                       }
                       System.out.print(values[values.length - 1]);

                       System.out.println();

                   }
               }
               System.out
                       .println("========================================================");
               System.out.println("Search end.");
           } catch (LDAPSearchException lse) {
               System.err.println("The search failed.");
           }
       } else {

           Entry groupEntry = null;
           try {
               groupEntry = ldapConnection.getEntry(dn);
           } catch (LDAPException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
           }

           List<Attribute> attributes = new ArrayList(
                   groupEntry.getAttributes());
           System.out
                   .println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
           System.out.println("Entry content: ");

           for (Attribute att : attributes) {

               System.out.print(att.getName() + ": ");

               String[] values = att.getValues();

               for (int i = 0; i < values.length - 1; i++) {
                   System.out.print(values[i]);
                   System.out.print(",");
               }
               System.out.print(values[values.length - 1]);

               System.out.println();

           }

           System.out.println("End Search.");

           /*
            * String[] memberValues = groupEntry.getAttributeValues("member");
            * 
            * if (memberValues != null)
            * 
            * {
            * 
            * DNEntrySource entrySource = new DNEntrySource(ldapConnection,
            * memberValues, "cn");
            * 
            * while (true) { Entry memberEntry = entrySource.nextEntry(); if
            * (memberEntry == null) { break; }
            * 
            * System.out.println("Retrieved member entry:  " +
            * memberEntry.getAttributeValue("cn")); } }
            */
       }

   }

   public void update(String psw, String entryDN, String cn, String newDn,
           String newCn, byte[] p12Cert, byte[] publicKey, byte[] privateKey) {

       LDAPConnection ldapConnection = null;
       String fullDn = "cn=" + cn + "," + entryDN;

       if (StringUtils.isNotEmpty(newDn) || StringUtils.isNotEmpty(newCn)) {

           ModifyDNRequest modifyDNRequest = null;

           if (StringUtils.isEmpty(newDn)) {
               newDn = entryDN;
               modifyDNRequest = new ModifyDNRequest(fullDn, "cn=" + newCn,
                       true);
           }
           if (StringUtils.isEmpty(newCn)) {
               newCn = cn;
               modifyDNRequest = new ModifyDNRequest(fullDn, "cn=" + newCn,
                       true, newDn);
           }
           if (StringUtils.isNotEmpty(newDn) && StringUtils.isNotEmpty(newCn)) {

               modifyDNRequest = new ModifyDNRequest(fullDn, "cn=" + newCn,
                       true, newDn);
           }

           fullDn = "cn=" + newCn + "," + newDn;

           try {
               ldapConnection = null;
               try {
                   ldapConnection = getAuthLdapConnection(psw);
               } catch (LDAPException e1) {
                   // TODO Auto-generated catch block
                   e1.printStackTrace();
               }
               modifyDNRequest.setDeleteOldRDN(true);
               LDAPResult modifyDNResult = ldapConnection
                       .modifyDN(modifyDNRequest);

               System.out.println("The entry was renamed successfully.");
           } catch (LDAPException le) {
               le.printStackTrace();
               System.err.println("The modify DN operation failed.");
           }
       }

       if (StringUtils.isNotEmpty(newCn) || p12Cert != null
               || publicKey != null || privateKey != null) {

           LDAPPersister<User> persister = null;
           try {
               persister = LDAPPersister.getInstance(User.class);
           } catch (LDAPPersistException e2) {
               // TODO Auto-generated catch block
               e2.printStackTrace();
           }

           // Create a new MyObject instance and add it to the directory. We
           // can
           // use
           // a parent DN of null to indicate that it should use the default
           // defined
           // in the @LDAPObject annotation.

           try {
               ldapConnection = getAuthLdapConnection(psw);
           } catch (LDAPException e1) {
               // TODO Auto-generated catch block
               e1.printStackTrace();
           }

           User user = new User();
           user.setDn(fullDn);
           if (StringUtils.isEmpty(newCn)) {
               user.setCn(cn);
               user.setSn(cn);
               user.setMail(cn);
           } else {
               user.setCn(newCn);
               user.setSn(newCn);
               user.setMail(newCn);
           }

           if (p12Cert != null) {
               byte[] encodedP12Cert = Base64.encodeBase64(p12Cert);
               user.setUserPKCS12(encodedP12Cert);
           }
           if (privateKey != null) {
               byte[] encodedPrivateCert = Base64.encodeBase64(privateKey);
               user.setPrivateKey(encodedPrivateCert);
           }

           if (publicKey != null) {
               user.setPublicKey(publicKey);
               user.setUserCertificate(publicKey);
           }

           LDAPResult ldapResult = null;
           int result;
           try {
               ldapResult = persister
                       .modify(user, ldapConnection, null, false);

               System.out.println(ldapResult);

           } catch (Exception e) {
               System.err.println(e);
           } finally {
               ldapConnection.close();

               // Convert the result code to an integer for use in the exit
               // method.
               result = ldapResult == null ? 1 : ldapResult.getResultCode()
                       .intValue();
           }
       }

       // System.exit(result);

   }

   public void deleteOneLevel(String psw, String entryDN) {

       LDAPConnection ldapConnection = null;
       try {
           ldapConnection = getAuthLdapConnection(psw);
       } catch (LDAPException e1) {
           // TODO Auto-generated catch block
           e1.printStackTrace();
       }

       DeleteRequest deleteRequest = new DeleteRequest(entryDN);
       deleteRequest.addControl(new SubtreeDeleteRequestControl());

       LDAPResult ldapResult = null;
       int result;
       try {
           ldapResult = ldapConnection.delete(deleteRequest);

           System.out.println("The entry was successfully deleted.");
       } catch (LDAPException le) {
           le.printStackTrace();
           System.err.println("The delete operation failed.");
       } finally {
           ldapConnection.close();

           // Convert the result code to an integer for use in the exit method.
           result = ldapResult == null ? 1 : ldapResult.getResultCode()
                   .intValue();
       }

       // System.exit(result);
   }

   public void delete(String psw, String entryDN) throws LDAPException {

       LDAPConnection ldapConnection = null;
       try {
           ldapConnection = getAuthLdapConnection(psw);
       } catch (LDAPException e1) {
           // TODO Auto-generated catch block
           e1.printStackTrace();
       }

       deleteSubEntry(entryDN, ldapConnection);

       return;
   }

   public void deleteSubEntry(String entry, LDAPConnection ldapConnection)
           throws LDAPException {

       // System.out.println("deleteSubEntry '"+entry+"' start.");

       SearchRequest searchRequest = null;
       String filter = "objectClass=*";

       searchRequest = new SearchRequest(entry, SearchScope.ONE, filter);

       SearchResult searchResult = null;

       // System.out.println("search sub-entry for entry '"+entry+"' start.");
       searchResult = ldapConnection.search(searchRequest);

       if (searchResult.getEntryCount() == 0) {
           // System.out.println("No sub entry.");
           deleteEntry(entry, ldapConnection);
       } else {
           // System.out.println("Sub entries exist.");
           for (SearchResultEntry entryResult : searchResult
                   .getSearchEntries()) {

               if (!entryResult.getDN().equalsIgnoreCase(entry)) {
                   // System.out.println("Get sub entry: " +
                   // entryResult.getDN());
                   deleteSubEntry(entryResult.getDN(), ldapConnection);
               }

           }
           // System.out.println("Start delete parent entry " + entry);
           deleteEntry(entry, ldapConnection);
       }

       // return;
   }

   public void deleteEntry(String entry, LDAPConnection ldapConnection) {

       DeleteRequest deleteRequest = new DeleteRequest(entry);

       LDAPResult ldapResult = null;
       int result;
       try {
           ldapResult = ldapConnection.delete(deleteRequest);

           System.out.println("The entry '" + entry
                   + "' was successfully deleted.");
       } catch (LDAPException le) {
           le.printStackTrace();
           System.err.println("The delete operation for entry '" + entry
                   + "' failed.");
       } finally {

           // Convert the result code to an integer for use in the exit method.
           result = ldapResult == null ? 1 : ldapResult.getResultCode()
                   .intValue();
       }
   }

   public LDAPConnection getLdapConnection() throws LDAPException {

       LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
       connectionOptions.setAbandonOnTimeout(true);
       connectionOptions.setConnectTimeoutMillis(OPERATION_TIMEOUT_MILLIS);

       LDAPConnection ldapConnection = new LDAPConnection(connectionOptions,
               host, port);

       return ldapConnection;
   }

   public LDAPConnection getAuthLdapConnection(String psw)
           throws LDAPException {

       LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
       connectionOptions.setAbandonOnTimeout(true);
       connectionOptions.setConnectTimeoutMillis(OPERATION_TIMEOUT_MILLIS);

       LDAPConnection ldapConnection = new LDAPConnection(connectionOptions,
               host, port, "uid=admin,ou=system", psw);

       return ldapConnection;
   }

   public byte[] readDataFromFile(String path) {

       InputStream is = null;
       ByteArrayOutputStream out = new ByteArrayOutputStream();

       try {
           is = new BufferedInputStream(new FileInputStream(path));
           byte[] b = new byte[1024];
           int n;
           while ((n = is.read(b)) != -1) {
               out.write(b, 0, n);
           }

       } catch (Exception e) {
           // e.printStackTrace();
           return null;
       } finally {
           if (is != null) {
               try {
                   is.close();
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
       }
       return out.toByteArray();
   }

   private byte[] InputStreamToByte(InputStream is) throws IOException {

       ByteArrayOutputStream bytestream = new ByteArrayOutputStream();
       int ch;
       while ((ch = is.read()) != -1) {
           bytestream.write(ch);
       }
       byte imgdata[] = bytestream.toByteArray();
       bytestream.close();
       return imgdata;

   }
}



App.java

package test.common.ldap.tool;

import java.util.Scanner;

import javax.naming.NamingException;

import test.common.ldap.service.TestService;

import org.apache.commons.lang.StringUtils;

import com.unboundid.ldap.sdk.LDAPException;

/**
*
*/
public class App
{
   public static void main( String[] args )
   {  
       TestService service = new TestService();
       manu(service);
   }
 
   public static void manu(TestService service){
       System.out.println("----------------------------------------");
       System.out.println("----------------MODE--------------------");
       System.out.println("1: create");
       System.out.println("2: retrieve");
       System.out.println("3: update");
       System.out.println("4: delete");
     
       Scanner input = new Scanner(System.in);
       System.out.print("Key in the selection : ");
     
       String selectionString = input.next();
     
       boolean isValid = StringUtils.isNumeric(selectionString);
       if(isValid) {
           int option = Integer.parseInt(selectionString);
           if(option==1){
             
               System.out.print("Please input the entry dn: ");
               Scanner inputScanner = new Scanner(System.in);
               String dn = inputScanner.next();
               //String dn = "ou=users,ou=system";
               System.out.println("entry dn: " + dn);
             
               System.out.print("Please input the entry cn: ");
               inputScanner = new Scanner(System.in);
               String cn = inputScanner.next();
             
               System.out.println("entry cn: " + cn);
             
               System.out.print("Please input the p12 cert location: ");
               byte[] p12certData = getCertDataFromPath(service);
             
               System.out.println("p12 cert data read: " + new String(p12certData) );
             
             
             
               System.out.print("Please input the public cert location: ");
               byte[] publicCertData =getCertDataFromPath(service);
             
               System.out.println("public cert data read: " + new String(publicCertData));
             
             
             
               System.out.print("Please input the private cert location: ");
               byte[] privateCertData = getCertDataFromPath(service);
             
               System.out.println("private cert data read: " + new String(privateCertData));
             
             
             
               service.createWithPersistObject(dn, cn, p12certData, publicCertData, privateCertData);
           }else if(option ==2){
             
               System.out.print("Please input the entry dn: ");
               Scanner inputScanner = new Scanner(System.in);
               String dn = inputScanner.next();
               //String dn = "ou=system";
               System.out.println("entry dn: " + dn);
             
               System.out.print("Please input the password: ");
               inputScanner = new Scanner(System.in);
               String psw = inputScanner.next();
             
               System.out.println("password: " + psw);
             
               System.out.print("Please input the filter(if do not have, input n): ");
               inputScanner = new Scanner(System.in);
               String filter = inputScanner.next();
             
               if(filter.equalsIgnoreCase("n")){
                   filter = null;
               }else{
                   System.out.println("entry filter: " + filter);
               }
             
             
               try {
                   service.search(dn, psw, filter);
               } catch (NamingException e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
               }
             
           }else if(option ==3){
             
               System.out.print("Please input the entry dn: ");
               Scanner inputScanner = new Scanner(System.in);
               String dn = inputScanner.next();
               //String dn = "ou=users,ou=system";
               System.out.println("entry dn: " + dn);
             
               System.out.print("Please input the password: ");
               inputScanner = new Scanner(System.in);
               String psw = inputScanner.next();
             
               System.out.println("password: " + psw);
             
               System.out.print("Please input the entry cn: ");
               inputScanner = new Scanner(System.in);
               String cn = inputScanner.next();
             
               System.out.println("entry cn: " + cn);
             
               System.out.print("Please input the new entry dn(if do not want to update, input n): ");
               inputScanner = new Scanner(System.in);
               String newDn = inputScanner.next();
               //String newDn = "ou=system";
               if(newDn.equalsIgnoreCase("n")){
                   newDn = null;
               }else{
                   System.out.println("New entry dn: " + newDn);
               }
             
               System.out.print("Please input the new cn(if do not want to update, input n): ");
               inputScanner = new Scanner(System.in);
               String newCn = inputScanner.next();
             
               if(newCn.equalsIgnoreCase("n")){
                   newCn = null;
               }else{
                   System.out.println("New cn: " + newCn);
               }
             
             
               System.out.println("For below options, if no update, please input n or any other invalid path");

               System.out.print("Please input the p12 cert location: ");
               inputScanner = new Scanner(System.in);
               String certLocation = inputScanner.next();
               byte[] p12certData = getCertData(service,certLocation);
               if(p12certData!=null){
                   System.out.println("p12 cert data read: " + new String(p12certData) );
               }
             
             
             
             
               System.out.print("Please input the public cert location: ");
               inputScanner = new Scanner(System.in);
               certLocation = inputScanner.next();
               byte[] publicCertData = getCertData(service,certLocation);

               if(publicCertData!=null){
                   System.out.println("public cert data read: " + new String(publicCertData));
               }
             
               System.out.print("Please input the private cert location: ");
               inputScanner = new Scanner(System.in);
               certLocation = inputScanner.next();
               byte[] privateCertData = getCertData(service,certLocation);

               if(privateCertData!=null){
                   System.out.println("private cert data read: " + new String(privateCertData));
               }
             
             

               service.update(psw, dn, cn, newDn, newCn, p12certData, publicCertData, privateCertData);
             
           }else if(option ==4){
               System.out.println("WARN: DELETE will delete all the sub-entries too, if exist.");
               System.out.print("Please input the entry dn: ");
               Scanner inputScanner = new Scanner(System.in);
               String dn = inputScanner.next();

               System.out.println("entry dn: " + dn);
             
               System.out.print("Please input the password: ");
               inputScanner = new Scanner(System.in);
               String psw = inputScanner.next();

               System.out.println("password: " + psw);
             
               try {
                   service.delete(psw, dn);
               } catch (LDAPException e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
               }
             
           }else{
               System.out.println("Invalid option: " + selectionString);
               manu(service);
           }
       } else {
           System.out.println("Invalid option: " + selectionString);
           manu(service);
       }
     
       manu(service);
   }
 
   public static byte[] getCertDataFromPath(TestService service){
     
       Scanner inputScanner = new Scanner(System.in);
       String certLocation = inputScanner.next();
     
       byte[] data = null;
       try{
           data = service.readDataFromFile(certLocation);
         
       }catch(Exception e){
           e.printStackTrace();
           data = null;
       }
       if(data==null){
           System.out.println("Invalid cert path: " + certLocation);
           System.out.print("Please enter again: ");
           return getCertDataFromPath(service);
       }
     
       return data;
   }

   static byte[] getCertData(TestService service, String certLocation){
     
       byte[] data = null;
       try{
           data = service.readDataFromFile(certLocation);
       }catch(Exception e){
           e.printStackTrace();
           data = null;
       }
       return data;
   }
 
 
}


User.java

package test.common.ldap.entity;

import com.unboundid.ldap.sdk.persist.FilterUsage;
import com.unboundid.ldap.sdk.persist.LDAPDNField;
import com.unboundid.ldap.sdk.persist.LDAPField;
import com.unboundid.ldap.sdk.persist.LDAPObject;

@LDAPObject(structuralClass = "inetOrgPerson", auxiliaryClass = "tlsKeyInfo", defaultParentDN = "ou=users,ou=system")
public class User {

   @LDAPDNField
   private String dn;
 
   // The field used for RDN attribute myStringAttr.
   @LDAPField(attribute = "cn", inRDN = true, filterUsage = FilterUsage.ALWAYS_ALLOWED, requiredForEncode = true)
   private String cn;

   @LDAPField(attribute = "sn")
   private String sn;
 
   @LDAPField(attribute = "mail")
   private String mail;

   @LDAPField(attribute = "keyAlgorithm")
   private String keyAlgorithm = "RSA";

   @LDAPField(attribute = "privateKeyFormat")
   private String privateKeyFormat = "PKCS#8";

   @LDAPField(attribute = "publicKeyFormat")
   private String publicKeyFormat = "X.509";

   @LDAPField(attribute = "privateKey")
   private byte[] privateKey;

   @LDAPField(attribute = "publicKey")
   private byte[] publicKey;

   @LDAPField(attribute = "userCertificate")
   private byte[] userCertificate;

   @LDAPField(attribute = "userPKCS12")
   private byte[] userPKCS12;

 
   public String getDn() {
       return dn;
   }

   public void setDn(String dn) {
       this.dn = dn;
   }
 
   public String getCn() {
       return cn;
   }

   public void setCn(String cn) {
       this.cn = cn;
   }

   public String getSn() {
       return sn;
   }

   public void setSn(String sn) {
       this.sn = sn;
   }

   public String getMail() {
       return mail;
   }

   public void setMail(String mail) {
       this.mail = mail;
   }

   public String getKeyAlgorithm() {
       return keyAlgorithm;
   }

   public void setKeyAlgorithm(String keyAlgorithm) {
       this.keyAlgorithm = keyAlgorithm;
   }

   public String getPrivateKeyFormat() {
       return privateKeyFormat;
   }

   public void setPrivateKeyFormat(String privateKeyFormat) {
       this.privateKeyFormat = privateKeyFormat;
   }

   public String getPublicKeyFormat() {
       return publicKeyFormat;
   }

   public void setPublicKeyFormat(String publicKeyFormat) {
       this.publicKeyFormat = publicKeyFormat;
   }

   public byte[] getPrivateKey() {
       return privateKey;
   }

   public void setPrivateKey(byte[] privateKey) {
       this.privateKey = privateKey;
   }

   public byte[] getPublicKey() {
       return publicKey;
   }

   public void setPublicKey(byte[] publicKey) {
       this.publicKey = publicKey;
   }

   public byte[] getUserCertificate() {
       return userCertificate;
   }

   public void setUserCertificate(byte[] userCertificate) {
       this.userCertificate = userCertificate;
   }

   public byte[] getUserPKCS12() {
       return userPKCS12;
   }

   public void setUserPKCS12(byte[] userPKCS12) {
       this.userPKCS12 = userPKCS12;
   }
}











5/08/2013

Resolve 'java.security.NoSuchProviderException No such provider: BC'

In the class, add:


static
    {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }

add bcprov-jdk jar file

 maven dependency
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>

This is for jdk 1.6, if jdk version is different, pick the right version instead.

5/03/2013

Doubts on Direct Project


Limitation of Current Direct Project

1. The WS of Direct Project does not have access limitation. Anyone can retrieve the private cert without authentication if the private cert is stored through WS into WS server’s local DB.

2. If uses LDAP as certificate store, the LDAP need to be maintained separately, the UI of Direct Project can only manipulate data which are stored in its own local DB.

3. If uses LDAP as private certificate store, need to configure connection parameters and search parameters in the WS UI. But the password is stored and displayed as plain text.

4. One James Server has limitations on multiple domains configuration, if these domains by default do not trust each other. This means, multiple domains are not supported on single James Server when these domains do not trust each other.

Reason: In James's configuration, you have to config local mail(sender and recipient all local) local delivery. Otherwise, if all messages configured to go through the Secure Mailet, the local message will not get decrypted before its delivery, current existed matchers do not support this kind of action. In this case, if multiple domains exist on one James server, it means that all these domains are considered as local and will not go through the trust validation. But if by default these domains do not trust each other, this impl will not meet the requirement.
Also, in this case, if trust is based on user, then the local users also have to by default trust each other. This limitation is really not good.

So if we want to fully realize trust based on user or domain, we have to write new matchers and adjust the validation process that configured in James.

5. The load balance of email server needs to be considered.

There are two ways to apply load balance for mail server, one is to use DNS MX Record, while the other is to use separate load balance server. But I did not find documents on this part for direct project. And personally I'm not an expert on this, need to do more research.

Just bring into mind, it is Reference Implementation.

We need to develop the direct project ourselves with the reference implementation as guideline.

LDAP configuration for certificates management of Direct Project

This summary is not available. Please click here to view the post.

5/01/2013

Public certs & Private certs storage in LDAP (Direct Project)

Target Direct Project Version: 1.0

Recently researching on the Direct Project. Wanna try the different ways that Direct Project supported to make the certificates management.
The certificates management includes public certificates management and private certificates management.

And these two repository should be separately maintained. You wont want to expose your private key...

First, I try to setup LDAP as the 'PrivateCertStore'.

http://api.nhindirect.org/java/site/gateway/1.2/users-guide/smtp-depl-wsconfig.html#SMTP_Agent_WebService_Configuration

Based on the document of direct project above about the configuration of LDAP, I config the Direct WS. And in the mean time, setup the LDAP server using ApacheDS. Run the James.

Issue1: The property 'PrivateStoreLDAPCertPassphrase' must be set, even though you are not using 'pkcs12' type private certificates. And this value must be set to the psw that to access the LDAP directory, the same as the 'PrivateStoreLDAPPassword'. And after opening the remote debug, found this value is used to access the keystore file...This does not make sense. But the truth is, you need to set it. :)

Issue2: The certificates that Direct Project Generated from the tool cetGen are not base64 encoded. Fine, the Direct WS also accept certs that hasn't been Base64 encoded. But if you use LDAP to store your private certificates, issue comes. Based on the RFC-2849, LDAP will mistake the content of the private key/ p12 certificate. So, the certificates that saved into LDAP must be Base64 encoded.
I went through the Direct Project Jave code, find that when parsing the cert data from LDAP, the private cert's data has the Base64 decode procedure before the data is converted to Certificates.
But, I didn't find document on this in Direct Wiki. Hope the instruction could have more detail, it'll save other user's time. And also, the tool should support this feature, otherwise, users need to generate tool themselves to encode each certificates that they generated.

So, Base64 encode the private certificates(p12 file) before you put them into LDAP, only in this way, your direct can read right value back.

Second, I try to setup LDAP as the 'PublicCertStore'.

http://api.nhindirect.org/java/site/gateway/1.2/users-guide/smtp-depl-wsconfig.html#SMTP_Agent_WebService_Configuration

Issue3: In the doc, there's no option for setting LDAP as public cert store. But when you look into the code, there's one option, called 'PublicLDAP'. If you add this value into the 'PublicCertStore' setting. For example, if you set "PublicCertStore = WS, PublicLDAP". Then when your Direct search recipient's public cert, it'll search on the recipient domain's LDAP server. This requires that recipient must set up their LDAP server and make it support 'anonymous access'.

When I do the testing, I set up two direct network on two pc and make them trusted.
Initially I put all the cert entries under 'ou=user, ou=system'. Then it does not work, the sender cannot find the recipient's public cert. I open the remote debug, find that Direct only search for base.
"ctls.setSearchScope(SearchControls.OBJECT_SCOPE);"
This means that it only search dn one level under base(which are the name contexts attributes in the base), which only access to "ou=system", I have to move all my certs from 'ou=user, ou=system' to 'ou=system'. Then Direct works fine.

So, if you wanna setup your LDAP server, you have to put all your cert entries under the level one entry under base. Like this:

OR, create one customized partition under base as entry for all your public certs.

And, need to mention that, the public cert must be saved in 'userCertificate' attribute, and the mail attribute must be set to the domain address or email address.
And the objectClass must be 'inetOrgPerson'. These values are hard-coded as search criteria in Direct Project.

In the 'DIRECT CERTIFICATE DISCOVERY TEST IMPLEMENTATION INSTRUCTION GUIDE'

Office of the National Coordinator for Health Information Technology
Version 1.0
April 27, 2012
They setup their LDAP in the structure like this:
They use DNS mixed LDAP, I guess it all goes to DNS for searching public cert instead of LDAP.


Reference:
http://www.ldapguru.info/ldap/ldap-search-best-practices.html(Good introducing level article for LDAP search)

4/24/2013

Direct Project Overview


Direct project.
Basically, it’s all about trust and security.
Based on this idea, it actually doesn't have to be emails. It could be socket communication, and could also be http messages. The government driven Direct Project choose SMTP as their communication protocol  which is emails.

From the sending and receiving point of view,
“If you trust me and would like to receive message from me, then you put my certificate in your trust list. And I’ll put your certificate into my trust list to send you secure message.”

Once one organization put one other organization’s certificate into its trust list, it means the organization can send message to the organization and read message from the organization.

How to trust?
Based on Certificate.
How to trust a certificate?
Actually, it’s trust of the authority who issued the certificate. Then verify the signature of the certificate is right or not, using the issuer CA’s public key.
Trust Chain.

In direct project’s Applicability Statement, it mentions that “The address trusts any valid leaf certificate whose certificate chain contains at least one certificate from the address’s Anchor list.”
It means that as long as your certificate has relationship to one CA that has been trusted by me. I’ll trust you.
So here come two concerns based on the core element ‘certificate’ of direct.

  -----Certificate Discovery.
 -----Certificate Management.

Currently in Direct Project, there are two ways build in to hold its own certificates. One is using DNS, the other is using LDAP. Main reasons of picking these two ways I think it’s because these two ways are the most widely known and supported mechanism. Direct has its ambition, it wants it to replace Fax in doctor’s work life. So to make this project easier to be widely accepted and easier to be integrated, DNS and LDAP is the most possible way.
So the certificate discovery is basically replying on the DNS or LDAP technology.

Certificate management.

The certificate could be issued to ‘domain(organization)’ and could be issued to ‘user’.
Different CAs could pick the certificates that it would like to trust.

Well, direct project is a really good push to MU2.
But personal concern is, with the increasing of trust partners, the management of certificates will be harder.
The certificates number could explode, higher pressure when more users involved.
But compared to its benefit, it seems like this kind of issue is not an issue.

In direct project, what they did is first create one domain in the service, then upload trusted certificates of that CA into the trust anchors of this CA.
If program can call the API of direct service directly, direct can be easily integrated with other system.




4/23/2013

Remote debug setup for eclipse

Recently found one debug method very convenient for the temp stable version of project.

Which is the remote debugging for tomcat/James Server and eclipse.

For Tomcat,

First update the startup.bat file of tomcat.

 ...

rem Get remaining unshifted command line arguments and save them in the
set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
:doneSetArgs

set JPDA_ADDRESS=8000
set JPDA_TRANSPORT=dt_socket

call "%EXECUTABLE%" jpda start %CMD_LINE_ARGS%


:end

Add the green block at the end of the bat file.

Or set the green block to

reset DEBUG=-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y
call "%EXECUTABLE%" %DEBUG% start %CMD_LINE_ARGS%

suspend=y/n,if you wanna to debug the app from the starting point, then you need to set this value to 'y'. Otherwise set to 'n', then you can start the remote debugging anytime after the app is up running.

When suspend=y , the app will be executed after Eclipse has connected to the remote port.
IMPORTANT! In this case, AFTER you start the app, then connect in Eclipse. Otherwise Eclipse will throw  Exception "Failed to connect to remote VM. Connection refused"

Then in Eclipse, make sure Window > Preferences > Java > Compiler 
Above setting is correctly set. And, go to Run> Debug Configuration>Remote Java Application. 
Double click to add a new app.

Make sure the debug project, server and port. And if you want terminate the project on server in eclipse, the  check the check box "Allow termination of remote JVM".

For James Server, there is one line in the run.bat.


Uncomment this line, and update the setting properties based on requirement.

Java App:

java -Xdebug -Xrunjdwp:transport=dt_socket,address=8001,server=y,suspend=y -jar stockTradingGUI.jar

Reference(Very useful tutorials):
http://javarevisited.blogspot.com/2011/02/how-to-setup-remote-debugging-in.html#ixzz1qCJwQtY2

http://javarevisited.blogspot.sg/2011/11/hotspot-jvm-options-java-examples.html