[Pki-devel] [PATCH] Lightweight CAs

Fraser Tweedale ftweedal at redhat.com
Mon Sep 14 07:25:00 UTC 2015


The latest lightweight CAs (f.k.a. Sub-CAs) patches are attached.
This cut has significant changes and additions including:

- CAs are now stored in a flat structure with references to parents

- CAs are now identified by "AuthorityID" (which for now is a UUID
  underneath).  Many variables, method names and user-visible
  strings were updated accordingly.  Out with "caRef" terminology.

- "Sub-CA" terminology is (mostly) out; "Authority" is in.  This is
  to support lightweight CAs that are not descendents of top-level
  CA (which can be implemented later).

- ca-cert-request-submit command and related client / service
  classes were updated to add "authority" parameter

- Some more specific use of exception (including some new exception
  classes) to indicate / catch particular errors.

- More appropriate HTTP status codes return when client has send
  invalid data (400), referenced unknown authority (404) or attempts
  to create an authority with Subject DN already uesd by another
  authority (409 Conflict)

- LDAP entry now gets added before key generation and signing.  If
  something goes wrong, the DB entry is removed in the catch block.

There are still notable gaps in functionality that are in progress
or will be implemented soon:

- Audit log events
- Resources to enable/disable/delete authority
- Resources to access cert and pkcs7 chain of authority
- Keygen params
- Param to specify token on which to generate authority key

Thanks,
Fraser
-------------- next part --------------
From b4d1dd0ac041eab66028661d6c6ab5117ca3980c 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/db.ldif                        |   5 +
 base/ca/src/com/netscape/ca/CAService.java         |  53 ++-
 .../src/com/netscape/ca/CertificateAuthority.java  | 443 ++++++++++++++++++---
 base/ca/src/com/netscape/ca/SigningUnit.java       |  22 +-
 .../dogtagpki/server/ca/rest/AuthorityService.java | 183 +++++++++
 .../dogtagpki/server/ca/rest/CAApplication.java    |   3 +
 .../netscape/certsrv/authority/AuthorityData.java  | 109 +++++
 .../certsrv/authority/AuthorityResource.java       |  36 ++
 .../src/com/netscape/certsrv/ca/AuthorityID.java   |  36 ++
 .../netscape/certsrv/ca/CANotFoundException.java   |  12 +
 .../src/com/netscape/certsrv/ca/ICAService.java    |  11 +-
 .../netscape/certsrv/ca/ICertificateAuthority.java |  44 ++
 .../certsrv/ca/IssuerUnavailableException.java     |  13 +
 .../netscape/certsrv/profile/IEnrollProfile.java   |   5 +
 .../netscape/certsrv/security/ISigningUnit.java    |   8 +
 .../cms/profile/common/CAEnrollProfile.java        |  11 +-
 .../netscape/cms/profile/common/EnrollProfile.java |   3 +
 .../def/AuthorityKeyIdentifierExtDefault.java      |  19 +-
 .../netscape/cms/profile/def/CAEnrollDefault.java  |   4 +-
 .../cms/servlet/cert/EnrollmentProcessor.java      |  22 +
 .../com/netscape/cms/servlet/csadmin/CertUtil.java |  38 +-
 base/server/share/conf/schema-subCA.ldif           |   5 +
 base/server/share/conf/schema.ldif                 |  10 +
 23 files changed, 988 insertions(+), 107 deletions(-)
 create mode 100644 base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
 create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityData.java
 create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityResource.java
 create mode 100644 base/common/src/com/netscape/certsrv/ca/AuthorityID.java
 create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java
 create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java
 create mode 100644 base/server/share/conf/schema-subCA.ldif

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/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java
index 36f0bd592e333a276da84662c1e64a2921c5e7d2..a49d641cec839b4dac33fe7a6be49bf86c3560a8 100644
--- a/base/ca/src/com/netscape/ca/CAService.java
+++ b/base/ca/src/com/netscape/ca/CAService.java
@@ -65,7 +65,9 @@ import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.base.MetaInfo;
 import com.netscape.certsrv.base.SessionContext;
+import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.ca.ECAException;
+import com.netscape.certsrv.ca.CANotFoundException;
 import com.netscape.certsrv.ca.ICAService;
 import com.netscape.certsrv.ca.ICRLIssuingPoint;
 import com.netscape.certsrv.ca.ICertificateAuthority;
@@ -565,18 +567,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(
+                AuthorityID aid, X509CertInfo certi,
+                String profileId, String rid)
             throws EBaseException {
         CMS.debug("issueX509Cert");
-        X509CertImpl certImpl = issueX509Cert("", certi, false, null);
+        X509CertImpl certImpl = issueX509Cert(aid, "", certi, false, null);
 
         CMS.debug("storeX509Cert " + certImpl.getSerialNumber());
         storeX509Cert(profileId, rid, certImpl);
@@ -615,9 +614,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(null, rid, certi, renewal, oldSerialNo);
+    }
+
+    private X509CertImpl issueX509Cert(
+                AuthorityID aid, String rid, X509CertInfo certi,
+                boolean renewal, BigInteger oldSerialNo
+            ) throws EBaseException {
+        ICertificateAuthority ca = mCA.getCA(aid);
+        if (ca == null)
+            throw new CANotFoundException("No such CA: " + aid);
+
         String algname = null;
         X509CertImpl cert = null;
 
@@ -642,7 +653,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 +676,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 +716,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 +725,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 +745,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 +831,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 +872,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..3b57f5b209160d7f35d1ebfbeeb325eccf29a072 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -25,15 +25,20 @@ 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.TreeMap;
 import java.util.Vector;
 
 import javax.servlet.http.HttpServletRequest;
@@ -53,6 +58,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 +72,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;
@@ -73,15 +88,20 @@ import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.base.ISubsystem;
 import com.netscape.certsrv.base.Nonces;
 import com.netscape.certsrv.base.PKIException;
+import com.netscape.certsrv.ca.AuthorityID;
+import com.netscape.certsrv.ca.CANotFoundException;
 import com.netscape.certsrv.ca.ECAException;
 import com.netscape.certsrv.ca.ICRLIssuingPoint;
 import com.netscape.certsrv.ca.ICertificateAuthority;
+import com.netscape.certsrv.ca.IssuerUnavailableException;
 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 +116,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 +128,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 +146,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 +162,12 @@ 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 Map<AuthorityID, ICertificateAuthority> caMap = new TreeMap();
+    protected CertificateAuthority topCA = null;
+    protected AuthorityID authorityID = null;
+    protected AuthorityID authorityParentID = null;
+    protected String authorityDescription = null;
+
     protected ISubsystem mOwner = null;
     protected IConfigStore mConfig = null;
     protected ILogger mLogger = CMS.getLogger();
@@ -234,6 +266,33 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      * Constructs a CA subsystem.
      */
     public CertificateAuthority() {
+        topCA = this;
+    }
+
+    /**
+     * Construct and initialise a sub-CA
+     */
+    public CertificateAuthority(
+            String subsystemId,
+            ISubsystem owner,
+            IConfigStore config,
+            CertificateAuthority topCA,
+            AuthorityID aid,
+            AuthorityID parentAID,
+            String signingKeyNickname,
+            String authorityDescription
+            ) throws EBaseException {
+        setId(subsystemId);
+        this.topCA = topCA;
+        this.authorityID = aid;
+        this.authorityParentID = parentAID;
+        this.authorityDescription = authorityDescription;
+        mNickname = signingKeyNickname;
+        init(owner, config);
+    }
+
+    private boolean isTopCA() {
+        return authorityID == null;
     }
 
     /**
@@ -334,8 +393,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 (isTopCA()) {
+                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 = topCA.mReplicaRepot;
+            }
 
             // init signing unit & CA cert.
             try {
@@ -358,51 +431,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 (isTopCA()) {
+                /* 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 +471,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             // being functional.
             initCRL();
 
+            if (isTopCA())
+                loadSubCAs();
+
         } catch (EBaseException e) {
             if (CMS.isPreOpMode())
                 return;
@@ -420,6 +482,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         }
     }
 
+    private void initCRLPublisher() throws EBaseException {
+        // instantiate CRL publisher
+        if (!isTopCA()) {
+            mByName = topCA.mByName;
+            mCRLPublisher = topCA.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 +633,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         mService.startup();
         mRequestQueue.recover();
 
-        // Note that this could be null.
-
-        // setup Admin operations
-
-        initNotificationListeners();
-
-        startPublish();
-        //		startCRL();
+        if (isTopCA()) {
+            // setup Admin operations
+            initNotificationListeners();
+            startPublish();
+        }
     }
 
     /**
@@ -555,6 +645,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      * <P>
      */
     public void shutdown() {
+        if (!isTopCA()) return;  // sub-CAs don't own these resources
+
         Enumeration<ICRLIssuingPoint> enums = mCRLIssuePoints.elements();
         while (enums.hasMoreElements()) {
             CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement();
@@ -1228,13 +1320,13 @@ 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 (isTopCA() && CrlStore != null && CrlStore.size() > 0) {
                 mCRLSigningUnit = new SigningUnit();
                 mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE));
             } else {
@@ -1304,7 +1396,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
 
             IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE);
 
-            if (OCSPStore != null && OCSPStore.size() > 0) {
+            if (isTopCA() && OCSPStore != null && OCSPStore.size() > 0) {
                 mOCSPSigningUnit = new SigningUnit();
                 mOCSPSigningUnit.init(this, mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE));
                 CMS.debug("Separate OCSP signing unit inited");
@@ -1443,8 +1535,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
     /**
      * init cert & crl database
      */
-    private void initCaDatabases()
+    private void initCertDatabase()
             throws EBaseException {
+        if (!isTopCA()) {
+            mCertRepot = topCA.mCertRepot;
+            return;
+        }
+
         int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5);
 
         String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null);
@@ -1471,8 +1568,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 (!isTopCA()) {
+            mCRLRepot = topCA.mCRLRepot;
+            return;
+        }
 
         int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5);
 
@@ -1482,14 +1588,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 +1611,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     private void initPublish()
             throws EBaseException {
+        if (!isTopCA()) {
+            mPublisherProcessor = topCA.mPublisherProcessor;
+            return;
+        }
+
         IConfigStore c = null;
 
         try {
@@ -1676,6 +1779,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     private void initRequestQueue()
             throws EBaseException {
+        if (!isTopCA()) {
+            mPolicy = topCA.mPolicy;
+            mService = topCA.mService;
+            mNotify = topCA.mNotify;
+            mPNotify = topCA.mPNotify;
+            mRequestQueue = topCA.mRequestQueue;
+            return;
+        }
+
         mPolicy = new CAPolicy();
         mPolicy.init(this, mConfig.getSubStore(PROP_POLICY));
         CMS.debug("CA policy inited");
@@ -1734,6 +1846,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
     @SuppressWarnings("unchecked")
     private void initCRL()
             throws EBaseException {
+        if (!isTopCA()) {
+            mCRLIssuePoints = topCA.mCRLIssuePoints;
+            mMasterCRLIssuePoint = topCA.mMasterCRLIssuePoint;
+            return;
+        }
         IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE);
 
         if ((crlConfig == null) || (crlConfig.size() <= 0)) {
@@ -1799,6 +1916,64 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         log(ILogger.LL_INFO, "CRL Issuing Points inited");
     }
 
+    /**
+     * Find, instantiate and register sub-CAs.
+     *
+     * This method must only be called by the top-level CA.
+     */
+    private synchronized void loadSubCAs() throws EBaseException {
+        ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadSubCAs");
+        dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+        LDAPConnection conn = dbFactory.getConn();
+
+        String searchDN = "ou=authorities,ou=" + getId()
+            + "," + getDBSubsystem().getBaseDN();
+        LDAPSearchResults results = null;
+        try {
+            results = conn.search(
+                searchDN, LDAPConnection.SCOPE_ONE,
+                "(objectclass=authority)", null, false);
+
+            while (results.hasMoreElements()) {
+                LDAPEntry entry = results.next();
+                LDAPAttribute aidAttr = entry.getAttribute("authorityID");
+                LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID");
+                LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname");
+                if (aidAttr == null || nickAttr == null)
+                    throw new ECAException("Malformed sub-CA object: " + entry.getDN());
+                String keyNick = (String) nickAttr.getStringValues().nextElement();
+
+                AuthorityID aid = new AuthorityID((String)
+                    aidAttr.getStringValues().nextElement());
+                AuthorityID parentAID = null;
+                if (parentAIDAttr != null)
+                    parentAID = new AuthorityID((String)
+                        parentAIDAttr.getStringValues().nextElement());
+
+                String desc = null;
+                LDAPAttribute descAttr = entry.getAttribute("description");
+                if (descAttr != null)
+                    desc = (String) descAttr.getStringValues().nextElement();
+
+                CertificateAuthority subCA = new CertificateAuthority(
+                    getId(), mOwner, mConfig, this,
+                    aid, parentAID, keyNick, desc);
+                caMap.put(aid, subCA);
+            }
+        } catch (LDAPException e) {
+            if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) {
+                CMS.debug(
+                    "Missing lightweight CAs container '" + searchDN
+                    + "'.  Disabling lightweight CAs.");
+            } else {
+                throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e);
+            }
+        } finally {
+            dbFactory.returnConn(conn);
+            dbFactory.reset();
+        }
+    }
+
     public String getOfficialName() {
         return OFFICIAL_NAME;
     }
@@ -2083,4 +2258,160 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
 
         return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate);
     }
+
+    /**
+     * Enumerate all sub-CAs.
+     */
+    public synchronized List<ICertificateAuthority> getCAs() {
+        List<ICertificateAuthority> cas = new ArrayList();
+        for (ICertificateAuthority ca : caMap.values()) {
+            cas.add(ca);
+        }
+        return cas;
+    }
+
+    /**
+     * Get the sub-CA by ID.
+     *
+     * @param aid The ID of the CA to retrieve, or null
+     *             to retreive the top-level CA.
+     *
+     * @return the authority, or null if not found
+     */
+    public ICertificateAuthority getCA(AuthorityID aid) {
+        return aid == null ? topCA : caMap.get(aid);
+    }
+
+    public AuthorityID getAuthorityID() {
+        return authorityID;
+    }
+
+    public AuthorityID getAuthorityParentID() {
+        return authorityParentID;
+    }
+
+    public String getAuthorityDescription() {
+        return authorityDescription;
+    }
+
+    /**
+     * Create a new sub-CA.
+     *
+     * @param subjectDN Subject DN for new CA
+     * @param parentAID ID of parent CA, or null to create
+     *                  sub-CA immediately below top-level CA
+     * @param description Optional string description of CA
+     */
+    public ICertificateAuthority createCA(
+            String subjectDN, AuthorityID parentAID,
+            String description)
+            throws EBaseException {
+        ICertificateAuthority parentCA = getCA(parentAID);
+        if (parentCA == null)
+            throw new CANotFoundException(
+                "Parent CA \"" + parentAID + "\" does not exist");
+
+        ICertificateAuthority subCA = parentCA.createSubCA(
+                subjectDN, description);
+        synchronized (this) {
+            caMap.put(subCA.getAuthorityID(), 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.
+     */
+    public ICertificateAuthority createSubCA(
+            String subjectDN, String description)
+            throws EBaseException {
+
+        // check uniqueness of DN
+        X500Name subjectX500Name = null;
+        try {
+            subjectX500Name = new X500Name(subjectDN);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(
+                "Invalid Subject DN: " + subjectDN);
+        }
+        for (ICertificateAuthority ca : caMap.values()) {
+            if (ca.getX500Name().equals(subjectX500Name))
+                throw new IssuerUnavailableException(
+                    "CA with Subject DN '" + subjectDN + "' already exists");
+        }
+
+        // generate authority ID and nickname
+        AuthorityID aid = new AuthorityID();
+        String aidString = aid.toString();
+        String nickname = topCA.getNickname() + " " + aidString;
+
+        // build database entry
+        String dn = "cn=" + aidString + ",ou=authorities,ou="
+            + getId() + "," + getDBSubsystem().getBaseDN();
+        CMS.debug("createSubCA: DN = " + dn);
+        LDAPAttribute[] attrs = {
+            new LDAPAttribute("objectclass", "authority"),
+            new LDAPAttribute("cn", aidString),
+            new LDAPAttribute("authorityID", aidString),
+            new LDAPAttribute("authorityKeyNickname", nickname)
+        };
+        LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs);
+        if (this.authorityID != null)
+            attrSet.add(new LDAPAttribute(
+                "authorityParentID", this.authorityID.toString()));
+        if (description != null)
+            attrSet.add(new LDAPAttribute("description", description));
+        LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet);
+
+        // connect to database
+        ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("createSubCA");
+        dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+        LDAPConnection conn = dbFactory.getConn();
+
+        try {
+            // add entry to database
+            conn.add(ldapEntry);
+
+            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) {
+                // something went wrong; delete just-added entry
+                conn.delete(dn);
+                throw new ECAException("Error creating sub-CA certificate: " + e);
+            }
+        } 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, topCA, aid, this.authorityID,
+            nickname, description);
+    }
 }
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/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ca21892d441e4b0e611d117e4dd490df05cf6e6
--- /dev/null
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
@@ -0,0 +1,183 @@
+//--- 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.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.BadRequestException;
+import com.netscape.certsrv.base.ConflictingOperationException;
+import com.netscape.certsrv.base.ResourceNotFoundException;
+import com.netscape.certsrv.base.UnauthorizedException;
+import com.netscape.certsrv.ca.AuthorityID;
+import com.netscape.certsrv.ca.CANotFoundException;
+import com.netscape.certsrv.ca.ICertificateAuthority;
+import com.netscape.certsrv.ca.IssuerUnavailableException;
+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.authority.AuthorityData;
+import com.netscape.certsrv.authority.AuthorityResource;
+import com.netscape.cms.realm.PKIPrincipal;
+import com.netscape.cms.servlet.base.PKIService;
+
+/**
+ * @author ftweedal
+ */
+public class AuthorityService extends PKIService implements AuthorityResource {
+
+    ICertificateAuthority topCA;
+
+    public AuthorityService() {
+        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<AuthorityData> results = new ArrayList();
+        for (ICertificateAuthority ca : topCA.getCAs())
+            results.add(readAuthorityData(ca));
+
+        GenericEntity entity = new GenericEntity<List<AuthorityData>>(results) {};
+        return Response.ok(entity).build();
+    }
+
+    @Override
+    public Response getCA(String aidString) {
+        AuthorityID aid;
+        try {
+            aid = new AuthorityID(aidString);
+        } catch (IllegalArgumentException e) {
+            throw new BadRequestException("Bad CA ID: " + aidString);
+        }
+
+        ICertificateAuthority ca = topCA.getCA(aid);
+        if (ca == null)
+            throw new ResourceNotFoundException("CA \"" + aidString + "\" not found");
+
+        return createOKResponse(readAuthorityData(ca));
+    }
+
+    @Override
+    public Response createCA(AuthorityData data) {
+        AuthorityID parentAID = null;
+        String parentAIDString = data.getParentID();
+        if (parentAIDString != null) {
+            try {
+                parentAID = new AuthorityID(parentAIDString);
+            } catch (IllegalArgumentException e) {
+                throw new BadRequestException("Bad CA ID: " + parentAIDString);
+            }
+        }
+
+        try {
+            ICertificateAuthority subCA = topCA.createCA(
+                data.getDN(), parentAID, data.getDescription());
+            return createOKResponse(readAuthorityData(subCA));
+        } catch (IllegalArgumentException e) {
+            throw new BadRequestException(e.toString());
+        } catch (CANotFoundException e) {
+            throw new ResourceNotFoundException(e.toString());
+        } catch (IssuerUnavailableException e) {
+            throw new ConflictingOperationException(e.toString());
+        } catch (Exception e) {
+            CMS.debug(e);
+            throw new PKIException("Error creating sub-CA: " + e.toString());
+        }
+    }
+
+    private static AuthorityData readAuthorityData(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");
+        }
+
+        AuthorityID parentAID = ca.getAuthorityParentID();
+        return new AuthorityData(
+            dn,
+            ca.getAuthorityID().toString(),
+            parentAID != null ? parentAID.toString() : null,
+            ca.getAuthorityDescription()
+        );
+    }
+
+    /* 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/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java
index 16eae7877059c7dc42479276b3111db1ce7f582d..235ea105bef0c738bccd53276a744b95a76f0627 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(AuthorityService.class);
+
         // certs and requests
         classes.add(CertService.class);
         classes.add(CertRequestService.class);
diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java
new file mode 100644
index 0000000000000000000000000000000000000000..64f2af83720311e3eac0ee7a9197a05ff0e7198a
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java
@@ -0,0 +1,109 @@
+// --- 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.authority;
+
+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 AuthorityData {
+
+    public static Marshaller marshaller;
+    public static Unmarshaller unmarshaller;
+
+    static {
+        try {
+            marshaller = JAXBContext.newInstance(AuthorityData.class).createMarshaller();
+            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+            unmarshaller = JAXBContext.newInstance(AuthorityData.class).createUnmarshaller();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @XmlAttribute
+    protected String aid;
+
+    public String getID() {
+        return aid;
+    }
+
+
+    @XmlAttribute
+    protected String parentAID;
+
+    public String getParentID() {
+        return parentAID;
+    }
+
+
+    @XmlAttribute
+    protected String dn;
+
+    public String getDN() {
+        return dn;
+    }
+
+
+    @XmlAttribute
+    protected String description;
+
+    public String getDescription() {
+        return description;
+    }
+
+
+    protected Link link;
+
+    public Link getLink() {
+        return link;
+    }
+
+    public void setLink(Link link) {
+        this.link = link;
+    }
+
+    protected AuthorityData() {
+    }
+
+    public AuthorityData(
+            String dn, String aid, String parentAID,
+            String description) {
+        this.dn = dn;
+        this.aid = aid;
+        this.parentAID = parentAID;
+        this.description = description;
+    }
+
+}
diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..817885687b04d1b10ea35dca9a1942fe5ce201f4
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java
@@ -0,0 +1,36 @@
+package com.netscape.certsrv.authority;
+
+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("authorities")
+public interface AuthorityResource {
+
+    @GET
+    public Response listCAs();
+    /*
+            @QueryParam("start") Integer start,
+            @QueryParam("size") Integer size);
+            */
+
+    @GET
+    @Path("{id}")
+    @ClientResponseType(entityType=AuthorityData.class)
+    public Response getCA(@PathParam("id") String caIDString);
+
+    @POST
+    @ClientResponseType(entityType=AuthorityData.class)
+    //@ACLMapping("certs")
+    //@AuthMethodMapping("certs")
+    public Response createCA(AuthorityData data);
+
+}
diff --git a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java
new file mode 100644
index 0000000000000000000000000000000000000000..86f3870ab8cec700ab27cd6a8555e8e8d6e84c01
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java
@@ -0,0 +1,36 @@
+package com.netscape.certsrv.ca;
+
+import java.util.UUID;
+
+/**
+ * Identifier for a CertificateAuthority.
+ */
+public class AuthorityID implements Comparable {
+
+    protected UUID uuid;
+
+    /**
+     * Parse a AuthorityID from the given string
+     */
+    public AuthorityID(String s) {
+        uuid = UUID.fromString(s);
+    }
+
+    /**
+     * Construct a random AuthorityID
+     */
+    public AuthorityID() {
+        uuid = UUID.randomUUID();
+    }
+
+    public String toString() {
+        return uuid.toString();
+    }
+
+    public int compareTo(Object o) {
+        if (o instanceof AuthorityID)
+            return uuid.compareTo(((AuthorityID) o).uuid);
+        throw new ClassCastException("Invalid comparsion operand for AuthorityID");
+    }
+
+}
diff --git a/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7574d4243d9941e30aba506b4ad5fd7b8394386
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java
@@ -0,0 +1,12 @@
+package com.netscape.certsrv.ca;
+
+/**
+ * Exception to throw when a (sub-)CA cannot be found.
+ */
+public class CANotFoundException extends ECAException {
+
+    public CANotFoundException(String msgFormat) {
+        super(msgFormat);
+    }
+
+}
diff --git a/base/common/src/com/netscape/certsrv/ca/ICAService.java b/base/common/src/com/netscape/certsrv/ca/ICAService.java
index 1d179e07692eee2f32780b33489975a571670685..a4b4a63038872fbf6d97cfc3fbcadce5234208a6 100644
--- a/base/common/src/com/netscape/certsrv/ca/ICAService.java
+++ b/base/common/src/com/netscape/certsrv/ca/ICAService.java
@@ -23,6 +23,7 @@ import netscape.security.x509.X509CertInfo;
 
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.IConfigStore;
+import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.connector.IConnector;
 import com.netscape.certsrv.request.IRequest;
 
@@ -59,13 +60,15 @@ public interface ICAService {
      * Issues certificate base on enrollment information,
      * creates certificate record, and stores all necessary data.
      *
+     * @param caID CA ID
      * @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(
+                AuthorityID aid, 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..d707bf848ba0973c2a817c45abeba8219bff574f 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,47 @@ public interface ICertificateAuthority extends ISubsystem {
 
     public CertificateIssuerName getIssuerObj();
     public CertificateSubjectName getSubjectObj();
+
+    /**
+     * Enumerate all sub-CA handles.
+     */
+    public List<ICertificateAuthority> getCAs();
+
+    /**
+     * Get the CA ID of this CA.  Returns null for the top-level CA.
+     */
+    public AuthorityID getAuthorityID();
+
+    /**
+     * Get the CA ID of this CA's parent CA.  Returns null for
+     * authorities signed by the top-level CA.
+     */
+    public AuthorityID getAuthorityParentID();
+
+    /**
+     * Return CA description.  May be null.
+     */
+    public String getAuthorityDescription();
+
+    /**
+     * Get the CA by ID.  Returns null if CA not found.
+     */
+    public ICertificateAuthority getCA(AuthorityID aid);
+
+    /**
+     * Create a new sub-CA under the specified parent CA.
+     */
+    public ICertificateAuthority createCA(
+            String dn, AuthorityID parentAID, String desc)
+        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.
+     */
+    public ICertificateAuthority createSubCA(
+            String dn, String desc)
+        throws EBaseException;
 }
diff --git a/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java
new file mode 100644
index 0000000000000000000000000000000000000000..541df308d413676da51e5a1bce27232464118920
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java
@@ -0,0 +1,13 @@
+package com.netscape.certsrv.ca;
+
+/**
+ * Exception to throw during CA creation when requested CA
+ * (issuer DN) already exists.
+ */
+public class IssuerUnavailableException extends ECAException {
+
+    public IssuerUnavailableException(String msgFormat) {
+        super(msgFormat);
+    }
+
+}
diff --git a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java
index 69a39d7e23232a1a0cc6e2fe98305d452234bb8c..a861a2e73a2e361971f010f63bd0ca615ba08e80 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";
 
     /**
+     * ID of requested certificate authority (unused for top-level CA)
+     */
+    public static final String REQUEST_AUTHORITY_ID = "req_authority_id";
+
+    /**
      * 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/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java
index d0bfdb8a64ee857a3f5ff544e41de905b4660f55..53edca3a93c28a4fdd6c476bbdd2dc3d83869505 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
@@ -29,6 +29,7 @@ import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.authority.IAuthority;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.SessionContext;
+import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.ca.ICAService;
 import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.connector.IConnector;
@@ -95,8 +96,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");
         }
@@ -190,9 +191,13 @@ public class CAEnrollProfile extends EnrollProfile {
         if (setId != null) {
             sc.put("profileSetId", setId);
         }
+        AuthorityID aid = null;
+        String aidString = request.getExtDataInString(REQUEST_AUTHORITY_ID);
+        if (aidString != null)
+            aid = new AuthorityID(aidString);
         try {
-            theCert = caService.issueX509Cert(info, getId() /* profileId */,
-                    id /* requestId */);
+            theCert = caService.issueX509Cert(
+                aid, 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..523e0117a55567d2f807dd3eb2e69c48d7eb7344 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_ID, ctx.get(REQUEST_AUTHORITY_ID));
         }
         return result;
     }
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..54cfafed22a0654dd993c9c67f247eefac09c1a3 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,12 @@ 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.AuthorityID;
+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 +165,27 @@ 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);
+        String aidString = request.getExtDataInString(
+                IEnrollProfile.REQUEST_AUTHORITY_ID);
+        if (aidString != null)
+            ca = ca.getCA(new AuthorityID(aidString));
+        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/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java
index 960f997cd4badd18bdd25393e9175fc935d52edb..7e358b87f35b8aef2d3ef9de3f8dfd4c7a2b7053 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,10 @@ 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.ca.AuthorityID;
+import com.netscape.certsrv.ca.CANotFoundException;
+import com.netscape.certsrv.ca.ICertificateAuthority;
+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 +150,24 @@ public class EnrollmentProcessor extends CertProcessor {
             }
 
             IProfileContext ctx = profile.createContext();
+
+            // Insert AuthorityID into request context
+            //
+            String aidString = request.getParameter("authority");
+            if (aidString != null) {
+                AuthorityID aid = null;
+                try {
+                    aid = new AuthorityID(aidString);
+                } catch (IllegalArgumentException e) {
+                    throw new BadRequestDataException("EnrollmentProcessor: invalid AuthorityID: " + aidString);
+                }
+                ICertificateAuthority ca = (ICertificateAuthority)
+                    CMS.getSubsystem(CMS.SUBSYSTEM_CA);
+                if (ca.getCA(aid) == null)
+                    throw new CANotFoundException("CA not found: " + aidString);
+                ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aidString);
+            }
+
             CMS.debug("EnrollmentProcessor: set Inputs into profile Context");
             setInputsIntoContext(data, profile, ctx);
 
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/share/conf/schema-subCA.ldif b/base/server/share/conf/schema-subCA.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..d03ca70ab466a6229f0efbbe2df6c587d8d5ea5c
--- /dev/null
+++ b/base/server/share/conf/schema-subCA.ldif
@@ -0,0 +1,5 @@
+dn: cn=schema
+attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' )
+attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' )
+attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' )
+objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' )
diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif
index 475758c5d66bf681e589995505a561bf4e4c40ef..3a692cac9370e9bb115a35fd0fe56be1d49b9ce9 100644
--- a/base/server/share/conf/schema.ldif
+++ b/base/server/share/conf/schema.ldif
@@ -667,3 +667,13 @@ 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: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' )
+attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' )
+attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' )
+-
+add: objectClasses
+objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' )
-- 
2.4.3

-------------- next part --------------
From 602672eca140cde9fb4f1bcc8eab9d297c52d5f0 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

---
 .../certsrv/authority/AuthorityClient.java         | 57 ++++++++++++++
 .../src/com/netscape/certsrv/ca/CAClient.java      |  3 +-
 .../netscape/cmstools/authority/AuthorityCLI.java  | 56 ++++++++++++++
 .../cmstools/authority/AuthorityCreateCLI.java     | 86 ++++++++++++++++++++++
 .../cmstools/authority/AuthorityFindCLI.java       | 62 ++++++++++++++++
 .../cmstools/authority/AuthorityShowCLI.java       | 57 ++++++++++++++
 .../src/com/netscape/cmstools/cli/CACLI.java       |  2 +
 7 files changed, 322 insertions(+), 1 deletion(-)
 create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityClient.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java

diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ad549c7d4bf85513aa64911fdf1c71c5ac4fc21
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.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.authority;
+
+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 AuthorityClient extends Client {
+
+    public AuthorityResource proxy;
+
+    public AuthorityClient(PKIClient client, String subsystem) throws URISyntaxException {
+        super(client, subsystem, "authority");
+        proxy = createProxy(AuthorityResource.class);
+    }
+
+    public List<AuthorityData> listCAs() {
+        Response response = proxy.listCAs();
+        GenericType<List<AuthorityData>> type = new GenericType<List<AuthorityData>>() {};
+        return client.getEntity(response, type);
+    }
+
+    public AuthorityData getCA(String caIDString) {
+        Response response = proxy.getCA(caIDString);
+        return client.getEntity(response, AuthorityData.class);
+    }
+
+    public AuthorityData createCA(AuthorityData data) {
+        Response response = proxy.createCA(data);
+        return client.getEntity(response, AuthorityData.class);
+    }
+
+}
diff --git a/base/common/src/com/netscape/certsrv/ca/CAClient.java b/base/common/src/com/netscape/certsrv/ca/CAClient.java
index e1a0a8c02f8a840acbdea924c164020b88557fc4..1fbd2a0b286ed09854373846510c392c5202307a 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.authority.AuthorityClient;
 
 public class CAClient extends SubsystemClient {
 
@@ -35,7 +36,7 @@ public class CAClient extends SubsystemClient {
     }
 
     public void init() throws URISyntaxException {
-
+        addClient(new AuthorityClient(client, name));
         addClient(new CertClient(client, name));
         addClient(new GroupClient(client, name));
         addClient(new ProfileClient(client, name));
diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..f2b630d32f9b8fc12792d14c84e90e12f7c23f4d
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
@@ -0,0 +1,56 @@
+package com.netscape.cmstools.authority;
+
+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.authority.AuthorityData;
+import com.netscape.certsrv.authority.AuthorityClient;
+import com.netscape.certsrv.cert.CertEnrollmentRequest;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+public class AuthorityCLI extends CLI {
+
+    public AuthorityClient authorityClient;
+
+    public AuthorityCLI(CLI parent) {
+        super("authority", "CA management commands", parent);
+
+        addModule(new AuthorityFindCLI(this));
+        addModule(new AuthorityShowCLI(this));
+        addModule(new AuthorityCreateCLI(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();
+        authorityClient = new AuthorityClient(client, "ca");
+        super.execute(args);
+    }
+
+    protected static void printAuthorityData(AuthorityData data) {
+        System.out.println("  Issuer DN:   " + data.getDN());
+        System.out.println("  ID:          " + data.getID());
+        System.out.println("  Parent ID:   " + data.getParentID());
+        String desc = data.getDescription();
+        if (desc != null)
+            System.out.println("  Description: " + desc);
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..19329cbba0fac3f8c9722cc6854cbeaf6a31c75c
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java
@@ -0,0 +1,86 @@
+package com.netscape.cmstools.authority;
+
+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.ca.AuthorityID;
+import com.netscape.certsrv.authority.AuthorityData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+public class AuthorityCreateCLI extends CLI {
+
+    public AuthorityCLI authorityCLI;
+
+    public AuthorityCreateCLI(AuthorityCLI authorityCLI) {
+        super("create", "Create CAs", authorityCLI);
+        this.authorityCLI = authorityCLI;
+
+        Option optParent = new Option(null, "parent", true, "ID of parent CA");
+        optParent.setArgName("id");
+        options.addOption(optParent);
+
+        Option optDesc = new Option(null, "desc", true, "Optional description");
+        optDesc.setArgName("string");
+        options.addOption(optDesc);
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <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();
+        if (cmdArgs.length != 1) {
+            if (cmdArgs.length < 1)
+                System.err.println("No DN specified.");
+            else
+                System.err.println("Too many arguments.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String parentAIDString = null;
+        if (cmd.hasOption("parent")) {
+            parentAIDString = cmd.getOptionValue("parent");
+            try {
+                new AuthorityID(parentAIDString);
+            } catch (IllegalArgumentException e) {
+                System.err.println("Bad CA ID: " + parentAIDString);
+                printHelp();
+                System.exit(-1);
+            }
+        }
+
+        String desc = null;
+        if (cmd.hasOption("desc"))
+            desc = cmd.getOptionValue("desc");
+
+        String dn = cmdArgs[0];
+        AuthorityData data = new AuthorityData(
+            dn, null, parentAIDString, desc);
+        AuthorityData newData = authorityCLI.authorityClient.createCA(data);
+        AuthorityCLI.printAuthorityData(newData);
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a5684671d6a778146de183a0d122aaa58c45d8d
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java
@@ -0,0 +1,62 @@
+package com.netscape.cmstools.authority;
+
+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.authority.AuthorityData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+public class AuthorityFindCLI extends CLI {
+
+    public AuthorityCLI authorityCLI;
+
+    public AuthorityFindCLI(AuthorityCLI authorityCLI) {
+        super("find", "Find CAs", authorityCLI);
+        this.authorityCLI = authorityCLI;
+    }
+
+    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<AuthorityData> datas = authorityCLI.authorityClient.listCAs();
+
+        MainCLI.printMessage(datas.size() + " entries matched");
+        if (datas.size() == 0) return;
+
+        boolean first = true;
+        for (AuthorityData data : datas) {
+            if (first)
+                first = false;
+            else
+                System.out.println();
+            AuthorityCLI.printAuthorityData(data);
+        }
+
+        MainCLI.printMessage("Number of entries returned " + datas.size());
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..a252f3001ae2581f770e58e68a077eb909a5490b
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java
@@ -0,0 +1,57 @@
+package com.netscape.cmstools.authority;
+
+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.authority.AuthorityData;
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+public class AuthorityShowCLI extends CLI {
+
+    public AuthorityCLI authorityCLI;
+
+    public AuthorityShowCLI(AuthorityCLI authorityCLI) {
+        super("show", "Show CAs", authorityCLI);
+        this.authorityCLI = authorityCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <ID>", 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 ID specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String caIDString = cmdArgs[0];
+        AuthorityData data = authorityCLI.authorityClient.getCA(caIDString);
+        AuthorityCLI.printAuthorityData(data);
+    }
+
+}
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..5c41f00c2eb6e393cc95d3b174cb14eefc7307ae 100644
--- a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java
@@ -20,6 +20,7 @@ package com.netscape.cmstools.cli;
 
 import com.netscape.certsrv.ca.CAClient;
 import com.netscape.certsrv.client.Client;
+import com.netscape.cmstools.authority.AuthorityCLI;
 import com.netscape.cmstools.cert.CertCLI;
 import com.netscape.cmstools.group.GroupCLI;
 import com.netscape.cmstools.profile.ProfileCLI;
@@ -37,6 +38,7 @@ public class CACLI extends SubsystemCLI {
     public CACLI(CLI parent) {
         super("ca", "CA management commands", parent);
 
+        addModule(new AuthorityCLI(this));
         addModule(new CertCLI(this));
         addModule(new GroupCLI(this));
         addModule(new KRAConnectorCLI(this));
-- 
2.4.3

-------------- next part --------------
From bb5d5b827bf326c2d931e2477977a90fdecf2405 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Tue, 1 Sep 2015 09:57:42 -0400
Subject: [PATCH] Add ability to request cert from subca via REST

Add the optional "ca" query parameter for REST cert request
submission.  Also update the ca-cert-request-submit CLI command with
an option to provide an AuthorityID.
---
 .../src/com/netscape/cms/servlet/test/CATest.java    |  4 ++--
 .../dogtagpki/server/ca/rest/CertRequestService.java | 11 ++++++++++-
 .../src/com/netscape/certsrv/cert/CertClient.java    |  7 +++++--
 .../netscape/certsrv/cert/CertRequestResource.java   |  4 +++-
 .../netscape/cmstools/cert/CertRequestSubmitCLI.java | 20 +++++++++++++++++++-
 .../cmstools/client/ClientCertRequestCLI.java        |  2 +-
 6 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java
index 15023cad939abb11927abc64fe5916e04cb65661..5876c57f985caa38ad5895f4368113620370910d 100644
--- a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java
+++ b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java
@@ -288,7 +288,7 @@ public class CATest {
     private static void enrollAndApproveCertRequest(CertClient client, CertEnrollmentRequest data) {
         CertRequestInfos reqInfo = null;
         try {
-            reqInfo = client.enrollRequest(data);
+            reqInfo = client.enrollRequest(data, null);
         } catch (Exception e) {
             e.printStackTrace();
             log(e.toString());
@@ -308,7 +308,7 @@ public class CATest {
     private static void enrollCertRequest(CertClient client, CertEnrollmentRequest data) {
         CertRequestInfos reqInfo = null;
         try {
-            reqInfo = client.enrollRequest(data);
+            reqInfo = client.enrollRequest(data, null);
         } catch (Exception e) {
             e.printStackTrace();
             log(e.toString());
diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
index 95f1f4c20086ddb45846f65b1db157bff238708a..654d814d8a963892a6b39a1f77745e1071a5408d 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
@@ -40,7 +40,9 @@ import com.netscape.certsrv.base.BadRequestDataException;
 import com.netscape.certsrv.base.BadRequestException;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.PKIException;
+import com.netscape.certsrv.base.ResourceNotFoundException;
 import com.netscape.certsrv.base.UnauthorizedException;
+import com.netscape.certsrv.ca.CANotFoundException;
 import com.netscape.certsrv.cert.CertEnrollmentRequest;
 import com.netscape.certsrv.cert.CertRequestInfo;
 import com.netscape.certsrv.cert.CertRequestInfos;
@@ -113,7 +115,11 @@ public class CertRequestService extends PKIService implements CertRequestResourc
     }
 
     @Override
-    public Response enrollCert(CertEnrollmentRequest data) {
+    public Response enrollCert(CertEnrollmentRequest data, String aidString) {
+        // Ignore the aidString param; it is pulled out of the
+        // servletRequest that is passed to CertRequestDAO,
+        // but is included in the signature so that clients
+        // can easily provide it via @QueryParam
 
         if (data == null) {
             CMS.debug("enrollCert: data is null");
@@ -137,6 +143,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc
         } catch (BadRequestDataException e) {
             CMS.debug("enrollCert: bad request data: " + e);
             throw new BadRequestException(e.toString());
+        } catch (CANotFoundException e) {
+            CMS.debug("enrollCert: unknown CA: " + e);
+            throw new ResourceNotFoundException(e.toString());
         } catch (EBaseException e) {
             throw new PKIException(e);
         } catch (Exception e) {
diff --git a/base/common/src/com/netscape/certsrv/cert/CertClient.java b/base/common/src/com/netscape/certsrv/cert/CertClient.java
index 42b04b7021f0063894c340c177915d799b621ddd..211711b3da64e5125beee000759c2f2926d85e86 100644
--- a/base/common/src/com/netscape/certsrv/cert/CertClient.java
+++ b/base/common/src/com/netscape/certsrv/cert/CertClient.java
@@ -21,6 +21,7 @@ import java.net.URISyntaxException;
 
 import javax.ws.rs.core.Response;
 
+import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.client.Client;
 import com.netscape.certsrv.client.PKIClient;
 import com.netscape.certsrv.client.SubsystemClient;
@@ -85,8 +86,10 @@ public class CertClient extends Client {
         return client.getEntity(response, CertRequestInfo.class);
     }
 
-    public CertRequestInfos enrollRequest(CertEnrollmentRequest data) {
-        Response response = certRequestClient.enrollCert(data);
+    public CertRequestInfos enrollRequest(
+            CertEnrollmentRequest data, AuthorityID aid) {
+        String aidString = aid != null ? aid.toString() : null;
+        Response response = certRequestClient.enrollCert(data, aidString);
         return client.getEntity(response, CertRequestInfos.class);
     }
 
diff --git a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java
index 7f08b4af392e3e56419abdad7cb66bd191688222..b877b681ccf905b4da2949fe04ec21e8a6407bba 100644
--- a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java
+++ b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java
@@ -37,7 +37,9 @@ public interface CertRequestResource {
     @POST
     @Path("certrequests")
     @ClientResponseType(entityType=CertRequestInfos.class)
-    public Response enrollCert(CertEnrollmentRequest data);
+    public Response enrollCert(
+        CertEnrollmentRequest data,
+        @QueryParam("authority") String caIDString);
 
     /**
      * Used to retrieve cert request info for a specific request
diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java
index 608490bb73d7df482d87e67e9c15322ddc2e5f5a..e46079406bfbd1dbd47e32567b52dde85f181233 100644
--- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java
@@ -8,8 +8,10 @@ import java.util.Scanner;
 import javax.xml.bind.JAXBException;
 
 import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
 
+import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.cert.CertEnrollmentRequest;
 import com.netscape.certsrv.cert.CertRequestInfos;
 import com.netscape.cmstools.cli.CLI;
@@ -22,6 +24,10 @@ public class CertRequestSubmitCLI extends CLI {
     public CertRequestSubmitCLI(CertCLI certCLI) {
         super("request-submit", "Submit certificate request", certCLI);
         this.certCLI = certCLI;
+
+        Option optCA = new Option(null, "authority", true, "Authority ID (omit for top-level CA)");
+        optCA.setArgName("id");
+        options.addOption(optCA);
     }
 
     public void printHelp() {
@@ -55,9 +61,21 @@ public class CertRequestSubmitCLI extends CLI {
             System.exit(-1);
         }
 
+        AuthorityID aid = null;
+        if (cmd.hasOption("authority")) {
+            String aidString = cmd.getOptionValue("authority");
+            try {
+                aid = new AuthorityID(aidString);
+            } catch (IllegalArgumentException e) {
+                System.err.println("Bad AuthorityID: " + aidString);
+                printHelp();
+                System.exit(-1);
+            }
+        }
+
         try {
             CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]);
-            CertRequestInfos cri = certCLI.certClient.enrollRequest(erd);
+            CertRequestInfos cri = certCLI.certClient.enrollRequest(erd, aid);
             MainCLI.printMessage("Submitted certificate request");
             CertCLI.printCertRequestInfos(cri);
 
diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java
index e6bd0d98120295ef8e798925f4e9aceb3a0d43f6..13b8c632f9b6d3fce96fb07547852bdef552873d 100644
--- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java
@@ -283,7 +283,7 @@ public class ClientCertRequestCLI extends CLI {
             System.out.println("Sending certificate request.");
         }
 
-        CertRequestInfos infos = certClient.enrollRequest(request);
+        CertRequestInfos infos = certClient.enrollRequest(request, null);
 
         MainCLI.printMessage("Submitted certificate request");
         CertCLI.printCertRequestInfos(infos);
-- 
2.4.3



More information about the Pki-devel mailing list