Both issues addressed in latest patchset.  Two new patches in the
mix; the order is:

    0095-4, 0098, 0099, 0096-4, 0097-3 (tip)

I also added another attribute to schema for the authority
certificate serial number.  It is not used in current code but I
have a hunch it may be needed for renewal, so I'm adding it now.

Thanks,
Fraser

On Thu, Apr 14, 2016 at 05:34:45PM -0400, Ade Lee wrote:
> Couple of points on 96/97.
> 
> 1. First off, I'm not sure you followed my concern about being able to
> distinguish between CA instances.
> 
> On an IPA system, this is not an issue because there is only one CA on
> the server.  In this case, I imagine there will be a well known
> directory which custodia would work with.
> 
> In general though, we have to imagine that someone could end up
> installing two different dogtag ca instances on the same server. 
>  CMS.getEEHost() would result in the same value (the hostname) for both
> CAs.  How does your helper program (or custodia) know which key to
> retrieve?
> 
> The way to distinguish Dogtag instances is host AND port.
> 
> 2.  So, we're very careful that the signing keys are never in memory in
> the server.  All accesses to the system certs are through JSS/NSS which
> essentially provides us handles to the keys.
> 
> Now, I see a case where we import PKCS12 data AND the password into
> memory, so that we can import it into NSS?  Say it ain't so ..
> 
> With custodia, we have a secure mechanism of transferring the keys from
> one server to another. It makes more sense to me to have the server
> kick off the custodia transfer and then have that process also import
> into the NSS db.  The server would then need to await status from the
> custodia/retriever process - and then initialize the signing unit from
> the NSS DB.  Or am I completely confused?
> 
> Ade
> 
> 
> 
> On Thu, 2016-04-14 at 16:35 -0400, Ade Lee wrote:
> > Still reviewing .. ACK on 87-95 (inclusive).
> > 
> > On Thu, 2016-04-14 at 16:18 +1000, Fraser Tweedale wrote:
> > > 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/msg0
> > > > > > 00
> > > > > > 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,
From f5322fdf885196275a93696cc31a489c9b4aab1d Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Wed, 30 Mar 2016 16:06:25 +1100
Subject: [PATCH] Lightweight CAs: authority schema changes

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

Add the 'authoritySerial' attribute which may contain the serial
number of the certificate most recently issued for the authority.

Change other attributes to be single-valued.

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

diff --git a/base/server/share/conf/schema-authority.ldif 
b/base/server/share/conf/schema-authority.ldif
index 
7d261f18fbc9475983bf93b1cddcc184d7f9d178..fd3c4fa225b036142a9aa4e99c65697365160dfd
 100644
--- a/base/server/share/conf/schema-authority.ldif
+++ b/base/server/share/conf/schema-authority.ldif
@@ -1,8 +1,10 @@
 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: ( authoritySerial-oid NAME 'authoritySerial' DESC 'Authority 
certificate serial number' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 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 ( authoritySerial $ 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 3d42d650abf5b4862570620ef7b130bb511bb17d Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Tue, 19 Apr 2016 13:25:12 +1000
Subject: [PATCH 98/99] Add method CryptoUtil.importPKIArchiveOptions

Add the method CryptoUtil.importPKIArchiveOptions for importing a
wrapped key from a PKIArchiveOptions object.  Also add another
variant of the createPKIArchiveOptions method, with a narrower API.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 .../com/netscape/cmsutil/crypto/CryptoUtil.java    | 58 ++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java 
b/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
index 
06caa0242ab192c5bbc14845dd7abc772601bd58..7ac3e4e9ad2702e09c704638b6ab773bb2dccb49
 100644
--- a/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
+++ b/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
@@ -80,7 +80,9 @@ import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.CryptoManager.NotInitializedException;
 import org.mozilla.jss.NoSuchTokenException;
 import org.mozilla.jss.SecretDecoderRing.KeyManager;
+import org.mozilla.jss.asn1.ANY;
 import org.mozilla.jss.asn1.ASN1Util;
+import org.mozilla.jss.asn1.ASN1Value;
 import org.mozilla.jss.asn1.BIT_STRING;
 import org.mozilla.jss.asn1.InvalidBERException;
 import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
@@ -1971,6 +1973,31 @@ public class CryptoUtil {
         // wrap session key using transport key
         byte[] session_data = wrapSymmetricKey(manager, token, transportCert, 
sk);
 
+        return createPKIArchiveOptions(IV, session_data, key_data);
+    }
+
+    public static byte[] createPKIArchiveOptions(
+            CryptoToken token, PublicKey wrappingKey, PrivateKey toBeWrapped,
+            KeyGenAlgorithm keyGenAlg, int symKeySize, IVParameterSpec IV)
+            throws TokenException, NoSuchAlgorithmException,
+            InvalidAlgorithmParameterException, InvalidKeyException,
+            IOException, InvalidBERException {
+        SymmetricKey sessionKey = CryptoUtil.generateKey(token, keyGenAlg, 
symKeySize);
+
+        KeyWrapper wrapper = 
token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
+        wrapper.initWrap(sessionKey, IV);
+        byte[] key_data = wrapper.wrap(toBeWrapped);
+
+        wrapper = token.getKeyWrapper(KeyWrapAlgorithm.RSA);
+        wrapper.initWrap(wrappingKey, null);
+        byte session_data[] = wrapper.wrap(sessionKey);
+
+        return createPKIArchiveOptions(IV, session_data, key_data);
+    }
+
+    private static byte[] createPKIArchiveOptions(
+            IVParameterSpec IV, byte[] session_data, byte[] key_data)
+            throws IOException, InvalidBERException {
         // create PKIArchiveOptions structure
         AlgorithmIdentifier algS = new AlgorithmIdentifier(new 
OBJECT_IDENTIFIER("1.2.840.113549.3.7"),
                 new OCTET_STRING(IV.getIV()));
@@ -1996,6 +2023,37 @@ public class CryptoUtil {
         return encoded;
     }
 
+    public static PrivateKey importPKIArchiveOptions(
+            CryptoToken token, PrivateKey unwrappingKey,
+            PublicKey pubkey, byte[] data)
+            throws InvalidBERException, Exception {
+        ByteArrayInputStream in = new ByteArrayInputStream(data);
+        PKIArchiveOptions options = (PKIArchiveOptions)
+            (new PKIArchiveOptions.Template()).decode(in);
+        EncryptedKey encKey = options.getEncryptedKey();
+        EncryptedValue encVal = encKey.getEncryptedValue();
+        AlgorithmIdentifier algId = encVal.getSymmAlg();
+        BIT_STRING encSymKey = encVal.getEncSymmKey();
+        BIT_STRING encPrivKey = encVal.getEncValue();
+
+        KeyWrapper wrapper = token.getKeyWrapper(KeyWrapAlgorithm.RSA);
+        wrapper.initUnwrap(unwrappingKey, null);
+        SymmetricKey sk = wrapper.unwrapSymmetric(
+            encSymKey.getBits(), SymmetricKey.Type.DES3, 0);
+
+        ASN1Value v = algId.getParameters();
+        v = ((ANY) v).decodeWith(new OCTET_STRING.Template());
+        byte iv[] = ((OCTET_STRING) v).toByteArray();
+        IVParameterSpec ivps = new IVParameterSpec(iv);
+
+        wrapper = token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
+        wrapper.initUnwrap(sk, ivps);
+        PrivateKey.Type keyType = pubkey.getAlgorithm().equals("EC")
+            ? PrivateKey.Type.EC
+            : PrivateKey.Type.RSA;
+        return wrapper.unwrapPrivate(encPrivKey.getBits(), keyType, pubkey);
+    }
+
     public static boolean sharedSecretExists(String nickname) throws 
NotInitializedException, TokenException {
         CryptoManager cm = CryptoManager.getInstance();
         CryptoToken token = cm.getInternalKeyStorageToken();
-- 
2.5.5

From ba32660ee26fc00132882809ce9365690f375c6c Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftwee...@redhat.com>
Date: Tue, 19 Apr 2016 13:28:56 +1000
Subject: [PATCH 99/99] Add ca-authority-key-export command

Add the 'pki ca-authority-key-export' CLI command for exporting a
PKIArchiveOptions object containing a nominated target key, wrapped
by a nominated wrapping key.  This command is to be used by Custodia
to export key data for transmission to a requesting clone.

Part of: https://fedorahosted.org/pki/ticket/1625
---
 .../netscape/cmstools/authority/AuthorityCLI.java  |   1 +
 .../cmstools/authority/AuthorityKeyExportCLI.java  | 109 +++++++++++++++++++++
 2 files changed, 110 insertions(+)
 create mode 100644 
base/java-tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.java

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 
ac06ea24ce824ad1b4be29a4176658caa9302e89..f42660d6727059bc76ab7ccd0bd0b22a87bc5f9a
 100644
--- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java
@@ -18,6 +18,7 @@ public class AuthorityCLI extends CLI {
         addModule(new AuthorityDisableCLI(this));
         addModule(new AuthorityEnableCLI(this));
         addModule(new AuthorityRemoveCLI(this));
+        addModule(new AuthorityKeyExportCLI(this));
     }
 
     public String getFullName() {
diff --git 
a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.java
 
b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.java
new file mode 100644
index 
0000000000000000000000000000000000000000..a3dee82c8d7ef3ad923aa53635b0825f3d272998
--- /dev/null
+++ 
b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.java
@@ -0,0 +1,109 @@
+package com.netscape.cmstools.authority;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.PublicKey;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+
+import org.mozilla.jss.CryptoManager;
+import org.mozilla.jss.crypto.CryptoToken;
+import org.mozilla.jss.crypto.IVParameterSpec;
+import org.mozilla.jss.crypto.KeyGenAlgorithm;
+import org.mozilla.jss.crypto.PrivateKey;
+import org.mozilla.jss.crypto.X509Certificate;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmsutil.crypto.CryptoUtil;
+
+public class AuthorityKeyExportCLI extends CLI {
+
+    public AuthorityCLI authorityCLI;
+
+    public AuthorityKeyExportCLI(AuthorityCLI authorityCLI) {
+        super("key-export", "Export wrapped CA signing key", authorityCLI);
+        this.authorityCLI = authorityCLI;
+
+        options.addOption(null, "help", false, "Show usage");
+
+        Option option = new Option("o", "output", true, "Output file");
+        option.setArgName("filename");
+        options.addOption(option);
+
+        option = new Option(null, "wrap-nickname", true, "Nickname of wrapping 
key");
+        option.setArgName("nickname");
+        options.addOption(option);
+
+        option = new Option(null, "target-nickname", true, "Nickname of target 
key");
+        option.setArgName("nickname");
+        options.addOption(option);
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + "--wrap-nickname NICKNAME 
--target-nickname NICKNAME -o FILENAME", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            // Display usage
+            printHelp();
+            System.exit(0);
+        }
+
+        String filename = cmd.getOptionValue("output");
+        if (filename == null) {
+            System.err.println("Error: No output file specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String wrapNick = cmd.getOptionValue("wrap-nickname");
+        if (wrapNick == null) {
+            System.err.println("Error: no wrapping key nickname specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String targetNick = cmd.getOptionValue("target-nickname");
+        if (targetNick == null) {
+            System.err.println("Error: no target key nickname specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        try {
+            CryptoManager cm = CryptoManager.getInstance();
+            X509Certificate wrapCert = cm.findCertByNickname(wrapNick);
+            X509Certificate targetCert = cm.findCertByNickname(targetNick);
+
+            PublicKey wrappingKey = wrapCert.getPublicKey();
+            PrivateKey toBeWrapped = cm.findPrivKeyByCert(targetCert);
+            CryptoToken token = cm.getInternalKeyStorageToken();
+
+            byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
+            IVParameterSpec ivps = new IVParameterSpec(iv);
+
+            byte[] data = CryptoUtil.createPKIArchiveOptions(
+                token, wrappingKey, toBeWrapped,
+                KeyGenAlgorithm.DES3, 0, ivps);
+
+            Files.newOutputStream(Paths.get(filename)).write(data);
+        } catch (Throwable e) {
+            e.printStackTrace();
+            System.exit(-1);
+        }
+
+    }
+}
-- 
2.5.5

From 4c8fdc1b11e052d469aee944e491cd8725b7a1e9 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..ac45141662aea732389353814e5a7e7b3ba516a7
 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 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");
@@ -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 
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 d2e57290b2d3392082335955be72dfaf3111afd0 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   | 75 ++++++++++++++++++++++
 base/server/CMakeLists.txt                         | 11 ++++
 base/server/libexec/pki-ipa-retrieve-key           | 42 ++++++++++++
 specs/pki-core.spec                                |  1 +
 5 files changed, 137 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..4a162d3702fccc19dfef792a5213c653286930f3
--- /dev/null
+++ b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java
@@ -0,0 +1,75 @@
+// --- 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> hostPorts) {
+        CMS.debug("Running IPACustodiaKeyRetriever");
+
+        Stack<String> command = new Stack<>();
+        command.push("/usr/libexec/pki-ipa-retrieve-key");
+        command.push(nickname);
+
+        for (String hostPort : hostPorts) {
+            String host = hostPort.split(":")[0];
+            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 PEM-encoded certificate and a
+                 * base64-encoded PKIArchiveOptions containing the
+                 * wrapped private key.  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(
+                    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..9305150a446bb4deed339b89daad821a19b9e7df
--- /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_wrapped/" + 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)
+certificate = result["certificate"]
+wrapped_key = base64.b64decode(result["wrapped_key"])
+
+# Custodia returns a PEM-encoded certificate and a base64-encoded
+# DER PKIArchiveOptions object.  Output these values, separated by a
+# null byte (certificate first), to be read by the Java
+# IPACustodiaKeyRetriever that invoked this program.
+
+print(certificate, wrapped_key, sep='\0', end='')
diff --git a/specs/pki-core.spec b/specs/pki-core.spec
index 
bce6bd2d298bc8e1ad5cb40618f982b5ba23b27d..509ecdafa4e5d0e651ce22497db06420abcdf259
 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