Hi all,

The attached patches implement replication support for lightweight
CAs.  These patches do not implement key replication via Custodia
(my next task) but they do implement the persistent search thread
and appropriate** API behaviour when the signing keys are not yet
available.

** In most cases, we respond 503 Service Unavailable; this is open
   for discussion.  ca-authority-find and ca-authority-show include
   a boolean field indicating whether the CA is ready to sign.
   There might be (probably are) endpoints I've missed.

Cheers,
Fraser
From fae1f14095cba4a9a14486230f9b0d353dcf7513 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 9 Mar 2016 02:18:41 -0500
Subject: [PATCH 84/86] Lightweight CAs: monitor database for changes

Implement a thread that performs an LDAP persistent search to keep a
running CA's view of lightweight CAs in sync with the database.

Signing key replication is not yet supported; this will be
implemented in a later patch and will not use the database to
propagate keys.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 .../src/com/netscape/ca/CertificateAuthority.java  | 689 ++++++++++++++-------
 base/ca/src/com/netscape/ca/SigningUnit.java       |   3 +-
 .../netscape/certsrv/ca/CAMissingKeyException.java |  15 +
 3 files changed, 486 insertions(+), 221 deletions(-)
 create mode 100644 
base/common/src/com/netscape/certsrv/ca/CAMissingKeyException.java

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
63c7ca4e4a8083dc58b54196af89cc7629e9fd97..d8177e1708dca15ae97c5c01534215a64dbe34d0
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -43,7 +43,9 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.Vector;
+import java.util.concurrent.CountDownLatch;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
@@ -51,11 +53,18 @@ import javax.servlet.http.HttpSession;
 import netscape.ldap.LDAPAttribute;
 import netscape.ldap.LDAPAttributeSet;
 import netscape.ldap.LDAPConnection;
+import netscape.ldap.LDAPConstraints;
+import netscape.ldap.LDAPControl;
 import netscape.ldap.LDAPEntry;
 import netscape.ldap.LDAPException;
 import netscape.ldap.LDAPModification;
 import netscape.ldap.LDAPModificationSet;
+import netscape.ldap.LDAPSearchConstraints;
 import netscape.ldap.LDAPSearchResults;
+import netscape.ldap.controls.LDAPEntryChangeControl;
+import netscape.ldap.controls.LDAPPersistSearchControl;
+import netscape.ldap.util.DN;
+
 import netscape.security.pkcs.PKCS10;
 import netscape.security.util.DerOutputStream;
 import netscape.security.util.DerValue;
@@ -101,6 +110,7 @@ import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.ca.CADisabledException;
 import com.netscape.certsrv.ca.CAEnabledException;
+import com.netscape.certsrv.ca.CAMissingKeyException;
 import com.netscape.certsrv.ca.CANotFoundException;
 import com.netscape.certsrv.ca.CANotLeafException;
 import com.netscape.certsrv.ca.CATypeException;
@@ -150,6 +160,8 @@ 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.ldap.LDAPPostReadControl;
+import com.netscape.cmsutil.ldap.LDAPUtil;
 import com.netscape.cmsutil.ocsp.BasicOCSPResponse;
 import com.netscape.cmsutil.ocsp.CertID;
 import com.netscape.cmsutil.ocsp.CertStatus;
@@ -176,11 +188,13 @@ import com.netscape.cmsutil.ocsp.UnknownInfo;
  * @author lhsiao
  * @version $Revision$, $Date$
  */
-public class CertificateAuthority implements ICertificateAuthority, 
ICertAuthority, IOCSPService {
+public class CertificateAuthority
+        implements ICertificateAuthority, ICertAuthority, IOCSPService, 
Runnable {
     public static final String OFFICIAL_NAME = "Certificate Manager";
 
     public final static OBJECT_IDENTIFIER OCSP_NONCE = new 
OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2");
 
+    private static ILdapConnFactory dbFactory = null;
     private static final Map<AuthorityID, ICertificateAuthority> caMap =
         Collections.synchronizedSortedMap(new TreeMap<AuthorityID, 
ICertificateAuthority>());
     protected CertificateAuthority hostCA = null;
@@ -188,6 +202,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
     protected AuthorityID authorityParentID = null;
     protected String authorityDescription = null;
     protected boolean authorityEnabled = true;
+    private boolean hasKeys = false;
 
     protected ISubsystem mOwner = null;
     protected IConfigStore mConfig = null;
@@ -283,6 +298,19 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
     private boolean mUseNonces = true;
     private int mMaxNonces = 100;
 
+    /* Variables to manage loading and tracking of lightweight CAs */
+    private boolean stopped = false;
+    private static boolean foundHostAuthority = false;
+    private static Integer initialNumAuthorities = null;
+    private static int numAuthoritiesLoaded = 0;
+    private static CountDownLatch initialLoadDone = new CountDownLatch(1);
+
+    /* Maps and sets of entryUSNs and nsUniqueIds for avoiding race
+     * conditions and unnecessary reloads related to replication */
+    private static TreeMap<AuthorityID,Integer> entryUSNs = new TreeMap<>();
+    private static TreeMap<AuthorityID,String> nsUniqueIds = new TreeMap<>();
+    private static TreeSet<String> deletedNsUniqueIds = new TreeSet<>();
+
     /**
      * Constructs a CA subsystem.
      */
@@ -422,6 +450,11 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             mOwner = owner;
             mConfig = config;
 
+            if (isHostAuthority()) {
+                dbFactory = 
CMS.getLdapBoundConnFactory("CertificateAuthority");
+                dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
+            }
+
             // init cert & crl database
             initCertDatabase();
             initCrlDatabase();
@@ -500,9 +533,23 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             // being functional.
             initCRL();
 
-            if (isHostAuthority())
-                loadLightweightCAs();
+            if (isHostAuthority() && haveLightweightCAsContainer()) {
+                new Thread(this, "authorityMonitor").start();
+                try {
+                    initialLoadDone.await();
+                } catch (InterruptedException e) {
+                    CMS.debug("CertificateAuthority: caught 
InterruptedException "
+                            + "while waiting for initial load of 
authorities.");
+                }
 
+                if (!foundHostAuthority) {
+                    CMS.debug("loadLightweightCAs: no entry for host 
authority");
+                    CMS.debug("loadLightweightCAs: adding entry for host 
authority");
+                    caMap.put(addHostAuthorityEntry(), this);
+                }
+
+                CMS.debug("CertificateAuthority: finished init of host 
authority");
+            }
         } catch (EBaseException e) {
             if (CMS.isPreOpMode())
                 return;
@@ -511,6 +558,24 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         }
     }
 
+    private String authorityBaseDN() {
+        return "ou=authorities,ou=" + getId()
+            + "," + getDBSubsystem().getBaseDN();
+    }
+
+    private boolean haveLightweightCAsContainer() throws ELdapException {
+        LDAPConnection conn = dbFactory.getConn();
+        try {
+            LDAPSearchResults results = conn.search(
+                authorityBaseDN(), LDAPConnection.SCOPE_BASE, null, null, 
false);
+            return results != null;
+        } catch (LDAPException e) {
+            return false;
+        } finally {
+            dbFactory.returnConn(conn);
+        }
+    }
+
     private void initCRLPublisher() throws EBaseException {
         // instantiate CRL publisher
         if (!isHostAuthority()) {
@@ -696,6 +761,22 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         if (mPublisherProcessor != null) {
             mPublisherProcessor.shutdown();
         }
+
+        /* Stop the activityMonitor thread
+         *
+         * dbFactory.reset() will disconnect all connections,
+         * causing the current conn.search() to throw.
+         * The search will not be restarted because 'stopped' has
+         * set, and the monitor thread will exit.
+         */
+        stopped = true;
+        try {
+            dbFactory.reset();
+        } catch (ELdapException e) {
+            CMS.debug("CertificateAuthority.shutdown: failed to reset "
+                    + "dbFactory: " + e);
+            // not much else we can do here.
+        }
     }
 
     /**
@@ -1354,7 +1435,13 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
                 mIssuerObj = new 
CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME));
             }
 
-            mSigningUnit.init(this, caSigningCfg, mNickname);
+            try {
+                mSigningUnit.init(this, caSigningCfg, mNickname);
+                hasKeys = true;
+            } catch (CAMissingKeyException e) {
+                CMS.debug("CA signing key not (yet) present in NSSDB");
+                return;
+            }
             CMS.debug("CA signing unit inited");
 
             // for identrus
@@ -1950,109 +2037,6 @@ 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 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;
     }
@@ -2499,8 +2483,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         String nickname = hostCA.getNickname() + " " + aidString;
 
         // build database entry
-        String dn = "cn=" + aidString + ",ou=authorities,ou="
-            + getId() + "," + getDBSubsystem().getBaseDN();
+        String dn = "cn=" + aidString + "," + authorityBaseDN();
         CMS.debug("createSubCA: DN = " + dn);
         String parentDNString = null;
         try {
@@ -2526,77 +2509,70 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             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();
+        commitAuthority(aid, ldapEntry);
 
         try {
-            // add entry to database
-            conn.add(ldapEntry);
+            // 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);
 
+            // Create pkcs10 request
+            CMS.debug("createSubCA: creating pkcs10 request");
+            PKCS10 pkcs10 = new PKCS10(x509key);
+            Signature signature = Signature.getInstance("SHA256withRSA");
+            signature.initSign(keypair.getPrivate());
+            pkcs10.encodeAndSign(
+                new X500Signer(signature, subjectX500Name));
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            pkcs10.print(new PrintStream(out));
+            String pkcs10String = out.toString();
+
+            // Sign certificate
+            Locale locale = Locale.getDefault();
+            String profileId = "caCACert";
+            IProfileSubsystem ps = (IProfileSubsystem)
+                CMS.getSubsystem(IProfileSubsystem.ID);
+            IProfile profile = ps.getProfile(profileId);
+            ArgBlock argBlock = new ArgBlock();
+            argBlock.set("cert_request_type", "pkcs10");
+            argBlock.set("cert_request", pkcs10String);
+            CertEnrollmentRequest certRequest =
+                CertEnrollmentRequestFactory.create(argBlock, profile, locale);
+            EnrollmentProcessor processor =
+                new EnrollmentProcessor("createSubCA", locale);
+            Map<String, Object> resultMap = processor.processEnrollment(
+                certRequest, null, authorityID, null, authToken);
+            IRequest requests[] = (IRequest[]) 
resultMap.get(CAProcessor.ARG_REQUESTS);
+            IRequest request = requests[0];
+            Integer result = request.getExtDataInInteger(IRequest.RESULT);
+            if (result != null && !result.equals(IRequest.RES_SUCCESS))
+                throw new EBaseException("createSubCA: certificate request 
submission resulted in error: " + result);
+            RequestStatus requestStatus = request.getRequestStatus();
+            if (requestStatus != RequestStatus.COMPLETE)
+                throw new EBaseException("createSubCA: certificate request did 
not complete; status: " + requestStatus);
+
+            // Add certificate to nssdb
+            X509CertImpl cert = 
request.getExtDataInCert(IEnrollProfile.REQUEST_ISSUED_CERT);
+            cryptoManager.importCertPackage(cert.getEncoded(), nickname);
+        } catch (Exception e) {
+            // something went wrong; delete just-added entry
+            CMS.debug("Error creating lightweight CA certificate");
+            CMS.debug(e);
             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);
-
-                // Create pkcs10 request
-                CMS.debug("createSubCA: creating pkcs10 request");
-                PKCS10 pkcs10 = new PKCS10(x509key);
-                Signature signature = Signature.getInstance("SHA256withRSA");
-                signature.initSign(keypair.getPrivate());
-                pkcs10.encodeAndSign(
-                    new X500Signer(signature, subjectX500Name));
-                ByteArrayOutputStream out = new ByteArrayOutputStream();
-                pkcs10.print(new PrintStream(out));
-                String pkcs10String = out.toString();
-
-                // Sign certificate
-                Locale locale = Locale.getDefault();
-                String profileId = "caCACert";
-                IProfileSubsystem ps = (IProfileSubsystem)
-                    CMS.getSubsystem(IProfileSubsystem.ID);
-                IProfile profile = ps.getProfile(profileId);
-                ArgBlock argBlock = new ArgBlock();
-                argBlock.set("cert_request_type", "pkcs10");
-                argBlock.set("cert_request", pkcs10String);
-                CertEnrollmentRequest certRequest =
-                    CertEnrollmentRequestFactory.create(argBlock, profile, 
locale);
-                EnrollmentProcessor processor =
-                    new EnrollmentProcessor("createSubCA", locale);
-                Map<String, Object> resultMap = processor.processEnrollment(
-                    certRequest, null, authorityID, null, authToken);
-                IRequest requests[] = (IRequest[]) 
resultMap.get(CAProcessor.ARG_REQUESTS);
-                IRequest request = requests[0];
-                Integer result = request.getExtDataInInteger(IRequest.RESULT);
-                if (result != null && !result.equals(IRequest.RES_SUCCESS))
-                    throw new EBaseException("createSubCA: certificate request 
submission resulted in error: " + result);
-                RequestStatus requestStatus = request.getRequestStatus();
-                if (requestStatus != RequestStatus.COMPLETE)
-                    throw new EBaseException("createSubCA: certificate request 
did not complete; status: " + requestStatus);
-
-                // Add certificate to nssdb
-                X509CertImpl cert = 
request.getExtDataInCert(IEnrollProfile.REQUEST_ISSUED_CERT);
-                cryptoManager.importCertPackage(cert.getEncoded(), nickname);
-            } catch (Exception e) {
-                // something went wrong; delete just-added entry
-                conn.delete(dn);
-                CMS.debug("Error creating lightweight CA certificate");
-                CMS.debug(e);
-                throw new ECAException("Error creating lightweight CA 
certificate: " + e);
+                deleteAuthorityEntry(aid);
+            } catch (ELdapException e2) {
+                // we are about to throw ECAException, so just
+                // log this error.
+                CMS.debug("Error deleting new authority entry after failure 
during certificate generation: " + e2);
             }
-        } catch (LDAPException e) {
-            throw new EBaseException("Error adding authority entry to 
database: " + e);
-        } finally {
-            dbFactory.returnConn(conn);
-            dbFactory.reset();
+            throw new ECAException("Error creating lightweight CA certificate: 
" + e);
         }
 
         return new CertificateAuthority(
@@ -2621,8 +2597,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         String aidString = aid.toString();
 
         // build database entry
-        String dn = "cn=" + aidString + ",ou=authorities,ou="
-            + getId() + "," + getDBSubsystem().getBaseDN();
+        String dn = "cn=" + aidString + "," + authorityBaseDN();
         String dnString = null;
         try {
             dnString = mName.toLdapDNString();
@@ -2643,25 +2618,82 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         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();
-        }
+        commitAuthority(aid, ldapEntry);
 
         this.authorityID = aid;
         this.authorityDescription = desc;
         return aid;
     }
 
+    private void commitAuthority(AuthorityID aid, LDAPEntry entry)
+            throws ELdapException {
+        LDAPControl[] responseControls;
+        LDAPConnection conn = dbFactory.getConn();
+        synchronized (hostCA) {
+            try {
+                conn.add(entry, getCommitConstraints());
+                responseControls = conn.getResponseControls();
+            } catch (LDAPException e) {
+                throw new ELdapException("commitAuthority: failed to add 
entry", e);
+            } finally {
+                dbFactory.returnConn(conn);
+            }
+            postCommit(aid, responseControls);
+        }
+    }
+
+    /**
+     * Modify _this_ authority with the given modification set.
+     */
+    private void commitModifyAuthority(LDAPModificationSet mods)
+            throws ELdapException {
+        String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN();
+        LDAPControl[] responseControls;
+        LDAPConnection conn = dbFactory.getConn();
+        synchronized (hostCA) {
+            try {
+                conn.modify(dn, mods, getCommitConstraints());
+                responseControls = conn.getResponseControls();
+            } catch (LDAPException e) {
+                throw new ELdapException("commitAuthority: failed to add 
entry", e);
+            } finally {
+                dbFactory.returnConn(conn);
+            }
+            postCommit(authorityID, responseControls);
+        }
+    }
+
+    private LDAPConstraints getCommitConstraints() {
+        String[] attrs = {"entryUSN", "nsUniqueId"};
+        LDAPConstraints cons = new LDAPConstraints();
+        LDAPPostReadControl control = new LDAPPostReadControl(true, attrs);
+        cons.setServerControls(control);
+        return cons;
+    }
+
+    /**
+     * Post-commit processing of authority to track its entryUSN and nsUniqueId
+     */
+    private void postCommit(AuthorityID aid, LDAPControl[] responseControls) {
+        LDAPPostReadControl control = (LDAPPostReadControl)
+            LDAPUtil.getControl(LDAPPostReadControl.class, responseControls);
+        LDAPEntry entry = control.getEntry();
+
+        LDAPAttribute attr = entry.getAttribute("entryUSN");
+        if (attr != null) {
+            Integer entryUSN = new Integer(attr.getStringValueArray()[0]);
+            entryUSNs.put(aid, entryUSN);
+            CMS.debug("postCommit: new entryUSN = " + entryUSN);
+        }
+
+        attr = entry.getAttribute("nsUniqueId");
+        if (attr != null) {
+            String nsUniqueId = attr.getStringValueArray()[0];
+            nsUniqueIds.put(aid, nsUniqueId);
+            CMS.debug("postCommit: nsUniqueId = " + nsUniqueId);
+        }
+    }
+
     /**
      * Update lightweight authority attributes.
      *
@@ -2709,21 +2741,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         }
 
         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();
-            }
+            commitModifyAuthority(mods);
 
             // update was successful; update CA's state
             authorityEnabled = nextEnabled;
@@ -2731,7 +2749,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         }
     }
 
-    public void deleteAuthority() throws EBaseException {
+    public synchronized void deleteAuthority() throws EBaseException {
         if (isHostAuthority())
             throw new CATypeException("Cannot delete the host CA");
 
@@ -2749,23 +2767,10 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         if (hasSubCAs)
             throw new CANotLeafException("CA with sub-CAs cannot be deleted 
(delete sub-CAs first)");
 
-        caMap.remove(authorityID);
         shutdown();
 
         // delete ldap entry
-        ILdapConnFactory dbFactory = 
CMS.getLdapBoundConnFactory("updateAuthority");
-        dbFactory.init(CMS.getConfigStore().getSubStore("internaldb"));
-        LDAPConnection conn = dbFactory.getConn();
-        String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou="
-            + getId() + "," + getDBSubsystem().getBaseDN();
-        try {
-            conn.delete(dn);
-        } catch (LDAPException e) {
-            throw new ELdapException("Error deleting authority entry '" + dn + 
"': " + e);
-        } finally {
-            dbFactory.returnConn(conn);
-            dbFactory.reset();
-        }
+        deleteAuthorityEntry(authorityID);
 
         CryptoManager cryptoManager;
         try {
@@ -2802,4 +2807,248 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         }
     }
 
+    private void deleteAuthorityEntry(AuthorityID aid) throws ELdapException {
+        String dn = "cn=" + aid.toString() + "," + authorityBaseDN();
+        LDAPConnection conn = dbFactory.getConn();
+        try {
+            conn.delete(dn);
+        } catch (LDAPException e) {
+            throw new ELdapException("Error deleting authority entry: " + dn, 
e);
+        } finally {
+            dbFactory.returnConn(conn);
+        }
+
+        deletedNsUniqueIds.add(nsUniqueIds.get(aid));
+        forgetAuthority(aid);
+    }
+
+    private void checkInitialLoadDone() {
+        if (initialNumAuthorities != null
+                && numAuthoritiesLoaded >= initialNumAuthorities)
+            initialLoadDone.countDown();
+    }
+
+    public void run() {
+        int op = LDAPPersistSearchControl.ADD
+            | LDAPPersistSearchControl.MODIFY
+            | LDAPPersistSearchControl.DELETE
+            | LDAPPersistSearchControl.MODDN;
+        LDAPPersistSearchControl persistCtrl =
+            new LDAPPersistSearchControl(op, false, true, true);
+
+        CMS.debug("authorityMonitor: starting.");
+
+        while (!stopped) {
+            LDAPConnection conn = null;
+            try {
+                conn = dbFactory.getConn();
+                LDAPSearchConstraints cons = conn.getSearchConstraints();
+                cons.setServerControls(persistCtrl);
+                cons.setBatchSize(1);
+                cons.setServerTimeLimit(0 /* seconds */);
+                String[] attrs = {"*", "entryUSN", "nsUniqueId", 
"numSubordinates"};
+                LDAPSearchResults results = conn.search(
+                    authorityBaseDN(), LDAPConnection.SCOPE_SUB,
+                    "(objectclass=*)", attrs, false, cons);
+                while (!stopped && results.hasMoreElements()) {
+                    LDAPEntry entry = results.next();
+
+                    String[] objectClasses =
+                        
entry.getAttribute("objectClass").getStringValueArray();
+                    if 
(Arrays.asList(objectClasses).contains("organizationalUnit")) {
+                        initialNumAuthorities = new Integer(
+                            entry.getAttribute("numSubordinates")
+                                .getStringValueArray()[0]);
+                        checkInitialLoadDone();
+                        continue;
+                    }
+
+                    LDAPEntryChangeControl changeControl = 
(LDAPEntryChangeControl)
+                        LDAPUtil.getControl(
+                            LDAPEntryChangeControl.class, 
results.getResponseControls());
+                    CMS.debug("authorityMonitor: Processed change controls.");
+                    if (changeControl != null) {
+                        int changeType = changeControl.getChangeType();
+                        switch (changeType) {
+                        case LDAPPersistSearchControl.ADD:
+                            CMS.debug("authorityMonitor: ADD");
+                            readAuthority(entry);
+                            // TODO kick off signing key replication via 
custodia
+                            break;
+                        case LDAPPersistSearchControl.DELETE:
+                            CMS.debug("authorityMonitor: DELETE");
+                            handleDELETE(entry);
+                            break;
+                        case LDAPPersistSearchControl.MODIFY:
+                            CMS.debug("authorityMonitor: MODIFY");
+                            // TODO how do we handle authorityID change?
+                            readAuthority(entry);
+                            break;
+                        case LDAPPersistSearchControl.MODDN:
+                            CMS.debug("authorityMonitor: MODDN");
+                            handleMODDN(new DN(changeControl.getPreviousDN()), 
entry);
+                            break;
+                        default:
+                            CMS.debug("authorityMonitor: unknown change type: 
" + changeType);
+                            break;
+                        }
+                    } else {
+                        CMS.debug("authorityMonitor: immediate result");
+                        readAuthority(entry);
+                        numAuthoritiesLoaded += 1;
+                        checkInitialLoadDone();
+                    }
+                }
+            } catch (ELdapException e) {
+                CMS.debug("authorityMonitor: failed to get LDAPConnection. 
Retrying in 1 second.");
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException ex) {
+                    Thread.currentThread().interrupt();
+                }
+            } catch (LDAPException e) {
+                CMS.debug("authorityMonitor: Failed to execute LDAP search for 
lightweight CAs: " + e);
+            } finally {
+                try {
+                    dbFactory.returnConn(conn);
+                } catch (Exception e) {
+                    CMS.debug("authorityMonitor: Error releasing the 
LDAPConnection" + e.toString());
+                }
+            }
+        }
+        CMS.debug("authorityMonitor: stopping.");
+    }
+
+    private synchronized void readAuthority(LDAPEntry entry) {
+        String nsUniqueId =
+            entry.getAttribute("nsUniqueId").getStringValueArray()[0];
+        if (deletedNsUniqueIds.contains(nsUniqueId)) {
+            CMS.debug("readAuthority: ignoring entry with nsUniqueId '"
+                    + nsUniqueId + "' due to deletion");
+            return;
+        }
+
+        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) {
+            CMS.debug("Malformed authority object; required attribute(s) 
missing: " + entry.getDN());
+            return;
+        }
+
+        AuthorityID aid = new AuthorityID((String)
+            aidAttr.getStringValues().nextElement());
+
+        Integer newEntryUSN = new Integer(
+            entry.getAttribute("entryUSN").getStringValueArray()[0]);
+        CMS.debug("readAuthority: new entryUSN = " + newEntryUSN);
+        Integer knownEntryUSN = entryUSNs.get(aid);
+        if (knownEntryUSN != null) {
+            CMS.debug("readAuthority: known entryUSN = " + knownEntryUSN);
+            if (newEntryUSN <= knownEntryUSN) {
+                CMS.debug("readAuthority: data is current");
+                return;
+            }
+        }
+
+        X500Name dn = null;
+        try {
+            dn = new X500Name((String) dnAttr.getStringValues().nextElement());
+        } catch (IOException e) {
+            CMS.debug("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);
+            return;
+        }
+
+        @SuppressWarnings("unused")
+        X500Name parentDN = null;
+        if (parentDNAttr != null) {
+            try {
+                parentDN = new X500Name((String) 
parentDNAttr.getStringValues().nextElement());
+            } catch (IOException e) {
+                CMS.debug("Malformed authority object; invalid 
authorityParentDN: " + entry.getDN());
+                return;
+            }
+        }
+
+        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");
+        }
+
+        try {
+            CertificateAuthority ca = new CertificateAuthority(
+                hostCA, aid, parentAID, keyNick, desc, enabled);
+            caMap.put(aid, ca);
+            entryUSNs.put(aid, newEntryUSN);
+            nsUniqueIds.put(aid, nsUniqueId);
+        } catch (EBaseException e) {
+            CMS.debug("Error initialising lightweight CA: " + e);
+        }
+    }
+
+    private synchronized void handleDELETE(LDAPEntry entry) {
+        LDAPAttribute attr = entry.getAttribute("nsUniqueId");
+        String nsUniqueId = null;
+        if (attr != null)
+            nsUniqueId = attr.getStringValueArray()[0];
+
+        if (deletedNsUniqueIds.remove(nsUniqueId)) {
+            CMS.debug("handleDELETE: delete was already effected");
+            return;
+        }
+
+        AuthorityID aid = null;
+        attr = entry.getAttribute("authorityID");
+        if (attr != null) {
+            aid = new AuthorityID((String) attr.getStringValueArray()[0]);
+            forgetAuthority(aid);
+        }
+    }
+
+    private void forgetAuthority(AuthorityID aid) {
+        caMap.remove(aid);
+        entryUSNs.remove(aid);
+        nsUniqueIds.remove(aid);
+    }
+
+    private synchronized void handleMODDN(DN oldDN, LDAPEntry entry) {
+        DN authorityBase = new DN(authorityBaseDN());
+
+        boolean wasMonitored = oldDN.isDescendantOf(authorityBase);
+        boolean isMonitored = (new 
DN(entry.getDN())).isDescendantOf(authorityBase);
+        if (wasMonitored && !isMonitored) {
+            LDAPAttribute attr = entry.getAttribute("authorityID");
+            if (attr != null) {
+                AuthorityID aid = new 
AuthorityID(attr.getStringValueArray()[0]);
+                forgetAuthority(aid);
+            }
+        } else if (!wasMonitored && isMonitored) {
+            readAuthority(entry);
+        }
+    }
+
 }
diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java 
b/base/ca/src/com/netscape/ca/SigningUnit.java
index 
0ac4b7a1cc640310a4fa06f5eb562218408abfa7..692842a76b9f1669385678c3143c042a58b30499
 100644
--- a/base/ca/src/com/netscape/ca/SigningUnit.java
+++ b/base/ca/src/com/netscape/ca/SigningUnit.java
@@ -43,6 +43,7 @@ import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.base.ISubsystem;
 import com.netscape.certsrv.ca.ECAException;
+import com.netscape.certsrv.ca.CAMissingKeyException;
 import com.netscape.certsrv.common.Constants;
 import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.security.ISigningUnit;
@@ -203,7 +204,7 @@ public final class SigningUnit implements ISigningUnit {
         } catch (ObjectNotFoundException e) {
             CMS.debug("SigningUnit init: debug " + e.toString());
             log(ILogger.LL_FAILURE, 
CMS.getLogMessage("CMSCORE_CA_SIGNING_CERT_NOT_FOUND", e.toString()));
-            throw new 
ECAException(CMS.getUserMessage("CMS_CA_CERT_OBJECT_NOT_FOUND"));
+            throw new 
CAMissingKeyException(CMS.getUserMessage("CMS_CA_CERT_OBJECT_NOT_FOUND"));
         } catch (TokenException e) {
             CMS.debug("SigningUnit init: debug " + e.toString());
             log(ILogger.LL_FAILURE, CMS.getLogMessage("OPERATION_ERROR", 
e.toString()));
diff --git a/base/common/src/com/netscape/certsrv/ca/CAMissingKeyException.java 
b/base/common/src/com/netscape/certsrv/ca/CAMissingKeyException.java
new file mode 100644
index 
0000000000000000000000000000000000000000..8f5e1e72a3cdb31b1f12985d9e52371277901ae1
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/CAMissingKeyException.java
@@ -0,0 +1,15 @@
+package com.netscape.certsrv.ca;
+
+/**
+ * Exception to throw when a (sub-)CA's signing key is not (yet)
+ * present in the local NSSDB.
+ */
+public class CAMissingKeyException extends ECAException {
+
+    private static final long serialVersionUID = -364157165997677925L;
+
+    public CAMissingKeyException(String msgFormat) {
+        super(msgFormat);
+    }
+
+}
-- 
2.5.0

From 3295f24c9d35819b9426be910a11451940c3236b Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 16 Mar 2016 13:07:43 +1100
Subject: [PATCH 85/86] Lightweight CAs: set DN based on data from LDAP

When initialising a lightweight CA, if we do not have the signing
cert and key in the NSSDB yet, we do not initialise the DN.  This
causes NPE in other code that expects getX500Name() to return a
value, e.g. REST API to list or show CA.

To work around this, when loading lightweight CAs set the DN based
on the 'authorityDN' value stored in its LDAP entry.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 base/ca/src/com/netscape/ca/CertificateAuthority.java | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
d8177e1708dca15ae97c5c01534215a64dbe34d0..d30ccaca9dec95e2dd15c16e201a88a53ddfb9ce
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -323,6 +323,7 @@ public class CertificateAuthority
      */
     private CertificateAuthority(
             CertificateAuthority hostCA,
+            X500Name dn,
             AuthorityID aid,
             AuthorityID parentAID,
             String signingKeyNickname,
@@ -331,6 +332,11 @@ public class CertificateAuthority
             ) throws EBaseException {
         setId(hostCA.getId());
         this.hostCA = hostCA;
+
+        // cert and key may not have been replicated to local nssdb
+        // yet, so set DN based on data from LDAP
+        this.mName = dn;
+
         this.authorityID = aid;
         this.authorityParentID = parentAID;
         this.authorityDescription = authorityDescription;
@@ -2576,7 +2582,8 @@ public class CertificateAuthority
         }
 
         return new CertificateAuthority(
-            hostCA, aid, this.authorityID, nickname, description, true);
+            hostCA, subjectX500Name,
+            aid, this.authorityID, nickname, description, true);
     }
 
     /**
@@ -3001,7 +3008,7 @@ public class CertificateAuthority
 
         try {
             CertificateAuthority ca = new CertificateAuthority(
-                hostCA, aid, parentAID, keyNick, desc, enabled);
+                hostCA, dn, aid, parentAID, keyNick, desc, enabled);
             caMap.put(aid, ca);
             entryUSNs.put(aid, newEntryUSN);
             nsUniqueIds.put(aid, nsUniqueId);
-- 
2.5.0

From b1553fbfe2b3c3c57d60618eefa41399931c7e79 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 16 Mar 2016 16:48:43 +1100
Subject: [PATCH 86/86] Lightweight CAs: indicate when CA does not yet have
 keys

When a lightweight CA is created, clones will initialise a local
object when the LDAP replication takes place, however, the signing
keys will not yet have been replicated.  Therefore, indicate CA
readiness in authority data and respond appropriately (HTTP 503)
when signing operations are attempted.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 base/ca/src/com/netscape/ca/CertificateAuthority.java   | 12 +++++++++---
 .../org/dogtagpki/server/ca/rest/AuthorityService.java  | 11 ++++++++---
 .../dogtagpki/server/ca/rest/CertRequestService.java    |  4 ++++
 .../com/netscape/certsrv/authority/AuthorityData.java   | 17 ++++++++++++++++-
 .../certsrv/base/ServiceUnavailableException.java       | 17 +++++++++++++++++
 .../com/netscape/certsrv/ca/ICertificateAuthority.java  |  5 +++++
 .../com/netscape/cmstools/authority/AuthorityCLI.java   |  1 +
 .../netscape/cmstools/authority/AuthorityCreateCLI.java |  2 +-
 .../cmstools/authority/AuthorityDisableCLI.java         |  2 +-
 .../netscape/cmstools/authority/AuthorityEnableCLI.java |  2 +-
 .../com/netscape/cms/servlet/cert/RequestProcessor.java |  3 +++
 11 files changed, 66 insertions(+), 10 deletions(-)
 create mode 100644 
base/common/src/com/netscape/certsrv/base/ServiceUnavailableException.java

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
d30ccaca9dec95e2dd15c16e201a88a53ddfb9ce..64d94d384d3874de856ca3fd42dc36aff8d136cd
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -349,9 +349,16 @@ public class CertificateAuthority
         return hostCA == this;
     }
 
-    private void ensureEnabled() throws CADisabledException {
+    private void ensureEnabled()
+            throws CADisabledException, CAMissingKeyException {
         if (!authorityEnabled)
             throw new CADisabledException("Authority is disabled");
+        if (!isReady())
+            throw new CAMissingKeyException("Authority does not yet have 
keys");
+    }
+
+    public boolean isReady() {
+        return hasKeys;
     }
 
     public boolean getAuthorityEnabled() {
@@ -2470,8 +2477,7 @@ public class CertificateAuthority
             String subjectDN, String description)
             throws EBaseException {
 
-        if (!authorityEnabled)
-            throw new CADisabledException("Parent CA is disabled");
+        ensureEnabled();
 
         // check requested DN
         X500Name subjectX500Name = null;
diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java 
b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
index 
fa9e1038b7b3ca718a7593052a852c31f48545f7..78c97abc7e456ad4929a88ce5a93f41783ae0517
 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
@@ -43,9 +43,11 @@ 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.base.ServiceUnavailableException;
 import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.ca.CAEnabledException;
 import com.netscape.certsrv.ca.CADisabledException;
+import com.netscape.certsrv.ca.CAMissingKeyException;
 import com.netscape.certsrv.ca.CANotFoundException;
 import com.netscape.certsrv.ca.CANotLeafException;
 import com.netscape.certsrv.ca.CATypeException;
@@ -207,6 +209,8 @@ public class AuthorityService extends PKIService implements 
AuthorityResource {
             auditParams.put("exception", e.toString());
             audit(ILogger.FAILURE, OpDef.OP_ADD, "<unknown>", auditParams);
             throw new ConflictingOperationException(e.toString());
+        } catch (CAMissingKeyException e) {
+            throw new ServiceUnavailableException(e.toString());
         } catch (Exception e) {
             CMS.debug(e);
             auditParams.put("exception", e.toString());
@@ -261,14 +265,14 @@ public class AuthorityService extends PKIService 
implements AuthorityResource {
     public Response enableCA(String aidString) {
         return modifyCA(
             aidString,
-            new AuthorityData(null, null, null, null, true, null));
+            new AuthorityData(null, null, null, null, true, null, null));
     }
 
     @Override
     public Response disableCA(String aidString) {
         return modifyCA(
             aidString,
-            new AuthorityData(null, null, null, null, false, null));
+            new AuthorityData(null, null, null, null, false, null, null));
     }
 
     @Override
@@ -322,7 +326,8 @@ public class AuthorityService extends PKIService implements 
AuthorityResource {
             ca.getAuthorityID().toString(),
             parentAID != null ? parentAID.toString() : null,
             ca.getAuthorityEnabled(),
-            ca.getAuthorityDescription()
+            ca.getAuthorityDescription(),
+            ca.isReady()
         );
     }
 
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 
cddbeb1ba47741673ab5eb3d22e2bf7c53c4c33d..6e7c7ae7c57bf72f15cb6e3cab33e47abfe96914
 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java
@@ -43,9 +43,11 @@ 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.ServiceUnavailableException;
 import com.netscape.certsrv.base.UnauthorizedException;
 import com.netscape.certsrv.ca.AuthorityID;
 import com.netscape.certsrv.ca.CADisabledException;
+import com.netscape.certsrv.ca.CAMissingKeyException;
 import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.cert.CertEnrollmentRequest;
 import com.netscape.certsrv.cert.CertRequestInfo;
@@ -252,6 +254,8 @@ public class CertRequestService extends PKIService 
implements CertRequestResourc
         } catch (CADisabledException e) {
             CMS.debug("changeRequestState: CA disabled: " + e);
             throw new ConflictingOperationException(e.toString());
+        } catch (CAMissingKeyException e) {
+            throw new ServiceUnavailableException(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
index 
2312c39895c09a4b7dbf994d43c2c068eeaec2d4..84679567eb527cbf9fedd21705a72ca9c1a34a93
 100644
--- a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java
@@ -95,6 +95,19 @@ public class AuthorityData {
     }
 
 
+    /**
+     * Whether the CA is ready to perform signing operations.
+     *
+     * This is a read-only attribute; it cannot be set by the user.
+     */
+    @XmlAttribute
+    protected Boolean ready;
+
+    public Boolean getReady() {
+        return ready;
+    }
+
+
     protected Link link;
 
     public Link getLink() {
@@ -111,13 +124,15 @@ public class AuthorityData {
     public AuthorityData(
             Boolean isHostAuthority,
             String dn, String id, String parentID,
-            Boolean enabled, String description) {
+            Boolean enabled, String description,
+            Boolean ready) {
         this.isHostAuthority = isHostAuthority;
         this.dn = dn;
         this.id = id;
         this.parentID = parentID;
         this.enabled = enabled;
         this.description = description;
+        this.ready = ready;
     }
 
 }
diff --git 
a/base/common/src/com/netscape/certsrv/base/ServiceUnavailableException.java 
b/base/common/src/com/netscape/certsrv/base/ServiceUnavailableException.java
new file mode 100644
index 
0000000000000000000000000000000000000000..0ee9c8a08d5144bf9cd7e13c1c4bf7d870a7d846
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/base/ServiceUnavailableException.java
@@ -0,0 +1,17 @@
+package com.netscape.certsrv.base;
+
+import javax.ws.rs.core.Response;
+
+public class ServiceUnavailableException extends PKIException {
+
+    private static final long serialVersionUID = -9160776882517621347L;
+
+    public ServiceUnavailableException(String message) {
+        super(Response.Status.SERVICE_UNAVAILABLE, message);
+    }
+
+    public ServiceUnavailableException(String message, Throwable cause) {
+        super(Response.Status.SERVICE_UNAVAILABLE, message, cause);
+    }
+
+}
diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java 
b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
index 
376654e98b0276a2c2add8b98fb30bf339165b87..81bb9c3493c0159561183bc1ffd8fa5f8ea3d644
 100644
--- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
+++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
@@ -545,6 +545,11 @@ public interface ICertificateAuthority extends ISubsystem {
     public boolean getAuthorityEnabled();
 
     /**
+     * Return whether CA is ready to perform signing operations.
+     */
+    public boolean isReady();
+
+    /**
      * Return CA description.  May be null.
      */
     public String getAuthorityDescription();
diff --git 
a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java 
b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
index 
4fbcfef760086928b2e0e75fe4fc56f1b249b5fd..ac06ea24ce824ad1b4be29a4176658caa9302e89
 100644
--- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
@@ -45,6 +45,7 @@ public class AuthorityCLI extends CLI {
         if (parentAID != null)
             System.out.println("  Parent ID:      " + data.getParentID());
         System.out.println("  Enabled:        " + data.getEnabled());
+        System.out.println("  Ready to sign:  " + data.getReady());
         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
index 
d1688fbd1933a567940164d86ac726df1489f7d2..3c36ac756aeedde8d89505be871da3555b548434
 100644
--- 
a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java
+++ 
b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java
@@ -81,7 +81,7 @@ public class AuthorityCreateCLI extends CLI {
 
         String dn = cmdArgs[0];
         AuthorityData data = new AuthorityData(
-            null, dn, null, parentAIDString, true /* enabled */, desc);
+            null, dn, null, parentAIDString, true /* enabled */, desc, null);
         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
index 
fc4cbf30be947233a9289089bb25bef70d532bb6..85b38f0810a6cff3a8c2293feab3153c85e8fee2
 100644
--- 
a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java
+++ 
b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java
@@ -48,7 +48,7 @@ public class AuthorityDisableCLI extends CLI {
         }
 
         AuthorityData data = new AuthorityData(
-            null, null, cmdArgs[0], null, false, null);
+            null, null, cmdArgs[0], null, false, null, 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
index 
f6fdab12f527975d2d0688b65968bfb992b5a97a..936edca599b7d6391370284535584953f0180bc8
 100644
--- 
a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java
+++ 
b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java
@@ -48,7 +48,7 @@ public class AuthorityEnableCLI extends CLI {
         }
 
         AuthorityData data = new AuthorityData(
-            null, null, cmdArgs[0], null, true, null);
+            null, null, cmdArgs[0], null, true, null, null);
         data = authorityCLI.authorityClient.modifyCA(data);
         AuthorityCLI.printAuthorityData(data);
     }
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 
8558ec23f6489407dc5f41951a363d22548851c0..ca88a74839f81f10cebbf2c80f5819940b9fb5fe
 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
@@ -38,6 +38,7 @@ 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.CAMissingKeyException;
 import com.netscape.certsrv.ca.CANotFoundException;
 import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.cert.CertReviewResponse;
@@ -353,6 +354,8 @@ public class RequestProcessor extends CertProcessor {
         if (!ca.getAuthorityEnabled())
             // authority was disabled after request was accepted
             throw new CADisabledException("CA '" + aidString + "' is 
disabled");
+        if (!ca.isReady())
+            throw new CAMissingKeyException("CA does not yet have signing 
key");
     }
 
     /**
-- 
2.5.0

_______________________________________________
Pki-devel mailing list
Pki-devel@redhat.com
https://www.redhat.com/mailman/listinfo/pki-devel

Reply via email to