Thanks Ade.  Updated patch 0096 attached.  Comments inline.

On Wed, Apr 20, 2016 at 11:30:52AM -0400, Ade Lee wrote:
> Comments:
> 
> 95 - ack
> 
> 96 -
> 
> 1. You have made the return type of initSigUnit() to be boolean. 
>  Should you be checking the return value in init()?
> 
It is not needed to check it here; only when re-entering init from
the KeyReplicatorRunner thread.

> 2. In addInstanceToAuthorityKeyHosts(), you are still using only the
> hostname.  Should be host:port
> 
Good pickup.  Fixed in latest patch.

> 3. The logic in the KeyRetrieverRunner class looks OK to me, but I'd
> like cfu and/or jmagne to check it and make sure we are calling the
> right primitives to wrap/unwrap inside the cryptographic token.
> 
> Also I'd like them to confirm that this would wor for an HSM.
> Statements like the following make me question that:
>    CryptoToken token = manager.getInternalKeyStorageToken()
> 
It won't work on HSM.  Can I get an HSM to test with? ;) I've filed
a ticket for HSM support[1].  FreeIPA does not yet support HSM[2] so
I think we can put it in 10.4 milestone (I've put it there for now).

[1] https://fedorahosted.org/pki/ticket/2292
[2] https://fedorahosted.org/freeipa/ticket/5608

> 4. Can you explain what happens if for some reason the script fails to
> retrieve the key?  Do we end up retrying later and if so, when?
> 
If the script fails to retrieve the key, it does not retry
automatically.  I filed a ticket[3] to implement retry with
backoff (this patchset is big enough already!) and put it in
10.3.1 milestone (that's up for discussion).

[3] https://fedorahosted.org/pki/ticket/2293

Right now, the following events cause authority reinitialisation,
entailing key retrieval if necessary:

- Dogtag is restarted
- LDAP disconnect-reconnect
- LDAP modification of authority replicated from another clone

> 97- ACK
> 
> 98 - ACK
>  
Thanks.  Any feedback on patch 0099?
From a256168d91c799d37e1e4f6e7af8dfb97b4340be Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 30 Mar 2016 12:38:24 +1100
Subject: [PATCH] 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  | 162 ++++++++++++++++++++-
 base/ca/src/com/netscape/ca/KeyRetriever.java      |  56 +++++++
 .../src/netscape/security/pkcs/PKCS12Util.java     |   3 +
 3 files changed, 215 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 
37f1e95fc97f3d21ec6dc379962e27b42fb5b074..253c4bb323692b8e9fe8bd87e202d71afb810c67
 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;
@@ -62,8 +63,10 @@ import org.mozilla.jss.crypto.CryptoToken;
 import org.mozilla.jss.crypto.KeyPairAlgorithm;
 import org.mozilla.jss.crypto.KeyPairGenerator;
 import org.mozilla.jss.crypto.NoSuchItemOnTokenException;
+import org.mozilla.jss.crypto.PrivateKey;
 import org.mozilla.jss.crypto.SignatureAlgorithm;
 import org.mozilla.jss.crypto.TokenException;
+import org.mozilla.jss.crypto.X509Certificate;
 import org.mozilla.jss.pkix.cert.Extension;
 import org.mozilla.jss.pkix.primitive.Name;
 
@@ -205,6 +208,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;
@@ -340,6 +344,7 @@ public class CertificateAuthority
             AuthorityID aid,
             AuthorityID parentAID,
             String signingKeyNickname,
+            Collection<String> authorityKeyHosts,
             String authorityDescription,
             boolean authorityEnabled
             ) throws EBaseException {
@@ -355,6 +360,7 @@ public class CertificateAuthority
         this.authorityDescription = authorityDescription;
         this.authorityEnabled = authorityEnabled;
         mNickname = signingKeyNickname;
+        this.authorityKeyHosts = authorityKeyHosts;
         init(hostCA.mOwner, hostCA.mConfig);
     }
 
@@ -504,7 +510,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) {
@@ -1446,7 +1452,7 @@ public class CertificateAuthority
     /**
      * init CA signing unit & cert chain.
      */
-    private void initSigUnit()
+    private boolean initSigUnit(boolean retrieveKeys)
             throws EBaseException {
         try {
             // init signing unit
@@ -1476,7 +1482,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");
 
@@ -1601,6 +1614,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"));
@@ -2527,11 +2542,14 @@ public class CertificateAuthority
             throw new EBaseException("Failed to convert issuer DN to string: " 
+ e);
         }
 
+        String thisClone = CMS.getEEHost() + ":" + CMS.getEESSLPort();
+
         LDAPAttribute[] attrs = {
             new LDAPAttribute("objectclass", "authority"),
             new LDAPAttribute("cn", aidString),
             new LDAPAttribute("authorityID", aidString),
             new LDAPAttribute("authorityKeyNickname", nickname),
+            new LDAPAttribute("authorityKeyHost", thisClone),
             new LDAPAttribute("authorityEnabled", "TRUE"),
             new LDAPAttribute("authorityDN", subjectDN),
             new LDAPAttribute("authorityParentDN", parentDNString)
@@ -2612,7 +2630,9 @@ public class CertificateAuthority
 
         return new CertificateAuthority(
             hostCA, subjectX500Name,
-            aid, this.authorityID, nickname, description, true);
+            aid, this.authorityID,
+            nickname, Collections.singleton(thisClone),
+            description, true);
     }
 
     /**
@@ -2785,6 +2805,23 @@ public class CertificateAuthority
         }
     }
 
+    /**
+     * Add this instance to the authorityKeyHosts
+     */
+    private void addInstanceToAuthorityKeyHosts() throws ELdapException {
+        String thisClone = CMS.getEEHost() + ":" + CMS.getEESSLPort();
+        if (authorityKeyHosts.contains(thisClone)) {
+            // already there; nothing to do
+            return;
+        }
+        LDAPModificationSet mods = new LDAPModificationSet();
+        mods.add(
+            LDAPModification.ADD,
+            new LDAPAttribute("authorityKeyHost", thisClone));
+        modifyAuthorityEntry(mods);
+        authorityKeyHosts.add(thisClone);
+    }
+
     public synchronized void deleteAuthority() throws EBaseException {
         if (isHostAuthority())
             throw new CATypeException("Cannot delete the host CA");
@@ -2933,7 +2970,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");
@@ -2990,6 +3026,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");
@@ -3052,6 +3089,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)
@@ -3067,7 +3114,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);
@@ -3117,4 +3164,107 @@ 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);
+                return;
+            }
+
+            if (krr == null) {
+                CMS.debug("KeyRetriever did not return a result.");
+                return;
+            }
+
+            CMS.debug("Importing key and cert");
+            byte[] certBytes = krr.getCertificate();
+            byte[] paoData = krr.getPKIArchiveOptions();
+            try {
+                CryptoManager manager = CryptoManager.getInstance();
+                CryptoToken token = manager.getInternalKeyStorageToken();
+
+                X509Certificate cert = manager.importCACertPackage(certBytes);
+                PublicKey pubkey = cert.getPublicKey();
+                token.getCryptoStore().deleteCert(cert);
+
+                PrivateKey unwrappingKey = hostCA.mSigningUnit.getPrivateKey();
+
+                CryptoUtil.importPKIArchiveOptions(
+                    token, unwrappingKey, pubkey, paoData);
+
+                cert = manager.importUserCACertPackage(certBytes, 
ca.mNickname);
+            } catch (Throwable e) {
+                CMS.debug("Caught exception during cert/key import");
+                CMS.debug(e);
+                return;
+            }
+
+            boolean initSigUnitSucceeded = false;
+            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);
+                return;
+            }
+
+            if (!initSigUnitSucceeded) {
+                CMS.debug("Failed to re-init SigningUnit");
+                return;
+            }
+
+            CMS.debug("Adding self to authorityKeyHosts attribute");
+            try {
+                ca.addInstanceToAuthorityKeyHosts();
+            } catch (Throwable e) {
+                CMS.debug("Failed to add self to authorityKeyHosts");
+                CMS.debug(e);
+                return;
+            }
+        }
+    }
+
 }
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..7c0df0bf56578b81062de77de47aa516b5c9d949
--- /dev/null
+++ b/base/ca/src/com/netscape/ca/KeyRetriever.java
@@ -0,0 +1,56 @@
+// --- 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 clone 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 PEM-encoded
+     * X.509 certificate and a DER-encoded PKIArchiveOptions object
+     * containing an EncryptedValue of the target private key
+     * wrapped by the host authority's public key.
+     *
+     * Upon failure the KeyRetriever SHALL return null.
+     */
+    Result retrieveKey(String nickname, Collection<String> hostPorts);
+
+    class Result {
+        private byte[] certificate;
+        private byte[] pkiArchiveOptions;
+
+        public Result(byte[] certificate, byte[] pkiArchiveOptions) {
+            this.certificate = certificate;
+            this.pkiArchiveOptions = pkiArchiveOptions;
+        }
+
+        public byte[] getCertificate() {
+            return certificate;
+        }
+
+        public byte[] getPKIArchiveOptions() {
+            return pkiArchiveOptions;
+        }
+    }
+}
diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java 
b/base/util/src/netscape/security/pkcs/PKCS12Util.java
index 
571ee18815e93b29e3dbd32eb0f52b2d60a421cc..b1b0f0768c8feae3508b1d5e7fb06f152f0ccd29
 100644
--- a/base/util/src/netscape/security/pkcs/PKCS12Util.java
+++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java
@@ -540,7 +540,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

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

Reply via email to