[Pki-devel] [PATCH] 0026-5, 0044-3 Lightweight sub-CAs

Fraser Tweedale ftweedal at redhat.com
Mon Aug 24 07:27:21 UTC 2015


Hi team,

The latest sub-CAs patches are attached.  It has been a while since
the last patchset (that was posted here, anyway) and there have been
some significant changes, outlined below.  (The patchset version
skipped a couple numbers due to versions distributed privately that
I felt were not stable enough to warrant posting to pki-devel.)

Major changes:

- The Java client and CLI were extracted to a separate patch (0044).
- An LDAP entry for each sub-CA is written to database.
  - Database searched and sub-CAs are initialised at startup
  - Key nickname is store in / read from LDAP entry
- Sub-CA "list" API call, client method and CLI was added
- More resources are shared between top-level CA and sub-CAs
  - Suprious task threads and LDAP connections hunted down :)

Dependencies:

- Patch 0026-5 probably depends on 0045[1] for a clean merge.
- Patch 0044-3 depends on my patch 0046[2].

[1] https://www.redhat.com/archives/pki-devel/2015-August/msg00072.html
[2] https://www.redhat.com/archives/pki-devel/2015-August/msg00073.html

Cheers,
Fraser
-------------- next part --------------
From d7efe6286bd7b6e561cadc3f4b6cb5c3a1f56873 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Wed, 28 Jan 2015 02:41:10 -0500
Subject: [PATCH] Add lightweight sub-CA support

---
 base/ca/shared/conf/catalina.properties            |   2 +
 base/ca/shared/conf/db.ldif                        |   5 +
 .../shared/webapps/ca/agent/ca/queryCert.template  |   9 +-
 .../webapps/ca/agent/ca/reasonToRevoke.template    |   8 +-
 .../shared/webapps/ca/agent/ca/srchCert.template   |   2 +
 base/ca/shared/webapps/ca/ee/ca/queryCert.template |   6 +-
 .../webapps/ca/ee/ca/reasonToRevoke.template       |   8 +-
 base/ca/src/com/netscape/ca/CAService.java         |  51 +--
 .../src/com/netscape/ca/CertificateAuthority.java  | 416 ++++++++++++++++++---
 base/ca/src/com/netscape/ca/SigningUnit.java       |  22 +-
 .../dogtagpki/server/ca/rest/CAApplication.java    |   3 +
 .../org/dogtagpki/server/ca/rest/SubCAService.java | 156 ++++++++
 .../src/com/netscape/certsrv/ca/ICAService.java    |  10 +-
 .../netscape/certsrv/ca/ICertificateAuthority.java |  35 ++
 .../netscape/certsrv/profile/IEnrollProfile.java   |   5 +
 .../netscape/certsrv/security/ISigningUnit.java    |   8 +
 .../src/com/netscape/certsrv/subca/CAData.java     |  88 +++++
 .../com/netscape/certsrv/subca/SubCAResource.java  |  36 ++
 .../cms/profile/common/CAEnrollProfile.java        |   7 +-
 .../netscape/cms/profile/common/EnrollProfile.java |   3 +
 .../cms/profile/def/AuthInfoAccessExtDefault.java  |   8 +-
 .../def/AuthorityKeyIdentifierExtDefault.java      |  15 +-
 .../netscape/cms/profile/def/CAEnrollDefault.java  |   4 +-
 .../netscape/cms/servlet/cert/DisplayBySerial.java |  31 +-
 .../com/netscape/cms/servlet/cert/DoRevoke.java    |  15 +-
 .../cms/servlet/cert/EnrollmentProcessor.java      |   9 +
 .../com/netscape/cms/servlet/cert/ListCerts.java   |  22 +-
 .../netscape/cms/servlet/cert/ReasonToRevoke.java  |  13 +-
 .../com/netscape/cms/servlet/cert/SrchCerts.java   |  24 +-
 .../com/netscape/cms/servlet/csadmin/CertUtil.java |  38 +-
 .../com/netscape/cms/servlet/ocsp/OCSPServlet.java |   5 +-
 base/server/share/conf/schema-subCA.ldif           |   3 +
 base/server/share/conf/schema.ldif                 |   8 +
 33 files changed, 902 insertions(+), 173 deletions(-)
 create mode 100644 base/ca/src/org/dogtagpki/server/ca/rest/SubCAService.java
 create mode 100644 base/common/src/com/netscape/certsrv/subca/CAData.java
 create mode 100644 base/common/src/com/netscape/certsrv/subca/SubCAResource.java
 create mode 100644 base/server/share/conf/schema-subCA.ldif

diff --git a/base/ca/shared/conf/catalina.properties b/base/ca/shared/conf/catalina.properties
index 70cb7c05e78e0c4ab4b64a74d3f9eaadf96a1420..7e104e52d14852a785b49013520e5102ff356c64 100644
--- a/base/ca/shared/conf/catalina.properties
+++ b/base/ca/shared/conf/catalina.properties
@@ -85,3 +85,5 @@ tomcat.util.buf.StringCache.byte.enabled=true
 #tomcat.util.buf.StringCache.char.enabled=true
 #tomcat.util.buf.StringCache.trainThreshold=500000
 #tomcat.util.buf.StringCache.cacheSize=5000
+
+org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif
index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..8918c5a22566dd5f6bd52a7dd33da46169060e54 100644
--- a/base/ca/shared/conf/db.ldif
+++ b/base/ca/shared/conf/db.ldif
@@ -164,3 +164,8 @@ dn: ou=certificateProfiles,ou=ca,{rootSuffix}
 objectClass: top
 objectClass: organizationalUnit
 ou: certificateProfiles
+
+dn: ou=subCAs,ou=ca,{rootSuffix}
+objectClass: top
+objectClass: organizationalUnit
+ou: subCAs
diff --git a/base/ca/shared/webapps/ca/agent/ca/queryCert.template b/base/ca/shared/webapps/ca/agent/ca/queryCert.template
index 0a423823fe874253f5bff5fad44608eae471c401..aed9655de924f72d9d74d91bd3d546d8cc504870 100644
--- a/base/ca/shared/webapps/ca/agent/ca/queryCert.template
+++ b/base/ca/shared/webapps/ca/agent/ca/queryCert.template
@@ -321,8 +321,10 @@ function displayCertificateRecord(i, cert)
      "<td style='overflow: hidden; white-space: nowrap;'>"+
      "     <font face='PrimaSans BT, Verdana, sans-serif' size='-1'>\n"+
      "   <div style='overflow: hidden; white-space: nowrap;'>"+
-     " <a index='"+i+"' href='displayBySerial?op=displayBySerial&serialNumber=0x"+
-           cert.serialNumber+"' onmouseover='mouseover(this,event);' "+
+     " <a index='"+i+"' href='displayBySerial?op=displayBySerial"
+        + "&serialNumber=0x" + cert.serialNumber
+        + (result.header.caRef ? "&caRef=" + result.header.caRef : "")
+        + "' onmouseover='mouseover(this,event);' "+
            "onmouseout='mouseout(this);'>"+
          cert.subject+"</a></div></font>"+
      "</td>"+
@@ -419,6 +421,7 @@ function doNext(element)
 	var form = element.form;
 //	form.action = "/"+result.header.op;
         form.action = "/ca/agent/ca/listCerts";
+        form.caRef.value = result.header.caRef || "";
 	form.op.value = result.header.op;
         form.queryCertFilter.value = result.header.queryCertFilter;
         form.direction.value= "down";
@@ -472,6 +475,8 @@ document.write(
 "<button "+disabledUp+" NAME=up onClick='doNext(this)' VALUE='<' width='72'><</button>\n"+
 "<INPUT TYPE=hidden NAME=totalRecordCount VALUE='"+
 result.header.totalRecordCount+ "'>\n"+
+"<INPUT TYPE=hidden NAME=caRef VALUE='"+
+(result.header.caRef || "") + "'>\n"+
 "<INPUT TYPE=hidden NAME=queryCertFilter VALUE='"+
 result.header.queryCertFilter+ "'>\n"+
 "<INPUT TYPE=hidden NAME=skipRevoked VALUE='"+
diff --git a/base/ca/shared/webapps/ca/agent/ca/reasonToRevoke.template b/base/ca/shared/webapps/ca/agent/ca/reasonToRevoke.template
index ffb648beb117a304a5b6382c129dade20fbb3a09..711592a4e463b6e791587644da4233af08e93cca 100644
--- a/base/ca/shared/webapps/ca/agent/ca/reasonToRevoke.template
+++ b/base/ca/shared/webapps/ca/agent/ca/reasonToRevoke.template
@@ -193,9 +193,9 @@ function displayCertInfo()
     document.write("<table border=\"0\" cellspacing=\"2\">");
     for (var i = 0; i < result.recordSet.length; ++i ) {
         if (result.recordSet[i].serialNumber != null) {
-            if (result.header.caSerialNumber != null &&
-                result.recordSet[i].serialNumber ==
-                result.header.caSerialNumber) {
+            if (result.header.caSerialNumber != null
+                    && result.recordSet[i].serialNumber == result.header.caSerialNumber
+                    && (result.header.caRef || "") == "") {
                 document.write(renderRowWithoutCheckbox("Serial Number:",
                                toHex(result.recordSet[i].serialNumber)));
             } else {
@@ -461,6 +461,8 @@ function revokeCert(serialNumber)
                          result.recordSet[i].serialNumber +"\">");
             }
         }
+        document.writeln("<INPUT TYPE=hidden name=caRef value=\"" +
+                         (result.header.caRef || "") + "\">");
         document.writeln("<INPUT TYPE=hidden name=revokeAll value=\"" +
                          result.header.revokeAll +"\">");
         document.writeln("<INPUT TYPE=hidden name=totalRecordCount value=\"" +
diff --git a/base/ca/shared/webapps/ca/agent/ca/srchCert.template b/base/ca/shared/webapps/ca/agent/ca/srchCert.template
index 001b0e3b5bde043997b6bdb4e1e12f3e10e87526..ebd59822806953ba6500a02b94a1219eba2fe534 100644
--- a/base/ca/shared/webapps/ca/agent/ca/srchCert.template
+++ b/base/ca/shared/webapps/ca/agent/ca/srchCert.template
@@ -106,6 +106,7 @@ function renderDetailsButton(serialNumber)
 	return "<FORM METHOD=post "+
 "ACTION=\""+ "displayBySerial" +"\">\n"+
 "<INPUT TYPE=hidden NAME=\"op\" VALUE=\""+ "displayBySerial" +"\">\n"+
+"<INPUT TYPE=hidden NAME=\"caRef\" VALUE=\"" + (result.header.caRef || "") + "\">\n" +
 "<INPUT TYPE=hidden NAME=\"serialNumber\" VALUE=\""+ "0x"+serialNumber +"\">\n"+
 "<INPUT TYPE=submit VALUE=\"Details\" width=\"72\"></FORM>\n";
 }
@@ -118,6 +119,7 @@ function renderRevokeButton(serialNumberDecimal)
 //"onSubmit=\"return revokeCert("+serialNumberDecimal+");\" "+
 "ACTION=\""+ "reasonToRevoke" +"\">\n"+
 "<INPUT TYPE=hidden NAME=\"op\" VALUE=\""+ "reasonToRevoke" +"\">\n"+
+"<INPUT TYPE=hidden NAME=\"caRef\" VALUE=\"" + (result.header.caRef || "") + "\">\n" +
 "<INPUT TYPE=hidden NAME=\"serialNumber\" VALUE=\""+ serialNumberDecimal +"\">\n"+
 "<INPUT TYPE=hidden NAME=\"revokeAll\" VALUE=\"(&(certRecordId="+serialNumberDecimal+"))\">\n"+
 "<INPUT TYPE=hidden NAME=\"totalRecordCount\" VALUE=\"1\">\n"+
diff --git a/base/ca/shared/webapps/ca/ee/ca/queryCert.template b/base/ca/shared/webapps/ca/ee/ca/queryCert.template
index d4a56a79a6667beef1f3822bc59288c7caeb2f11..cd3b4975168898293c595bdff41126bf52e58682 100644
--- a/base/ca/shared/webapps/ca/ee/ca/queryCert.template
+++ b/base/ca/shared/webapps/ca/ee/ca/queryCert.template
@@ -301,8 +301,10 @@ function displayCertificateRecord(i, cert)
      "<td style='overflow: hidden; white-space: nowrap;'>"+
      "     <font face='PrimaSans BT, Verdana, sans-serif' size='-1'>\n"+
      "   <div style='overflow: hidden; white-space: nowrap;'>"+
-     " <a index='"+i+"' href='/ca/ee/ca/displayBySerial?op=displayBySerial&serialNumber=0x"+
-           cert.serialNumber+"' onmouseover='mouseover(this,event);' "+
+     " <a index='"+i+"' href='/ca/ee/ca/displayBySerial?op=displayBySerial"
+        + "&serialNumber=0x" + cert.serialNumber
+        + (result.header.caRef ? "&caRef=" + result.header.caRef : "")
+        + "' onmouseover='mouseover(this,event);' " +
            "onmouseout='mouseout(this);'>"+
          addEscapes(cert.subject)+"</a></div></font>"+
      "</td>"+
diff --git a/base/ca/shared/webapps/ca/ee/ca/reasonToRevoke.template b/base/ca/shared/webapps/ca/ee/ca/reasonToRevoke.template
index 2a608438b1f46b7695a8692ed857ce7de6e07d42..d81e37a1dab30b079fcbb82c19f6a8ec940deb46 100644
--- a/base/ca/shared/webapps/ca/ee/ca/reasonToRevoke.template
+++ b/base/ca/shared/webapps/ca/ee/ca/reasonToRevoke.template
@@ -187,9 +187,9 @@ function displayCertInfo()
     document.write("<table border=\"0\" cellspacing=\"2\">");
     for (var i = 0; i < result.recordSet.length; ++i ) {
         if (result.recordSet[i].serialNumber != null) {
-            if (result.header.caSerialNumber != null &&
-                result.recordSet[i].serialNumber ==
-                result.header.caSerialNumber) {
+            if (result.header.caSerialNumber != null
+                    && result.recordSet[i].serialNumber == result.header.caSerialNumber
+                    && (result.header.caRef || "") == "") {
                 document.write(renderRowWithoutCheckbox("Serial Number: ",
                                toHex(result.recordSet[i].serialNumber)));
             } else {
@@ -448,6 +448,8 @@ function revokeCert(serialNumber)
         <input type="reset" value="Reset" name="reset" width="72">  
 <SCRIPT LANGUAGE="JavaScript">
 //<!--
+        document.writeln("<INPUT TYPE=hidden name=caRef value=\"" +
+                         (result.header.caRef || "") + "\">");
         document.writeln("<INPUT TYPE=hidden name=serialNumber value=\"" +
                          result.header.serialNumber +"\">");
         document.writeln("<INPUT TYPE=hidden name=revokeAll value=\"" +
diff --git a/base/ca/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java
index 36f0bd592e333a276da84662c1e64a2921c5e7d2..3e36b9000d083ed50577bf3eecf06dbd04067f0b 100644
--- a/base/ca/src/com/netscape/ca/CAService.java
+++ b/base/ca/src/com/netscape/ca/CAService.java
@@ -565,18 +565,15 @@ public class CAService implements ICAService, IService {
     /// CA related routines.
     ///
 
-    public X509CertImpl issueX509Cert(X509CertInfo certi)
-            throws EBaseException {
-        return issueX509Cert(certi, null, null);
-    }
-
     /**
      * issue cert for enrollment.
      */
-    public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid)
+    public X509CertImpl issueX509Cert(
+                String caRef, X509CertInfo certi,
+                String profileId, String rid)
             throws EBaseException {
         CMS.debug("issueX509Cert");
-        X509CertImpl certImpl = issueX509Cert("", certi, false, null);
+        X509CertImpl certImpl = issueX509Cert(caRef, "", certi, false, null);
 
         CMS.debug("storeX509Cert " + certImpl.getSerialNumber());
         storeX509Cert(profileId, rid, certImpl);
@@ -615,9 +612,21 @@ public class CAService implements ICAService, IService {
      * renewal is expected to have original cert serial no. in cert info
      * field.
      */
-    X509CertImpl issueX509Cert(String rid, X509CertInfo certi,
-            boolean renewal, BigInteger oldSerialNo)
-            throws EBaseException {
+    X509CertImpl issueX509Cert(
+                String rid, X509CertInfo certi,
+                boolean renewal, BigInteger oldSerialNo
+            ) throws EBaseException {
+        return issueX509Cert("", rid, certi, renewal, oldSerialNo);
+    }
+
+    private X509CertImpl issueX509Cert(
+                String caRef, String rid, X509CertInfo certi,
+                boolean renewal, BigInteger oldSerialNo
+            ) throws EBaseException {
+        ICertificateAuthority ca = mCA.getSubCA(caRef);
+        if (ca == null)
+            throw new ECAException("No such CA: " + caRef);
+
         String algname = null;
         X509CertImpl cert = null;
 
@@ -642,7 +651,7 @@ public class CAService implements ICAService, IService {
             // set default cert version. If policies added a extensions
             // the version would already be set to version 3.
             if (certi.get(X509CertInfo.VERSION) == null) {
-                certi.set(X509CertInfo.VERSION, mCA.getDefaultCertVersion());
+                certi.set(X509CertInfo.VERSION, ca.getDefaultCertVersion());
             }
 
             // set default validity if not set.
@@ -665,7 +674,7 @@ public class CAService implements ICAService, IService {
                 }
 
                 begin = CMS.getCurrentDate();
-                end = new Date(begin.getTime() + mCA.getDefaultValidity());
+                end = new Date(begin.getTime() + ca.getDefaultValidity());
                 certi.set(CertificateValidity.NAME,
                         new CertificateValidity(begin, end));
             }
@@ -705,7 +714,7 @@ public class CAService implements ICAService, IService {
             }
 
             Date caNotAfter =
-                    mCA.getSigningUnit().getCertImpl().getNotAfter();
+                    ca.getSigningUnit().getCertImpl().getNotAfter();
 
             if (begin.after(caNotAfter)) {
                 mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_PAST_VALIDITY"));
@@ -714,7 +723,7 @@ public class CAService implements ICAService, IService {
 
             if (end.after(caNotAfter)) {
                 if (!is_ca) {
-                    if (!mCA.isEnablePastCATime()) {
+                    if (!ca.isEnablePastCATime()) {
                         end = caNotAfter;
                         certi.set(CertificateValidity.NAME,
                                 new CertificateValidity(begin, caNotAfter));
@@ -734,7 +743,7 @@ public class CAService implements ICAService, IService {
                     certi.get(X509CertInfo.ALGORITHM_ID);
 
             if (algor == null || algor.toString().equals(CertInfo.SERIALIZE_ALGOR.toString())) {
-                algname = mCA.getSigningUnit().getDefaultAlgorithm();
+                algname = ca.getSigningUnit().getDefaultAlgorithm();
                 algid = AlgorithmId.get(algname);
                 certi.set(X509CertInfo.ALGORITHM_ID,
                         new CertificateAlgorithmId(algid));
@@ -820,16 +829,16 @@ public class CAService implements ICAService, IService {
         }
 
         try {
-            if (mCA.getIssuerObj() != null) {
+            if (ca.getIssuerObj() != null) {
                 // this ensures the isserDN has the same encoding as the
                 // subjectDN of the CA signing cert
                 CMS.debug("CAService: issueX509Cert: setting issuerDN using exact CA signing cert subjectDN encoding");
                 certi.set(X509CertInfo.ISSUER,
-                        mCA.getIssuerObj());
+                        ca.getIssuerObj());
             } else {
-                CMS.debug("CAService: issueX509Cert: mCA.getIssuerObj() is null, creating new CertificateIssuerName");
+                CMS.debug("CAService: issueX509Cert: ca.getIssuerObj() is null, creating new CertificateIssuerName");
                 certi.set(X509CertInfo.ISSUER,
-                        new CertificateIssuerName(mCA.getX500Name()));
+                        new CertificateIssuerName(ca.getX500Name()));
             }
         } catch (CertificateException e) {
             mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_SET_ISSUER", e.toString()));
@@ -861,8 +870,8 @@ public class CAService implements ICAService, IService {
             }
         }
 
-        CMS.debug("About to mCA.sign cert.");
-        cert = mCA.sign(certi, algname);
+        CMS.debug("About to ca.sign cert.");
+        cert = ca.sign(certi, algname);
         return cert;
     }
 
diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index acf07b9bde2a05f7c62740293a0c66cf92f50771..25d9290e1da82d5938f70595b56f74769b0ebbd1 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -25,14 +25,18 @@ import java.io.IOException;
 import java.math.BigInteger;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.KeyPair;
 import java.security.PublicKey;
 import java.security.cert.CRLException;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateParsingException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
@@ -53,6 +57,13 @@ import netscape.security.x509.X509CertInfo;
 import netscape.security.x509.X509ExtensionException;
 import netscape.security.x509.X509Key;
 
+import netscape.ldap.LDAPAttribute;
+import netscape.ldap.LDAPAttributeSet;
+import netscape.ldap.LDAPConnection;
+import netscape.ldap.LDAPEntry;
+import netscape.ldap.LDAPException;
+import netscape.ldap.LDAPSearchResults;
+
 import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.asn1.ASN1Util;
 import org.mozilla.jss.asn1.GeneralizedTime;
@@ -60,6 +71,9 @@ import org.mozilla.jss.asn1.INTEGER;
 import org.mozilla.jss.asn1.InvalidBERException;
 import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
 import org.mozilla.jss.asn1.OCTET_STRING;
+import org.mozilla.jss.crypto.CryptoToken;
+import org.mozilla.jss.crypto.KeyPairAlgorithm;
+import org.mozilla.jss.crypto.KeyPairGenerator;
 import org.mozilla.jss.crypto.SignatureAlgorithm;
 import org.mozilla.jss.crypto.TokenException;
 import org.mozilla.jss.pkix.cert.Extension;
@@ -77,11 +91,13 @@ import com.netscape.certsrv.ca.ECAException;
 import com.netscape.certsrv.ca.ICRLIssuingPoint;
 import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.dbs.IDBSubsystem;
+import com.netscape.certsrv.dbs.IDBSSession;
 import com.netscape.certsrv.dbs.certdb.ICertRecord;
 import com.netscape.certsrv.dbs.certdb.ICertificateRepository;
 import com.netscape.certsrv.dbs.crldb.ICRLRepository;
 import com.netscape.certsrv.dbs.replicadb.IReplicaIDRepository;
 import com.netscape.certsrv.ldap.ELdapException;
+import com.netscape.certsrv.ldap.ILdapConnFactory;
 import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.ocsp.IOCSPService;
 import com.netscape.certsrv.policy.IPolicyProcessor;
@@ -96,6 +112,8 @@ import com.netscape.certsrv.request.IRequestScheduler;
 import com.netscape.certsrv.request.IService;
 import com.netscape.certsrv.security.ISigningUnit;
 import com.netscape.certsrv.util.IStatsSubsystem;
+import com.netscape.cms.servlet.csadmin.CertUtil;
+import com.netscape.cmscore.base.PropConfigStore;
 import com.netscape.cmscore.dbs.CRLRepository;
 import com.netscape.cmscore.dbs.CertRecord;
 import com.netscape.cmscore.dbs.CertificateRepository;
@@ -106,6 +124,7 @@ import com.netscape.cmscore.listeners.ListenerPlugin;
 import com.netscape.cmscore.request.RequestSubsystem;
 import com.netscape.cmscore.security.KeyCertUtil;
 import com.netscape.cmscore.util.Debug;
+import com.netscape.cmsutil.crypto.CryptoUtil;
 import com.netscape.cmsutil.ocsp.BasicOCSPResponse;
 import com.netscape.cmsutil.ocsp.CertID;
 import com.netscape.cmsutil.ocsp.CertStatus;
@@ -123,6 +142,9 @@ import com.netscape.cmsutil.ocsp.SingleResponse;
 import com.netscape.cmsutil.ocsp.TBSRequest;
 import com.netscape.cmsutil.ocsp.UnknownInfo;
 
+import org.apache.commons.lang.StringUtils;
+
+
 /**
  * A class represents a Certificate Authority that is
  * responsible for certificate specific operations.
@@ -136,6 +158,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
 
     public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2");
 
+    private static final Hashtable<String, ICertificateAuthority> caMap = new Hashtable();
+    protected CertificateAuthority parentCA = null;
+    protected List<String> subCAHier;
+    protected String subCAOUs;
+
     protected ISubsystem mOwner = null;
     protected IConfigStore mConfig = null;
     protected ILogger mLogger = CMS.getLogger();
@@ -234,6 +261,32 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      * Constructs a CA subsystem.
      */
     public CertificateAuthority() {
+        subCAHier = new ArrayList(0);
+        subCAOUs = joinSubCAOUs(subCAHier);
+    }
+
+    /**
+     * Construct a CA subsystem with given owner subsystem, config and
+     * path in CA hierarchy.
+     *
+     * The primary CA has an empty path.  For Sub-CAs, the
+     * certification path runs left-to-right, i.e., CA at index
+     * N is signed by CA at index (N - 1).
+     */
+    public CertificateAuthority(
+            String subsystemId,
+            ISubsystem owner,
+            IConfigStore config,
+            CertificateAuthority parentCA,
+            List<String> path,
+            String signingKeyNickname
+            ) throws EBaseException {
+        subCAHier = path;
+        subCAOUs = joinSubCAOUs(subCAHier);
+        setId(subsystemId);
+        this.parentCA = parentCA;
+        mNickname = signingKeyNickname;
+        init(owner, config);
     }
 
     /**
@@ -334,8 +387,22 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             mOwner = owner;
             mConfig = config;
 
-            // init cert & crl database.
-            initCaDatabases();
+            // init cert & crl database
+            initCertDatabase();
+            initCrlDatabase();
+
+            // init replica id repository
+            if (parentCA == null) {
+                String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null);
+                if (replicaReposDN == null) {
+                    replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN();
+                }
+                mReplicaRepot = new ReplicaIDRepository(
+                        DBSubsystem.getInstance(), 1, replicaReposDN);
+                CMS.debug("Replica Repot inited");
+            } else {
+                mReplicaRepot = parentCA.mReplicaRepot;
+            }
 
             // init signing unit & CA cert.
             try {
@@ -358,51 +425,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             if (CMS.isPreOpMode())
                 return;
 
-            // set certificate status to 10 minutes
-            mCertRepot.setCertStatusUpdateInterval(
+            /* The top-level CA owns these resources so skip these
+             * steps for sub-CAs.
+             */
+            if (parentCA == null) {
+                /* These methods configure and start threads related to
+                 * CertificateRepository.  Ideally all of the config would
+                 * be pushed into CertificateRepository constructor and a
+                 * single 'start' method would start the threads.
+                 */
+                // set certificate status to 10 minutes
+                mCertRepot.setCertStatusUpdateInterval(
                     mRequestQueue.getRequestRepository(),
                     mConfig.getInteger("certStatusUpdateInterval", 10 * 60),
                     mConfig.getBoolean("listenToCloneModifications", false));
-            mCertRepot.setConsistencyCheck(
+                mCertRepot.setConsistencyCheck(
                     mConfig.getBoolean("ConsistencyCheck", false));
-            mCertRepot.setSkipIfInConsistent(
+                mCertRepot.setSkipIfInConsistent(
                     mConfig.getBoolean("SkipIfInConsistent", false));
 
-            // set serial number update task to run every 10 minutes
-            mCertRepot.setSerialNumberUpdateInterval(
+                // set serial number update task to run every 10 minutes
+                mCertRepot.setSerialNumberUpdateInterval(
                     mRequestQueue.getRequestRepository(),
                     mConfig.getInteger("serialNumberUpdateInterval", 10 * 60));
 
-            mService.init(config.getSubStore("connector"));
+                mService.init(config.getSubStore("connector"));
 
-            initMiscellaneousListeners();
-
-            // instantiate CRL publisher
-            IConfigStore cpStore = null;
-
-            mByName = config.getBoolean("byName", true);
-
-            cpStore = config.getSubStore("crlPublisher");
-            if (cpStore != null && cpStore.size() > 0) {
-                String publisherClass = cpStore.getString("class");
-
-                if (publisherClass != null) {
-                    try {
-                        @SuppressWarnings("unchecked")
-                        Class<ICRLPublisher> pc = (Class<ICRLPublisher>) Class.forName(publisherClass);
-
-                        mCRLPublisher = pc.newInstance();
-                        mCRLPublisher.init(this, cpStore);
-                    } catch (ClassNotFoundException ee) {
-                        log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString()));
-                    } catch (IllegalAccessException ee) {
-                        log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString()));
-                    } catch (InstantiationException ee) {
-                        log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString()));
-                    }
-                }
+                initMiscellaneousListeners();
             }
 
+            initCRLPublisher();
+
             // initialize publisher processor (publish remote admin
             // rely on this subsystem, so it has to be initialized)
             initPublish();
@@ -412,6 +465,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             // being functional.
             initCRL();
 
+            loadSubCAs();
+
         } catch (EBaseException e) {
             if (CMS.isPreOpMode())
                 return;
@@ -420,6 +475,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         }
     }
 
+    private void initCRLPublisher() throws EBaseException {
+        // instantiate CRL publisher
+        if (parentCA != null) {
+            mByName = parentCA.mByName;
+            mCRLPublisher = parentCA.mCRLPublisher;
+            return;
+        }
+
+        mByName = mConfig.getBoolean("byName", true);
+        IConfigStore cpStore = mConfig.getSubStore("crlPublisher");
+        if (cpStore != null && cpStore.size() > 0) {
+            String publisherClass = cpStore.getString("class");
+
+            if (publisherClass != null) {
+                try {
+                    @SuppressWarnings("unchecked")
+                    Class<ICRLPublisher> pc = (Class<ICRLPublisher>) Class.forName(publisherClass);
+
+                    mCRLPublisher = pc.newInstance();
+                    mCRLPublisher.init(this, cpStore);
+                } catch (ClassNotFoundException ee) {
+                    log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString()));
+                } catch (IllegalAccessException ee) {
+                    log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString()));
+                } catch (InstantiationException ee) {
+                    log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString()));
+                }
+            }
+        }
+    }
+
     /**
      * return CA's request queue processor
      */
@@ -540,14 +626,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         mService.startup();
         mRequestQueue.recover();
 
-        // Note that this could be null.
-
-        // setup Admin operations
-
-        initNotificationListeners();
-
-        startPublish();
-        //		startCRL();
+        if (parentCA == null) {
+            // setup Admin operations
+            initNotificationListeners();
+            startPublish();
+        }
     }
 
     /**
@@ -555,6 +638,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      * <P>
      */
     public void shutdown() {
+        if (parentCA != null) return;  // sub-CAs don't own these resources
+
         Enumeration<ICRLIssuingPoint> enums = mCRLIssuePoints.elements();
         while (enums.hasMoreElements()) {
             CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement();
@@ -1228,13 +1313,14 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
                 mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME));
             }
 
-            mSigningUnit.init(this, caSigningCfg);
+            mSigningUnit.init(this, caSigningCfg, mNickname);
             CMS.debug("CA signing unit inited");
 
             // for identrus
             IConfigStore CrlStore = mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE);
 
-            if (CrlStore != null && CrlStore.size() > 0) {
+            if (CrlStore != null && CrlStore.size() > 0
+                    && (subCAHier == null || subCAHier.size() == 0)) {
                 mCRLSigningUnit = new SigningUnit();
                 mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE));
             } else {
@@ -1304,7 +1390,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
 
             IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE);
 
-            if (OCSPStore != null && OCSPStore.size() > 0) {
+            if (OCSPStore != null && OCSPStore.size() > 0
+                    && (subCAHier == null || subCAHier.size() == 0)) {
                 mOCSPSigningUnit = new SigningUnit();
                 mOCSPSigningUnit.init(this, mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE));
                 CMS.debug("Separate OCSP signing unit inited");
@@ -1443,8 +1530,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
     /**
      * init cert & crl database
      */
-    private void initCaDatabases()
+    private void initCertDatabase()
             throws EBaseException {
+        if (parentCA != null) {
+            mCertRepot = parentCA.mCertRepot;
+            return;
+        }
+
         int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5);
 
         String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null);
@@ -1471,8 +1563,17 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         mCertRepot.setTransitRecordPageSize(transitRecordPageSize);
 
         CMS.debug("Cert Repot inited");
+    }
 
-        // init crl repot.
+    /**
+     * init cert & crl database
+     */
+    private void initCrlDatabase()
+            throws EBaseException {
+        if (parentCA != null) {
+            mCRLRepot = parentCA.mCRLRepot;
+            return;
+        }
 
         int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5);
 
@@ -1482,14 +1583,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
                     "ou=crlIssuingPoints, ou=" + getId() + ", " +
                             getDBSubsystem().getBaseDN());
         CMS.debug("CRL Repot inited");
-
-        String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null);
-        if (replicaReposDN == null) {
-            replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN();
-        }
-        mReplicaRepot = new ReplicaIDRepository(
-                DBSubsystem.getInstance(), 1, replicaReposDN);
-        CMS.debug("Replica Repot inited");
     }
 
     private void startPublish()
@@ -1513,6 +1606,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     private void initPublish()
             throws EBaseException {
+        if (parentCA != null) {
+            mPublisherProcessor = parentCA.mPublisherProcessor;
+            return;
+        }
+
         IConfigStore c = null;
 
         try {
@@ -1676,6 +1774,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     private void initRequestQueue()
             throws EBaseException {
+        if (parentCA != null) {
+            mPolicy = parentCA.mPolicy;
+            mService = parentCA.mService;
+            mNotify = parentCA.mNotify;
+            mPNotify = parentCA.mPNotify;
+            mRequestQueue = parentCA.mRequestQueue;
+            return;
+        }
+
         mPolicy = new CAPolicy();
         mPolicy.init(this, mConfig.getSubStore(PROP_POLICY));
         CMS.debug("CA policy inited");
@@ -1734,6 +1841,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
     @SuppressWarnings("unchecked")
     private void initCRL()
             throws EBaseException {
+        if (parentCA != null) {
+            mCRLIssuePoints = parentCA.mCRLIssuePoints;
+            mMasterCRLIssuePoint = parentCA.mMasterCRLIssuePoint;
+            return;
+        }
         IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE);
 
         if ((crlConfig == null) || (crlConfig.size() <= 0)) {
@@ -1799,6 +1911,44 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         log(ILogger.LL_INFO, "CRL Issuing Points inited");
     }
 
+    /**
+     * Find, instantiate and register this CA's sub-CAs.
+     */
+    private void loadSubCAs() throws EBaseException {
+        ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadSubCAs");
+        dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+        LDAPConnection conn = dbFactory.getConn();
+
+        LDAPSearchResults results = null;
+        try {
+            results = conn.search(
+                subCAOUs + "ou=subCAs,ou=" + getId() + "," + getDBSubsystem().getBaseDN(),
+                LDAPConnection.SCOPE_ONE, "(objectclass=subCA)", null, false);
+
+            while (results.hasMoreElements()) {
+                LDAPEntry entry = results.next();
+                LDAPAttribute ouAttr = entry.getAttribute("ou");
+                LDAPAttribute nickAttr = entry.getAttribute("subCAKeyNickname");
+                if (ouAttr == null || nickAttr == null)
+                    throw new ECAException("Malformed sub-CA object: " + entry.getDN());
+                List<String> newHier = new ArrayList();
+                if (subCAHier != null) newHier.addAll(subCAHier);
+                newHier.add((String) ouAttr.getStringValues().nextElement());
+                String subCAHandle = StringUtils.join(newHier, "/");
+                String keyNick = (String) nickAttr.getStringValues().nextElement();
+
+                CertificateAuthority subCA = new CertificateAuthority(
+                    getId(), mOwner, mConfig, this, newHier, keyNick);
+                caMap.put(subCAHandle, subCA);
+            }
+        } catch (LDAPException e) {
+            throw new ECAException("Failed to execute LDAP search for sub-CAs: " + e);
+        } finally {
+            dbFactory.returnConn(conn);
+            dbFactory.reset();
+        }
+    }
+
     public String getOfficialName() {
         return OFFICIAL_NAME;
     }
@@ -2083,4 +2233,158 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
 
         return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate);
     }
+
+    /**
+     * Enumerate all sub-CA handles.
+     */
+    public List<String> subCAHandles() {
+        return Collections.list(caMap.keys());
+    }
+
+    /**
+     * Get the sub-CA specified by a slash-delimited string.
+     *
+     * Leading and trailing whitespace and slashes are stripped and
+     * multiple slashes appearing together in the interior are
+     * treated as a single delimiter.
+     *
+     * @return the authority, or null if not found
+     */
+    public ICertificateAuthority getSubCA(String handle) {
+        handle = normalizeCAHandle(handle);
+        return handle != "" ? caMap.get(handle) : topCA();
+    }
+
+    /**
+     * Return the top-level CA in the heirarchy.
+     */
+    private ICertificateAuthority topCA() {
+        return parentCA == null ? this : parentCA.topCA();
+    }
+
+    private static List<String> splitCAHandle(String raw) {
+        if (raw == null) return new ArrayList(0);
+        String stripped = StringUtils.strip(raw, " /");
+        return Arrays.asList(StringUtils.split(stripped, "/"));
+    }
+
+    private static String normalizeCAHandle(String raw) {
+        return StringUtils.join(splitCAHandle(raw), "/");
+    }
+
+    private static String joinSubCAOUs(List<String> handles) {
+        String s = "";
+        if (handles != null) {
+            for (String handle : handles) {
+                s = "ou=" + handle + "," + s;
+            }
+        }
+        return s;
+    }
+
+    /**
+     * Create a new sub-CA.
+     *
+     * @param handle The FULL handle to the CA.
+     */
+    public ICertificateAuthority createSubCA(String handle, String subjectDN)
+            throws EBaseException {
+        if (getSubCA(handle) != null)
+            throw new ECAException("Sub-CA already exists" /* TODO getUserMessage */);
+
+        // load parent CA
+        List<String> components = splitCAHandle(handle);
+        String parentHandle = StringUtils.join(
+            components.subList(0, components.size() - 1), "/");
+        ICertificateAuthority parentCA = getSubCA(parentHandle);
+        if (parentCA == null)
+            throw new ECAException(
+                "Parent CA \"" + parentHandle + "\" does not exist");
+
+        String relHandle = components.get(components.size() - 1);
+        ICertificateAuthority subCA =
+            parentCA.createRelativeSubCA(relHandle, subjectDN);
+        caMap.put(handle, subCA);
+        return subCA;
+    }
+
+    /**
+     * Create a new sub-CA IMMEDIATELY beneath this one.
+     *
+     * This method DOES NOT add the new CA to caMap; it is the
+     * caller's responsibility.
+     *
+     * @param handle The RELATIVE caRef; should be a single fragment.
+     */
+    public ICertificateAuthority createRelativeSubCA(
+            String handle, String subjectDN)
+            throws EBaseException {
+        ArrayList<String> hier = new ArrayList<>(subCAHier);
+        hier.add(handle);
+
+        // compute key nickname
+        IConfigStore sigCS = mConfig.getSubStore(PROP_SIGNING_SUBSTORE);
+        String topNick = null;
+        try {
+            topNick = sigCS.getString(ISigningUnit.PROP_RENAMED_CERT_NICKNAME);
+        } catch (EBaseException e) {
+            topNick = sigCS.getString(ISigningUnit.PROP_CERT_NICKNAME);
+        }
+        String nickname = topNick + " " + StringUtils.join(hier, ",");
+
+        try {
+            // Generate sub-CA signing key
+            CryptoManager cryptoManager = CryptoManager.getInstance();
+            // TODO read PROP_TOKEN_NAME config
+            CryptoToken token = cryptoManager.getInternalKeyStorageToken();
+            // TODO algorithm parameter
+            KeyPairGenerator gen = token.getKeyPairGenerator(KeyPairAlgorithm.RSA);
+            gen.initialize(2048);
+            KeyPair keypair = gen.genKeyPair();
+            PublicKey pub = keypair.getPublic();
+            X509Key x509key = CryptoUtil.convertPublicKeyToX509Key(pub);
+
+            // Sign certificate
+            String algName = mSigningUnit.getDefaultAlgorithm();
+            IConfigStore cs = new PropConfigStore("cs");
+            cs.put(".profile", "caCert.profile");
+            cs.put(".dn", subjectDN);
+            cs.put(".keyalgorithm", algName);
+            X509CertImpl cert =
+                CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this);
+
+            // Add certificate to nssdb
+            cryptoManager.importCertPackage(cert.getEncoded(), nickname);
+        } catch (Exception e) {
+            throw new ECAException("Error creating sub-CA certificate: " + e);
+        }
+
+        // Add sub-CA entry to database
+        String dn = joinSubCAOUs(hier) + "ou=subCAs,ou="
+            + getId() + "," + getDBSubsystem().getBaseDN();
+        CMS.debug("createSubCA: DN = " + dn);
+        LDAPAttribute[] attrs = {
+            new LDAPAttribute("objectclass", "subCA"),
+            new LDAPAttribute("ou", handle),
+            new LDAPAttribute("subCAKeyNickname", nickname)
+        };
+        LDAPEntry ldapEntry = new LDAPEntry(
+            dn, new LDAPAttributeSet(attrs));
+
+        ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("createSubCA");
+        dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+        LDAPConnection conn = dbFactory.getConn();
+
+        try {
+            conn.add(ldapEntry);
+        } catch (LDAPException e) {
+            throw new EBaseException("Error adding sub-CA entry to database: " + e);
+        } finally {
+            dbFactory.returnConn(conn);
+            dbFactory.reset();
+        }
+
+        return new CertificateAuthority(
+            getId(), mOwner, mConfig, this, hier, nickname);
+    }
 }
diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java
index 2466fb652a46a3b5faede616cb397d18e592f5a0..0410bd2909bc71ca7d7443b3c9db0b085d62eaea 100644
--- a/base/ca/src/com/netscape/ca/SigningUnit.java
+++ b/base/ca/src/com/netscape/ca/SigningUnit.java
@@ -123,16 +123,14 @@ public final class SigningUnit implements ISigningUnit {
         return mConfig.getString(PROP_TOKEN_NAME);
     }
 
-    public String getNickName() throws EBaseException {
-        try {
-            return mConfig.getString(PROP_RENAMED_CERT_NICKNAME);
-        } catch (EBaseException e) {
-            return mConfig.getString(PROP_CERT_NICKNAME);
-        }
-    }
 
     public void init(ISubsystem owner, IConfigStore config)
             throws EBaseException {
+        init(owner, config, null);
+    }
+
+    public void init(ISubsystem owner, IConfigStore config, String nickname)
+            throws EBaseException {
         mOwner = owner;
         mConfig = config;
 
@@ -140,7 +138,15 @@ public final class SigningUnit implements ISigningUnit {
         try {
             mManager = CryptoManager.getInstance();
 
-            mNickname = getNickName();
+            if (nickname == null) {
+                try {
+                    mNickname = mConfig.getString(PROP_RENAMED_CERT_NICKNAME);
+                } catch (EBaseException e) {
+                    mNickname = mConfig.getString(PROP_CERT_NICKNAME);
+                }
+            } else {
+                mNickname = nickname;
+            }
 
             tokenname = config.getString(PROP_TOKEN_NAME);
             if (tokenname.equalsIgnoreCase(Constants.PR_INTERNAL_TOKEN) ||
diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java
index 16eae7877059c7dc42479276b3111db1ce7f582d..73616b429a66312eb7722775788dfc33ee199394 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java
@@ -34,6 +34,9 @@ public class CAApplication extends Application {
         // installer
         classes.add(CAInstallerService.class);
 
+        // sub-ca management
+        classes.add(SubCAService.class);
+
         // certs and requests
         classes.add(CertService.class);
         classes.add(CertRequestService.class);
diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/SubCAService.java b/base/ca/src/org/dogtagpki/server/ca/rest/SubCAService.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8b29c8d6749149fd8c7e8b86d27094430ef20bc
--- /dev/null
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/SubCAService.java
@@ -0,0 +1,156 @@
+//--- BEGIN COPYRIGHT BLOCK ---
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; version 2 of the License.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//GNU General Public License for more details.
+//
+//You should have received a copy of the GNU General Public License along
+//with this program; if not, write to the Free Software Foundation, Inc.,
+//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//(C) 2015 Red Hat, Inc.
+//All rights reserved.
+//--- END COPYRIGHT BLOCK ---
+
+package org.dogtagpki.server.ca.rest;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.IConfigStore;
+import com.netscape.certsrv.base.PKIException;
+import com.netscape.certsrv.base.ResourceNotFoundException;
+import com.netscape.certsrv.base.UnauthorizedException;
+import com.netscape.certsrv.ca.ICertificateAuthority;
+import com.netscape.certsrv.common.NameValuePairs;
+import com.netscape.certsrv.common.OpDef;
+import com.netscape.certsrv.common.ScopeDef;
+import com.netscape.certsrv.logging.ILogger;
+import com.netscape.certsrv.subca.CAData;
+import com.netscape.certsrv.subca.SubCAResource;
+import com.netscape.cms.realm.PKIPrincipal;
+import com.netscape.cms.servlet.base.PKIService;
+
+/**
+ * @author ftweedal
+ */
+public class SubCAService extends PKIService implements SubCAResource {
+
+    ICertificateAuthority topCA;
+
+    public SubCAService() {
+        topCA = (ICertificateAuthority) CMS.getSubsystem("ca");
+    }
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private HttpHeaders headers;
+
+    @Context
+    private Request request;
+
+    @Context
+    private HttpServletRequest servletRequest;
+
+    /*
+    private final static String LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL =
+            "LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL_4";
+    private final static String LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE =
+            "LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE_3";
+    */
+
+    @Override
+    public Response listCAs() {
+        List<String> handles = topCA.subCAHandles();
+        Collections.sort(handles);
+
+        List<CAData> results = new ArrayList();
+        for (String handle : handles)
+            results.add(readCAData(handle, topCA.getSubCA(handle)));
+
+        GenericEntity entity = new GenericEntity<List<CAData>>(results) {};
+        return Response.ok(entity).build();
+    }
+
+    @Override
+    public Response getCA(String caRef) {
+        ICertificateAuthority ca = topCA.getSubCA(caRef);
+        if (ca == null)
+            throw new ResourceNotFoundException("CA \"" + caRef + "\" not found");
+
+        return createOKResponse(readCAData(caRef, ca));
+    }
+
+    @Override
+    public Response createCA(CAData data) {
+        String caRef = data.getCARef();
+
+        try {
+            ICertificateAuthority subCA =
+                topCA.createSubCA(caRef, data.getDN());
+            return createOKResponse(readCAData(caRef, subCA));
+        } catch (Exception e) {
+            // TODO catch more specific exception
+            CMS.debug(e);
+            throw new PKIException("Error creating sub-CA: " + e.toString());
+        }
+    }
+
+    private static CAData readCAData(String caRef, ICertificateAuthority ca)
+            throws PKIException {
+        String dn;
+        try {
+            dn = ca.getX500Name().toLdapDNString();
+        } catch (IOException e) {
+            throw new PKIException("Error reading CA data: could not determine Issuer DN");
+        }
+
+        return new CAData(caRef, dn);
+    }
+
+    /* TODO work out what audit messages are needed
+    public void auditProfileChangeState(String profileId, String op, String status) {
+        String msg = CMS.getLogMessage(
+                LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL,
+                auditor.getSubjectID(),
+                status,
+                profileId,
+                op);
+        auditor.log(msg);
+    }
+
+    public void auditProfileChange(String scope, String type, String id, String status, Map<String, String> params) {
+        String msg = CMS.getLogMessage(
+                LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE,
+                auditor.getSubjectID(),
+                status,
+                auditor.getParamString(scope, type, id, params));
+        auditor.log(msg);
+    }
+    */
+
+}
diff --git a/base/common/src/com/netscape/certsrv/ca/ICAService.java b/base/common/src/com/netscape/certsrv/ca/ICAService.java
index 1d179e07692eee2f32780b33489975a571670685..e24bfad7c017deb1123537a1b28add3d7f27c161 100644
--- a/base/common/src/com/netscape/certsrv/ca/ICAService.java
+++ b/base/common/src/com/netscape/certsrv/ca/ICAService.java
@@ -59,13 +59,15 @@ public interface ICAService {
      * Issues certificate base on enrollment information,
      * creates certificate record, and stores all necessary data.
      *
+     * @param caRef CA reference
      * @param certi information obtain from revocation request
+     * @param profileId Name of profile used
+     * @param rid Request ID
      * @exception EBaseException failed to issue certificate or create certificate record
      */
-    public X509CertImpl issueX509Cert(X509CertInfo certi)
-            throws EBaseException;
-
-    public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid)
+    public X509CertImpl issueX509Cert(
+                String caRef, X509CertInfo certi,
+                String profileId, String rid)
             throws EBaseException;
 
     /**
diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
index f87f15420b3ea6e02e5ce47b5c350e86f67ccc9f..56abd937970674d34119a076361f74fe80df7ed7 100644
--- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
+++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
@@ -18,6 +18,7 @@
 package com.netscape.certsrv.ca;
 
 import java.util.Enumeration;
+import java.util.List;
 import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
@@ -515,4 +516,38 @@ public interface ICertificateAuthority extends ISubsystem {
 
     public CertificateIssuerName getIssuerObj();
     public CertificateSubjectName getSubjectObj();
+
+    /**
+     * Enumerate all sub-CA handles.
+     */
+    public List<String> subCAHandles();
+
+    /**
+     * Get the sub-CA specified by a slash-delimited string.
+     *
+     * Leading and trailing slashes are stripped and multiple
+     * slashes appearing together in the interior are treated
+     * as a single delimiter.
+     */
+    public ICertificateAuthority getSubCA(String handle);
+
+    /**
+     * Create a new sub-CA.
+     *
+     * @param handle The FULL handle to the CA.
+     */
+    public ICertificateAuthority createSubCA(String fullHandle, String dn)
+        throws EBaseException;
+
+    /**
+     * Create a new sub-CA IMMEDIATELY beneath this one.
+     *
+     * This method DOES NOT add the new CA to caMap; it is the
+     * caller's responsibility.
+     *
+     * @param handle The RELATIVE caRef; should be a single fragment.
+     */
+    public ICertificateAuthority createRelativeSubCA(
+            String relHandle, String dn)
+        throws EBaseException;
 }
diff --git a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java
index 69a39d7e23232a1a0cc6e2fe98305d452234bb8c..a28ffb3586c88d0a92d976531ee682dfb4997337 100644
--- a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java
+++ b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java
@@ -175,6 +175,11 @@ public interface IEnrollProfile extends IProfile {
     public static final String REQUEST_ALGORITHM_PARAMS = "req_algorithm_params";
 
     /**
+     * Reference to requested certificate authority
+     */
+    public static final String REQUEST_AUTHORITY_REF = "req_authority_ref";
+
+    /**
      * Set Default X509CertInfo in the request.
      *
      * @param request profile-based certificate request.
diff --git a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java
index 34d2a5109170c560b5a449d08f43eeeda5035b88..75b45bb8b31cde22881e0ddc310c0bdb51a66338 100644
--- a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java
+++ b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java
@@ -17,6 +17,7 @@
 // --- END COPYRIGHT BLOCK ---
 package com.netscape.certsrv.security;
 
+import java.security.PrivateKey;
 import java.security.PublicKey;
 
 import netscape.security.x509.X509CertImpl;
@@ -161,4 +162,11 @@ public interface ISigningUnit {
      * @return public key
      */
     public PublicKey getPublicKey();
+
+    /**
+     * Retrieves the public key associated in this unit.
+     *
+     * @return public key
+     */
+    public PrivateKey getPrivateKey();
 }
diff --git a/base/common/src/com/netscape/certsrv/subca/CAData.java b/base/common/src/com/netscape/certsrv/subca/CAData.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2482b877b22bcaa830032b51660ed67bf61d7d8
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/subca/CAData.java
@@ -0,0 +1,88 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2015 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+/**
+ * @author ftweedal
+ */
+package com.netscape.certsrv.subca;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+ at XmlRootElement(name = "ca")
+ at XmlAccessorType(XmlAccessType.FIELD)
+public class CAData {
+
+    public static Marshaller marshaller;
+    public static Unmarshaller unmarshaller;
+
+    static {
+        try {
+            marshaller = JAXBContext.newInstance(CAData.class).createMarshaller();
+            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+            unmarshaller = JAXBContext.newInstance(CAData.class).createUnmarshaller();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @XmlAttribute
+    protected String caRef;
+
+    public String getCARef() {
+        return caRef;
+    }
+
+    @XmlAttribute
+    protected String dn;
+
+    public String getDN() {
+        return dn;
+    }
+
+
+    protected Link link;
+
+    public Link getLink() {
+        return link;
+    }
+
+    public void setLink(Link link) {
+        this.link = link;
+    }
+
+    protected CAData() {
+    }
+
+    public CAData(String caRef, String dn) {
+        this.caRef = caRef;
+        this.dn = dn;
+    }
+
+}
diff --git a/base/common/src/com/netscape/certsrv/subca/SubCAResource.java b/base/common/src/com/netscape/certsrv/subca/SubCAResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..c50498d01cd29bf4b19fd833a05cfd2483097a4b
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/subca/SubCAResource.java
@@ -0,0 +1,36 @@
+package com.netscape.certsrv.subca;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+
+import org.jboss.resteasy.annotations.ClientResponseType;
+
+import com.netscape.certsrv.acls.ACLMapping;
+import com.netscape.certsrv.authentication.AuthMethodMapping;
+
+ at Path("subca")
+public interface SubCAResource {
+
+    @GET
+    public Response listCAs();
+    /*
+            @QueryParam("start") Integer start,
+            @QueryParam("size") Integer size);
+            */
+
+    @GET
+    @Path("{caRef}")
+    @ClientResponseType(entityType=CAData.class)
+    public Response getCA(@PathParam("caRef") String caRef);
+
+    @POST
+    @ClientResponseType(entityType=CAData.class)
+    //@ACLMapping("certs")
+    //@AuthMethodMapping("certs")
+    public Response createCA(CAData data);
+
+}
diff --git a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java
index d0bfdb8a64ee857a3f5ff544e41de905b4660f55..27280bf8b968c265019cbb6bd68e8de005e3350e 100644
--- a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java
+++ b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java
@@ -95,8 +95,8 @@ public class CAEnrollProfile extends EnrollProfile {
         CMS.debug("CAEnrollProfile: execute reqId=" +
                 request.getRequestId().toString());
         ICertificateAuthority ca = (ICertificateAuthority) getAuthority();
+
         ICAService caService = (ICAService) ca.getCAService();
-
         if (caService == null) {
             throw new EProfileException("No CA Service");
         }
@@ -191,8 +191,9 @@ public class CAEnrollProfile extends EnrollProfile {
             sc.put("profileSetId", setId);
         }
         try {
-            theCert = caService.issueX509Cert(info, getId() /* profileId */,
-                    id /* requestId */);
+            theCert = caService.issueX509Cert(
+                request.getExtDataInString(REQUEST_AUTHORITY_REF),
+                info, getId() /* profileId */, id /* requestId */);
         } catch (EBaseException e) {
             CMS.debug(e.toString());
 
diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java
index fe3b424a4b8e13215d4029d328d4a1e280be63ff..36938ab8f927dbf98804b0c833b65604c884b213 100644
--- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java
+++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java
@@ -190,6 +190,9 @@ public abstract class EnrollProfile extends BasicProfile
             if (locale != null) {
                 result[i].setExtData(REQUEST_LOCALE, locale.getLanguage());
             }
+
+            // set requested CA
+            result[i].setExtData(REQUEST_AUTHORITY_REF, ctx.get(REQUEST_AUTHORITY_REF));
         }
         return result;
     }
diff --git a/base/server/cms/src/com/netscape/cms/profile/def/AuthInfoAccessExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/AuthInfoAccessExtDefault.java
index 36818a90753b75f958cca4dd4c93f18629b93411..3762ad33974529f329eeda7da2dc18b43a3b4306 100644
--- a/base/server/cms/src/com/netscape/cms/profile/def/AuthInfoAccessExtDefault.java
+++ b/base/server/cms/src/com/netscape/cms/profile/def/AuthInfoAccessExtDefault.java
@@ -33,6 +33,7 @@ import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.common.NameValuePairs;
 import com.netscape.certsrv.profile.EProfileException;
+import com.netscape.certsrv.profile.IEnrollProfile;
 import com.netscape.certsrv.profile.IProfile;
 import com.netscape.certsrv.property.Descriptor;
 import com.netscape.certsrv.property.EPropertyException;
@@ -403,12 +404,13 @@ public class AuthInfoAccessExtDefault extends EnrollExtDefault {
      */
     public void populate(IRequest request, X509CertInfo info)
             throws EProfileException {
-        AuthInfoAccessExtension ext = createExtension();
+        String caRef = request.getExtDataInString(IEnrollProfile.REQUEST_AUTHORITY_REF);
+        AuthInfoAccessExtension ext = createExtension(caRef);
 
         addExtension(ext.getExtensionId().toString(), ext, info);
     }
 
-    public AuthInfoAccessExtension createExtension() {
+    public AuthInfoAccessExtension createExtension(String caRef) {
         AuthInfoAccessExtension ext = null;
         int num = getNumAds();
 
@@ -433,6 +435,8 @@ public class AuthInfoAccessExtDefault extends EnrollExtDefault {
                             if (hostname != null && port != null)
                                 // location = "http://"+hostname+":"+port+"/ocsp/ee/ocsp";
                                 location = "http://" + hostname + ":" + port + "/ca/ocsp";
+                                if (caRef != null && !caRef.isEmpty())
+                                    location += "?caRef=" + caRef;
                         }
                     }
 
diff --git a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java
index 095f8bb5ffa2f950b58c868a6daee99991a80daa..8594c2ffbe30f7d12dbb4a8c54a78a7a943b882d 100644
--- a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java
+++ b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java
@@ -26,8 +26,11 @@ import netscape.security.x509.PKIXExtensions;
 import netscape.security.x509.X509CertInfo;
 
 import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.IConfigStore;
+import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.profile.EProfileException;
+import com.netscape.certsrv.profile.IEnrollProfile;
 import com.netscape.certsrv.profile.IProfile;
 import com.netscape.certsrv.property.Descriptor;
 import com.netscape.certsrv.property.EPropertyException;
@@ -161,18 +164,24 @@ public class AuthorityKeyIdentifierExtDefault extends CAEnrollDefault {
      */
     public void populate(IRequest request, X509CertInfo info)
             throws EProfileException {
-        AuthorityKeyIdentifierExtension ext = createExtension(info);
+        ICertificateAuthority ca = (ICertificateAuthority)
+                CMS.getSubsystem(CMS.SUBSYSTEM_CA);
+        ca = ca.getSubCA(request.getExtDataInString(IEnrollProfile.REQUEST_AUTHORITY_REF));
+        if (ca == null)
+            throw new EProfileException("Could not reach requested CA");
 
+        AuthorityKeyIdentifierExtension ext = createExtension(ca, info);
         addExtension(PKIXExtensions.AuthorityKey_Id.toString(), ext, info);
     }
 
-    public AuthorityKeyIdentifierExtension createExtension(X509CertInfo info) {
+    public AuthorityKeyIdentifierExtension createExtension(
+            ICertificateAuthority ca, X509CertInfo info) {
         KeyIdentifier kid = null;
         String localKey = getConfig("localKey");
         if (localKey != null && localKey.equals("true")) {
             kid = getKeyIdentifier(info);
         } else {
-            kid = getCAKeyIdentifier();
+            kid = getCAKeyIdentifier(ca);
         }
 
         if (kid == null)
diff --git a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java
index 1d1d05ed55ef30114781521ac607eae118546250..696830ead842767892f77bd8f8c9ea6f667225aa 100644
--- a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java
+++ b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java
@@ -68,9 +68,7 @@ public abstract class CAEnrollDefault extends EnrollDefault {
         return null;
     }
 
-    public KeyIdentifier getCAKeyIdentifier() {
-        ICertificateAuthority ca = (ICertificateAuthority)
-                CMS.getSubsystem(CMS.SUBSYSTEM_CA);
+    public KeyIdentifier getCAKeyIdentifier(ICertificateAuthority ca) {
         X509CertImpl caCert = ca.getCACert();
         if (caCert == null) {
             // during configuration, we dont have the CA certificate
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/DisplayBySerial.java b/base/server/cms/src/com/netscape/cms/servlet/cert/DisplayBySerial.java
index 283cf499e1ca6f12da93db40c2cb08cbeba5ad0b..52c0b34def086677e767ce44be8e5a3d2cad4995 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/DisplayBySerial.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/DisplayBySerial.java
@@ -85,7 +85,6 @@ public class DisplayBySerial extends CMSServlet {
     private final static String TPL_FILE1 = "displayBySerial.template";
     private final static BigInteger MINUS_ONE = new BigInteger("-1");
 
-    private ICertificateRepository mCertDB = null;
     private String mForm1Path = null;
     private X509Certificate mCACerts[] = null;
 
@@ -103,9 +102,6 @@ public class DisplayBySerial extends CMSServlet {
      */
     public void init(ServletConfig sc) throws ServletException {
         super.init(sc);
-        if (mAuthority instanceof ICertificateAuthority) {
-            mCertDB = ((ICertificateAuthority) mAuthority).getCertificateRepository();
-        }
         try {
             mCACerts = ((ICertAuthority) mAuthority).getCACertChain().getChain();
         } catch (Exception e) {
@@ -143,6 +139,9 @@ public class DisplayBySerial extends CMSServlet {
         CMSTemplate form = null;
         Locale[] locale = new Locale[1];
 
+        ICertificateAuthority targetCA = certAuthority.getSubCA(req.getParameter("caRef"));
+        ICertificateRepository certDB = targetCA.getCertificateRepository();
+
         try {
             AuthzToken authzToken = null;
 
@@ -160,7 +159,7 @@ public class DisplayBySerial extends CMSServlet {
             }
 
             serialNumber = getSerialNumber(req);
-            getCertRecord(serialNumber, certType); //throw exception on error
+            getCertRecord(certDB, serialNumber, certType); //throw exception on error
 
             if (certType[0].equalsIgnoreCase("x509")) {
                 form = getTemplate(mForm1Path, req, locale);
@@ -185,7 +184,7 @@ public class DisplayBySerial extends CMSServlet {
 
         try {
             if (serialNumber.compareTo(MINUS_ONE) > 0) {
-                process(argSet, header, serialNumber,
+                process(certDB, argSet, header, serialNumber,
                         req, resp, locale[0]);
             } else {
                 error = new ECMSGWException(
@@ -222,7 +221,9 @@ public class DisplayBySerial extends CMSServlet {
     /**
      * Display information about a particular certificate
      */
-    private void process(CMSTemplateParams argSet, IArgBlock header,
+    private void process(
+            ICertificateRepository certDB,
+            CMSTemplateParams argSet, IArgBlock header,
             BigInteger seq, HttpServletRequest req,
             HttpServletResponse resp,
             Locale locale)
@@ -230,10 +231,10 @@ public class DisplayBySerial extends CMSServlet {
         String certType[] = new String[1];
 
         try {
-            getCertRecord(seq, certType); // throw exception on error
+            getCertRecord(certDB, seq, certType); // throw exception on error
 
             if (certType[0].equalsIgnoreCase("x509")) {
-                processX509(argSet, header, seq, req, resp, locale);
+                processX509(certDB, argSet, header, seq, req, resp, locale);
                 return;
             }
         } catch (EBaseException e) {
@@ -245,7 +246,9 @@ public class DisplayBySerial extends CMSServlet {
         return;
     }
 
-    private void processX509(CMSTemplateParams argSet, IArgBlock header,
+    private void processX509(
+            ICertificateRepository certDB,
+            CMSTemplateParams argSet, IArgBlock header,
             BigInteger seq, HttpServletRequest req,
             HttpServletResponse resp,
             Locale locale)
@@ -257,7 +260,7 @@ public class DisplayBySerial extends CMSServlet {
         }
 
         try {
-            ICertRecord rec = mCertDB.readCertificateRecord(seq);
+            ICertRecord rec = certDB.readCertificateRecord(seq);
             if (rec == null) {
                 CMS.debug("DisplayBySerial: failed to read record");
                 throw new ECMSGWException(
@@ -461,12 +464,14 @@ public class DisplayBySerial extends CMSServlet {
         return;
     }
 
-    private ICertRecord getCertRecord(BigInteger seq, String certtype[])
+    private ICertRecord getCertRecord(
+            ICertificateRepository certDB,
+            BigInteger seq, String certtype[])
             throws EBaseException {
         ICertRecord rec = null;
 
         try {
-            rec = mCertDB.readCertificateRecord(seq);
+            rec = certDB.readCertificateRecord(seq);
             X509CertImpl x509cert = rec.getCertificate();
 
             if (x509cert != null) {
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/DoRevoke.java b/base/server/cms/src/com/netscape/cms/servlet/cert/DoRevoke.java
index 1788be3039d12280d573dc9209ae2dbff3b9efd1..395425a17f88c3da38f00fb73caf5e5a2e29ee98 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/DoRevoke.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/DoRevoke.java
@@ -84,7 +84,6 @@ public class DoRevoke extends CMSServlet {
     private static final long serialVersionUID = 1693115906265904238L;
     private final static String TPL_FILE = "revocationResult.template";
 
-    private ICertificateRepository mCertDB = null;
     private String mFormPath = null;
     private IPublisherProcessor mPublisherProcessor = null;
     private int mTimeLimits = 30; /* in seconds */
@@ -103,9 +102,6 @@ public class DoRevoke extends CMSServlet {
         super.init(sc);
         mFormPath = "/" + mAuthority.getId() + "/" + TPL_FILE;
 
-        if (mAuthority instanceof ICertificateAuthority) {
-            mCertDB = ((ICertificateAuthority) mAuthority).getCertificateRepository();
-        }
         if (mAuthority instanceof ICertAuthority) {
             mPublisherProcessor = ((ICertAuthority) mAuthority).getPublisherProcessor();
         }
@@ -375,6 +371,8 @@ public class DoRevoke extends CMSServlet {
         CMS.debug("DoRevoke: eeSerialNumber: " + eeSerialNumber);
         long startTime = CMS.getCurrentDate().getTime();
 
+        ICertificateAuthority targetCA = certAuthority.getSubCA(req.getParameter("caRef"));
+
         RevocationProcessor processor =
                 new RevocationProcessor(servletConfig.getServletName(), getLocale(req));
 
@@ -395,9 +393,9 @@ public class DoRevoke extends CMSServlet {
         X509Certificate clientCert = getSSLClientCertificate(req);
 
         if (mAuthority instanceof ICertificateAuthority) {
-            processor.setAuthority(certAuthority);
+            processor.setAuthority(targetCA);
 
-            if (certAuthority.noncesEnabled()) {
+            if (targetCA.noncesEnabled()) {
                 String nonces = req.getParameter("nonce");
                 if (nonces == null) {
                     throw new ForbiddenException("Missing nonce.");
@@ -418,7 +416,8 @@ public class DoRevoke extends CMSServlet {
 
             if (mAuthority instanceof ICertificateAuthority) {
 
-                Enumeration<ICertRecord> e = mCertDB.searchCertificates(revokeAll, totalRecordCount, mTimeLimits);
+                ICertificateRepository certDB = targetCA.getCertificateRepository();
+                Enumeration<ICertRecord> e = certDB.searchCertificates(revokeAll, totalRecordCount, mTimeLimits);
 
                 while (e != null && e.hasMoreElements()) {
                     ICertRecord targetRecord = e.nextElement();
@@ -441,7 +440,7 @@ public class DoRevoke extends CMSServlet {
 
                     try {
                         if (mAuthority instanceof ICertificateAuthority &&
-                            certAuthority.noncesEnabled() &&
+                            targetCA.noncesEnabled() &&
                             !processor.isMemberOfSubsystemGroup(clientCert)) {
                             // validate nonce for each certificate
                             Long nonce = nonceMap.get(targetRecord.getSerialNumber());
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java
index 960f997cd4badd18bdd25393e9175fc935d52edb..e1dcee19ac6a64aee39a770907ef56a35c573b77 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java
@@ -30,6 +30,7 @@ import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.EPropertyNotFound;
 import com.netscape.certsrv.base.SessionContext;
 import com.netscape.certsrv.cert.CertEnrollmentRequest;
+import com.netscape.certsrv.profile.IEnrollProfile;
 import com.netscape.certsrv.profile.IProfile;
 import com.netscape.certsrv.profile.IProfileAuthenticator;
 import com.netscape.certsrv.profile.IProfileContext;
@@ -146,6 +147,14 @@ public class EnrollmentProcessor extends CertProcessor {
             }
 
             IProfileContext ctx = profile.createContext();
+
+            // insert request path into profile context
+            // (it is used to determine the (sub-)CA.
+            //
+            String caRef = request.getParameter("caRef");
+            if (caRef != null)
+                ctx.set(IEnrollProfile.REQUEST_AUTHORITY_REF, caRef);
+
             CMS.debug("EnrollmentProcessor: set Inputs into profile Context");
             setInputsIntoContext(data, profile, ctx);
 
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/ListCerts.java b/base/server/cms/src/com/netscape/cms/servlet/cert/ListCerts.java
index 3794f109f417f67e85fb827959de6230fedc7ce9..c418ea4fc73d390dd297012b2f4b016bf5fd10f6 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/ListCerts.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/ListCerts.java
@@ -75,8 +75,6 @@ public class ListCerts extends CMSServlet {
     private final static String USE_CLIENT_FILTER = "useClientFilter";
     private final static String ALLOWED_CLIENT_FILTERS = "allowedClientFilters";
 
-    private ICertificateRepository mCertDB = null;
-    private X500Name mAuthName = null;
     private String mFormPath = null;
     private boolean mReverse = false;
     private boolean mHardJumpTo = false; //jump to the end
@@ -103,13 +101,6 @@ public class ListCerts extends CMSServlet {
         // override success to render own template.
         mTemplates.remove(ICMSRequest.SUCCESS);
 
-        if (mAuthority instanceof ICertificateAuthority) {
-            ICertificateAuthority ca = (ICertificateAuthority) mAuthority;
-
-            mCertDB = ca.getCertificateRepository();
-            mAuthName = ca.getX500Name();
-        }
-
         mFormPath = "/" + mAuthority.getId() + "/" + TPL_FILE;
         if (mOutputTemplatePath != null)
             mFormPath = mOutputTemplatePath;
@@ -398,8 +389,11 @@ public class ListCerts extends CMSServlet {
 
         CMS.debug("ListCerts: pSize: " + pSize);
 
+        ICertificateAuthority targetCA = certAuthority.getSubCA(req.getParameter("caRef"));
+        ICertificateRepository certDB = targetCA.getCertificateRepository();
+
         CMS.debug("ListCerts: calling findCertRecordsInList() with jumpTo");
-        ICertRecordList list = mCertDB.findCertRecordsInList(
+        ICertRecordList list = certDB.findCertRecordsInList(
                 filter, (String[]) null, jumpTo, mHardJumpTo, "serialno",
                 pSize);
         // retrive maxCount + 1 entries
@@ -414,7 +408,7 @@ public class ListCerts extends CMSServlet {
             // if user specify a range, we need to
             // calculate the totalRecordCount
             CMS.debug("ListCerts: calling findCertRecordsInList() with serialTo");
-            tolist = mCertDB.findCertRecordsInList(
+            tolist = certDB.findCertRecordsInList(
                         filter,
                         (String[]) null, serialTo,
                         "serialno", maxCount);
@@ -534,12 +528,14 @@ public class ListCerts extends CMSServlet {
             CMS.debug("ListCerts: no next record");
         }
 
+        header.addStringValue("caRef", req.getParameter("caRef"));
         header.addStringValue("op", req.getParameter("op"));
         if (revokeAll != null)
             header.addStringValue("revokeAll", revokeAll);
 
-        if (mAuthName != null)
-            header.addStringValue("issuerName", mAuthName.toString());
+        X500Name issuerName = targetCA.getX500Name();
+        if (issuerName != null)
+            header.addStringValue("issuerName", issuerName.toString());
 
         if (!serialToVal.equals(MINUS_ONE))
             header.addStringValue("serialTo", serialToVal.toString());
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/ReasonToRevoke.java b/base/server/cms/src/com/netscape/cms/servlet/cert/ReasonToRevoke.java
index c65c482b14a8670c2f87274d4194c9a40b883c68..3e3db1fec30cadfbfc9a81ca5169e19be1e85862 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/ReasonToRevoke.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/ReasonToRevoke.java
@@ -65,7 +65,6 @@ public class ReasonToRevoke extends CMSServlet {
     private final static String TPL_FILE = "reasonToRevoke.template";
     private final static String INFO = "ReasonToRevoke";
 
-    private ICertificateRepository mCertDB = null;
     private String mFormPath = null;
     private ICertificateAuthority mCA = null;
     private Random mRandom = null;
@@ -84,10 +83,8 @@ public class ReasonToRevoke extends CMSServlet {
     public void init(ServletConfig sc) throws ServletException {
         super.init(sc);
         mFormPath = "/" + mAuthority.getId() + "/" + TPL_FILE;
-        if (mAuthority instanceof ICertificateAuthority) {
+        if (mAuthority instanceof ICertificateAuthority)
             mCA = (ICertificateAuthority) mAuthority;
-            mCertDB = ((ICertificateAuthority) mAuthority).getCertificateRepository();
-        }
 
         if (mCA != null && mCA.noncesEnabled()) {
             mRandom = new Random();
@@ -221,6 +218,7 @@ public class ReasonToRevoke extends CMSServlet {
             Locale locale)
             throws EBaseException {
 
+        header.addStringValue("caRef", req.getParameter("caRef"));
         header.addStringValue("revokeAll", revokeAll);
         header.addIntegerValue("totalRecordCount", totalRecordCount);
 
@@ -234,12 +232,15 @@ public class ReasonToRevoke extends CMSServlet {
                 }
             }
 
+            ICertificateAuthority targetCA = mCA.getSubCA(req.getParameter("caRef"));
+            ICertificateRepository certDB = targetCA.getCertificateRepository();
+
             /**
-             * ICertRecordList list = mCertDB.findCertRecordsInList(
+             * ICertRecordList list = certDB.findCertRecordsInList(
              * revokeAll, null, totalRecordCount);
              * Enumeration e = list.getCertRecords(0, totalRecordCount - 1);
              **/
-            Enumeration<ICertRecord> e = mCertDB.searchCertificates(revokeAll,
+            Enumeration<ICertRecord> e = certDB.searchCertificates(revokeAll,
                     totalRecordCount, mTimeLimits);
 
             ArrayList<String> noncesList = new ArrayList<String>();
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/SrchCerts.java b/base/server/cms/src/com/netscape/cms/servlet/cert/SrchCerts.java
index 508a8df70a3fc8401879907c62f1939dbb410722..66898cd5d7a83681d8ec3ec364c31eb63866d040 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/SrchCerts.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/SrchCerts.java
@@ -78,8 +78,6 @@ public class SrchCerts extends CMSServlet {
     private final static String CURRENT_TIME = "currentTime";
     private final static int MAX_RESULTS = 1000;
 
-    private ICertificateRepository mCertDB = null;
-    private X500Name mAuthName = null;
     private String mFormPath = null;
     private int mMaxReturns = MAX_RESULTS;
     private int mTimeLimits = 30; /* in seconds */
@@ -115,12 +113,6 @@ public class SrchCerts extends CMSServlet {
                 }
             }
         }
-        if (mAuthority instanceof ICertificateAuthority) {
-            ICertificateAuthority ca = (ICertificateAuthority) mAuthority;
-
-            mCertDB = ca.getCertificateRepository();
-            mAuthName = ca.getX500Name();
-        }
 
         mFormPath = "/" + mAuthority.getId() + "/" + TPL_FILE;
 
@@ -548,7 +540,8 @@ public class SrchCerts extends CMSServlet {
                 timeLimit = Integer.parseInt(timeLimitStr);
 
             String queryCertFilter = buildFilter(req);
-            process(argSet, header, queryCertFilter,
+            ICertificateAuthority targetCA = certAuthority.getSubCA(req.getParameter("caRef"));
+            process(targetCA, argSet, header, queryCertFilter,
                     revokeAll, maxResults, timeLimit, req, resp, locale[0]);
         } catch (NumberFormatException e) {
             log(ILogger.LL_FAILURE, CMS.getLogMessage("BASE_INVALID_NUMBER_FORMAT"));
@@ -584,13 +577,16 @@ public class SrchCerts extends CMSServlet {
     /**
      * Process the key search.
      */
-    private void process(CMSTemplateParams argSet, IArgBlock header,
+    private void process(ICertificateAuthority ca,
+            CMSTemplateParams argSet, IArgBlock header,
             String filter, String revokeAll,
             int maxResults, int timeLimit,
             HttpServletRequest req, HttpServletResponse resp,
             Locale locale)
             throws EBaseException {
         try {
+            ICertificateRepository certDB = ca.getCertificateRepository();
+
             long startTime = CMS.getCurrentDate().getTime();
 
             if (filter.indexOf(CURRENT_TIME, 0) > -1) {
@@ -608,7 +604,7 @@ public class SrchCerts extends CMSServlet {
             }
             CMS.debug("Start searching ... "
                     + "filter=" + filter + " maxreturns=" + maxResults + " timelimit=" + timeLimit);
-            Enumeration<ICertRecord> e = mCertDB.searchCertificates(filter, maxResults, timeLimit);
+            Enumeration<ICertRecord> e = certDB.searchCertificates(filter, maxResults, timeLimit);
 
             int count = 0;
 
@@ -626,9 +622,11 @@ public class SrchCerts extends CMSServlet {
 
             long endTime = CMS.getCurrentDate().getTime();
 
+            header.addStringValue("caRef", req.getParameter("caRef"));
             header.addStringValue("op", req.getParameter("op"));
-            if (mAuthName != null)
-                header.addStringValue("issuerName", mAuthName.toString());
+            X500Name issuerName = ca.getX500Name();
+            if (issuerName != null)
+                header.addStringValue("issuerName", issuerName.toString());
             header.addStringValue("time", Long.toString(endTime - startTime));
             header.addStringValue("serviceURL", req.getRequestURI());
             header.addStringValue("queryFilter", filter);
diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java
index 36b0e4d0d44ec8987856ebaaa3f4919c4a3f7071..c0729d88100e64d06c099bc4f1d73a14bcb9918f 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java
@@ -434,8 +434,19 @@ public class CertUtil {
                (signingKeyType.equals("dsa") && algorithm.contains("DSA")));
     }
 
+    public static X509CertImpl createLocalCertWithCA(IConfigStore config, X509Key x509key,
+            String prefix, String certTag, String type, ICertificateAuthority ca) throws IOException {
+        return createLocalCert(config, x509key, prefix, certTag, type, ca, null);
+    }
+
     public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key,
             String prefix, String certTag, String type, Context context) throws IOException {
+        return createLocalCert(config, x509key, prefix, certTag, type, null, context);
+    }
+
+    public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key,
+            String prefix, String certTag, String type,
+            ICertificateAuthority ca, Context context) throws IOException {
 
         CMS.debug("Creating local certificate... certTag=" + certTag);
         String profile = null;
@@ -446,13 +457,14 @@ public class CertUtil {
         }
 
         X509CertImpl cert = null;
-        ICertificateAuthority ca = null;
         ICertificateRepository cr = null;
         RequestId reqId = null;
         String profileId = null;
         IRequestQueue queue = null;
         IRequest req = null;
 
+        boolean caProvided = ca != null;
+
         try {
             Boolean injectSAN = config.getBoolean(
                                       "service.injectSAN", false);
@@ -468,7 +480,8 @@ public class CertUtil {
             } else {
                 keyAlgorithm = config.getString(prefix + certTag + ".keyalgorithm");
             }
-            ca = (ICertificateAuthority) CMS.getSubsystem(
+            if (!caProvided)
+                ca = (ICertificateAuthority) CMS.getSubsystem(
                     ICertificateAuthority.ID);
             cr = ca.getCertificateRepository();
             BigInteger serialNo = cr.getNextSerialNumber();
@@ -496,9 +509,9 @@ public class CertUtil {
             }
             CMS.debug("Cert Template: " + info.toString());
 
-            String instanceRoot = config.getString("instanceRoot");
+            String instanceRoot = CMS.getConfigStore().getString("instanceRoot");
 
-            String configurationRoot = config.getString("configurationRoot");
+            String configurationRoot = CMS.getConfigStore().getString("configurationRoot");
 
             CertInfoProfile processor = new CertInfoProfile(
                     instanceRoot + configurationRoot + profile);
@@ -541,11 +554,18 @@ public class CertUtil {
 
             processor.populate(req, info);
 
-            String caPriKeyID = config.getString(
-                    prefix + "signing" + ".privkey.id");
-            byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID);
-            PrivateKey caPrik = CryptoUtil.findPrivateKeyFromID(
-                    keyIDb);
+            PrivateKey caPrik = null;
+            if (caProvided) {
+                java.security.PrivateKey pk = ca.getSigningUnit().getPrivateKey();
+                if (!(pk instanceof PrivateKey))
+                    throw new IOException("CA Private key must be a JSS PrivateKey");
+                caPrik = (PrivateKey) pk;
+            } else {
+                String caPriKeyID = config.getString(
+                        prefix + "signing" + ".privkey.id");
+                byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID);
+                caPrik = CryptoUtil.findPrivateKeyFromID(keyIDb);
+            }
 
             if (caPrik == null) {
                 CMS.debug("CertUtil::createSelfSignedCert() - "
diff --git a/base/server/cms/src/com/netscape/cms/servlet/ocsp/OCSPServlet.java b/base/server/cms/src/com/netscape/cms/servlet/ocsp/OCSPServlet.java
index 940bf657c6febdb9ec01757daa2f1a49ac2ee2be..77554ea24513635e37304a3b8870698c0f3e845d 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/ocsp/OCSPServlet.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/ocsp/OCSPServlet.java
@@ -35,6 +35,7 @@ import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.authentication.IAuthToken;
 import com.netscape.certsrv.authorization.AuthzToken;
 import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.common.ICMSRequest;
 import com.netscape.certsrv.ocsp.IOCSPService;
 import com.netscape.certsrv.util.IStatsSubsystem;
@@ -204,7 +205,9 @@ public class OCSPServlet extends CMSServlet {
                     throw new Exception("OCSPServlet: Decoded OCSP request "
                                        + "is empty or malformed");
                 }
-                response = ((IOCSPService) mAuthority).validate(ocspReq);
+                ICertificateAuthority targetCA =
+                    certAuthority.getSubCA(httpReq.getParameter("caRef"));
+                response = ((IOCSPService) targetCA).validate(ocspReq);
             } catch (Exception e) {
                 ;
                 CMS.debug("OCSPServlet: " + e.toString());
diff --git a/base/server/share/conf/schema-subCA.ldif b/base/server/share/conf/schema-subCA.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..eecb78c9b7f1e91aa5dc4a34c82cfc4c6863cbdc
--- /dev/null
+++ b/base/server/share/conf/schema-subCA.ldif
@@ -0,0 +1,3 @@
+dn: cn=schema
+attributeTypes: ( subCAKeyNickname-oid NAME 'subCAKeyNickname' DESC 'Sub-CA key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' )
+objectClasses: ( subCA-oid NAME 'subCA' DESC 'Sub-CA' SUP organizationalUnit STRUCTURAL MAY subCAKeyNickname X-ORIGIN 'user defined' )
diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif
index 475758c5d66bf681e589995505a561bf4e4c40ef..a6a9e38542cd9a3d34e108003f527820e7fd6ade 100644
--- a/base/server/share/conf/schema.ldif
+++ b/base/server/share/conf/schema.ldif
@@ -667,3 +667,11 @@ dn: cn=schema
 changetype: modify
 add: objectClasses
 objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'Certificate profile' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' )
+
+dn: cn=schema
+changetype: modify
+add: attributeTypes
+attributeTypes: ( subCAKeyNickname-oid NAME 'subCAKeyNickname' DESC 'Sub-CA key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' )
+-
+add: objectClasses
+objectClasses: ( subCA-oid NAME 'subCA' DESC 'Sub-CA' SUP organizationalUnit STRUCTURAL MAY subCAKeyNickname X-ORIGIN 'user defined' )
-- 
2.4.3

-------------- next part --------------
From f11afde311f819399aad5b974f4238591766bc5f Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Wed, 10 Jun 2015 03:02:35 -0400
Subject: [PATCH] Add subca CLI

---
 .../src/com/netscape/certsrv/ca/CAClient.java      |  3 +-
 .../com/netscape/certsrv/subca/SubCAClient.java    | 57 +++++++++++++++++++
 .../src/com/netscape/cmstools/cli/CACLI.java       |  2 +
 .../src/com/netscape/cmstools/subca/SubCACLI.java  | 52 +++++++++++++++++
 .../netscape/cmstools/subca/SubCACreateCLI.java    | 66 ++++++++++++++++++++++
 .../com/netscape/cmstools/subca/SubCAFindCLI.java  | 62 ++++++++++++++++++++
 .../com/netscape/cmstools/subca/SubCAShowCLI.java  | 57 +++++++++++++++++++
 7 files changed, 298 insertions(+), 1 deletion(-)
 create mode 100644 base/common/src/com/netscape/certsrv/subca/SubCAClient.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/subca/SubCACLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/subca/SubCACreateCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/subca/SubCAFindCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/subca/SubCAShowCLI.java

diff --git a/base/common/src/com/netscape/certsrv/ca/CAClient.java b/base/common/src/com/netscape/certsrv/ca/CAClient.java
index e1a0a8c02f8a840acbdea924c164020b88557fc4..b3122054f9a25dac286f16eeca106630e5186cf0 100644
--- a/base/common/src/com/netscape/certsrv/ca/CAClient.java
+++ b/base/common/src/com/netscape/certsrv/ca/CAClient.java
@@ -26,6 +26,7 @@ import com.netscape.certsrv.group.GroupClient;
 import com.netscape.certsrv.profile.ProfileClient;
 import com.netscape.certsrv.selftests.SelfTestClient;
 import com.netscape.certsrv.user.UserClient;
+import com.netscape.certsrv.subca.SubCAClient;
 
 public class CAClient extends SubsystemClient {
 
@@ -35,7 +36,7 @@ public class CAClient extends SubsystemClient {
     }
 
     public void init() throws URISyntaxException {
-
+        addClient(new SubCAClient(client, name));
         addClient(new CertClient(client, name));
         addClient(new GroupClient(client, name));
         addClient(new ProfileClient(client, name));
diff --git a/base/common/src/com/netscape/certsrv/subca/SubCAClient.java b/base/common/src/com/netscape/certsrv/subca/SubCAClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a89510ea5bb7a1085d4a542745ba9d64a6eccee
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/subca/SubCAClient.java
@@ -0,0 +1,57 @@
+//--- BEGIN COPYRIGHT BLOCK ---
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; version 2 of the License.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//GNU General Public License for more details.
+//
+//You should have received a copy of the GNU General Public License along
+//with this program; if not, write to the Free Software Foundation, Inc.,
+//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//(C) 2015 Red Hat, Inc.
+//All rights reserved.
+//--- END COPYRIGHT BLOCK ---
+package com.netscape.certsrv.subca;
+
+import java.net.URISyntaxException;
+import java.util.List;
+
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import com.netscape.certsrv.client.Client;
+import com.netscape.certsrv.client.PKIClient;
+
+/**
+ * @author Fraser Tweedale <ftweedal at redhat.com>
+ */
+public class SubCAClient extends Client {
+
+    public SubCAResource proxy;
+
+    public SubCAClient(PKIClient client, String subsystem) throws URISyntaxException {
+        super(client, subsystem, "subca");
+        proxy = createProxy(SubCAResource.class);
+    }
+
+    public List<CAData> listCAs() {
+        Response response = proxy.listCAs();
+        GenericType<List<CAData>> type = new GenericType<List<CAData>>() {};
+        return client.getEntity(response, type);
+    }
+
+    public CAData getCA(String caRef) {
+        Response response = proxy.getCA(caRef);
+        return client.getEntity(response, CAData.class);
+    }
+
+    public CAData createCA(CAData data) {
+        Response response = proxy.createCA(data);
+        return client.getEntity(response, CAData.class);
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java
index 17fb4866f38f05f7ead02b6145ef7d09140a90c5..abdfc396a29a1bd506fc75e821f476313e3b2ff1 100644
--- a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java
@@ -24,6 +24,7 @@ import com.netscape.cmstools.cert.CertCLI;
 import com.netscape.cmstools.group.GroupCLI;
 import com.netscape.cmstools.profile.ProfileCLI;
 import com.netscape.cmstools.selftests.SelfTestCLI;
+import com.netscape.cmstools.subca.SubCACLI;
 import com.netscape.cmstools.system.KRAConnectorCLI;
 import com.netscape.cmstools.user.UserCLI;
 
@@ -37,6 +38,7 @@ public class CACLI extends SubsystemCLI {
     public CACLI(CLI parent) {
         super("ca", "CA management commands", parent);
 
+        addModule(new SubCACLI(this));
         addModule(new CertCLI(this));
         addModule(new GroupCLI(this));
         addModule(new KRAConnectorCLI(this));
diff --git a/base/java-tools/src/com/netscape/cmstools/subca/SubCACLI.java b/base/java-tools/src/com/netscape/cmstools/subca/SubCACLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..311359999851f56dcea736d1747a7f0405b6a9e4
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/subca/SubCACLI.java
@@ -0,0 +1,52 @@
+package com.netscape.cmstools.subca;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.net.URI;
+import java.util.Locale;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+
+import com.netscape.certsrv.cert.CertEnrollmentRequest;
+import com.netscape.certsrv.subca.CAData;
+import com.netscape.certsrv.subca.SubCAClient;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+public class SubCACLI extends CLI {
+
+    public SubCAClient subcaClient;
+
+    public SubCACLI(CLI parent) {
+        super("subca", "Sub-CA management commands", parent);
+
+        addModule(new SubCAFindCLI(this));
+        addModule(new SubCAShowCLI(this));
+        addModule(new SubCACreateCLI(this));
+    }
+
+    public String getFullName() {
+        if (parent instanceof MainCLI) {
+            // do not include MainCLI's name
+            return name;
+        } else {
+            return parent.getFullName() + "-" + name;
+        }
+    }
+
+    public void execute(String[] args) throws Exception {
+        client = parent.getClient();
+        subcaClient = new SubCAClient(client, "ca");
+        super.execute(args);
+    }
+
+    protected static void printCAData(CAData data) {
+        System.out.println("  CA handle: " + data.getCARef());
+        System.out.println("  Issuer DN: " + data.getDN());
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/subca/SubCACreateCLI.java b/base/java-tools/src/com/netscape/cmstools/subca/SubCACreateCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a1f7ad08addc77103bb2d32c94ff5a7c5c6b737
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/subca/SubCACreateCLI.java
@@ -0,0 +1,66 @@
+package com.netscape.cmstools.subca;
+
+import java.util.Arrays;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+
+import com.netscape.certsrv.subca.CAData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+public class SubCACreateCLI extends CLI {
+
+    public SubCACLI subcaCLI;
+
+    public SubCACreateCLI(SubCACLI subcaCLI) {
+        super("create", "Create sub-CAs", subcaCLI);
+        this.subcaCLI = subcaCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <handle> <dn>", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+        // Always check for "--help" prior to parsing
+        if (Arrays.asList(args).contains("--help")) {
+            // Display usage
+            printHelp();
+            System.exit(0);
+        }
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        String[] positionalArgNames = {
+            "CA handle",
+            "Issuer DN"
+        };
+
+        if (cmdArgs.length < positionalArgNames.length) {
+            System.err.println("Error: No "
+                    + positionalArgNames[cmdArgs.length]
+                    + " specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String caRef = cmdArgs[0];
+        String dn = cmdArgs[1];
+        CAData data = new CAData(caRef, dn);
+        CAData newData = subcaCLI.subcaClient.createCA(data);
+        SubCACLI.printCAData(newData);
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/subca/SubCAFindCLI.java b/base/java-tools/src/com/netscape/cmstools/subca/SubCAFindCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c3ccce4a607e96ba29470f8902485cdaf21032c
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/subca/SubCAFindCLI.java
@@ -0,0 +1,62 @@
+package com.netscape.cmstools.subca;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+
+import com.netscape.certsrv.subca.CAData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+public class SubCAFindCLI extends CLI {
+
+    public SubCACLI subcaCLI;
+
+    public SubCAFindCLI(SubCACLI subcaCLI) {
+        super("find", "Find sub-CAs", subcaCLI);
+        this.subcaCLI = subcaCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName(), options);
+    }
+
+    public void execute(String[] args) throws Exception {
+        // Always check for "--help" prior to parsing
+        if (Arrays.asList(args).contains("--help")) {
+            // Display usage
+            printHelp();
+            System.exit(0);
+        }
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        List<CAData> datas = subcaCLI.subcaClient.listCAs();
+
+        MainCLI.printMessage(datas.size() + " entries matched");
+        if (datas.size() == 0) return;
+
+        boolean first = true;
+        for (CAData data : datas) {
+            if (first)
+                first = false;
+            else
+                System.out.println();
+            SubCACLI.printCAData(data);
+        }
+
+        MainCLI.printMessage("Number of entries returned " + datas.size());
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/subca/SubCAShowCLI.java b/base/java-tools/src/com/netscape/cmstools/subca/SubCAShowCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..e05ab84259b438d2df97880ef96a3aa694a2ba0e
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/subca/SubCAShowCLI.java
@@ -0,0 +1,57 @@
+package com.netscape.cmstools.subca;
+
+import java.util.Arrays;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+
+import com.netscape.certsrv.subca.CAData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+public class SubCAShowCLI extends CLI {
+
+    public SubCACLI subcaCLI;
+
+    public SubCAShowCLI(SubCACLI subcaCLI) {
+        super("show", "Show sub-CAs", subcaCLI);
+        this.subcaCLI = subcaCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <caRef>", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+        // Always check for "--help" prior to parsing
+        if (Arrays.asList(args).contains("--help")) {
+            // Display usage
+            printHelp();
+            System.exit(0);
+        }
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length < 1) {
+            System.err.println("Error: No caRef specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String caRef = cmdArgs[0];
+        CAData data = subcaCLI.subcaClient.getCA(caRef);
+        SubCACLI.printCAData(data);
+    }
+
+}
-- 
2.4.3



More information about the Pki-devel mailing list