On Thu, Apr 14, 2016 at 09:04:31AM +1000, Fraser Tweedale wrote:
> On Wed, Apr 13, 2016 at 05:26:44PM -0400, Ade Lee wrote:
> > Still reviewing ..
> > 
> > See comment on 87.  ACK on 88,89,90,91,92,93, 94, 95.
> > 
> > Ade
> > 
> > On Mon, 2016-04-11 at 12:32 +1000, Fraser Tweedale wrote:
> > > Thanks for review, Ade.  Comments to specific feedback inline.
> > > Rebased and updated patches attached.  The substantive changes are:
> > > 
> > > - KeyRetriever implementations are now required NOT to import the
> > >   key themselves.  Instead the API is updated with
> > >   KeyRetriever.retrieveKey returning a Result, which contains PKCS
> > >   #12 data and password for same.
> > > 
> > > - KeyRetrieverRunner reads the Result and imports the PKCS #12 into
> > >   NSSDB.
> > > 
> > > - Added new patch 0097 which provides the IPACustodiaKeyRetriever
> > >   and assoicated Python helper script.  It depends on an unmerged
> > >   FreeIPA patch[1] as well as a particular principal and associated
> > >   keytab and Custodia keys existing.  I'm working on FreeIPA updates
> > >   to satisfy these requirements automatically on install or upgrade
> > >   but if you want to test this patch LMK and I'll provide detailed
> > >   instructions.
> > > 
> > >   [1] https://www.redhat.com/archives/freeipa-devel/2016-April/msg000
> > > 55.html
> > > 
> > > Other comments inline.
> > > 
> > > Cheers,
> > > Fraser
> > > 
> > > On Fri, Apr 08, 2016 at 11:16:19AM -0400, Ade Lee wrote:
> > > > 
> > > > 0087
> > > > 
> > > > 1. In SigningUnit.java -- you catch an ObjectNotFound exception and
> > > > rethrow that as a CAMissingKey exception.  Is that the only way the
> > > > ObjectNotFound exception can be thrown?  What if the key is present
> > > > but
> > > > the cert is not?  Can we refactor here to ensure that the correct
> > > > exception is thrown?
> > > > 
> > > One can't get additional info out of ObjectNotFound without
> > > inspecting the String message, which I'm not comfortable doing.  The
> > > key retrieval system should import key and cert at same time so I've
> > > renamed the exception to CAMissingKeyOrCert for clarity.
> > > 
> > 
> > Well, you can always nest exceptions like so :
> > 
> >         mToken.login(cb); // ONE_TIME by default.
> > 
> >             try {
> >                 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");
> >             } catch (ObjectNotFoundException e) {
> >                 throw new CAMissingCertException();
> >             }
> > 
> >             try {
> >                 mPrivk = mManager.findPrivKeyByCert(mCert);
> >                 CMS.debug("Got private key from cert");
> >             } catch (ObjectNotFoundException e) {
> >                throw new CAMissingKeyException();
> >             }
> >             ....
> > 
> > The only reason that I suggest this is that I could imagine this kind
> > of differentiation being useful in debugging failed custodia
> > replications.  If you think otherwise, I'm prepare to be convinced
> > otherwise.
> > 
> I think a scenario where we get key but not cert, or vice versa, is
> unlikely (Custodia gives us a PKCS #12 file with both).  However,
> your suggestion should work and it is a relatively small change.
> I'll cut a new patchset with this change today, along with the
> rebase.
> 
Updated and rebased patches attached.

The suggested changes were made to 0087.  This also resulted in
changes to patch 0094 (indicate when CA does not yet have keys).

No substantive changes to any other patches.

Cheers,
Fraser
From 6d72a9c7fc067df42a3259fc5ea87b65e94f76ad Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Thu, 31 Mar 2016 12:46:03 +1100
Subject: [PATCH 87/96] Lightweight CAs: add exceptions for missing signing key
 or cert

Add the CAMissingCertException and CAMissingKeyException classes and
throw when signing unit initialisation fails due to a missing
object.  In CertificateAuthority, store the exception if it occurs
for possible re-throwing later.  Also add the private 'hasKeys'
field for internal use.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 .../src/com/netscape/ca/CertificateAuthority.java  | 14 +++++++++++++-
 base/ca/src/com/netscape/ca/SigningUnit.java       | 22 ++++++++++++++++------
 .../certsrv/ca/CAMissingCertException.java         | 15 +++++++++++++++
 .../netscape/certsrv/ca/CAMissingKeyException.java | 15 +++++++++++++++
 4 files changed, 59 insertions(+), 7 deletions(-)
 create mode 100644 
base/common/src/com/netscape/certsrv/ca/CAMissingCertException.java
 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 
2e1f9d7c8f2202d1e755537caa3b10f3b8c6e014..b087f26b6a43c4806b826e368d14feffdea85e56
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -77,6 +77,8 @@ 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.CAMissingCertException;
+import com.netscape.certsrv.ca.CAMissingKeyException;
 import com.netscape.certsrv.ca.CANotFoundException;
 import com.netscape.certsrv.ca.CANotLeafException;
 import com.netscape.certsrv.ca.CATypeException;
@@ -188,6 +190,8 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
     protected AuthorityID authorityParentID = null;
     protected String authorityDescription = null;
     protected boolean authorityEnabled = true;
+    private boolean hasKeys = false;
+    private ECAException signingUnitException = null;
 
     protected ISubsystem mOwner = null;
     protected IConfigStore mConfig = null;
@@ -1358,7 +1362,15 @@ 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;
+                signingUnitException = null;
+            } catch (CAMissingCertException | CAMissingKeyException e) {
+                CMS.debug("CA signing key and cert not (yet) present in 
NSSDB");
+                signingUnitException = e;
+                return;
+            }
             CMS.debug("CA signing unit inited");
 
             // for identrus
diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java 
b/base/ca/src/com/netscape/ca/SigningUnit.java
index 
0ac4b7a1cc640310a4fa06f5eb562218408abfa7..60bd84e3b365b8ea4db53314427bf525668597cb
 100644
--- a/base/ca/src/com/netscape/ca/SigningUnit.java
+++ b/base/ca/src/com/netscape/ca/SigningUnit.java
@@ -43,6 +43,8 @@ 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.CAMissingCertException;
+import com.netscape.certsrv.ca.CAMissingKeyException;
 import com.netscape.certsrv.common.Constants;
 import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.security.ISigningUnit;
@@ -165,14 +167,22 @@ 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());
+            try {
+                mCert = mManager.findCertByNickname(mNickname);
+                CMS.debug("Found cert by nickname: '" + mNickname + "' with 
serial number: " + mCert.getSerialNumber());
+            } catch (ObjectNotFoundException e) {
+                throw new 
CAMissingCertException(CMS.getUserMessage("CMS_CA_CERT_OBJECT_NOT_FOUND"));
+            }
 
             mCertImpl = new X509CertImpl(mCert.getEncoded());
             CMS.debug("converted to x509CertImpl");
 
-            mPrivk = mManager.findPrivKeyByCert(mCert);
-            CMS.debug("Got private key from cert");
+            try {
+                mPrivk = mManager.findPrivKeyByCert(mCert);
+                CMS.debug("Got private key from cert");
+            } catch (ObjectNotFoundException e) {
+                throw new 
CAMissingKeyException(CMS.getUserMessage("CMS_CA_CERT_OBJECT_NOT_FOUND"));
+            }
 
             mPubk = mCert.getPublicKey();
             CMS.debug("Got public key from cert");
@@ -200,10 +210,10 @@ public final class SigningUnit implements ISigningUnit {
             CMS.debug("SigningUnit init: debug " + e.toString());
             log(ILogger.LL_FAILURE, 
CMS.getLogMessage("CMSCORE_CA_SIGNING_TOKEN_NOT_FOUND", tokenname, 
e.toString()));
             throw new 
ECAException(CMS.getUserMessage("CMS_CA_TOKEN_NOT_FOUND", tokenname));
-        } catch (ObjectNotFoundException e) {
+        } catch (CAMissingCertException | CAMissingKeyException 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 e;  // re-throw
         } 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/CAMissingCertException.java 
b/base/common/src/com/netscape/certsrv/ca/CAMissingCertException.java
new file mode 100644
index 
0000000000000000000000000000000000000000..49c5063f2a79a6fa2f977054dd2363e128c80d8f
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/ca/CAMissingCertException.java
@@ -0,0 +1,15 @@
+package com.netscape.certsrv.ca;
+
+/**
+ * Exception to throw when a (sub-)CA's signing certificate is not
+ * (yet) present in the local NSSDB.
+ */
+public class CAMissingCertException extends ECAException {
+
+    private static final long serialVersionUID = 7261805480088539689L;
+
+    public CAMissingCertException(String msgFormat) {
+        super(msgFormat);
+    }
+
+}
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.5

From 908c75dcefcb5030b2e3328835c506bf4c53704f Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Thu, 31 Mar 2016 12:51:18 +1100
Subject: [PATCH 88/96] Lightweight CAs: use static db connection factory

Use a static database connection factory that is initialised by the
host authority and used by all CertificateAuthority instances.

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

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
b087f26b6a43c4806b826e368d14feffdea85e56..a44482a77fdd9b953b832529c85b825b25346da2
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -183,6 +183,11 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
 
     public final static OBJECT_IDENTIFIER OCSP_NONCE = new 
OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2");
 
+    /* The static conn factory is initialised by the host authority's
+     * 'init' method, before any lightweight CAs are instantiated
+     */
+    private static ILdapConnFactory dbFactory = null;
+
     private static final Map<AuthorityID, ICertificateAuthority> caMap =
         Collections.synchronizedSortedMap(new TreeMap<AuthorityID, 
ICertificateAuthority>());
     protected CertificateAuthority hostCA = null;
@@ -426,6 +431,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();
@@ -1972,8 +1982,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
      * 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()
@@ -2059,7 +2067,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             }
         } finally {
             dbFactory.returnConn(conn);
-            dbFactory.reset();
         }
 
         if (haveLightweightCAsContainer && !foundHostAuthority) {
@@ -2543,8 +2550,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         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 {
@@ -2612,7 +2617,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             throw new EBaseException("Error adding authority entry to 
database: " + e);
         } finally {
             dbFactory.returnConn(conn);
-            dbFactory.reset();
         }
 
         return new CertificateAuthority(
@@ -2660,8 +2664,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         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 {
@@ -2670,7 +2672,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             throw new ELdapException("Error adding host authority entry to 
database: " + e);
         } finally {
             dbFactory.returnConn(conn);
-            dbFactory.reset();
         }
 
         this.authorityID = aid;
@@ -2729,8 +2730,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
                 + 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);
@@ -2738,7 +2737,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
                 throw new EBaseException("Error adding authority entry to 
database: " + e);
             } finally {
                 dbFactory.returnConn(conn);
-                dbFactory.reset();
             }
 
             // update was successful; update CA's state
@@ -2769,8 +2767,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         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();
@@ -2780,7 +2776,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             throw new ELdapException("Error deleting authority entry '" + dn + 
"': " + e);
         } finally {
             dbFactory.returnConn(conn);
-            dbFactory.reset();
         }
 
         CryptoManager cryptoManager;
-- 
2.5.5

From 536312af6798ca688556f559f8bdc76e2ba53e4d Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Thu, 31 Mar 2016 13:08:48 +1100
Subject: [PATCH 89/96] Lightweight CAs: avoid repeat definition of authorities
 DN

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

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
a44482a77fdd9b953b832529c85b825b25346da2..951521d04d8a80dd719264b6f285e3fdbb75d696
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -525,6 +525,11 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         }
     }
 
+    private String authorityBaseDN() {
+        return "ou=authorities,ou=" + getId()
+            + "," + getDBSubsystem().getBaseDN();
+    }
+
     private void initCRLPublisher() throws EBaseException {
         // instantiate CRL publisher
         if (!isHostAuthority()) {
@@ -1984,14 +1989,12 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
     private void loadLightweightCAs() throws EBaseException {
         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,
+                authorityBaseDN(), LDAPConnection.SCOPE_ONE,
                 "(objectclass=authority)", null, false);
 
             while (results.hasMoreElements()) {
@@ -2059,7 +2062,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         } catch (LDAPException e) {
             if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) {
                 CMS.debug(
-                    "Missing lightweight CAs container '" + searchDN
+                    "Missing lightweight CAs container '" + authorityBaseDN()
                     + "'.  Disabling lightweight CAs.");
                 haveLightweightCAsContainer = false;
             } else {
@@ -2522,8 +2525,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 {
@@ -2641,8 +2643,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();
@@ -2726,8 +2727,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         }
 
         if (mods.size() > 0) {
-            String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou="
-                + getId() + "," + getDBSubsystem().getBaseDN();
+            String dn = "cn=" + authorityID.toString() + "," + 
authorityBaseDN();
 
             // connect to database
             LDAPConnection conn = dbFactory.getConn();
@@ -2768,8 +2768,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
 
         // delete ldap entry
         LDAPConnection conn = dbFactory.getConn();
-        String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou="
-            + getId() + "," + getDBSubsystem().getBaseDN();
+        String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN();
         try {
             conn.delete(dn);
         } catch (LDAPException e) {
-- 
2.5.5

From 18ed063edde8608f2ef30f62c118e24b835f1d83 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Thu, 31 Mar 2016 13:35:49 +1100
Subject: [PATCH 90/96] Lightweight CAs: move host authority creation out of
 load method

To reduce the amount of code that would be run in the persistent
search thread, extract the host authority entry creation out of the
'loadLightweightCAs' method, into 'CertificateAuthority.init'.

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

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
951521d04d8a80dd719264b6f285e3fdbb75d696..6afcc1037647312aa435bbb8187b7c0df0be3405
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -292,6 +292,8 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
     private boolean mUseNonces = true;
     private int mMaxNonces = 100;
 
+    private static boolean foundHostAuthority = false;
+
     /**
      * Constructs a CA subsystem.
      */
@@ -514,9 +516,17 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             // being functional.
             initCRL();
 
-            if (isHostAuthority())
+            if (isHostAuthority() && haveLightweightCAsContainer()) {
                 loadLightweightCAs();
 
+                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;
@@ -530,6 +540,19 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             + "," + 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()) {
@@ -1990,8 +2013,6 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         LDAPConnection conn = dbFactory.getConn();
 
         LDAPSearchResults results = null;
-        boolean foundHostAuthority = false;
-        boolean haveLightweightCAsContainer = true;
         try {
             results = conn.search(
                 authorityBaseDN(), LDAPConnection.SCOPE_ONE,
@@ -2060,23 +2081,10 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
                 caMap.put(aid, ca);
             }
         } catch (LDAPException e) {
-            if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) {
-                CMS.debug(
-                    "Missing lightweight CAs container '" + authorityBaseDN()
-                    + "'.  Disabling lightweight CAs.");
-                haveLightweightCAsContainer = false;
-            } else {
-                throw new ECAException("Failed to execute LDAP search for 
lightweight CAs: " + e);
-            }
+            throw new ECAException("Failed to execute LDAP search for 
lightweight CAs: " + e);
         } finally {
             dbFactory.returnConn(conn);
         }
-
-        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() {
-- 
2.5.5

From 6a2195d6dab0dd4d20155177892af88aeb67d517 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Thu, 31 Mar 2016 15:37:51 +1100
Subject: [PATCH 91/96] Lightweight CAs: extract LDAP commit/delete methods

LDAP code to add, modify and delete authority entries exists in
multiple places.  Extract these methods to remove this duplication
and provide a cleaner basis for upcoming implementation of
replication handling.

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

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
6afcc1037647312aa435bbb8187b7c0df0be3405..6608b393d108303eedb1812dcda72bc93ab4c8f2
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -2559,74 +2559,70 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             attrSet.add(new LDAPAttribute("description", description));
         LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet);
 
-        // connect to database
-        LDAPConnection conn = dbFactory.getConn();
+        addAuthorityEntry(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);
+            throw new ECAException("Error creating lightweight CA certificate: 
" + e);
         }
 
         return new CertificateAuthority(
@@ -2672,22 +2668,45 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs);
         LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet);
 
-        // connect to database
-        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);
-        }
+        addAuthorityEntry(aid, ldapEntry);
 
         this.authorityID = aid;
         this.authorityDescription = desc;
         return aid;
     }
 
+    private void addAuthorityEntry(AuthorityID aid, LDAPEntry entry)
+            throws ELdapException {
+        LDAPConnection conn = dbFactory.getConn();
+        synchronized (hostCA) {
+            try {
+                conn.add(entry);
+            } catch (LDAPException e) {
+                throw new ELdapException("addAuthorityEntry: failed to add 
entry", e);
+            } finally {
+                dbFactory.returnConn(conn);
+            }
+        }
+    }
+
+    /**
+     * Modify _this_ authority with the given modification set.
+     */
+    private void modifyAuthorityEntry(LDAPModificationSet mods)
+            throws ELdapException {
+        String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN();
+        LDAPConnection conn = dbFactory.getConn();
+        synchronized (hostCA) {
+            try {
+                conn.modify(dn, mods);
+            } catch (LDAPException e) {
+                throw new ELdapException("modifyAuthorityEntry: failed to 
modify entry", e);
+            } finally {
+                dbFactory.returnConn(conn);
+            }
+        }
+    }
+
     /**
      * Update lightweight authority attributes.
      *
@@ -2735,17 +2754,7 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         }
 
         if (mods.size() > 0) {
-            String dn = "cn=" + authorityID.toString() + "," + 
authorityBaseDN();
-
-            // connect to database
-            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);
-            }
+            modifyAuthorityEntry(mods);
 
             // update was successful; update CA's state
             authorityEnabled = nextEnabled;
@@ -2753,7 +2762,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");
 
@@ -2771,19 +2780,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
-        LDAPConnection conn = dbFactory.getConn();
-        String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN();
-        try {
-            conn.delete(dn);
-        } catch (LDAPException e) {
-            throw new ELdapException("Error deleting authority entry '" + dn + 
"': " + e);
-        } finally {
-            dbFactory.returnConn(conn);
-        }
+        deleteAuthorityEntry(authorityID);
 
         CryptoManager cryptoManager;
         try {
@@ -2820,4 +2820,24 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
         }
     }
 
+    private void deleteAuthorityEntry(AuthorityID aid) throws ELdapException {
+        String dn = "cn=" + aid.toString() + "," + authorityBaseDN();
+        LDAPConnection conn = dbFactory.getConn();
+        synchronized (hostCA) {
+            try {
+                conn.delete(dn);
+            } catch (LDAPException e) {
+                throw new ELdapException("Error deleting authority entry: " + 
dn, e);
+            } finally {
+                dbFactory.returnConn(conn);
+            }
+
+            forgetAuthority(aid);
+        }
+    }
+
+    private void forgetAuthority(AuthorityID aid) {
+        caMap.remove(aid);
+    }
+
 }
-- 
2.5.5

From a39499f08966a517d52c97ef0cd54d8e6f098fb9 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 9 Mar 2016 02:18:41 -0500
Subject: [PATCH 92/96] 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  | 429 ++++++++++++++++-----
 1 file changed, 342 insertions(+), 87 deletions(-)

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
6608b393d108303eedb1812dcda72bc93ab4c8f2..3bda33f277458df0a5ef54166a1c7988104bb19a
 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;
@@ -128,6 +130,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;
@@ -148,11 +152,18 @@ import com.netscape.cmsutil.ocsp.UnknownInfo;
 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;
@@ -178,7 +189,8 @@ import netscape.security.x509.X509Key;
  * @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");
@@ -292,7 +304,26 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
     private boolean mUseNonces = true;
     private int mMaxNonces = 100;
 
+    /* Variables to manage loading and tracking of lightweight CAs
+     *
+     * The initialLoadDone latch causes the host authority's 'init'
+     * method to block until the monitor thread has finished the
+     * initial loading of lightweight CAs.
+     *
+     * In other words: the "server startup" cannot complete until
+     * all the lightweight CAs that exist at start time are loaded.
+     */
+    private static 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.
@@ -517,7 +548,13 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
             initCRL();
 
             if (isHostAuthority() && haveLightweightCAsContainer()) {
-                loadLightweightCAs();
+                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");
@@ -738,6 +775,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.
+        }
     }
 
     /**
@@ -2004,89 +2057,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 {
-        LDAPConnection conn = dbFactory.getConn();
-
-        LDAPSearchResults results = null;
-        try {
-            results = conn.search(
-                authorityBaseDN(), 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) {
-            throw new ECAException("Failed to execute LDAP search for 
lightweight CAs: " + e);
-        } finally {
-            dbFactory.returnConn(conn);
-        }
-    }
-
     public String getOfficialName() {
         return OFFICIAL_NAME;
     }
@@ -2677,15 +2647,18 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
 
     private void addAuthorityEntry(AuthorityID aid, LDAPEntry entry)
             throws ELdapException {
+        LDAPControl[] responseControls;
         LDAPConnection conn = dbFactory.getConn();
         synchronized (hostCA) {
             try {
-                conn.add(entry);
+                conn.add(entry, getCommitConstraints());
+                responseControls = conn.getResponseControls();
             } catch (LDAPException e) {
                 throw new ELdapException("addAuthorityEntry: failed to add 
entry", e);
             } finally {
                 dbFactory.returnConn(conn);
             }
+            postCommit(aid, responseControls);
         }
     }
 
@@ -2695,15 +2668,49 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
     private void modifyAuthorityEntry(LDAPModificationSet mods)
             throws ELdapException {
         String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN();
+        LDAPControl[] responseControls;
         LDAPConnection conn = dbFactory.getConn();
         synchronized (hostCA) {
             try {
-                conn.modify(dn, mods);
+                conn.modify(dn, mods, getCommitConstraints());
+                responseControls = conn.getResponseControls();
             } catch (LDAPException e) {
                 throw new ELdapException("modifyAuthorityEntry: failed to 
modify 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);
         }
     }
 
@@ -2832,12 +2839,260 @@ public class CertificateAuthority implements 
ICertificateAuthority, ICertAuthori
                 dbFactory.returnConn(conn);
             }
 
+            String nsUniqueId = nsUniqueIds.get(aid);
+            if (nsUniqueId != null)
+                deletedNsUniqueIds.add(nsUniqueId);
+            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();
+
+                    /* This behaviour requires detailed explanation.
+                     *
+                     * We want to block startup until all the
+                     * lightweight CAs existing at startup time are
+                     * loaded.  To do this, we need to know how many
+                     * authority entries there are.  And we must do
+                     * this atomically - we cannot issue two LDAP
+                     * searches in case things change.
+                     *
+                     * Therefore, we do a subtree search from the
+                     * authority container.  When we find the
+                     * container (objectClass=organizationalUnit),
+                     * we set initialNumAuthorities to the value of
+                     * its numSubordinates attribute.
+                     *
+                     * We increment numAuthoritiesLoaded for each
+                     * authority entry.  When numAuthoritiesLoaded
+                     * equals initialNumAuthorities, we unlock the
+                     * initialLoadDone latch.
+                     */
+                    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);
+        }
     }
 
 }
-- 
2.5.5

From 28bc4ed903bc9e2618390ec412602d889e28354b Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 16 Mar 2016 13:07:43 +1100
Subject: [PATCH 93/96] 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 
3bda33f277458df0a5ef54166a1c7988104bb19a..60f6b36219fda09a246013707c245a00d89ea84c
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -337,6 +337,7 @@ public class CertificateAuthority
      */
     private CertificateAuthority(
             CertificateAuthority hostCA,
+            X500Name dn,
             AuthorityID aid,
             AuthorityID parentAID,
             String signingKeyNickname,
@@ -345,6 +346,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;
@@ -2596,7 +2602,8 @@ public class CertificateAuthority
         }
 
         return new CertificateAuthority(
-            hostCA, aid, this.authorityID, nickname, description, true);
+            hostCA, subjectX500Name,
+            aid, this.authorityID, nickname, description, true);
     }
 
     /**
@@ -3045,7 +3052,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.5

From 8f93e60e0057b0706c5d5ad762d7ff7ce20b7b39 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 16 Mar 2016 16:48:43 +1100
Subject: [PATCH 94/96] 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
---
 .../src/com/netscape/ca/CertificateAuthority.java  | 24 +++++++++++++++-------
 .../dogtagpki/server/ca/rest/AuthorityService.java | 12 ++++++++---
 .../server/ca/rest/CertRequestService.java         |  5 +++++
 .../netscape/certsrv/authority/AuthorityData.java  | 17 ++++++++++++++-
 .../certsrv/base/ServiceUnavailableException.java  | 17 +++++++++++++++
 .../netscape/certsrv/ca/ICertificateAuthority.java | 10 +++++++++
 .../netscape/cmstools/authority/AuthorityCLI.java  |  1 +
 .../cmstools/authority/AuthorityCreateCLI.java     |  2 +-
 .../cmstools/authority/AuthorityDisableCLI.java    |  2 +-
 .../cmstools/authority/AuthorityEnableCLI.java     |  2 +-
 .../cms/servlet/cert/RequestProcessor.java         |  5 +----
 11 files changed, 79 insertions(+), 18 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 
60f6b36219fda09a246013707c245a00d89ea84c..d96b8841449f4a19e652cc2512f834fed87f64e5
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -363,9 +363,20 @@ public class CertificateAuthority
         return hostCA == this;
     }
 
-    private void ensureEnabled() throws CADisabledException {
+    public void ensureReady()
+            throws ECAException {
         if (!authorityEnabled)
             throw new CADisabledException("Authority is disabled");
+        if (!isReady()) {
+            if (signingUnitException != null)
+                throw signingUnitException;
+            else
+                throw new CAMissingKeyException("Authority does not yet have 
signing key and cert in local NSSDB");
+        }
+    }
+
+    public boolean isReady() {
+        return hasKeys;
     }
 
     public boolean getAuthorityEnabled() {
@@ -1191,7 +1202,7 @@ public class CertificateAuthority
      */
     public X509CRLImpl sign(X509CRLImpl crl, String algname)
             throws EBaseException {
-        ensureEnabled();
+        ensureReady();
         X509CRLImpl signedcrl = null;
 
         IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats");
@@ -1264,7 +1275,7 @@ public class CertificateAuthority
      */
     public X509CertImpl sign(X509CertInfo certInfo, String algname)
             throws EBaseException {
-        ensureEnabled();
+        ensureReady();
 
         X509CertImpl signedcert = null;
 
@@ -1349,7 +1360,7 @@ public class CertificateAuthority
      */
     public byte[] sign(byte[] data, String algname)
             throws EBaseException {
-        ensureEnabled();
+        ensureReady();
         return mSigningUnit.sign(data, algname);
     }
 
@@ -2261,7 +2272,7 @@ public class CertificateAuthority
     }
 
     private BasicOCSPResponse sign(ResponseData rd) throws EBaseException {
-        ensureEnabled();
+        ensureReady();
         try (DerOutputStream out = new DerOutputStream()) {
             DerOutputStream tmp = new DerOutputStream();
 
@@ -2490,8 +2501,7 @@ public class CertificateAuthority
             String subjectDN, String description)
             throws EBaseException {
 
-        if (!authorityEnabled)
-            throw new CADisabledException("Parent CA is disabled");
+        ensureReady();
 
         // 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..582248d4cf284fb759c5c483810da87683862c1f
 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,12 @@ 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.CAMissingCertException;
+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 +210,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 (CAMissingCertException | CAMissingKeyException e) {
+            throw new ServiceUnavailableException(e.toString());
         } catch (Exception e) {
             CMS.debug(e);
             auditParams.put("exception", e.toString());
@@ -261,14 +266,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 +327,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..80aaf6f7899d92675c15c6f944b7a3a491784145
 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,12 @@ 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.CAMissingCertException;
+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 +255,8 @@ public class CertRequestService extends PKIService 
implements CertRequestResourc
         } catch (CADisabledException e) {
             CMS.debug("changeRequestState: CA disabled: " + e);
             throw new ConflictingOperationException(e.toString());
+        } catch (CAMissingCertException | 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 
6d83e6d07bf7100d03954ac7caec69134dbb5ec1..dd0d1b0851f03b3df8b69f7595a3524e1f9bd9ba
 100644
--- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
+++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
@@ -545,6 +545,16 @@ public interface ICertificateAuthority extends ISubsystem {
     public boolean getAuthorityEnabled();
 
     /**
+     * Return whether CA is ready to perform signing operations.
+     */
+    public boolean isReady();
+
+    /**
+     * Throw an exception if CA is not ready to perform signing operations.
+     */
+    public void ensureReady() throws ECAException;
+
+    /**
      * 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..b92ffb1d7527178e38eeaa4e35b83940167e9f4d
 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
@@ -37,7 +37,6 @@ 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;
@@ -350,9 +349,7 @@ public class RequestProcessor extends CertProcessor {
         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");
+        ca.ensureReady();
     }
 
     /**
-- 
2.5.5

From e62b8d9c6b4a4c2e0a75e8661371f46d5b6a8d3e Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 30 Mar 2016 16:06:25 +1100
Subject: [PATCH 95/96] Lightweight CAs: authority schema changes

Add the 'authorityKeyHost' attribute which will contain names of
hosts that possess the authority's signing keys.

Change other attributes to be single-valued.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 base/server/share/conf/schema-authority.ldif | 15 ++++++++-------
 base/server/share/conf/schema.ldif           | 15 ++++++++-------
 2 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/base/server/share/conf/schema-authority.ldif 
b/base/server/share/conf/schema-authority.ldif
index 
7d261f18fbc9475983bf93b1cddcc184d7f9d178..2e7c0d7af13b6af7131d6444261b09f452aa4b12
 100644
--- a/base/server/share/conf/schema-authority.ldif
+++ b/base/server/share/conf/schema-authority.ldif
@@ -1,8 +1,9 @@
 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' )
+attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' 
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE 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 SINGLE-VALUE 
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 SINGLE-VALUE 
X-ORIGIN 'user defined' )
+attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority 
Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'user 
defined' )
+attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority DN' 
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'user defined' )
+attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 
'Authority Parent DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE 
X-ORIGIN 'user defined' )
+attributeTypes: ( authorityKeyHost-oid NAME 'authorityKeyHost' DESC 'Authority 
Key Hosts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'user defined' )
+objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' 
SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ 
authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ 
authorityKeyHost $ description ) X-ORIGIN 'user defined' )
diff --git a/base/server/share/conf/schema.ldif 
b/base/server/share/conf/schema.ldif
index 
a15601ae7a362635bc398b92b9bfda1c72f0dfc8..5e4118d328ebe1fcac2743b3f51fb5ca9d57f9eb
 100644
--- a/base/server/share/conf/schema.ldif
+++ b/base/server/share/conf/schema.ldif
@@ -671,12 +671,13 @@ objectClasses: ( certProfile-oid NAME 'certProfile' DESC 
'Certificate profile' S
 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' )
+attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' 
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE 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 SINGLE-VALUE 
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 SINGLE-VALUE 
X-ORIGIN 'user defined' )
+attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority 
Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'user 
defined' )
+attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority DN' 
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'user defined' )
+attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 
'Authority Parent DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE 
X-ORIGIN 'user defined' )
+attributeTypes: ( authorityKeyHost-oid NAME 'authorityKeyHost' DESC 'Authority 
Key Hosts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 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 ) MAY ( authorityParentID $ authorityParentDN $ 
authorityKeyHost $ description ) X-ORIGIN 'user defined' )
-- 
2.5.5

From 802d30e899dd98c7c1a5d8dd0569b55e473ca59e Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 30 Mar 2016 12:38:24 +1100
Subject: [PATCH 96/96] Lightweight CAs: add key retrieval framework

Add the framework for key retrieval when a lightweight CA is missing
its signing key.  This includes all the bits for loading a
KeyRetriever implementation, initiating retrieval in a thread and
updating the record of which clones possess the key if retrieval was
successful.

It does not include a KeyRetriever implementation.  A subsequent
commit will provide this.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 .../src/com/netscape/ca/CertificateAuthority.java  | 149 ++++++++++++++++++++-
 base/ca/src/com/netscape/ca/KeyRetriever.java      |  55 ++++++++
 .../src/netscape/security/pkcs/PKCS12Util.java     |   3 +
 3 files changed, 201 insertions(+), 6 deletions(-)
 create mode 100644 base/ca/src/com/netscape/ca/KeyRetriever.java

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java 
b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 
d96b8841449f4a19e652cc2512f834fed87f64e5..d91f623055e97a74e459c4a7e03668e2c3c37622
 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -35,6 +35,7 @@ import java.security.cert.CertificateException;
 import java.security.cert.CertificateParsingException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
@@ -66,6 +67,7 @@ import org.mozilla.jss.crypto.SignatureAlgorithm;
 import org.mozilla.jss.crypto.TokenException;
 import org.mozilla.jss.pkix.cert.Extension;
 import org.mozilla.jss.pkix.primitive.Name;
+import org.mozilla.jss.util.Password;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.authentication.IAuthToken;
@@ -165,6 +167,8 @@ import netscape.ldap.controls.LDAPPersistSearchControl;
 import netscape.ldap.util.DN;
 
 import netscape.security.pkcs.PKCS10;
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12Util;
 import netscape.security.util.DerOutputStream;
 import netscape.security.util.DerValue;
 import netscape.security.x509.AlgorithmId;
@@ -206,6 +210,7 @@ public class CertificateAuthority
     protected AuthorityID authorityID = null;
     protected AuthorityID authorityParentID = null;
     protected String authorityDescription = null;
+    protected Collection<String> authorityKeyHosts = null;
     protected boolean authorityEnabled = true;
     private boolean hasKeys = false;
     private ECAException signingUnitException = null;
@@ -341,6 +346,7 @@ public class CertificateAuthority
             AuthorityID aid,
             AuthorityID parentAID,
             String signingKeyNickname,
+            Collection<String> authorityKeyHosts,
             String authorityDescription,
             boolean authorityEnabled
             ) throws EBaseException {
@@ -356,6 +362,7 @@ public class CertificateAuthority
         this.authorityDescription = authorityDescription;
         this.authorityEnabled = authorityEnabled;
         mNickname = signingKeyNickname;
+        this.authorityKeyHosts = authorityKeyHosts;
         init(hostCA.mOwner, hostCA.mConfig);
     }
 
@@ -505,7 +512,7 @@ public class CertificateAuthority
 
             // init signing unit & CA cert.
             try {
-                initSigUnit();
+                initSigUnit(/* retrieveKeys */ true);
                 // init default CA attributes like cert version, validity.
                 initDefCaAttrs();
             } catch (EBaseException e) {
@@ -1447,7 +1454,7 @@ public class CertificateAuthority
     /**
      * init CA signing unit & cert chain.
      */
-    private void initSigUnit()
+    private boolean initSigUnit(boolean retrieveKeys)
             throws EBaseException {
         try {
             // init signing unit
@@ -1477,7 +1484,14 @@ public class CertificateAuthority
             } catch (CAMissingCertException | CAMissingKeyException e) {
                 CMS.debug("CA signing key and cert not (yet) present in 
NSSDB");
                 signingUnitException = e;
-                return;
+                if (retrieveKeys == true) {
+                    CMS.debug("Starting KeyRetrieverRunner thread");
+                    new Thread(
+                        new KeyRetrieverRunner(this),
+                        "KeyRetrieverRunner-" + authorityID
+                    ).start();
+                }
+                return false;
             }
             CMS.debug("CA signing unit inited");
 
@@ -1602,6 +1616,8 @@ public class CertificateAuthority
             mNickname = mSigningUnit.getNickname();
             CMS.debug("in init - got CA name " + mName);
 
+            return true;
+
         } catch (CryptoManager.NotInitializedException e) {
             log(ILogger.LL_FAILURE, 
CMS.getLogMessage("CMSCORE_CA_CA_OCSP_SIGNING", e.toString()));
             throw new 
ECAException(CMS.getUserMessage("CMS_CA_CRYPTO_NOT_INITIALIZED"));
@@ -2528,11 +2544,14 @@ public class CertificateAuthority
             throw new EBaseException("Failed to convert issuer DN to string: " 
+ e);
         }
 
+        String thisHost = CMS.getEEHost();
+
         LDAPAttribute[] attrs = {
             new LDAPAttribute("objectclass", "authority"),
             new LDAPAttribute("cn", aidString),
             new LDAPAttribute("authorityID", aidString),
             new LDAPAttribute("authorityKeyNickname", nickname),
+            new LDAPAttribute("authorityKeyHost", thisHost),
             new LDAPAttribute("authorityEnabled", "TRUE"),
             new LDAPAttribute("authorityDN", subjectDN),
             new LDAPAttribute("authorityParentDN", parentDNString)
@@ -2613,7 +2632,9 @@ public class CertificateAuthority
 
         return new CertificateAuthority(
             hostCA, subjectX500Name,
-            aid, this.authorityID, nickname, description, true);
+            aid, this.authorityID,
+            nickname, Collections.singleton(thisHost),
+            description, true);
     }
 
     /**
@@ -2786,6 +2807,23 @@ public class CertificateAuthority
         }
     }
 
+    /**
+     * Add this instance to the authorityKeyHosts
+     */
+    private void addInstanceToAuthorityKeyHosts() throws ELdapException {
+        String hostname = CMS.getEEHost();
+        if (authorityKeyHosts.contains(hostname)) {
+            // already there; nothing to do
+            return;
+        }
+        LDAPModificationSet mods = new LDAPModificationSet();
+        mods.add(
+            LDAPModification.ADD,
+            new LDAPAttribute("authorityKeyHost", hostname));
+        modifyAuthorityEntry(mods);
+        authorityKeyHosts.add(hostname);
+    }
+
     public synchronized void deleteAuthority() throws EBaseException {
         if (isHostAuthority())
             throw new CATypeException("Cannot delete the host CA");
@@ -2934,7 +2972,6 @@ public class CertificateAuthority
                         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");
@@ -2991,6 +3028,7 @@ public class CertificateAuthority
 
         LDAPAttribute aidAttr = entry.getAttribute("authorityID");
         LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname");
+        LDAPAttribute keyHostsAttr = entry.getAttribute("authorityKeyHost");
         LDAPAttribute dnAttr = entry.getAttribute("authorityDN");
         LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID");
         LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN");
@@ -3047,6 +3085,16 @@ public class CertificateAuthority
         }
 
         String keyNick = (String) nickAttr.getStringValues().nextElement();
+
+        Collection<String> keyHosts;
+        if (keyHostsAttr == null) {
+            keyHosts = Collections.emptyList();
+        } else {
+            @SuppressWarnings("unchecked")
+            Enumeration<String> keyHostsEnum = keyHostsAttr.getStringValues();
+            keyHosts = Collections.list(keyHostsEnum);
+        }
+
         AuthorityID parentAID = null;
         if (parentAIDAttr != null)
             parentAID = new AuthorityID((String)
@@ -3062,7 +3110,7 @@ public class CertificateAuthority
 
         try {
             CertificateAuthority ca = new CertificateAuthority(
-                hostCA, dn, aid, parentAID, keyNick, desc, enabled);
+                hostCA, dn, aid, parentAID, keyNick, keyHosts, desc, enabled);
             caMap.put(aid, ca);
             entryUSNs.put(aid, newEntryUSN);
             nsUniqueIds.put(aid, nsUniqueId);
@@ -3112,4 +3160,93 @@ public class CertificateAuthority
         }
     }
 
+    private class KeyRetrieverRunner implements Runnable {
+        private CertificateAuthority ca;
+
+        public KeyRetrieverRunner(CertificateAuthority ca) {
+            this.ca = ca;
+        }
+
+        public void run() {
+            String KR_CLASS_KEY = "features.authority.keyRetrieverClass";
+            String className = null;
+            try {
+                className = CMS.getConfigStore().getString(KR_CLASS_KEY);
+            } catch (EBaseException e) {
+                CMS.debug("Unable to read key retriever class from CS.cfg: " + 
e);
+                return;
+            }
+
+            KeyRetriever kr = null;
+            try {
+                kr = Class.forName(className)
+                    .asSubclass(KeyRetriever.class)
+                    .newInstance();
+            } catch (ClassNotFoundException e) {
+                CMS.debug("Could not find class: " + className);
+                CMS.debug(e);
+                return;
+            } catch (ClassCastException e) {
+                CMS.debug("Class is not an instance of KeyRetriever: " + 
className);
+                CMS.debug(e);
+                return;
+            } catch (InstantiationException | IllegalAccessException e) {
+                CMS.debug("Could not instantiate class: " + className);
+                CMS.debug(e);
+                return;
+            }
+
+            KeyRetriever.Result krr = null;
+            try {
+                krr = kr.retrieveKey(ca.mNickname, ca.authorityKeyHosts);
+            } catch (Throwable e) {
+                CMS.debug("Caught exception during execution of 
KeyRetriever.retrieveKey");
+                CMS.debug(e);
+            }
+
+            boolean importSucceeded = false;
+            if (krr != null) {
+                CMS.debug("Importing key and cert");
+                PKCS12Util p12util = new PKCS12Util();
+                Password password = new 
Password(krr.getPassword().toCharArray());
+                try {
+                    PKCS12 p12 = p12util.loadFromByteArray(krr.getPKCS12(), 
password);
+                    p12util.storeCertIntoNSS(p12, ca.mNickname);
+                    importSucceeded = true;
+                } catch (Throwable e) {
+                    CMS.debug("Caught exception during PKCS12 import: " + e);
+                }
+            }
+
+            boolean initSigUnitSucceeded = false;
+            if (importSucceeded) {
+                try {
+                    CMS.debug("Reinitialising SigningUnit");
+                    // re-init signing unit, but avoid triggering
+                    // key replication if initialisation fails again
+                    // for some reason
+                    //
+                    initSigUnitSucceeded = ca.initSigUnit(/* retrieveKeys */ 
false);
+                } catch (Throwable e) {
+                    CMS.debug("Caught exception during SigningUnit re-init");
+                    CMS.debug(e);
+                }
+            } else {
+                CMS.debug("Failed to import key and cert");
+            }
+
+            if (initSigUnitSucceeded == true) {
+                CMS.debug("Adding self to authorityKeyHosts attribute");
+                try {
+                    ca.addInstanceToAuthorityKeyHosts();
+                } catch (Throwable e) {
+                    CMS.debug("Failed to add self to authorityKeyHosts");
+                    CMS.debug(e);
+                }
+            } else {
+                CMS.debug("Failed to re-init SigningUnit");
+            }
+        }
+    }
+
 }
diff --git a/base/ca/src/com/netscape/ca/KeyRetriever.java 
b/base/ca/src/com/netscape/ca/KeyRetriever.java
new file mode 100644
index 
0000000000000000000000000000000000000000..deb51b8c3a857c4b57b0fe3c705e304768ebb08b
--- /dev/null
+++ b/base/ca/src/com/netscape/ca/KeyRetriever.java
@@ -0,0 +1,55 @@
+// --- 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) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.ca;
+
+import java.util.Collection;
+
+public interface KeyRetriever {
+    /**
+     * Retrieve the specified signing key from specified host and
+     * return to the KeyRetrieverRunner.
+     *
+     * A KeyRetriever MUST NOT import the cert and key to the NSSDB
+     * itself.  It SHALL, if successful in retrieving the key and
+     * certificate, return a Result which contains a DER-encoded
+     * PKCS #12 object and the password for importing the
+     * certificates and keys contained within the PKCS #12 object.
+     *
+     * Upon failure the KeyRetriever SHALL return null.
+     */
+    Result retrieveKey(String nickname, Collection<String> hostname);
+
+    class Result {
+        private String password;
+        private byte[] pkcs12;
+
+        public Result(String password, byte[] pkcs12) {
+            this.password = password;
+            this.pkcs12 = pkcs12;
+        }
+
+        public String getPassword() {
+            return password;
+        }
+
+        public byte[] getPKCS12() {
+            return pkcs12;
+        }
+    }
+}
diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java 
b/base/util/src/netscape/security/pkcs/PKCS12Util.java
index 
43435c822c9400248fe556bf066cd2659e18ae17..9931027dacfe88e636b694b7c490ffc6804068dd
 100644
--- a/base/util/src/netscape/security/pkcs/PKCS12Util.java
+++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java
@@ -536,7 +536,10 @@ public class PKCS12Util {
 
         Path path = Paths.get(filename);
         byte[] b = Files.readAllBytes(path);
+        return loadFromByteArray(b, password);
+    }
 
+    public PKCS12 loadFromByteArray(byte[] b, Password password) throws 
Exception {
         ByteArrayInputStream bis = new ByteArrayInputStream(b);
 
         PFX pfx = (PFX) (new PFX.Template()).decode(bis);
-- 
2.5.5

From d7d623ee202d685ef46bacce1cc8e88c1852bfe4 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Fri, 8 Apr 2016 22:23:42 +1000
Subject: [PATCH] Lightweight CAs: add IPACustodiaKeyRetriever

Add 'IPACustodiaKeyRetriever', a 'KeyRetriever' implementation for
use when Dogtag is deployed as a FreeIPA CA.  The Java class invokes
'pki-ipa-key-retriever', a Python script that retrieves lightweight
CA keys from the Custodia server on a replica that possesses the
keys.  'pki-ipa-key-retriever' depends on FreeIPA libraries, FreeIPA
server configuration, and Kerberos and Custodia keys owned by
'pkiuser'.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 base/ca/src/CMakeLists.txt                         |  9 ++-
 .../com/netscape/ca/IPACustodiaKeyRetriever.java   | 74 ++++++++++++++++++++++
 base/server/CMakeLists.txt                         | 11 ++++
 base/server/libexec/pki-ipa-retrieve-key           | 42 ++++++++++++
 specs/pki-core.spec                                |  1 +
 5 files changed, 136 insertions(+), 1 deletion(-)
 create mode 100644 base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java
 create mode 100755 base/server/libexec/pki-ipa-retrieve-key

diff --git a/base/ca/src/CMakeLists.txt b/base/ca/src/CMakeLists.txt
index 
5b805e1b3a5eddb46d17178ca2ac204c36ae5680..1817dacfbacaeb2635db2550e32ff62c26d628ef
 100644
--- a/base/ca/src/CMakeLists.txt
+++ b/base/ca/src/CMakeLists.txt
@@ -24,6 +24,13 @@ find_file(COMMONS_CODEC_JAR
         /usr/share/java
 )
 
+find_file(COMMONS_IO_JAR
+    NAMES
+        commons-io.jar
+    PATHS
+        /usr/share/java
+)
+
 find_file(COMMONS_LANG_JAR
     NAMES
         commons-lang.jar
@@ -73,7 +80,7 @@ javac(pki-ca-classes
         com/netscape/ca/*.java
         org/dogtagpki/server/ca/*.java
     CLASSPATH
-        ${COMMONS_CODEC_JAR} ${COMMONS_LANG_JAR}
+        ${COMMONS_CODEC_JAR} ${COMMONS_IO_JAR} ${COMMONS_LANG_JAR}
         ${JSS_JAR} ${SYMKEY_JAR}
         ${LDAPJDK_JAR}
         ${SERVLET_JAR} ${TOMCAT_CATALINA_JAR}
diff --git a/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java 
b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java
new file mode 100644
index 
0000000000000000000000000000000000000000..59faa58a94ff4e616dc040c635ca96014828ec00
--- /dev/null
+++ b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java
@@ -0,0 +1,74 @@
+// --- 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) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.ca;
+
+import java.lang.Process;
+import java.lang.ProcessBuilder;
+import java.util.Collection;
+import java.util.Stack;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.ArrayUtils;
+
+import com.netscape.certsrv.apps.CMS;
+
+public class IPACustodiaKeyRetriever implements KeyRetriever {
+    public Result retrieveKey(String nickname, Collection<String> hosts) {
+        CMS.debug("Running IPACustodiaKeyRetriever");
+
+        Stack<String> command = new Stack<>();
+        command.push("/usr/libexec/pki-ipa-retrieve-key");
+        command.push(nickname);
+
+        for (String host : hosts) {
+            command.push(host);
+            CMS.debug("About to execute command: " + command);
+            ProcessBuilder pb = new ProcessBuilder(command);
+            try {
+                Process p = pb.start();
+                int exitValue = p.waitFor();
+                if (exitValue != 0)
+                    continue;
+
+                /* Custodia returns a PKCS #12 object and the
+                 * password to import it.  These values are output
+                 * by the Python 'pki-ipa-retrieve-key' program,
+                 * separated by a null byte (password first)
+                 */
+
+                byte[] output = IOUtils.toByteArray(p.getInputStream());
+                int splitIndex = ArrayUtils.indexOf(output, (byte) 0);
+                if (splitIndex == ArrayUtils.INDEX_NOT_FOUND) {
+                    CMS.debug("Invalid output: null byte not found");
+                    continue;
+                }
+                return new Result(
+                    new String(ArrayUtils.subarray(output, 0, splitIndex)),
+                    ArrayUtils.subarray(output, splitIndex + 1, output.length)
+                );
+            } catch (Throwable e) {
+                CMS.debug("Caught exception while executing command: " + e);
+            } finally {
+                command.pop();
+            }
+        }
+        CMS.debug("Failed to retrieve key from any host.");
+        return null;
+    }
+}
diff --git a/base/server/CMakeLists.txt b/base/server/CMakeLists.txt
index 
5a6aea96a2317655fb454967f9f218020443bcb8..9e5b27833c8d023e63320c43d64ad64b0055c254
 100644
--- a/base/server/CMakeLists.txt
+++ b/base/server/CMakeLists.txt
@@ -81,6 +81,17 @@ install(
 
 install(
     DIRECTORY
+        libexec/
+    DESTINATION
+        ${LIBEXEC_INSTALL_DIR}
+    FILE_PERMISSIONS
+        OWNER_EXECUTE OWNER_WRITE OWNER_READ
+        GROUP_EXECUTE GROUP_READ
+        WORLD_EXECUTE WORLD_READ
+)
+
+install(
+    DIRECTORY
         upgrade
     DESTINATION
         ${DATA_INSTALL_DIR}/server/
diff --git a/base/server/libexec/pki-ipa-retrieve-key 
b/base/server/libexec/pki-ipa-retrieve-key
new file mode 100755
index 
0000000000000000000000000000000000000000..757414ce7be43d675831f29d4811b94101f494c0
--- /dev/null
+++ b/base/server/libexec/pki-ipa-retrieve-key
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+
+from __future__ import print_function
+
+import ConfigParser
+import base64
+import sys
+
+from jwcrypto.common import json_decode
+
+from ipaplatform.paths import paths
+from ipapython.secrets.client import CustodiaClient
+
+conf = ConfigParser.ConfigParser()
+conf.read(paths.IPA_DEFAULT_CONF)
+hostname = conf.get('global', 'host')
+realm = conf.get('global', 'realm')
+
+keyname = "ca/" + sys.argv[1]
+servername = sys.argv[2]
+
+client_keyfile = "/etc/pki/pki-tomcat/dogtag-ipa-custodia.keys"
+client_keytab = "/etc/pki/pki-tomcat/dogtag-ipa-custodia.keytab"
+
+client = CustodiaClient(
+    client=hostname, server=servername, realm=realm,
+    ldap_uri="ldaps://" + hostname,
+    client_servicename='dogtag-ipa-custodia',
+    keyfile=client_keyfile, keytab=client_keytab,
+    )
+
+result_json = client.fetch_key(keyname, store=False)
+result = json_decode(result_json)
+password = result["export password"]
+pkcs12 = base64.b64decode(result["pkcs12 data"])
+
+# Custodia returns a PKCS #12 object and the password to import it.
+# These values are output separated by a null byte (password first),
+# and read by the Java IPACustodiaKeyRetriever that invoked this
+# program.
+
+print(password, pkcs12, sep='\0', end='')
diff --git a/specs/pki-core.spec b/specs/pki-core.spec
index 
b7e59a43f65ef8740e5fac42071240ad3c4bce9a..63c44329872659ca208bc300baad39fc87214ee8
 100644
--- a/specs/pki-core.spec
+++ b/specs/pki-core.spec
@@ -1007,6 +1007,7 @@ systemctl daemon-reload
 %{_sbindir}/pki-server
 %{_sbindir}/pki-server-nuxwdog
 %{_sbindir}/pki-server-upgrade
+%{_libexecdir}/pki-ipa-retrieve-key
 %{python2_sitelib}/pki/server/
 %dir %{_datadir}/pki/deployment
 %{_datadir}/pki/deployment/config/
-- 
2.5.5

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

Reply via email to