[Pki-devel] [PATCH] Lightweight CAs

Fraser Tweedale ftweedal at redhat.com
Fri Sep 25 13:30:12 UTC 2015


Latest patches attached.  Most issued addressed; see inline for
comments and ticket URLs for deferred items.

There is a problem with allowing authority DNs to be reused - when
adding the cert to the NSSDB, despite what nickname you tell it to
you, it will put the cert under the nickname of the existing cert
with that subject DN.  Thus when you go to find the cert by
nickname, it cannot locate it.  Failure ensues.  This is possibly a
bug in NSS (it's certainly surprising), but I need more time to
analyse it.

I've attached an experimental (builds, but as yet completely
untested with high chance of brokens) patch on top of my current
patches that switches to looking up the cert by issuer DN and
serial.  I need to consider implications of this switch including
for renewal in replicated environments.  It might not be the right
approach but afaict it is the only other way CryptoManager gives you
to look up a cert.

Ideally we discover that NSS/JSS is doing the wrong thing with what
it is doing with nicknames and we can get a fix there are move on
with life.

Anyhow, other comments inline.

Thanks,
Fraser

On Thu, Sep 24, 2015 at 06:26:39PM -0500, Endi Sukma Dewata wrote:
> Some comments:
> 
> 1. Right now the create & modify operations over non-secure URL will fail:
> 
> $ pki -d ~/.dogtag/pki-tomcat/ca/alias -c Secret123 -n caadmin
> ca-authority-create o=test --parent 85a2c5c2-869d-467c-9adf-dcc34367e836
> ForbiddenException: No user principal provided.
> 
> It works with the secure URL:
> 
> $ pki -U https://$HOSTNAME:8443 -d ~/.dogtag/pki-tomcat/ca/alias -u caadmin
> -w Secret123 ca-authority-create o=test --parent
> 85a2c5c2-869d-467c-9adf-dcc34367e836
>   Authority DN: O=test
>   ID: 14004c0f-3531-49c2-ae7a-99f715af7cc4
>   Parent DN: 85a2c5c2-869d-467c-9adf-dcc34367e836
>   Enabled: true
> 
> This can be fixed by adding <security-constraint> into the web.xml and
> registering it in auth-method.properties.
> 
Thanks for this explanation.  Worked a treat!

> 2. The "Parent DN" field in the output above should show the DN of the
> parent authority instead of the ID. We probably should show both Parent DN
> and Parent ID.
> 
Fixed the label, filed ticket for including the Parent/Issuer DN:
https://fedorahosted.org/pki/ticket/1618

> 3. Per discussion with alee, we need a way to find the host/main CA using
> something like:
> 
> $ pki ca-authority-show --host-authority
> 
Done.

> 4. I think we also need a way to translate a DN into ID:
> 
> $ pki ca-authority-show --dn <DN>
> 
Filed ticket: https://fedorahosted.org/pki/ticket/1617

> 5. Also per discussion with alee, the authority DN should be unique only
> among active CAs. So you should be able to create a CA, disable it, then
> create another one with the same DN. If you try to enable the old CA it
> should fail. This can be implemented later.
> 
Per discussion above, implemented, but breaks your CA if you try it!

> 6. In AuthorityData.java the @XmlRootElement probably should be changed to
> "authority" for consistency. Also the following fields can be renamed
> because the "a" is redundant:
> * aid -> id
> * parentAID -> parentID
> I think the XML output will look better that way.
> 
Done.

> 7. The method description in ISigningUnit.java doesn't match the method name
> (public vs. private).
> 
Fixed; well spotted.

> I think these are not difficult to fix, and once fixed it should be
> sufficient to push as initial implementation, so consider this a conditional
> ACK (unless alee has other comments). Item #5 (or #4 too) can be implemented
> later.
> 
> I also created this page to document the CLI:
> http://pki.fedoraproject.org/wiki/PKI_CA_Authority_CLI
> Feel free to expand it further.
> 
Thanks a bunch; I will review this Monday.  This also reminds me to
spend some time updating the design pages as well - there have been
many changes!

> -- 
> Endi S. Dewata
-------------- next part --------------
From 037e62d46fa38d56c558291202b62f0e73e0b5d7 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/acl.ldif                       |   2 +
 base/ca/shared/conf/acl.properties                 |   4 +
 base/ca/shared/conf/auth-method.properties         |   1 +
 base/ca/shared/conf/db.ldif                        |   5 +
 base/ca/shared/webapps/ca/WEB-INF/web.xml          |  10 +
 base/ca/src/com/netscape/ca/CAService.java         |  53 +-
 .../src/com/netscape/ca/CertificateAuthority.java  | 682 ++++++++++++++++++---
 base/ca/src/com/netscape/ca/SigningUnit.java       |  22 +-
 .../dogtagpki/server/ca/rest/AuthorityService.java | 282 +++++++++
 .../dogtagpki/server/ca/rest/CAApplication.java    |   3 +
 .../server/ca/rest/CertRequestService.java         |   5 +
 .../netscape/certsrv/authority/AuthorityData.java  | 123 ++++
 .../certsrv/authority/AuthorityResource.java       |  96 +++
 .../src/com/netscape/certsrv/ca/AuthorityID.java   |  36 ++
 .../netscape/certsrv/ca/CADisabledException.java   |  15 +
 .../netscape/certsrv/ca/CANotFoundException.java   |  14 +
 .../com/netscape/certsrv/ca/CATypeException.java   |  16 +
 .../src/com/netscape/certsrv/ca/ICAService.java    |  11 +-
 .../netscape/certsrv/ca/ICertificateAuthority.java |  68 ++
 .../certsrv/ca/IssuerUnavailableException.java     |  15 +
 .../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      |  28 +-
 .../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 +
 33 files changed, 1507 insertions(+), 132 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/acl.ldif b/base/ca/shared/conf/acl.ldif
index 0da10939fc64dc88b32016c308fc13a1bab2d14f..54c9f1d5c64b6578de83f1b7ffdff922a69975f4 100644
--- a/base/ca/shared/conf/acl.ldif
+++ b/base/ca/shared/conf/acl.ldif
@@ -57,3 +57,5 @@ resourceACLS: certServer.ca.certs:execute:allow (execute) group="Certificate Man
 resourceACLS: certServer.ca.groups:execute:allow (execute) group="Administrators":Admins may execute group operations
 resourceACLS: certServer.ca.selftests:read,execute:allow (read,execute) group="Administrators":Only admins can access selftests.
 resourceACLS: certServer.ca.users:execute:allow (execute) group="Administrators":Admins may execute user operations
+resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities
+resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities
diff --git a/base/ca/shared/conf/acl.properties b/base/ca/shared/conf/acl.properties
index d14d1832c7cff7db055f2274f1ed30223d16cad8..f0b5b9f650ad2fc4bde531ade94347a7280d3089 100644
--- a/base/ca/shared/conf/acl.properties
+++ b/base/ca/shared/conf/acl.properties
@@ -21,3 +21,7 @@ securityDomain.installToken = certServer.securitydomain.domainxml,read
 selftests.read = certServer.ca.selftests,read
 selftests.execute = certServer.ca.selftests,execute
 users = certServer.ca.users,execute
+authorities.create = certServer.ca.authorities,create
+authorities.list = certServer.ca.authorities,list
+authorities.modify = certServer.ca.authorities,modify
+authorities.read = certServer.ca.authorities,read
diff --git a/base/ca/shared/conf/auth-method.properties b/base/ca/shared/conf/auth-method.properties
index a213534adcbf427c3c76b9981624edb8c93ea28d..8d67690af88d387f38fd8fcf1c2fdfa8bbb492fe 100644
--- a/base/ca/shared/conf/auth-method.properties
+++ b/base/ca/shared/conf/auth-method.properties
@@ -8,6 +8,7 @@
 
 default = *
 account = certUserDBAuthMgr,passwdUserDBAuthMgr
+authorities = certUserDBAuthMgr
 certs = certUserDBAuthMgr
 certrequests = certUserDBAuthMgr
 groups = certUserDBAuthMgr
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/shared/webapps/ca/WEB-INF/web.xml b/base/ca/shared/webapps/ca/WEB-INF/web.xml
index bba40e203463ac97138e6c6749bca8f979711444..628eea2a2d78147560e2ce4bd5eddaf634dd11ea 100644
--- a/base/ca/shared/webapps/ca/WEB-INF/web.xml
+++ b/base/ca/shared/webapps/ca/WEB-INF/web.xml
@@ -2417,6 +2417,16 @@
 
     <security-constraint>
         <web-resource-collection>
+            <web-resource-name>Authority Services</web-resource-name>
+            <url-pattern>/rest/authorities/*</url-pattern>
+        </web-resource-collection>
+        <user-data-constraint>
+            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+        </user-data-constraint>
+    </security-constraint>
+
+    <security-constraint>
+        <web-resource-collection>
             <web-resource-name>Security Domain Services</web-resource-name>
             <url-pattern>/rest/securityDomain/installToken</url-pattern>
         </web-resource-collection>
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..e9ac6a9712f58e8024623956d3a6dce6a718a9db 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -23,36 +23,26 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.security.KeyPair;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 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.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;
 import javax.servlet.http.HttpSession;
 
-import netscape.security.util.DerOutputStream;
-import netscape.security.util.DerValue;
-import netscape.security.x509.AlgorithmId;
-import netscape.security.x509.CertificateChain;
-import netscape.security.x509.CertificateIssuerName;
-import netscape.security.x509.CertificateSubjectName;
-import netscape.security.x509.CertificateVersion;
-import netscape.security.x509.X500Name;
-import netscape.security.x509.X509CRLImpl;
-import netscape.security.x509.X509CertImpl;
-import netscape.security.x509.X509CertInfo;
-import netscape.security.x509.X509ExtensionException;
-import netscape.security.x509.X509Key;
-
 import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.asn1.ASN1Util;
 import org.mozilla.jss.asn1.GeneralizedTime;
@@ -60,6 +50,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 +66,21 @@ 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.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 +95,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 +107,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 +125,29 @@ import com.netscape.cmsutil.ocsp.SingleResponse;
 import com.netscape.cmsutil.ocsp.TBSRequest;
 import com.netscape.cmsutil.ocsp.UnknownInfo;
 
+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 netscape.security.util.DerOutputStream;
+import netscape.security.util.DerValue;
+import netscape.security.x509.AlgorithmId;
+import netscape.security.x509.CertificateChain;
+import netscape.security.x509.CertificateIssuerName;
+import netscape.security.x509.CertificateSubjectName;
+import netscape.security.x509.CertificateVersion;
+import netscape.security.x509.X500Name;
+import netscape.security.x509.X509CRLImpl;
+import netscape.security.x509.X509CertImpl;
+import netscape.security.x509.X509CertInfo;
+import netscape.security.x509.X509ExtensionException;
+import netscape.security.x509.X509Key;
+
+
 /**
  * A class represents a Certificate Authority that is
  * responsible for certificate specific operations.
@@ -136,6 +161,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 hostCA = 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 +266,41 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      * Constructs a CA subsystem.
      */
     public CertificateAuthority() {
+        hostCA = this;
+    }
+
+    /**
+     * Construct and initialise a lightweight authority
+     */
+    private CertificateAuthority(
+            CertificateAuthority hostCA,
+            AuthorityID aid,
+            AuthorityID parentAID,
+            String signingKeyNickname,
+            String authorityDescription,
+            boolean authorityEnabled
+            ) throws EBaseException {
+        setId(hostCA.getId());
+        this.hostCA = hostCA;
+        this.authorityID = aid;
+        this.authorityParentID = parentAID;
+        this.authorityDescription = authorityDescription;
+        this.authorityEnabled = authorityEnabled;
+        mNickname = signingKeyNickname;
+        init(hostCA.mOwner, hostCA.mConfig);
+    }
+
+    public boolean isHostAuthority() {
+        return hostCA == this;
+    }
+
+    private void ensureEnabled() throws CADisabledException {
+        if (!authorityEnabled)
+            throw new CADisabledException("Authority is disabled");
+    }
+
+    public boolean getAuthorityEnabled() {
+        return authorityEnabled;
     }
 
     /**
@@ -334,8 +401,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 (isHostAuthority()) {
+                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 = hostCA.mReplicaRepot;
+            }
 
             // init signing unit & CA cert.
             try {
@@ -358,51 +439,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             if (CMS.isPreOpMode())
                 return;
 
-            // set certificate status to 10 minutes
-            mCertRepot.setCertStatusUpdateInterval(
+            /* The host CA owns these resources so skip these
+             * steps for lightweight CAs.
+             */
+            if (isHostAuthority()) {
+                /* 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 +479,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             // being functional.
             initCRL();
 
+            if (isHostAuthority())
+                loadLightweightCAs();
+
         } catch (EBaseException e) {
             if (CMS.isPreOpMode())
                 return;
@@ -420,6 +490,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         }
     }
 
+    private void initCRLPublisher() throws EBaseException {
+        // instantiate CRL publisher
+        if (!isHostAuthority()) {
+            mByName = hostCA.mByName;
+            mCRLPublisher = hostCA.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 +641,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         mService.startup();
         mRequestQueue.recover();
 
-        // Note that this could be null.
-
-        // setup Admin operations
-
-        initNotificationListeners();
-
-        startPublish();
-        //		startCRL();
+        if (isHostAuthority()) {
+            // setup Admin operations
+            initNotificationListeners();
+            startPublish();
+        }
     }
 
     /**
@@ -555,6 +653,10 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      * <P>
      */
     public void shutdown() {
+        // lightweight authorities don't own these resources
+        if (!isHostAuthority())
+            return;
+
         Enumeration<ICRLIssuingPoint> enums = mCRLIssuePoints.elements();
         while (enums.hasMoreElements()) {
             CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement();
@@ -967,6 +1069,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 +1142,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     public X509CertImpl sign(X509CertInfo certInfo, String algname)
             throws EBaseException {
+        ensureEnabled();
 
         X509CertImpl signedcert = null;
 
@@ -1123,6 +1227,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     public byte[] sign(byte[] data, String algname)
             throws EBaseException {
+        ensureEnabled();
         return mSigningUnit.sign(data, algname);
     }
 
@@ -1228,13 +1333,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 (isHostAuthority() && CrlStore != null && CrlStore.size() > 0) {
                 mCRLSigningUnit = new SigningUnit();
                 mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE));
             } else {
@@ -1304,7 +1409,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
 
             IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE);
 
-            if (OCSPStore != null && OCSPStore.size() > 0) {
+            if (isHostAuthority() && 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 +1548,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
     /**
      * init cert & crl database
      */
-    private void initCaDatabases()
+    private void initCertDatabase()
             throws EBaseException {
+        if (!isHostAuthority()) {
+            mCertRepot = hostCA.mCertRepot;
+            return;
+        }
+
         int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5);
 
         String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null);
@@ -1471,8 +1581,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 (!isHostAuthority()) {
+            mCRLRepot = hostCA.mCRLRepot;
+            return;
+        }
 
         int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5);
 
@@ -1482,14 +1601,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 +1624,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     private void initPublish()
             throws EBaseException {
+        if (!isHostAuthority()) {
+            mPublisherProcessor = hostCA.mPublisherProcessor;
+            return;
+        }
+
         IConfigStore c = null;
 
         try {
@@ -1676,6 +1792,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
      */
     private void initRequestQueue()
             throws EBaseException {
+        if (!isHostAuthority()) {
+            mPolicy = hostCA.mPolicy;
+            mService = hostCA.mService;
+            mNotify = hostCA.mNotify;
+            mPNotify = hostCA.mPNotify;
+            mRequestQueue = hostCA.mRequestQueue;
+            return;
+        }
+
         mPolicy = new CAPolicy();
         mPolicy.init(this, mConfig.getSubStore(PROP_POLICY));
         CMS.debug("CA policy inited");
@@ -1734,6 +1859,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
     @SuppressWarnings("unchecked")
     private void initCRL()
             throws EBaseException {
+        if (!isHostAuthority()) {
+            mCRLIssuePoints = hostCA.mCRLIssuePoints;
+            mMasterCRLIssuePoint = hostCA.mMasterCRLIssuePoint;
+            return;
+        }
         IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE);
 
         if ((crlConfig == null) || (crlConfig.size() <= 0)) {
@@ -1799,6 +1929,109 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         log(ILogger.LL_INFO, "CRL Issuing Points inited");
     }
 
+    /**
+     * Find, instantiate and register lightweight CAs.
+     *
+     * This method must only be called by the host 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 foundHostAuthority = 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)) {
+                    foundHostAuthority = true;
+                    this.authorityID = aid;
+                    this.authorityDescription = desc;
+                    caMap.put(aid, this);
+                    continue;
+                }
+
+                @SuppressWarnings("unused")
+                X500Name parentDN = null;
+                if (parentDNAttr != null) {
+                    try {
+                        parentDN = 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 ca = new CertificateAuthority(
+                    this, aid, parentAID, keyNick, desc, enabled);
+                caMap.put(aid, ca);
+            }
+        } 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 && !foundHostAuthority) {
+            CMS.debug("loadLightweightCAs: no entry for host authority");
+            CMS.debug("loadLightweightCAs: adding entry for host authority");
+            caMap.put(addHostAuthorityEntry(), this);
+        }
+    }
+
     public String getOfficialName() {
         return OFFICIAL_NAME;
     }
@@ -1960,6 +2193,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 +2317,312 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
 
         return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate);
     }
+
+    /**
+     * Enumerate all authorities (including host authority)
+     */
+    public synchronized List<ICertificateAuthority> getCAs() {
+        List<ICertificateAuthority> cas = new ArrayList<>();
+        for (ICertificateAuthority ca : caMap.values()) {
+            cas.add(ca);
+        }
+        return cas;
+    }
+
+    /**
+     * Get authority by ID.
+     *
+     * @param aid The ID of the CA to retrieve, or null
+     *             to retreive the host authority.
+     *
+     * @return the authority, or null if not found
+     */
+    public ICertificateAuthority getCA(AuthorityID aid) {
+        return aid == null ? hostCA : 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 lightweight authority.
+     *
+     * @param subjectDN Subject DN for new CA
+     * @param parentAID ID of parent 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 ca = parentCA.createSubCA(
+                subjectDN, description);
+        synchronized (this) {
+            caMap.put(ca.getAuthorityID(), ca);
+        }
+        return ca;
+    }
+
+    private void ensureAuthorityDNAvailable(X500Name dn)
+            throws IssuerUnavailableException {
+        for (ICertificateAuthority ca : getCAs()) {
+            if (ca.getAuthorityEnabled() && ca.getX500Name().equals(dn))
+                throw new IssuerUnavailableException(
+                    "DN '" + dn + "' is in use by an enabled authority");
+        }
+    }
+
+    /**
+     * Create a new lightweight authority signed by this authority.
+     *
+     * 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 requested DN
+        X500Name subjectX500Name = null;
+        try {
+            subjectX500Name = new X500Name(subjectDN);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(
+                "Invalid Subject DN: " + subjectDN);
+        }
+        ensureAuthorityDNAvailable(subjectX500Name);
+
+        // generate authority ID and nickname
+        AuthorityID aid = new AuthorityID();
+        String aidString = aid.toString();
+        String nickname = hostCA.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 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 lightweight CA certificate: " + e);
+            }
+        } catch (LDAPException e) {
+            throw new EBaseException("Error adding authority entry to database: " + e);
+        } finally {
+            dbFactory.returnConn(conn);
+            dbFactory.reset();
+        }
+
+        return new CertificateAuthority(
+            hostCA, aid, this.authorityID, nickname, description, true);
+    }
+
+    /**
+     * Add an LDAP entry for the host authority.
+     *
+     * This method also sets the authorityID and authorityDescription
+     * fields.
+     *
+     * It is the caller's responsibility to add the returned
+     * AuthorityID to the caMap.
+     */
+    private AuthorityID addHostAuthorityEntry() throws EBaseException {
+        if (!isHostAuthority())
+            throw new EBaseException("Can only invoke from host 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);
+        }
+
+        String desc = "Host authority";
+        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", desc)
+        };
+        LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs);
+        LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet);
+
+        // connect to database
+        ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("addHostAuthorityEntry");
+        dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+        LDAPConnection conn = dbFactory.getConn();
+
+        try {
+            conn.add(ldapEntry);
+        } catch (LDAPException e) {
+            throw new ELdapException("Error adding host authority entry to database: " + e);
+        } finally {
+            dbFactory.returnConn(conn);
+            dbFactory.reset();
+        }
+
+        this.authorityID = aid;
+        this.authorityDescription = desc;
+        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 (isHostAuthority() && enabled != null && !enabled)
+            throw new CATypeException("Cannot disable the host CA");
+
+        LDAPModificationSet mods = new LDAPModificationSet();
+
+        boolean nextEnabled = authorityEnabled;
+        if (enabled != null && enabled.booleanValue() != authorityEnabled) {
+            if (enabled)
+                ensureAuthorityDNAvailable(mName);
+            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 authority 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..820f8ab6499eed9fdb8e3d8d782df64c71ad1fc3
--- /dev/null
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
@@ -0,0 +1,282 @@
+//--- 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.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+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.UriInfo;
+
+import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.authority.AuthorityData;
+import com.netscape.certsrv.authority.AuthorityResource;
+import com.netscape.certsrv.base.BadRequestException;
+import com.netscape.certsrv.base.ConflictingOperationException;
+import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.ForbiddenException;
+import com.netscape.certsrv.base.PKIException;
+import com.netscape.certsrv.base.ResourceNotFoundException;
+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.cms.servlet.base.PKIService;
+import com.netscape.cmsutil.util.Utils;
+
+/**
+ * @author ftweedal
+ */
+public class AuthorityService extends PKIService implements AuthorityResource {
+
+    ICertificateAuthority hostCA;
+
+    public AuthorityService() {
+        hostCA = (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 : hostCA.getCAs())
+            results.add(readAuthorityData(ca));
+
+        GenericEntity<List<AuthorityData>> entity =
+            new GenericEntity<List<AuthorityData>>(results) {};
+        return Response.ok(entity).build();
+    }
+
+    @Override
+    public Response getCA(String aidString) {
+        ICertificateAuthority ca = hostCA;
+
+        if (!AuthorityResource.HOST_AUTHORITY.equals(aidString)) {
+            AuthorityID aid;
+            try {
+                aid = new AuthorityID(aidString);
+            } catch (IllegalArgumentException e) {
+                throw new BadRequestException("Bad CA ID: " + aidString);
+            }
+
+            ca = hostCA.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 = hostCA.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 = hostCA.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 = hostCA.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 = hostCA.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 (IssuerUnavailableException e) {
+            throw new ConflictingOperationException(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, null, true, null));
+    }
+
+    @Override
+    public Response disableCA(String aidString) {
+        return modifyCA(
+            aidString,
+            new AuthorityData(null, 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(
+            ca.isHostAuthority(),
+            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..2312c39895c09a4b7dbf994d43c2c068eeaec2d4
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java
@@ -0,0 +1,123 @@
+// --- 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.XmlRootElement;
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+ at XmlRootElement(name = "authority")
+ 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 Boolean isHostAuthority;
+
+    public Boolean getIsHostAuthority() {
+        return isHostAuthority;
+    }
+
+
+    @XmlAttribute
+    protected String id;
+
+    public String getID() {
+        return id;
+    }
+
+
+    @XmlAttribute
+    protected String parentID;
+
+    public String getParentID() {
+        return parentID;
+    }
+
+
+    @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(
+            Boolean isHostAuthority,
+            String dn, String id, String parentID,
+            Boolean enabled, String description) {
+        this.isHostAuthority = isHostAuthority;
+        this.dn = dn;
+        this.id = id;
+        this.parentID = parentID;
+        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..eaef903db444512dbea6c87b11800130d94a944d
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java
@@ -0,0 +1,96 @@
+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.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 {
+
+    public static final String HOST_AUTHORITY = "host-authority";
+
+    @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)
+    @AuthMethodMapping("authorities")
+    @ACLMapping("authorities.create")
+    public Response createCA(AuthorityData data);
+
+    /**
+     * Modify a CA (supports partial updates).
+     *
+     * isHostEnabled, 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)
+    @AuthMethodMapping("authorities")
+    @ACLMapping("authorities.modify")
+    public Response modifyCA(
+        @PathParam("id") String caIDString,
+        AuthorityData data);
+
+    @POST
+    @Path("{id}/enable")
+    @ClientResponseType(entityType=AuthorityData.class)
+    @AuthMethodMapping("authorities")
+    @ACLMapping("authorities.modify")
+    public Response enableCA(@PathParam("id") String caIDString);
+
+    @POST
+    @Path("{id}/disable")
+    @ClientResponseType(entityType=AuthorityData.class)
+    @AuthMethodMapping("authorities")
+    @ACLMapping("authorities.modify")
+    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..daac587b75843f5faf75e556d4d0135c8ffc8fd7
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java
@@ -0,0 +1,36 @@
+package com.netscape.certsrv.ca;
+
+import java.util.UUID;
+
+/**
+ * Identifier for a CertificateAuthority.
+ */
+public class AuthorityID implements Comparable<AuthorityID> {
+
+    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(AuthorityID aid) {
+        return uuid.compareTo(aid.uuid);
+    }
+
+}
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..9b3f16b90a8d28b87e993575037a2a19517e17b9
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java
@@ -0,0 +1,15 @@
+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 {
+
+    private static final long serialVersionUID = -8827509070155037699L;
+
+    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..f292077ece3089d398ad0cf3a008b7e5218a6bcd
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java
@@ -0,0 +1,14 @@
+package com.netscape.certsrv.ca;
+
+/**
+ * Exception to throw when a (sub-)CA cannot be found.
+ */
+public class CANotFoundException extends ECAException {
+
+    private static final long serialVersionUID = -4618887355685066120L;
+
+    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..19eb680e88aee3baac51abd95439c46487bf5720
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/CATypeException.java
@@ -0,0 +1,16 @@
+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 {
+
+    private static final long serialVersionUID = -6004456461295692150L;
+
+    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..31d5c9277a63ca7c916f39651300b0c9a9061c1e 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,71 @@ public interface ICertificateAuthority extends ISubsystem {
 
     public CertificateIssuerName getIssuerObj();
     public CertificateSubjectName getSubjectObj();
+
+    /**
+     * Enumerate all authorities, including host authority.
+     */
+    public List<ICertificateAuthority> getCAs();
+
+    /**
+     * Return whether this CA is the host authority (not a
+     * lightweight authority).
+     */
+    public boolean isHostAuthority();
+
+    /**
+     * Get the AuthorityID of this CA.
+     */
+    public AuthorityID getAuthorityID();
+
+    /**
+     * Get the AuthorityID of this CA's parent CA, if available.
+     */
+    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..75bf88251bfadb687c7bf85944a806339f6f3b9f
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java
@@ -0,0 +1,15 @@
+package com.netscape.certsrv.ca;
+
+/**
+ * Exception to throw during CA creation when requested CA
+ * (issuer DN) already exists.
+ */
+public class IssuerUnavailableException extends ECAException {
+
+    private static final long serialVersionUID = -6247493607604418446L;
+
+    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..12667120e3d87deecb786965b4abcef492ac556d 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 (absense implies host authority)
+     */
+    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..a5d641e93f9aca9cd47dac3084227dbc637d83d9 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 private 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..bd71a4ef8cf710008fc861a022a553d5064c37ba 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
@@ -20,20 +20,23 @@ package com.netscape.cms.profile.def;
 import java.io.IOException;
 import java.util.Locale;
 
-import netscape.security.x509.AuthorityKeyIdentifierExtension;
-import netscape.security.x509.KeyIdentifier;
-import netscape.security.x509.PKIXExtensions;
-import netscape.security.x509.X509CertInfo;
-
 import com.netscape.certsrv.apps.CMS;
 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;
 import com.netscape.certsrv.property.IDescriptor;
 import com.netscape.certsrv.request.IRequest;
 
+import netscape.security.x509.AuthorityKeyIdentifierExtension;
+import netscape.security.x509.KeyIdentifier;
+import netscape.security.x509.PKIXExtensions;
+import netscape.security.x509.X509CertInfo;
+
 /**
  * This class implements an enrollment default policy
  * that populates Authority Key Identifier extension
@@ -161,18 +164,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..8558ec23f6489407dc5f41951a363d22548851c0 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 host authority");  // 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 214d44ddc0985e3d49febd99cf66a2290455b8ca 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  | 52 +++++++++++++
 .../cmstools/authority/AuthorityCreateCLI.java     | 89 ++++++++++++++++++++++
 .../cmstools/authority/AuthorityDisableCLI.java    | 56 ++++++++++++++
 .../cmstools/authority/AuthorityEnableCLI.java     | 56 ++++++++++++++
 .../cmstools/authority/AuthorityFindCLI.java       | 62 +++++++++++++++
 .../cmstools/authority/AuthorityShowCLI.java       | 78 +++++++++++++++++++
 .../src/com/netscape/cmstools/cli/CACLI.java       |  2 +
 9 files changed, 459 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..99d38ad1b989e171079df78ddd8b2774817ccb33
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
@@ -0,0 +1,52 @@
+package com.netscape.cmstools.authority;
+
+import com.netscape.certsrv.authority.AuthorityClient;
+import com.netscape.certsrv.authority.AuthorityData;
+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) {
+        Boolean isHostAuthority = data.getIsHostAuthority();
+        if (isHostAuthority != null && isHostAuthority)
+            System.out.println("  Host authority: true");
+        System.out.println("  Authority DN:   " + data.getDN());
+        System.out.println("  ID:             " + data.getID());
+        String parentAID = data.getParentID();
+        if (parentAID != null)
+            System.out.println("  Parent ID:      " + 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..d1688fbd1933a567940164d86ac726df1489f7d2
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java
@@ -0,0 +1,89 @@
+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.certsrv.ca.AuthorityID;
+import com.netscape.cmstools.cli.CLI;
+
+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(
+            null, 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..fc4cbf30be947233a9289089bb25bef70d532bb6
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java
@@ -0,0 +1,56 @@
+package com.netscape.cmstools.authority;
+
+import java.util.Arrays;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.ParseException;
+
+import com.netscape.certsrv.authority.AuthorityData;
+import com.netscape.cmstools.cli.CLI;
+
+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, 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..f6fdab12f527975d2d0688b65968bfb992b5a97a
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java
@@ -0,0 +1,56 @@
+package com.netscape.cmstools.authority;
+
+import java.util.Arrays;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.ParseException;
+
+import com.netscape.certsrv.authority.AuthorityData;
+import com.netscape.cmstools.cli.CLI;
+
+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, 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..c1aa99fc627e8e0ccfd1f12a23610a13dd5cfbbb
--- /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.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);
+        }
+
+        @SuppressWarnings("unused")
+        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..c9566024872fdfa781f956ed7acfd494367a7d32
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java
@@ -0,0 +1,78 @@
+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.certsrv.authority.AuthorityResource;
+import com.netscape.cmstools.cli.CLI;
+
+public class AuthorityShowCLI extends CLI {
+
+    public AuthorityCLI authorityCLI;
+
+    public AuthorityShowCLI(AuthorityCLI authorityCLI) {
+        super("show", "Show CAs", authorityCLI);
+        this.authorityCLI = authorityCLI;
+
+        Option optParent = new Option(
+            null, "host-authority", false, "Show host authority");
+        options.addOption(optParent);
+    }
+
+    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();
+
+        String caIDString = null;
+        if (cmdArgs.length > 1) {
+            System.err.println("Error: too many arguments.");
+            printHelp();
+            System.exit(-1);
+        } else if (cmdArgs.length == 1) {
+            caIDString = cmdArgs[0];
+        }
+
+        if (cmd.hasOption("host-authority")) {
+            if (caIDString != null) {
+                System.err.println("Error: authority ID and --host-authority are mutually exclusive.");
+                printHelp();
+                System.exit(-1);
+            }
+            caIDString = AuthorityResource.HOST_AUTHORITY;
+        }
+
+        if (caIDString == null) {
+            System.err.println("Error: No ID specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        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 c3ce826f529094f35fecd5cea70ebe3670389f92 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..92c776d17a3993fdd50ecbdfd3e5e3b5692888f2 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, 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, 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..9611159681b65844c1fc32937ca0a65c2c31980d 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 (host authority if omitted)");
+        optAID.setArgName("id");
+        options.addOption(optAID);
+
+        Option optADN = new Option(null, "issuer-dn", true, "Authority DN (host authority 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

-------------- next part --------------
From 99722211582103bd5185b21c5fde3cab44783c84 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Fri, 25 Sep 2015 09:00:29 -0400
Subject: [PATCH] [EXPERIMENTAL] Lightweight CAs: find cert by issuer and
 serial

---
 .../src/com/netscape/ca/CertificateAuthority.java  | 56 ++++++++++++++--------
 base/ca/src/com/netscape/ca/SigningUnit.java       | 28 +++++++++--
 base/server/share/conf/schema-authority.ldif       |  7 +--
 base/server/share/conf/schema.ldif                 |  7 +--
 4 files changed, 69 insertions(+), 29 deletions(-)

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index e9ac6a9712f58e8024623956d3a6dce6a718a9db..b07276d0929e22d67531a3eac2ec62438626e220 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -167,6 +167,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
     protected AuthorityID authorityParentID = null;
     protected String authorityDescription = null;
     protected boolean authorityEnabled = true;
+    protected X500Name authorityIssuerDN = null;
+    protected BigInteger authoritySerial = null;
 
     protected ISubsystem mOwner = null;
     protected IConfigStore mConfig = null;
@@ -278,7 +280,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             AuthorityID parentAID,
             String signingKeyNickname,
             String authorityDescription,
-            boolean authorityEnabled
+            boolean authorityEnabled,
+            X500Name issuerDN,
+            BigInteger serial
             ) throws EBaseException {
         setId(hostCA.getId());
         this.hostCA = hostCA;
@@ -286,6 +290,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         this.authorityParentID = parentAID;
         this.authorityDescription = authorityDescription;
         this.authorityEnabled = authorityEnabled;
+        this.authorityIssuerDN = issuerDN;
+        this.authoritySerial = serial;
         mNickname = signingKeyNickname;
         init(hostCA.mOwner, hostCA.mConfig);
     }
@@ -1333,7 +1339,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
                 mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME));
             }
 
-            mSigningUnit.init(this, caSigningCfg, mNickname);
+            mSigningUnit.init(
+                    this, caSigningCfg, mNickname,
+                    authorityIssuerDN, authoritySerial);
             CMS.debug("CA signing unit inited");
 
             // for identrus
@@ -1955,9 +1963,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
                 LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname");
                 LDAPAttribute dnAttr = entry.getAttribute("authorityDN");
                 LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID");
-                LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN");
+                LDAPAttribute issuerDNAttr = entry.getAttribute("authorityIssuerDN");
+                LDAPAttribute serialAttr = entry.getAttribute("authoritySerial");
 
-                if (aidAttr == null || nickAttr == null || dnAttr == null)
+                if (aidAttr == null || nickAttr == null || dnAttr == null
+                        || issuerDNAttr == null || serialAttr == null)
                     throw new ECAException("Malformed authority object; required attribute(s) missing: " + entry.getDN());
 
                 AuthorityID aid = new AuthorityID((String)
@@ -1983,14 +1993,18 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
                     continue;
                 }
 
-                @SuppressWarnings("unused")
-                X500Name parentDN = null;
-                if (parentDNAttr != null) {
-                    try {
-                        parentDN = new X500Name((String) parentDNAttr.getStringValues().nextElement());
-                    } catch (IOException e) {
-                        throw new ECAException("Malformed authority object; invalid authorityParentDN: " + entry.getDN());
-                    }
+                X500Name issuerDN = null;
+                try {
+                    issuerDN = new X500Name((String) issuerDNAttr.getStringValues().nextElement());
+                } catch (IOException e) {
+                    throw new ECAException("Malformed authority object; invalid authorityIssuerDN: " + entry.getDN());
+                }
+
+                BigInteger serial = null;
+                try {
+                    serial = new BigInteger((String) serialAttr.getStringValues().nextElement());
+                } catch (NumberFormatException e) {
+                    throw new ECAException("Malformed authority object; invalid serial: " + entry.getDN());
                 }
 
                 String keyNick = (String) nickAttr.getStringValues().nextElement();
@@ -2008,7 +2022,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
                 }
 
                 CertificateAuthority ca = new CertificateAuthority(
-                    this, aid, parentAID, keyNick, desc, enabled);
+                    this, aid, parentAID, keyNick, desc, enabled,
+                    issuerDN, serial);
                 caMap.put(aid, ca);
             }
         } catch (LDAPException e) {
@@ -2423,9 +2438,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         String dn = "cn=" + aidString + ",ou=authorities,ou="
             + getId() + "," + getDBSubsystem().getBaseDN();
         CMS.debug("createSubCA: DN = " + dn);
-        String parentDNString = null;
+        String issuerDNString = null;
         try {
-            parentDNString = mName.toLdapDNString();
+            issuerDNString = mName.toLdapDNString();
         } catch (IOException e) {
             throw new EBaseException("Failed to convert issuer DN to string: " + e);
         }
@@ -2437,7 +2452,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             new LDAPAttribute("authorityKeyNickname", nickname),
             new LDAPAttribute("authorityEnabled", "TRUE"),
             new LDAPAttribute("authorityDN", subjectDN),
-            new LDAPAttribute("authorityParentDN", parentDNString)
+            new LDAPAttribute("authorityParentDN", issuerDNString)
         };
         LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs);
         if (this.authorityID != null)
@@ -2452,6 +2467,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
         LDAPConnection conn = dbFactory.getConn();
 
+        X509CertImpl cert = null;
         try {
             // add entry to database
             conn.add(ldapEntry);
@@ -2474,8 +2490,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
                 cs.put(".profile", "caCert.profile");
                 cs.put(".dn", subjectDN);
                 cs.put(".keyalgorithm", algName);
-                X509CertImpl cert =
-                    CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this);
+                cert = CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this);
 
                 // Add certificate to nssdb
                 cryptoManager.importCertPackage(cert.getEncoded(), nickname);
@@ -2492,7 +2507,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
         }
 
         return new CertificateAuthority(
-            hostCA, aid, this.authorityID, nickname, description, true);
+            hostCA, aid, this.authorityID, nickname, description, true,
+            mName /* this CA is the issuer */, cert.getSerialNumber());
     }
 
     /**
@@ -2530,6 +2546,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori
             new LDAPAttribute("authorityKeyNickname", getNickname()),
             new LDAPAttribute("authorityEnabled", "TRUE"),
             new LDAPAttribute("authorityDN", dnString),
+            new LDAPAttribute("authorityIssuerDN", mIssuerObj.toString()),
+            new LDAPAttribute("authoritySerial", mCaCert.getSerialNumber().toString()),
             new LDAPAttribute("description", desc)
         };
         LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs);
diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java
index 0410bd2909bc71ca7d7443b3c9db0b085d62eaea..9fd3687eb01553fbc1701772da75c8ada186a175 100644
--- a/base/ca/src/com/netscape/ca/SigningUnit.java
+++ b/base/ca/src/com/netscape/ca/SigningUnit.java
@@ -17,6 +17,8 @@
 // --- END COPYRIGHT BLOCK ---
 package com.netscape.ca;
 
+import java.io.IOException;
+import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
@@ -25,9 +27,11 @@ import java.security.SignatureException;
 import netscape.security.x509.AlgorithmId;
 import netscape.security.x509.X509CertImpl;
 import netscape.security.x509.X509Key;
+import netscape.security.x509.X500Name;
 
 import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.NoSuchTokenException;
+import org.mozilla.jss.asn1.INTEGER;
 import org.mozilla.jss.crypto.CryptoToken;
 import org.mozilla.jss.crypto.ObjectNotFoundException;
 import org.mozilla.jss.crypto.PrivateKey;
@@ -126,10 +130,11 @@ public final class SigningUnit implements ISigningUnit {
 
     public void init(ISubsystem owner, IConfigStore config)
             throws EBaseException {
-        init(owner, config, null);
+        init(owner, config, null, null, null);
     }
 
-    public void init(ISubsystem owner, IConfigStore config, String nickname)
+    public void init(ISubsystem owner, IConfigStore config,
+            String nickname, X500Name issuerDN, BigInteger serial)
             throws EBaseException {
         mOwner = owner;
         mConfig = config;
@@ -165,8 +170,23 @@ public final class SigningUnit implements ISigningUnit {
 
             mToken.login(cb); // ONE_TIME by default.
 
-            mCert = mManager.findCertByNickname(mNickname);
-            CMS.debug("Found cert by nickname: '" + mNickname + "' with serial number: " + mCert.getSerialNumber());
+            byte[] derIssuer = null;
+            if (issuerDN != null) {
+                try {
+                    derIssuer = issuerDN.getEncoded();
+                } catch (IOException e) {
+                    CMS.debug("Error getting DER encoding of issuer DN; falling back to nickname");
+                }
+            }
+
+            if (derIssuer != null && serial != null) {
+                mCert = mManager.findCertByIssuerAndSerialNumber(
+                    derIssuer, new INTEGER(serial));
+                CMS.debug("Found cert by issuer: '" + issuerDN + "' and serial number: " + serial);
+            } else {
+                mCert = mManager.findCertByNickname(mNickname);
+                CMS.debug("Found cert by nickname: '" + mNickname + "' with serial number: " + mCert.getSerialNumber());
+            }
 
             mCertImpl = new X509CertImpl(mCert.getEncoded());
             CMS.debug("converted to x509CertImpl");
diff --git a/base/server/share/conf/schema-authority.ldif b/base/server/share/conf/schema-authority.ldif
index 7d261f18fbc9475983bf93b1cddcc184d7f9d178..fb831c9b40cb79fbc17867c0e2904d288f205207 100644
--- a/base/server/share/conf/schema-authority.ldif
+++ b/base/server/share/conf/schema-authority.ldif
@@ -3,6 +3,7 @@ attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX
 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' )
+attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Subject DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' )
+attributeTypes: ( authorityIssuerDN-oid NAME 'authorityIssuerDN' DESC 'Authority Issuer DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' )
+attributeTypes: ( authoritySerial-oid NAME 'authoritySerial' DESC 'Authority Serial Number' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'user defined' )
+objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN $ authorityIssuerDN $ authoritySerial ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' )
diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif
index a15601ae7a362635bc398b92b9bfda1c72f0dfc8..cbba4bfcd7798fec6bcd72200b7ec6b1e71d946a 100644
--- a/base/server/share/conf/schema.ldif
+++ b/base/server/share/conf/schema.ldif
@@ -675,8 +675,9 @@ attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX
 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' )
+attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Subject DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' )
+attributeTypes: ( authorityIssuerDN-oid NAME 'authorityParentDN' DESC 'Authority Issuer DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' )
+attributeTypes: ( authoritySerial-oid NAME 'authoritySerial' DESC 'Authority Serial Number' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 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' )
+objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN $ authorityIssuerDN $ authoritySerial ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' )
-- 
2.4.3



More information about the Pki-devel mailing list