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()?
2. In addInstanceToAuthorityKeyHosts(), you are still using only the
hostname. Should be host:port
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()
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?
97- ACK
98 - ACK
On Wed, 2016-04-20 at 16:15 +1000, Fraser Tweedale wrote:
New version of 0097 attached (0097-4). The only change is some
minor improvements to the pki-ipa-retrieve-key Python program.
Cheers,
Fraser
On Tue, Apr 19, 2016 at 07:32:16PM +1000, Fraser Tweedale wrote:
> 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-Apri
> > > > > > > l/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 <ftweedal(a)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..fd3c4fa225b036142a9aa4e99
> c65697365160dfd 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..5e4118d328ebe1fcac2743b3f
> 51fb5ca9d57f9eb 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 <ftweedal(a)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..7ac3e4e9ad2702e09c704638b
> 6ab773bb2dccb49 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 <ftweedal(a)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.ja
> va
>
> 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..f42660d6727059bc76ab7ccd0
> bd0b22a87bc5f9a 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.ja
> va b/base/java
> -tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.ja
> va
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..a3dee82c8d7ef3ad923aa5363
> 5b0825f3d272998
> --- /dev/null
> +++ b/base/java
> -tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.ja
> va
> @@ -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 <ftweedal(a)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..ac45141662aea732389353814
> e5a7e7b3ba516a7 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..7c0df0bf56578b81062de77de
> 47aa516b5c9d949
> --- /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..9931027dacfe88e636b694b7c
> 490ffc6804068dd 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 <ftweedal(a)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..1817dacfbacaeb2635db2550e
> 32ff62c26d628ef 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..4a162d3702fccc19dfef792a5
> 213c653286930f3
> --- /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..9e5b27833c8d023e63320c43d
> 64ad64b0055c254 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..9305150a446bb4deed339b89d
> aad821a19b9e7df
> --- /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..509ecdafa4e5d0e651ce22497
> db06420abcdf259 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/