[Pki-devel] [PATCH] 0084..0086 Lightweight CA replication support
Fraser Tweedale
ftweedal at redhat.com
Thu Apr 21 04:58:54 UTC 2016
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?
-------------- next part --------------
From a256168d91c799d37e1e4f6e7af8dfb97b4340be Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at 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
More information about the Pki-devel
mailing list