[Pki-devel] [PATCH] Lightweight CAs

Fraser Tweedale ftweedal at redhat.com
Wed Sep 23 10:24:08 UTC 2015


Updated patches attached.  I have addressed all items from the most
recent review** and from my IRC conversations with Ade on Wednesday.

** not all items from earlier reviews addressed yet

The subject and issuer DNs are now stored in LDAP but I have not yet
refactored the lightweight CA loading code to read them instead of
reading the DN from the cert in in the NSSDB.  This will be fixed of
necessity when implementing replication support.

Also note that patches 0048..0050 which appeared earlier have been
squashed down into 0026 and 0044.

Thanks,
Fraser
-------------- next part --------------
From 83e27ac6cd04120d5dd8012327d8166cd1882647 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] Lightweight CAs: initial support

This commit adds initial support for "lightweight CAs" - CAs that
inhabit an existing CA instance and share the request queue and
certificate database of the "top-level CA".

We initially support only sub-CAs under the top-level CA - either
direct sub-CAs or nested.  The general design will support hosting
unrelated CAs but creation or import of unrelated CAs is not yet
implemented.

Part of: https://fedorahosted.org/pki/ticket/1213
---
 base/ca/shared/conf/db.ldif                        |   5 +
 base/ca/src/com/netscape/ca/CAService.java         |  53 +-
 .../src/com/netscape/ca/CertificateAuthority.java  | 640 +++++++++++++++++++--
 base/ca/src/com/netscape/ca/SigningUnit.java       |  22 +-
 .../dogtagpki/server/ca/rest/AuthorityService.java | 287 +++++++++
 .../dogtagpki/server/ca/rest/CAApplication.java    |   3 +
 .../server/ca/rest/CertRequestService.java         |   5 +
 .../netscape/certsrv/authority/AuthorityData.java  | 118 ++++
 .../certsrv/authority/AuthorityResource.java       |  89 +++
 .../src/com/netscape/certsrv/ca/AuthorityID.java   |  38 ++
 .../netscape/certsrv/ca/CADisabledException.java   |  13 +
 .../netscape/certsrv/ca/CANotFoundException.java   |  12 +
 .../com/netscape/certsrv/ca/CATypeException.java   |  14 +
 .../src/com/netscape/certsrv/ca/ICAService.java    |  11 +-
 .../netscape/certsrv/ca/ICertificateAuthority.java |  63 ++
 .../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 +-
 .../com/netscape/cms/servlet/base/PKIService.java  |   5 +-
 .../netscape/cms/servlet/cert/CertRequestDAO.java  |   2 +-
 .../cms/servlet/cert/EnrollmentProcessor.java      |  15 +-
 .../cms/servlet/cert/RequestProcessor.java         |  36 +-
 .../com/netscape/cms/servlet/csadmin/CertUtil.java |  38 +-
 base/server/share/conf/schema-authority.ldif       |   8 +
 base/server/share/conf/schema.ldif                 |  13 +
 29 files changed, 1440 insertions(+), 113 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/CADisabledException.java
 create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java
 create mode 100644 base/common/src/com/netscape/certsrv/ca/CATypeException.java
 create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java
 create mode 100644 base/server/share/conf/schema-authority.ldif

diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif
index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..704b8d11be7dcffd7d57fb3ec90c11f3c0ef9cbc 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=authorities,ou=ca,{rootSuffix}
+objectClass: top
+objectClass: organizationalUnit
+ou: authorities
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..90aeba6e8e695e0dee9cbbfad69ccd2349009224 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,15 @@ 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.LDAPModification;
+import netscape.ldap.LDAPModificationSet;
+import netscape.ldap.LDAPSearchResults;
+
 import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.asn1.ASN1Util;
 import org.mozilla.jss.asn1.GeneralizedTime;
@@ -60,6 +74,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 +90,22 @@ 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.CADisabledException;
+import com.netscape.certsrv.ca.CANotFoundException;
+import com.netscape.certsrv.ca.CATypeException;
 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 +120,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 +132,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 +150,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 +166,13 @@ 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 boolean authorityEnabled = true;
+
     protected ISubsystem mOwner = null;
     protected IConfigStore mConfig = null;
     protected ILogger mLogger = CMS.getLogger();
@@ -234,6 +271,41 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      * Constructs a CA subsystem.
      */
     public CertificateAuthority() {
+        topCA = this;
+    }
+
+    /**
+     * Construct and initialise a sub-CA
+     */
+    private CertificateAuthority(
+            CertificateAuthority topCA,
+            AuthorityID aid,
+            AuthorityID parentAID,
+            String signingKeyNickname,
+            String authorityDescription,
+            boolean authorityEnabled
+            ) throws EBaseException {
+        setId(topCA.getId());
+        this.topCA = topCA;
+        this.authorityID = aid;
+        this.authorityParentID = parentAID;
+        this.authorityDescription = authorityDescription;
+        this.authorityEnabled = authorityEnabled;
+        mNickname = signingKeyNickname;
+        init(topCA.mOwner, topCA.mConfig);
+    }
+
+    private boolean isTopCA() {
+        return topCA == this;
+    }
+
+    private void ensureEnabled() throws CADisabledException {
+        if (!authorityEnabled)
+            throw new CADisabledException("Authority is disabled");
+    }
+
+    public boolean getAuthorityEnabled() {
+        return authorityEnabled;
     }
 
     /**
@@ -334,8 +406,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 +444,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 +484,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             // being functional.
             initCRL();
 
+            if (isTopCA())
+                loadLightweightCAs();
+
         } catch (EBaseException e) {
             if (CMS.isPreOpMode())
                 return;
@@ -420,6 +495,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 +646,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 +658,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();
@@ -967,6 +1072,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     public X509CRLImpl sign(X509CRLImpl crl, String algname)
             throws EBaseException {
+        ensureEnabled();
         X509CRLImpl signedcrl = null;
 
         IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats");
@@ -1039,6 +1145,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     public X509CertImpl sign(X509CertInfo certInfo, String algname)
             throws EBaseException {
+        ensureEnabled();
 
         X509CertImpl signedcert = null;
 
@@ -1123,6 +1230,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     public byte[] sign(byte[] data, String algname)
             throws EBaseException {
+        ensureEnabled();
         return mSigningUnit.sign(data, algname);
     }
 
@@ -1228,13 +1336,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 +1412,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 +1551,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 +1584,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 +1604,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 +1627,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     private void initPublish()
             throws EBaseException {
+        if (!isTopCA()) {
+            mPublisherProcessor = topCA.mPublisherProcessor;
+            return;
+        }
+
         IConfigStore c = null;
 
         try {
@@ -1676,6 +1795,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 +1862,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 +1932,110 @@ 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 loadLightweightCAs() throws EBaseException {
+        ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadLightweightCAs");
+        dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+        LDAPConnection conn = dbFactory.getConn();
+
+        String searchDN = "ou=authorities,ou=" + getId()
+            + "," + getDBSubsystem().getBaseDN();
+        LDAPSearchResults results = null;
+        boolean foundTopCA = false;
+        boolean haveLightweightCAsContainer = true;
+        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 nickAttr = entry.getAttribute("authorityKeyNickname");
+                LDAPAttribute dnAttr = entry.getAttribute("authorityDN");
+                LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID");
+                LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN");
+
+                if (aidAttr == null || nickAttr == null || dnAttr == null)
+                    throw new ECAException("Malformed authority object; required attribute(s) missing: " + entry.getDN());
+
+                AuthorityID aid = new AuthorityID((String)
+                    aidAttr.getStringValues().nextElement());
+
+                X500Name dn = null;
+                try {
+                    dn = new X500Name((String) dnAttr.getStringValues().nextElement());
+                } catch (IOException e) {
+                    throw new ECAException("Malformed authority object; invalid authorityDN: " + entry.getDN());
+                }
+
+                String desc = null;
+                LDAPAttribute descAttr = entry.getAttribute("description");
+                if (descAttr != null)
+                    desc = (String) descAttr.getStringValues().nextElement();
+
+                if (dn.equals(mName)) {
+                    foundTopCA = true;
+                    this.authorityID = aid;
+                    this.authorityDescription = desc;
+                    caMap.put(aid, this);
+                    continue;
+                }
+
+                X500Name parentDN = null;
+                if (parentDNAttr != null) {
+                    try {
+                        dn = new X500Name((String) parentDNAttr.getStringValues().nextElement());
+                    } catch (IOException e) {
+                        throw new ECAException("Malformed authority object; invalid authorityParentDN: " + entry.getDN());
+                    }
+                }
+
+                String keyNick = (String) nickAttr.getStringValues().nextElement();
+                AuthorityID parentAID = null;
+                if (parentAIDAttr != null)
+                    parentAID = new AuthorityID((String)
+                        parentAIDAttr.getStringValues().nextElement());
+
+                boolean enabled = true;
+                LDAPAttribute enabledAttr = entry.getAttribute("authorityEnabled");
+                if (enabledAttr != null) {
+                    String enabledString = (String)
+                        enabledAttr.getStringValues().nextElement();
+                    enabled = enabledString.equalsIgnoreCase("TRUE");
+                }
+
+                CertificateAuthority subCA = new CertificateAuthority(
+                    this, aid, parentAID, keyNick, desc, enabled);
+                caMap.put(aid, subCA);
+            }
+        } catch (LDAPException e) {
+            if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) {
+                CMS.debug(
+                    "Missing lightweight CAs container '" + searchDN
+                    + "'.  Disabling lightweight CAs.");
+                haveLightweightCAsContainer = false;
+            } else {
+                throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e);
+            }
+        } finally {
+            dbFactory.returnConn(conn);
+            dbFactory.reset();
+        }
+
+        if (haveLightweightCAsContainer && !foundTopCA) {
+            CMS.debug("loadLightweightCAs: no entry for top-level CA");
+            CMS.debug("loadLightweightCAs: adding entry for top-level CA");
+            AuthorityID aid = addTopCAEntry();
+            this.authorityID = aid;
+            caMap.put(aid, this);
+        }
+    }
+
     public String getOfficialName() {
         return OFFICIAL_NAME;
     }
@@ -1960,6 +2197,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
     }
 
     private BasicOCSPResponse sign(ResponseData rd) throws EBaseException {
+        ensureEnabled();
         try (DerOutputStream out = new DerOutputStream()) {
             DerOutputStream tmp = new DerOutputStream();
 
@@ -2083,4 +2321,294 @@ 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 ICertificateAuthority getCA(X500Name dn) {
+        for (ICertificateAuthority ca : getCAs()) {
+            if (ca.getX500Name().equals(dn))
+                return ca;
+        }
+        return null;
+    }
+
+    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);
+        String parentDNString = null;
+        try {
+            parentDNString = mName.toLdapDNString();
+        } catch (IOException e) {
+            throw new EBaseException("Failed to convert issuer DN to string: " + e);
+        }
+
+        LDAPAttribute[] attrs = {
+            new LDAPAttribute("objectclass", "authority"),
+            new LDAPAttribute("cn", aidString),
+            new LDAPAttribute("authorityID", aidString),
+            new LDAPAttribute("authorityKeyNickname", nickname),
+            new LDAPAttribute("authorityEnabled", "TRUE"),
+            new LDAPAttribute("authorityDN", subjectDN),
+            new LDAPAttribute("authorityParentDN", parentDNString)
+        };
+        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(
+            topCA, aid, this.authorityID, nickname, description, true);
+    }
+
+    private AuthorityID addTopCAEntry() throws EBaseException {
+        if (!isTopCA())
+            throw new EBaseException("Can only invoke from top-level CA");
+
+        // generate authority ID
+        AuthorityID aid = new AuthorityID();
+        String aidString = aid.toString();
+
+        // build database entry
+        String dn = "cn=" + aidString + ",ou=authorities,ou="
+            + getId() + "," + getDBSubsystem().getBaseDN();
+        String dnString = null;
+        try {
+            dnString = mName.toLdapDNString();
+        } catch (IOException e) {
+            throw new EBaseException("Failed to convert issuer DN to string: " + e);
+        }
+
+        LDAPAttribute[] attrs = {
+            new LDAPAttribute("objectclass", "authority"),
+            new LDAPAttribute("cn", aidString),
+            new LDAPAttribute("authorityID", aidString),
+            new LDAPAttribute("authorityKeyNickname", getNickname()),
+            new LDAPAttribute("authorityEnabled", "TRUE"),
+            new LDAPAttribute("authorityDN", dnString),
+            new LDAPAttribute("description", "Top-level (host) CA")
+        };
+        LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs);
+        LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet);
+
+        // connect to database
+        ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("addTopCAEntry");
+        dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+        LDAPConnection conn = dbFactory.getConn();
+
+        try {
+            conn.add(ldapEntry);
+        } catch (LDAPException e) {
+            throw new ELdapException("Error adding top-level CA entry to database: " + e);
+        } finally {
+            dbFactory.returnConn(conn);
+            dbFactory.reset();
+        }
+
+        return aid;
+    }
+
+    /**
+     * Update lightweight authority attributes.
+     *
+     * Pass null values to exclude an attribute from the update.
+     *
+     * If a passed value matches the current value, it is excluded
+     * from the update.
+     *
+     * To remove optional string values, pass the empty string.
+     */
+    public void modifyAuthority(Boolean enabled, String desc)
+            throws EBaseException {
+        if (isTopCA() && enabled != null && !enabled)
+            throw new CATypeException("Cannot disable the top-level CA");
+
+        LDAPModificationSet mods = new LDAPModificationSet();
+
+        boolean nextEnabled = authorityEnabled;
+        if (enabled != null && enabled.booleanValue() != authorityEnabled) {
+            mods.add(
+                LDAPModification.REPLACE,
+                new LDAPAttribute("authorityEnabled", enabled ? "TRUE" : "FALSE"));
+            nextEnabled = enabled;
+        }
+
+        String nextDesc = authorityDescription;
+        if (desc != null) {
+            if (!desc.isEmpty() && authorityDescription != null
+                    && !desc.equals(authorityDescription)) {
+                mods.add(
+                    LDAPModification.REPLACE,
+                    new LDAPAttribute("description", desc));
+                nextDesc = desc;
+            } else if (desc.isEmpty() && authorityDescription != null) {
+                mods.add(
+                    LDAPModification.DELETE,
+                    new LDAPAttribute("description", authorityDescription));
+                nextDesc = null;
+            } else if (!desc.isEmpty() && authorityDescription == null) {
+                mods.add(
+                    LDAPModification.ADD,
+                    new LDAPAttribute("description", desc));
+                nextDesc = desc;
+            }
+        }
+
+        if (mods.size() > 0) {
+            String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou="
+                + getId() + "," + getDBSubsystem().getBaseDN();
+
+            // connect to database
+            ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority");
+            dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+            LDAPConnection conn = dbFactory.getConn();
+            try {
+                conn.modify(dn, mods);
+            } catch (LDAPException e) {
+                throw new EBaseException("Error adding sub-CA entry to database: " + e);
+            } finally {
+                dbFactory.returnConn(conn);
+                dbFactory.reset();
+            }
+
+            // update was successful; update CA's state
+            authorityEnabled = nextEnabled;
+            authorityDescription = nextDesc;
+        }
+    }
+
 }
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..e266ca98a1b8bdac7694ce7fc82864023303670c
--- /dev/null
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
@@ -0,0 +1,287 @@
+//--- 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.cert.CertificateEncodingException;
+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.ForbiddenException;
+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.CATypeException;
+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;
+import com.netscape.cmsutil.util.Utils;
+
+/**
+ * @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 getCert(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");
+
+        try {
+            return Response.ok(ca.getCaX509Cert().getEncoded()).build();
+        } catch (CertificateEncodingException e) {
+            // this really is a 500 Internal Server Error
+            throw new PKIException("Error encoding certificate: " + e);
+        }
+    }
+
+    @Override
+    public Response getCertPEM(String aidString) {
+        byte[] der = (byte[]) getCert(aidString).getEntity();
+        return Response.ok(toPem("CERTIFICATE", der)).build();
+    }
+
+    @Override
+    public Response getChain(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");
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+            ca.getCACertChain().encode(out);
+        } catch (IOException e) {
+            throw new PKIException("Error encoding certificate chain: " + e);
+        }
+
+        return Response.ok(out.toByteArray()).build();
+    }
+
+    @Override
+    public Response getChainPEM(String aidString) {
+        byte[] der = (byte[]) getCert(aidString).getEntity();
+        return Response.ok(toPem("PKCS7", der)).build();
+    }
+
+    @Override
+    public Response createCA(AuthorityData data) {
+        String parentAIDString = data.getParentID();
+        AuthorityID parentAID = null;
+        try {
+            parentAID = new AuthorityID(parentAIDString);
+        } catch (IllegalArgumentException e) {
+            throw new BadRequestException("Bad Authority 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 CA: " + e.toString());
+        }
+    }
+
+    @Override
+    public Response modifyCA(String aidString, AuthorityData data) {
+        AuthorityID aid = null;
+        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");
+
+        try {
+            ca.modifyAuthority(data.getEnabled(), data.getDescription());
+            return createOKResponse(readAuthorityData(ca));
+        } catch (CATypeException e) {
+            throw new ForbiddenException(e.toString());
+        } catch (EBaseException e) {
+            CMS.debug(e);
+            throw new PKIException("Error modifying authority: " + e.toString());
+        }
+    }
+
+    @Override
+    public Response enableCA(String aidString) {
+        return modifyCA(
+            aidString,
+            new AuthorityData(null, null, null, true, null));
+    }
+
+    @Override
+    public Response disableCA(String aidString) {
+        return modifyCA(
+            aidString,
+            new AuthorityData(null, null, null, false, null));
+    }
+
+    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.getAuthorityEnabled(),
+            ca.getAuthorityDescription()
+        );
+    }
+
+    private String toPem(String name, byte[] data) {
+        return "-----BEGIN " + name + "-----\n" +
+                Utils.base64encode(data) +
+                "-----END " + name + "-----\n";
+    }
+
+    /* 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/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
index 95f1f4c20086ddb45846f65b1db157bff238708a..1da1ce1713541e52164e9e8fbcbf39ca2332540d 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
@@ -38,9 +38,11 @@ import com.netscape.certsrv.authentication.EAuthException;
 import com.netscape.certsrv.authorization.EAuthzException;
 import com.netscape.certsrv.base.BadRequestDataException;
 import com.netscape.certsrv.base.BadRequestException;
+import com.netscape.certsrv.base.ConflictingOperationException;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.base.UnauthorizedException;
+import com.netscape.certsrv.ca.CADisabledException;
 import com.netscape.certsrv.cert.CertEnrollmentRequest;
 import com.netscape.certsrv.cert.CertRequestInfo;
 import com.netscape.certsrv.cert.CertRequestInfos;
@@ -210,6 +212,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc
         } catch (BadRequestDataException e) {
             CMS.debug("changeRequestState: bad request data: " + e);
             throw new BadRequestException(e.toString());
+        } catch (CADisabledException e) {
+            CMS.debug("changeRequestState: CA disabled: " + e);
+            throw new ConflictingOperationException(e.toString());
         } catch (EPropertyException e) {
             CMS.debug("changeRequestState: execution error " + e);
             throw new PKIException(CMS.getUserMessage(getLocale(headers),
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..13c0cecab03816eee8f77409902a0ee17ec50f80
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java
@@ -0,0 +1,118 @@
+// --- 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 Boolean enabled;
+
+    public Boolean getEnabled() {
+        return enabled;
+    }
+
+
+    @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,
+            Boolean enabled, String description) {
+        this.dn = dn;
+        this.aid = aid;
+        this.parentAID = parentAID;
+        this.enabled = enabled;
+        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..74a5a39070fe325dba38a809b8c02168ab892f01
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java
@@ -0,0 +1,89 @@
+package com.netscape.certsrv.authority;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+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);
+
+    @GET
+    @Path("{id}/cert")
+    @Produces("application/pkix-cert")
+    @ClientResponseType(entityType=byte[].class)
+    public Response getCert(@PathParam("id") String caIDString);
+
+    @GET
+    @Path("{id}/cert")
+    @Produces("application/x-pem-file")
+    @ClientResponseType(entityType=String.class)
+    public Response getCertPEM(@PathParam("id") String caIDString);
+
+    @GET
+    @Path("{id}/chain")
+    @Produces("application/pkcs7-mime")
+    @ClientResponseType(entityType=byte[].class)
+    public Response getChain(@PathParam("id") String caIDString);
+
+    @GET
+    @Path("{id}/chain")
+    @Produces("application/x-pem-file")
+    @ClientResponseType(entityType=String.class)
+    public Response getChainPEM(@PathParam("id") String caIDString);
+
+    @POST
+    @ClientResponseType(entityType=AuthorityData.class)
+    //@ACLMapping("certs")
+    //@AuthMethodMapping("certs")
+    public Response createCA(AuthorityData data);
+
+    /**
+     * Modify a CA (supports partial updates).
+     *
+     * AuthorityID, AuthorityParentID and DN are immutable;
+     * differences in these values are ignored.
+     *
+     * Other values, if null, are ignored, otherwise they are
+     * set to the new value.  To remove the description, use an
+     * empty string.
+     */
+    @PUT
+    @Path("{id}")
+    @ClientResponseType(entityType=AuthorityData.class)
+    public Response modifyCA(
+        @PathParam("id") String caIDString,
+        AuthorityData data);
+
+    @POST
+    @Path("{id}/enable")
+    @ClientResponseType(entityType=AuthorityData.class)
+    public Response enableCA(@PathParam("id") String caIDString);
+
+    @POST
+    @Path("{id}/disable")
+    @ClientResponseType(entityType=AuthorityData.class)
+    public Response disableCA(@PathParam("id") String caIDString);
+
+}
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..854b56d79da38bb7895951b096523dd7385a9e1c
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java
@@ -0,0 +1,38 @@
+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) {
+        if (s == null)
+            throw new IllegalArgumentException("null AuthorityID string");
+        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/CADisabledException.java b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java
new file mode 100644
index 0000000000000000000000000000000000000000..df5fdf809f716202c38287e670a88e55eb93ce75
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java
@@ -0,0 +1,13 @@
+package com.netscape.certsrv.ca;
+
+/**
+ * Exception to throw when a (sub-)CA cannot perform an operation
+ * because it is disabled.
+ */
+public class CADisabledException extends ECAException {
+
+    public CADisabledException(String msgFormat) {
+        super(msgFormat);
+    }
+
+}
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/CATypeException.java b/base/common/src/com/netscape/certsrv/ca/CATypeException.java
new file mode 100644
index 0000000000000000000000000000000000000000..b51925127356737bea8f84271d6fc7ceb845f4b5
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/CATypeException.java
@@ -0,0 +1,14 @@
+package com.netscape.certsrv.ca;
+
+/**
+ * Exception to throw when an operation cannot be completed
+ * because the CA is the wrong type (e.g., an operation that
+ * only applies to lightweight CAs).
+ */
+public class CATypeException extends ECAException {
+
+    public CATypeException(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..d63f92aa2b2b9040fb14dac8cced4ecc8a5039ec 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,66 @@ 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 boolean getAuthorityEnabled();
+
+    /**
+     * 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);
+
+    /**
+     * Get the CA by DN.  Returns null if CA not found.
+     */
+    public ICertificateAuthority getCA(X500Name dn);
+
+    /**
+     * 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;
+
+    /**
+     * Update authority configurables.
+     *
+     * @param enabled Whether CA is enabled or disabled
+     * @param desc Description; null or empty removes it
+     */
+    public void modifyAuthority(Boolean enabled, 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/base/PKIService.java b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java
index 4ebf075cb709e813fb6a919c507e9847455e70b2..fe77fd567922a49641938cde99d533c091398b75 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java
@@ -56,7 +56,10 @@ public class PKIService {
     public static List<MediaType> MESSAGE_FORMATS = Arrays.asList(
             MediaType.APPLICATION_XML_TYPE,
             MediaType.APPLICATION_JSON_TYPE,
-            MediaType.APPLICATION_FORM_URLENCODED_TYPE
+            MediaType.APPLICATION_FORM_URLENCODED_TYPE,
+            MediaType.valueOf("application/pkix-cert"),
+            MediaType.valueOf("application/pkcs7-mime"),
+            MediaType.valueOf("application/x-pem-file")
     );
 
     public final static int MIN_FILTER_LENGTH = 3;
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java
index c94ee14961ef39681a53f506b24e4ca5ab06a27e..27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java
@@ -175,7 +175,7 @@ public class CertRequestDAO extends CMSRequestDAO {
             results = processor.processRenewal(data, request);
         } else {
             EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale);
-            results = processor.processEnrollment(data, request);
+            results = processor.processEnrollment(data, request, null);
         }
 
         IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS);
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..e5b9a14df99f29da8ad5c4f76c088c98ff766540 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,8 @@ 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.profile.IEnrollProfile;
 import com.netscape.certsrv.profile.IProfile;
 import com.netscape.certsrv.profile.IProfileAuthenticator;
 import com.netscape.certsrv.profile.IProfileContext;
@@ -98,7 +100,7 @@ public class EnrollmentProcessor extends CertProcessor {
         }
 
         CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale);
-        return processEnrollment(data, cmsReq.getHttpReq());
+        return processEnrollment(data, cmsReq.getHttpReq(), null);
     }
 
     /**
@@ -118,8 +120,11 @@ public class EnrollmentProcessor extends CertProcessor {
      * @param cmsReq the object holding the request and response information
      * @exception EBaseException an error has occurred
      */
-    public HashMap<String, Object> processEnrollment(CertEnrollmentRequest data, HttpServletRequest request)
-            throws EBaseException {
+    public HashMap<String, Object> processEnrollment(
+            CertEnrollmentRequest data,
+            HttpServletRequest request,
+            AuthorityID aid)
+        throws EBaseException {
 
         try {
             if (CMS.debugOn()) {
@@ -146,6 +151,10 @@ public class EnrollmentProcessor extends CertProcessor {
             }
 
             IProfileContext ctx = profile.createContext();
+
+            if (aid != null)
+                ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aid.toString());
+
             CMS.debug("EnrollmentProcessor: set Inputs into profile Context");
             setInputsIntoContext(data, profile, ctx);
 
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java
index 2826f477e358a5e16657e985d7f13079cdb14a33..ca2d6f6739906aad06289708a05f0983155b72e8 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java
@@ -36,6 +36,10 @@ import com.netscape.certsrv.base.BadRequestException;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.EPropertyNotFound;
 import com.netscape.certsrv.base.IConfigStore;
+import com.netscape.certsrv.ca.AuthorityID;
+import com.netscape.certsrv.ca.CADisabledException;
+import com.netscape.certsrv.ca.CANotFoundException;
+import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.cert.CertReviewResponse;
 import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.profile.EDeferException;
@@ -327,6 +331,31 @@ public class RequestProcessor extends CertProcessor {
     }
 
     /**
+     * Ensure validity of AuthorityID and that CA exists and is enabled.
+     */
+    private void ensureCAEnabled(String aidString) throws EBaseException {
+        AuthorityID aid = null;
+        try {
+            aid = new AuthorityID(aidString);
+        } catch (IllegalArgumentException e) {
+            // this shouldn't happen because request was already accepted
+            throw new BadRequestDataException("Invalid AuthorityID in request data");
+        }
+        ICertificateAuthority ca = (ICertificateAuthority)
+            CMS.getSubsystem("ca");
+        if (ca == null)
+            // this shouldn't happen
+            throw new CANotFoundException("Could not get top-level CA");  // shouldn't happen
+        ca = ca.getCA(aid);
+        if (ca == null)
+            // this shouldn't happen because request was already accepted
+            throw new CANotFoundException("Unknown CA: " + aidString);
+        if (!ca.getAuthorityEnabled())
+            // authority was disabled after request was accepted
+            throw new CADisabledException("CA '" + aidString + "' is disabled");
+    }
+
+    /**
      * Approve request
      * <P>
      *
@@ -346,11 +375,16 @@ public class RequestProcessor extends CertProcessor {
      *                occurred
      */
     private void approveRequest(IRequest req, CertReviewResponse data, IProfile profile, Locale locale)
-            throws EProfileException {
+            throws EBaseException {
         String auditMessage = null;
         String auditSubjectID = auditSubjectID();
         String auditRequesterID = auditRequesterID(req);
 
+        // ensure target CA is enabled
+        String aidString = req.getExtDataInString(IEnrollProfile.REQUEST_AUTHORITY_ID);
+        if (aidString != null)
+            ensureCAEnabled(aidString);
+
         try {
             profile.execute(req);
             req.setRequestStatus(RequestStatus.COMPLETE);
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-authority.ldif b/base/server/share/conf/schema-authority.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..7d261f18fbc9475983bf93b1cddcc184d7f9d178
--- /dev/null
+++ b/base/server/share/conf/schema-authority.ldif
@@ -0,0 +1,8 @@
+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' )
+attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' )
+attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' )
+attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' )
+objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' )
diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif
index 475758c5d66bf681e589995505a561bf4e4c40ef..a15601ae7a362635bc398b92b9bfda1c72f0dfc8 100644
--- a/base/server/share/conf/schema.ldif
+++ b/base/server/share/conf/schema.ldif
@@ -667,3 +667,16 @@ 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' )
+attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' )
+attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' )
+attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' )
+-
+add: objectClasses
+objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' )
-- 
2.4.3

-------------- next part --------------
From c16c7051f8c178efe711d0b27ef8c02266b74ed4 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] Lightweight CAs: add ca-authority CLI

Add CLI commands for creating, listing and showing lightweight CAs.

Part of: https://fedorahosted.org/pki/ticket/1213
---
 .../certsrv/authority/AuthorityClient.java         | 62 +++++++++++++++
 .../src/com/netscape/certsrv/ca/CAClient.java      |  3 +-
 .../netscape/cmstools/authority/AuthorityCLI.java  | 61 +++++++++++++++
 .../cmstools/authority/AuthorityCreateCLI.java     | 90 ++++++++++++++++++++++
 .../cmstools/authority/AuthorityDisableCLI.java    | 59 ++++++++++++++
 .../cmstools/authority/AuthorityEnableCLI.java     | 59 ++++++++++++++
 .../cmstools/authority/AuthorityFindCLI.java       | 62 +++++++++++++++
 .../cmstools/authority/AuthorityShowCLI.java       | 57 ++++++++++++++
 .../src/com/netscape/cmstools/cli/CACLI.java       |  2 +
 9 files changed, 454 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/AuthorityDisableCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.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..86de3352e2424211125c146edf759481448a2694
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java
@@ -0,0 +1,62 @@
+//--- 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);
+    }
+
+    public AuthorityData modifyCA(AuthorityData data) {
+        Response response = proxy.modifyCA(data.getID(), 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..3aacd86c76061de22f8fff2a1cda7f5b76b97350
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
@@ -0,0 +1,61 @@
+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));
+        addModule(new AuthorityDisableCLI(this));
+        addModule(new AuthorityEnableCLI(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("  Authority DN: " + data.getDN());
+        System.out.println("  ID:           " + data.getID());
+        String parentAID = data.getParentID();
+        if (parentAID != null)
+            System.out.println("  Parent DN:    " + data.getParentID());
+        System.out.println("  Enabled:      " + data.getEnabled());
+        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..ec27132079814d47bd2d54383d114c089baae931
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java
@@ -0,0 +1,90 @@
+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);
+            }
+        } else {
+            System.err.println("Must specify parent authority");
+            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, true /* enabled */, desc);
+        AuthorityData newData = authorityCLI.authorityClient.createCA(data);
+        AuthorityCLI.printAuthorityData(newData);
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..56bb28e3431dbebc0ecc3faa4738631dcadf8dbe
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java
@@ -0,0 +1,59 @@
+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 AuthorityDisableCLI extends CLI {
+
+    public AuthorityCLI authorityCLI;
+
+    public AuthorityDisableCLI(AuthorityCLI authorityCLI) {
+        super("disable", "Disable 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);
+        }
+
+        AuthorityData data = new AuthorityData(
+            null, cmdArgs[0], null, false, null);
+        data = authorityCLI.authorityClient.modifyCA(data);
+        AuthorityCLI.printAuthorityData(data);
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb67665b73b4fac3d9469ec766161abea054df27
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java
@@ -0,0 +1,59 @@
+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 AuthorityEnableCLI extends CLI {
+
+    public AuthorityCLI authorityCLI;
+
+    public AuthorityEnableCLI(AuthorityCLI authorityCLI) {
+        super("enable", "Enable 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);
+        }
+
+        AuthorityData data = new AuthorityData(
+            null, cmdArgs[0], null, true, null);
+        data = authorityCLI.authorityClient.modifyCA(data);
+        AuthorityCLI.printAuthorityData(data);
+    }
+
+}
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 73f9c503229ba1a414a48fe17771e146a0d1e0b8 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] Lightweight CAs: REST cert request param to specify authority

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.

Part of: https://fedorahosted.org/pki/ticket/1213
---
 .../src/com/netscape/cms/servlet/test/CATest.java  |  4 +-
 .../server/ca/rest/CertRequestService.java         | 41 ++++++++++++++++++--
 .../src/com/netscape/certsrv/cert/CertClient.java  | 16 +++++++-
 .../netscape/certsrv/cert/CertRequestResource.java |  5 ++-
 .../cmstools/cert/CertRequestSubmitCLI.java        | 44 +++++++++++++++++++++-
 .../cmstools/client/ClientCertRequestCLI.java      |  2 +-
 .../netscape/cms/servlet/cert/CertRequestDAO.java  | 12 ++++--
 7 files changed, 111 insertions(+), 13 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 1da1ce1713541e52164e9e8fbcbf39ca2332540d..7cb4ff71e18b6e29bf55c11dc99bbfb9b83dd60f 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
@@ -18,6 +18,7 @@
 
 package org.dogtagpki.server.ca.rest;
 
+import java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -41,8 +42,11 @@ import com.netscape.certsrv.base.BadRequestException;
 import com.netscape.certsrv.base.ConflictingOperationException;
 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.AuthorityID;
 import com.netscape.certsrv.ca.CADisabledException;
+import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.cert.CertEnrollmentRequest;
 import com.netscape.certsrv.cert.CertRequestInfo;
 import com.netscape.certsrv.cert.CertRequestInfos;
@@ -63,6 +67,7 @@ import com.netscape.certsrv.request.RequestNotFoundException;
 import com.netscape.cms.servlet.base.PKIService;
 import com.netscape.cms.servlet.cert.CertRequestDAO;
 import com.netscape.cmsutil.ldap.LDAPUtil;
+import netscape.security.x509.X500Name;
 
 /**
  * @author alee
@@ -115,13 +120,43 @@ public class CertRequestService extends PKIService implements CertRequestResourc
     }
 
     @Override
-    public Response enrollCert(CertEnrollmentRequest data) {
-
+    public Response enrollCert(CertEnrollmentRequest data, String aidString, String adnString) {
         if (data == null) {
             CMS.debug("enrollCert: data is null");
             throw new BadRequestException("Unable to create enrollment reequest: Invalid input data");
         }
 
+        if (aidString != null && adnString != null)
+            throw new BadRequestException("Cannot provide both issuer-id and issuer-dn");
+
+        AuthorityID aid = null;
+        ICertificateAuthority ca = (ICertificateAuthority)
+            CMS.getSubsystem(CMS.SUBSYSTEM_CA);
+        if (aidString != null) {
+            try {
+                aid = new AuthorityID(aidString);
+            } catch (IllegalArgumentException e) {
+                throw new BadRequestException("invalid AuthorityID: " + aidString);
+            }
+            ca = ca.getCA(aid);
+            if (ca == null)
+                throw new ResourceNotFoundException("CA not found: " + aidString);
+        }
+        if (adnString != null) {
+            X500Name adn = null;
+            try {
+                adn = new X500Name(adnString);
+            } catch (IOException e) {
+                throw new BadRequestException("invalid DN: " + adnString);
+            }
+            ca = ca.getCA(adn);
+            if (ca == null)
+                throw new ResourceNotFoundException("CA not found: " + adnString);
+            aid = ca.getAuthorityID();
+        }
+        if (!ca.getAuthorityEnabled())
+            throw new ConflictingOperationException("CA not enabled: " + aid.toString());
+
         data.setRemoteHost(servletRequest.getRemoteHost());
         data.setRemoteAddr(servletRequest.getRemoteAddr());
 
@@ -129,7 +164,7 @@ public class CertRequestService extends PKIService implements CertRequestResourc
 
         CertRequestInfos infos;
         try {
-            infos = dao.submitRequest(data, servletRequest, uriInfo, getLocale(headers));
+            infos = dao.submitRequest(aid, data, servletRequest, uriInfo, getLocale(headers));
         } catch (EAuthException e) {
             CMS.debug("enrollCert: authentication failed: " + e);
             throw new UnauthorizedException(e.toString());
diff --git a/base/common/src/com/netscape/certsrv/cert/CertClient.java b/base/common/src/com/netscape/certsrv/cert/CertClient.java
index 42b04b7021f0063894c340c177915d799b621ddd..1d4ccd2cf7e83a8ed3b33253b1416110d5504125 100644
--- a/base/common/src/com/netscape/certsrv/cert/CertClient.java
+++ b/base/common/src/com/netscape/certsrv/cert/CertClient.java
@@ -17,16 +17,19 @@
 //--- END COPYRIGHT BLOCK ---
 package com.netscape.certsrv.cert;
 
+import java.io.IOException;
 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;
 import com.netscape.certsrv.dbs.certdb.CertId;
 import com.netscape.certsrv.profile.ProfileDataInfos;
 import com.netscape.certsrv.request.RequestId;
+import netscape.security.x509.X500Name;
 
 /**
  * @author Endi S. Dewata
@@ -85,8 +88,17 @@ 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, X500Name adn) {
+        String aidString = aid != null ? aid.toString() : null;
+        String adnString = null;
+        if (adn != null) {
+            try {
+                adnString = adn.toLdapDNString();
+            } catch (IOException e) {
+            }
+        }
+        Response response = certRequestClient.enrollCert(data, aidString, adnString);
         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..493f6f53a5a5e30804532305b199d44a66eecd24 100644
--- a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java
+++ b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java
@@ -37,7 +37,10 @@ public interface CertRequestResource {
     @POST
     @Path("certrequests")
     @ClientResponseType(entityType=CertRequestInfos.class)
-    public Response enrollCert(CertEnrollmentRequest data);
+    public Response enrollCert(
+        CertEnrollmentRequest data,
+        @QueryParam("issuer-id") String caIDString,
+        @QueryParam("issuer-dn") String caDNString);
 
     /**
      * 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..e6b59a5d7fcaced100699ea067a8f95b992722f3 100644
--- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java
@@ -2,18 +2,22 @@ package com.netscape.cmstools.cert;
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.Arrays;
 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;
 import com.netscape.cmstools.cli.MainCLI;
+import netscape.security.x509.X500Name;
 
 public class CertRequestSubmitCLI extends CLI {
 
@@ -22,6 +26,14 @@ public class CertRequestSubmitCLI extends CLI {
     public CertRequestSubmitCLI(CertCLI certCLI) {
         super("request-submit", "Submit certificate request", certCLI);
         this.certCLI = certCLI;
+
+        Option optAID = new Option(null, "issuer-id", true, "Authority ID (top-level CA if omitted)");
+        optAID.setArgName("id");
+        options.addOption(optAID);
+
+        Option optADN = new Option(null, "issuer-dn", true, "Authority DN (top-level CA if omitted)");
+        optADN.setArgName("dn");
+        options.addOption(optADN);
     }
 
     public void printHelp() {
@@ -55,9 +67,39 @@ public class CertRequestSubmitCLI extends CLI {
             System.exit(-1);
         }
 
+        AuthorityID aid = null;
+        if (cmd.hasOption("issuer-id")) {
+            String aidString = cmd.getOptionValue("issuer-id");
+            try {
+                aid = new AuthorityID(aidString);
+            } catch (IllegalArgumentException e) {
+                System.err.println("Bad AuthorityID: " + aidString);
+                printHelp();
+                System.exit(-1);
+            }
+        }
+
+        X500Name adn = null;
+        if (cmd.hasOption("issuer-dn")) {
+            String adnString = cmd.getOptionValue("issuer-dn");
+            try {
+                adn = new X500Name(adnString);
+            } catch (IOException e) {
+                System.err.println("Bad DN: " + adnString);
+                printHelp();
+                System.exit(-1);
+            }
+        }
+
+        if (aid != null && adn != null) {
+            System.err.println("--issuer-id and --issuer-dn options are mutually exclusive");
+            printHelp();
+            System.exit(-1);
+        }
+
         try {
             CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]);
-            CertRequestInfos cri = certCLI.certClient.enrollRequest(erd);
+            CertRequestInfos cri = certCLI.certClient.enrollRequest(erd, aid, adn);
             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..db71c8a0f7db4644290efb766178b76668c22377 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, null);
 
         MainCLI.printMessage("Submitted certificate request");
         CertCLI.printCertRequestInfos(infos);
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java
index 27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b..a2e4b583d318ac8412361850d91233b77a447e13 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java
@@ -30,6 +30,7 @@ import javax.ws.rs.core.UriInfo;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.cert.CertEnrollmentRequest;
 import com.netscape.certsrv.cert.CertRequestInfo;
@@ -164,8 +165,13 @@ public class CertRequestDAO extends CMSRequestDAO {
      * @throws EBaseException
      * @throws ServletException
      */
-    public CertRequestInfos submitRequest(CertEnrollmentRequest data, HttpServletRequest request, UriInfo uriInfo,
-            Locale locale) throws EBaseException {
+    public CertRequestInfos submitRequest(
+            AuthorityID aid,
+            CertEnrollmentRequest data,
+            HttpServletRequest request,
+            UriInfo uriInfo,
+            Locale locale)
+        throws EBaseException {
 
         CertRequestInfos ret = new CertRequestInfos();
 
@@ -175,7 +181,7 @@ public class CertRequestDAO extends CMSRequestDAO {
             results = processor.processRenewal(data, request);
         } else {
             EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale);
-            results = processor.processEnrollment(data, request, null);
+            results = processor.processEnrollment(data, request, aid);
         }
 
         IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS);
-- 
2.4.3



More information about the Pki-devel mailing list