diff options
Diffstat (limited to 'base/kra/src')
-rw-r--r-- | base/kra/src/CMakeLists.txt | 109 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/ArchiveOptions.java | 154 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/EncryptionUnit.java | 741 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/EnrollmentService.java | 872 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/KRANotify.java | 50 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/KRAPolicy.java | 78 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/KRAService.java | 101 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/KeyRecoveryAuthority.java | 1785 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/NetkeyKeygenService.java | 608 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/RecoveryService.java | 710 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/SecurityDataRecoveryService.java | 388 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/SecurityDataService.java | 171 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/StorageKeyUnit.java | 978 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/TokenKeyRecoveryService.java | 627 | ||||
-rw-r--r-- | base/kra/src/com/netscape/kra/TransportKeyUnit.java | 195 |
15 files changed, 7567 insertions, 0 deletions
diff --git a/base/kra/src/CMakeLists.txt b/base/kra/src/CMakeLists.txt new file mode 100644 index 000000000..df7e1929b --- /dev/null +++ b/base/kra/src/CMakeLists.txt @@ -0,0 +1,109 @@ +project(pki-kra_java Java) + +# '/usr/share/java/pki' jars +find_file(PKI_CERTSRV_JAR + NAMES + pki-certsrv.jar + PATHS + /usr/share/java/pki +) + +find_file(PKI_CMS_JAR + NAMES + pki-cms.jar + PATHS + /usr/share/java/pki +) + +find_file(PKI_CMSCORE_JAR + NAMES + pki-cmscore.jar + PATHS + /usr/share/java/pki +) + +find_file(PKI_CMSUTIL_JAR + NAMES + pki-cmsutil.jar + PATHS + /usr/share/java/pki +) + +find_file(PKI_NSUTIL_JAR + NAMES + pki-nsutil.jar + PATHS + ${JAVA_LIB_INSTALL_DIR} + /usr/share/java/pki +) + + +# '/usr/share/java' jars +find_file(LDAPJDK_JAR + NAMES + ldapjdk.jar + PATHS + /usr/share/java +) + + +# '${JAVA_LIB_INSTALL_DIR}' jars +find_file(JSS_JAR + NAMES + jss4.jar + PATHS + ${JAVA_LIB_INSTALL_DIR} +) + +find_file(COMMONS_CODEC_JAR + NAMES + commons-codec.jar + PATHS + /usr/share/java +) + +find_file(SYMKEY_JAR + NAMES + symkey.jar + PATHS + ${JAVA_LIB_INSTALL_DIR} +) + + +# identify java sources +set(pki-kra_java_SRCS + com/netscape/kra/KeyRecoveryAuthority.java + com/netscape/kra/EnrollmentService.java + com/netscape/kra/RecoveryService.java + com/netscape/kra/SecurityDataRecoveryService.java + com/netscape/kra/TokenKeyRecoveryService.java + com/netscape/kra/EncryptionUnit.java + com/netscape/kra/KRAService.java + com/netscape/kra/NetkeyKeygenService.java + com/netscape/kra/SecurityDataService.java + com/netscape/kra/KRANotify.java + com/netscape/kra/KRAPolicy.java + com/netscape/kra/TransportKeyUnit.java + com/netscape/kra/StorageKeyUnit.java + com/netscape/kra/ArchiveOptions.java +) + + +# set classpath +set(CMAKE_JAVA_INCLUDE_PATH + ${PKI_CERTSRV_JAR} ${PKI_CMS_JAR} ${PKI_CMSCORE_JAR} + ${PKI_CMSUTIL_JAR} ${PKI_NSUTIL_JAR} + ${LDAPJDK_JAR} + ${JSS_JAR} ${COMMONS_CODEC_JAR} ${SYMKEY_JAR}) + + +# set version +set(CMAKE_JAVA_TARGET_VERSION ${APPLICATION_VERSION}) + + +# build pki-kra.jar +add_jar(pki-kra ${pki-kra_java_SRCS}) +add_dependencies(pki-kra symkey pki-nsutil pki-cmsutil pki-certsrv pki-cms pki-cmscore) +install_jar(pki-kra ${JAVA_JAR_INSTALL_DIR}/pki) +set(PKI_KRA_JAR ${pki-kra_JAR_FILE} CACHE INTERNAL "pki-kra jar file") + diff --git a/base/kra/src/com/netscape/kra/ArchiveOptions.java b/base/kra/src/com/netscape/kra/ArchiveOptions.java new file mode 100644 index 000000000..c4650f17e --- /dev/null +++ b/base/kra/src/com/netscape/kra/ArchiveOptions.java @@ -0,0 +1,154 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.mozilla.jss.asn1.ANY; +import org.mozilla.jss.asn1.BIT_STRING; +import org.mozilla.jss.asn1.InvalidBERException; +import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.asn1.SET; +import org.mozilla.jss.pkix.cms.EncryptedContentInfo; +import org.mozilla.jss.pkix.cms.EnvelopedData; +import org.mozilla.jss.pkix.cms.RecipientInfo; +import org.mozilla.jss.pkix.crmf.EncryptedKey; +import org.mozilla.jss.pkix.crmf.EncryptedValue; +import org.mozilla.jss.pkix.crmf.PKIArchiveOptions; +import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; + +class ArchiveOptions { + private String mSymmAlgOID = null; + private byte mSymmAlgParams[] = null; + private byte mEncSymmKey[] = null; + private byte mEncValue[] = null; + + public ArchiveOptions(PKIArchiveOptions opts) throws EBaseException { + try { + EncryptedKey key = opts.getEncryptedKey(); + ANY enveloped_val = null; + EncryptedValue val = null; + AlgorithmIdentifier symmAlg = null; + + if (key.getType() == org.mozilla.jss.pkix.crmf.EncryptedKey.ENVELOPED_DATA) { + CMS.debug("EnrollService: ArchiveOptions() EncryptedKey type= ENVELOPED_DATA"); + // this is the new RFC4211 EncryptedKey that should + // have EnvelopedData to replace the deprecated EncryptedValue + enveloped_val = key.getEnvelopedData(); + byte[] env_b = enveloped_val.getEncoded(); + EnvelopedData.Template env_template = new EnvelopedData.Template(); + EnvelopedData env_data = + (EnvelopedData) env_template.decode(new ByteArrayInputStream(env_b)); + EncryptedContentInfo eCI = env_data.getEncryptedContentInfo(); + symmAlg = eCI.getContentEncryptionAlgorithm(); + mSymmAlgOID = symmAlg.getOID().toString(); + mSymmAlgParams = + ((OCTET_STRING) ((ANY) symmAlg.getParameters()).decodeWith(OCTET_STRING.getTemplate())) + .toByteArray(); + + SET recipients = env_data.getRecipientInfos(); + if (recipients.size() <= 0) { + CMS.debug("EnrollService: ArchiveOptions() - missing recipient information "); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", + "[PKIArchiveOptions] missing recipient information ")); + } + //check recpient - later + //we only handle one recipient here anyways. so, either the key + //can be decrypted or it can't. No risk here. + RecipientInfo ri = (RecipientInfo) recipients.elementAt(0); + OCTET_STRING key_o = ri.getEncryptedKey(); + mEncSymmKey = key_o.toByteArray(); + + OCTET_STRING oString = eCI.getEncryptedContent(); + BIT_STRING encVal = new BIT_STRING(oString.toByteArray(), 0); + mEncValue = encVal.getBits(); + CMS.debug("EnrollService: ArchiveOptions() EncryptedKey type= ENVELOPED_DATA done"); + } else if (key.getType() == org.mozilla.jss.pkix.crmf.EncryptedKey.ENCRYPTED_VALUE) { + CMS.debug("EnrollService: ArchiveOptions() EncryptedKey type= ENCRYPTED_VALUE"); + // this is deprecated: EncryptedValue + val = key.getEncryptedValue(); + symmAlg = val.getSymmAlg(); + mSymmAlgOID = symmAlg.getOID().toString(); + mSymmAlgParams = + ((OCTET_STRING) ((ANY) symmAlg.getParameters()).decodeWith(OCTET_STRING.getTemplate())) + .toByteArray(); + BIT_STRING encSymmKey = val.getEncSymmKey(); + + mEncSymmKey = encSymmKey.getBits(); + BIT_STRING encVal = val.getEncValue(); + + mEncValue = encVal.getBits(); + CMS.debug("EnrollService: ArchiveOptions() EncryptedKey type= ENCRYPTED_VALUE done"); + } else { + CMS.debug("EnrollService: ArchiveOptions() invalid EncryptedKey type"); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "[PKIArchiveOptions] type " + + key.getType())); + } + + } catch (InvalidBERException e) { + CMS.debug("EnrollService: ArchiveOptions(): " + e.toString()); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", + "[PKIArchiveOptions]" + e.toString())); + } catch (IOException e) { + CMS.debug("EnrollService: ArchiveOptions(): " + e.toString()); + throw new EBaseException("ArchiveOptions() exception caught: " + + e.toString()); + } catch (Exception e) { + CMS.debug("EnrollService: ArchiveOptions(): " + e.toString()); + throw new EBaseException("ArchiveOptions() exception caught: " + + e.toString()); + } + + } + + static public ArchiveOptions toArchiveOptions(byte options[]) throws + EBaseException { + ByteArrayInputStream bis = new ByteArrayInputStream(options); + PKIArchiveOptions archOpts = null; + + try { + archOpts = (PKIArchiveOptions) + (new PKIArchiveOptions.Template()).decode(bis); + } catch (Exception e) { + throw new EBaseException("Failed to decode input PKIArchiveOptions."); + } + + return new ArchiveOptions(archOpts); + + } + + public String getSymmAlgOID() { + return mSymmAlgOID; + } + + public byte[] getSymmAlgParams() { + return mSymmAlgParams; + } + + public byte[] getEncSymmKey() { + return mEncSymmKey; + } + + public byte[] getEncValue() { + return mEncValue; + } +} diff --git a/base/kra/src/com/netscape/kra/EncryptionUnit.java b/base/kra/src/com/netscape/kra/EncryptionUnit.java new file mode 100644 index 000000000..946f57613 --- /dev/null +++ b/base/kra/src/com/netscape/kra/EncryptionUnit.java @@ -0,0 +1,741 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.CharConversionException; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; + +import netscape.security.util.DerInputStream; +import netscape.security.util.DerOutputStream; +import netscape.security.util.DerValue; + +import org.mozilla.jss.crypto.BadPaddingException; +import org.mozilla.jss.crypto.Cipher; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.EncryptionAlgorithm; +import org.mozilla.jss.crypto.IVParameterSpec; +import org.mozilla.jss.crypto.IllegalBlockSizeException; +import org.mozilla.jss.crypto.KeyGenAlgorithm; +import org.mozilla.jss.crypto.KeyWrapAlgorithm; +import org.mozilla.jss.crypto.KeyWrapper; +import org.mozilla.jss.crypto.PrivateKey; +import org.mozilla.jss.crypto.SymmetricKey; +import org.mozilla.jss.crypto.TokenException; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.security.IEncryptionUnit; +import com.netscape.cmscore.util.Debug; + +/** + * A class represents the transport key pair. This key pair + * is used to protected EE's private key in transit. + * + * @author thomask + * @version $Revision$, $Date$ + */ +@SuppressWarnings("deprecation") +public abstract class EncryptionUnit implements IEncryptionUnit { + + /* Establish one constant IV for base class, to be used for + internal operations. Constant IV acceptable for symmetric keys. + */ + private byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; + protected IVParameterSpec IV = null; + + public EncryptionUnit() { + CMS.debug("EncryptionUnit.EncryptionUnit this: " + this.toString()); + + IV = new IVParameterSpec(iv); + } + + public abstract CryptoToken getToken(); + + public abstract CryptoToken getInternalToken(); + + public abstract PublicKey getPublicKey(); + + public abstract PrivateKey getPrivateKey(); + + /** + * Protects the private key so that it can be stored in + * internal database. + */ + public byte[] encryptInternalPrivate(byte priKey[]) + throws EBaseException { + try { + CMS.debug("EncryptionUnit.encryptInternalPrivate"); + CryptoToken internalToken = getInternalToken(); + + // (1) generate session key + org.mozilla.jss.crypto.KeyGenerator kg = + internalToken.getKeyGenerator(KeyGenAlgorithm.DES3); + SymmetricKey sk = kg.generate(); + + // (2) wrap private key with session key + Cipher cipher = internalToken.getCipherContext( + EncryptionAlgorithm.DES3_CBC_PAD); + + cipher.initEncrypt(sk, IV); + byte pri[] = cipher.doFinal(priKey); + + // (3) wrap session with transport public + KeyWrapper rsaWrap = internalToken.getKeyWrapper( + KeyWrapAlgorithm.RSA); + + rsaWrap.initWrap(getPublicKey(), null); + byte session[] = rsaWrap.wrap(sk); + + // use MY own structure for now: + // SEQUENCE { + // encryptedSession OCTET STRING, + // encryptedPrivate OCTET STRING + // } + + DerOutputStream tmp = new DerOutputStream(); + DerOutputStream out = new DerOutputStream(); + + tmp.putOctetString(session); + tmp.putOctetString(pri); + out.write(DerValue.tag_Sequence, tmp); + + return out.toByteArray(); + } catch (TokenException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } catch (NoSuchAlgorithmException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } catch (CharConversionException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } catch (InvalidAlgorithmParameterException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } catch (InvalidKeyException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } catch (BadPaddingException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } catch (IllegalBlockSizeException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } catch (IOException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } catch (Exception e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_INTERNAL", e.toString())); + Debug.trace("EncryptionUnit::encryptInternalPrivate " + e.toString()); + return null; + } + } + + public byte[] wrap(PrivateKey privKey) throws EBaseException { + return _wrap(privKey,null); + } + + public byte[] wrap(SymmetricKey symmKey) throws EBaseException { + return _wrap(null,symmKey); + } + /** + * External unwrapping. Unwraps the data using + * the transport private key. + */ + public SymmetricKey unwrap_sym(byte encSymmKey[], SymmetricKey.Usage usage) { + try { + CryptoToken token = getToken(); + + // (1) unwrap the session + PrivateKey priKey = getPrivateKey(); + String priKeyAlgo = priKey.getAlgorithm(); + CMS.debug("EncryptionUnit::unwrap_sym() private key algo: " + priKeyAlgo); + KeyWrapper keyWrapper = null; + if (priKeyAlgo.equals("EC")) { + keyWrapper = token.getKeyWrapper(KeyWrapAlgorithm.AES_ECB); + keyWrapper.initUnwrap(priKey, null); + } else { + keyWrapper = token.getKeyWrapper(KeyWrapAlgorithm.RSA); + keyWrapper.initUnwrap(priKey, null); + } + SymmetricKey sk = keyWrapper.unwrapSymmetric(encSymmKey, + SymmetricKey.DES3, usage, + 0); + CMS.debug("EncryptionUnit::unwrap_sym() unwrapped on slot: " + + token.getName()); + return sk; + } catch (Exception e) { + CMS.debug("EncryptionUnit::unwrap_sym() error:" + + e.toString()); + return null; + } + } + + public SymmetricKey unwrap_sym(byte encSymmKey[]) { + return unwrap_sym(encSymmKey, SymmetricKey.Usage.WRAP); + } + + public SymmetricKey unwrap_encrypt_sym(byte encSymmKey[]) { + return unwrap_sym(encSymmKey, SymmetricKey.Usage.ENCRYPT); + } + + /** + * Decrypts the user private key. + */ + public byte[] decryptExternalPrivate(byte encSymmKey[], + String symmAlgOID, byte symmAlgParams[], + byte encValue[]) + throws EBaseException { + try { + + CMS.debug("EncryptionUnit.decryptExternalPrivate"); + CryptoToken token = getToken(); + + // (1) unwrap the session + KeyWrapper rsaWrap = token.getKeyWrapper( + KeyWrapAlgorithm.RSA); + + rsaWrap.initUnwrap(getPrivateKey(), null); + SymmetricKey sk = rsaWrap.unwrapSymmetric(encSymmKey, + SymmetricKey.DES3, SymmetricKey.Usage.DECRYPT, + 0); + + // (2) unwrap the pri + Cipher cipher = token.getCipherContext( + EncryptionAlgorithm.DES3_CBC_PAD // XXX + ); + + cipher.initDecrypt(sk, new IVParameterSpec( + symmAlgParams)); + return cipher.doFinal(encValue); + } catch (IllegalBlockSizeException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_EXTERNAL", e.toString())); + Debug.trace("EncryptionUnit::decryptExternalPrivate " + e.toString()); + return null; + } catch (BadPaddingException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_EXTERNAL", e.toString())); + Debug.trace("EncryptionUnit::decryptExternalPrivate " + e.toString()); + return null; + } catch (TokenException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_EXTERNAL", e.toString())); + Debug.trace("EncryptionUnit::decryptExternalPrivate " + e.toString()); + return null; + } catch (NoSuchAlgorithmException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_EXTERNAL", e.toString())); + Debug.trace("EncryptionUnit::decryptExternalPrivate " + e.toString()); + return null; + } catch (InvalidAlgorithmParameterException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_EXTERNAL", e.toString())); + Debug.trace("EncryptionUnit::decryptExternalPrivate " + e.toString()); + return null; + } catch (InvalidKeyException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_EXTERNAL", e.toString())); + Debug.trace("EncryptionUnit::decryptExternalPrivate " + e.toString()); + return null; + } catch (Exception e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_EXTERNAL", e.toString())); + Debug.trace("EncryptionUnit::decryptExternalPrivate " + e.toString()); + return null; + } + } + + /** + * External unwrapping. Unwraps the symmetric key using + * the transport private key. + */ + public SymmetricKey unwrap_symmetric(byte encSymmKey[], + String symmAlgOID, byte symmAlgParams[], + byte encValue[]) + throws EBaseException { + try { + CryptoToken token = getToken(); + + // (1) unwrap the session + KeyWrapper rsaWrap = token.getKeyWrapper( + KeyWrapAlgorithm.RSA); + + rsaWrap.initUnwrap(getPrivateKey(), null); + SymmetricKey sk = rsaWrap.unwrapSymmetric(encSymmKey, + SymmetricKey.DES3, SymmetricKey.Usage.UNWRAP, + 0); + + // (2) unwrap the sym key + KeyWrapper wrapper = token.getKeyWrapper( + KeyWrapAlgorithm.DES3_CBC_PAD // XXX + ); + + wrapper.initUnwrap(sk, new IVParameterSpec( + symmAlgParams)); + + SymmetricKey symKey = wrapper.unwrapSymmetric(encValue, SymmetricKey.DES3, SymmetricKey.Usage.DECRYPT, 0); + + return symKey; + } catch (TokenException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (NoSuchAlgorithmException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (InvalidAlgorithmParameterException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (InvalidKeyException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (Exception e) { + CMS.debug("EncryptionUnit.unwrap : Exception:" + e.toString()); + return null; + } + } + + /** + * External unwrapping. Unwraps the data using + * the transport private key. + */ + public PrivateKey unwrap(byte encSymmKey[], + String symmAlgOID, byte symmAlgParams[], + byte encValue[], PublicKey pubKey) + throws EBaseException { + try { + CryptoToken token = getToken(); + + // (1) unwrap the session + KeyWrapper rsaWrap = token.getKeyWrapper( + KeyWrapAlgorithm.RSA); + + rsaWrap.initUnwrap(getPrivateKey(), null); + SymmetricKey sk = rsaWrap.unwrapSymmetric(encSymmKey, + SymmetricKey.DES3, SymmetricKey.Usage.UNWRAP, + 0); + + // (2) unwrap the pri + KeyWrapper wrapper = token.getKeyWrapper( + KeyWrapAlgorithm.DES3_CBC_PAD // XXX + ); + + wrapper.initUnwrap(sk, new IVParameterSpec( + symmAlgParams)); + + PrivateKey.Type keytype = null; + String alg = pubKey.getAlgorithm(); + if (alg.equals("DSA")) { + keytype = PrivateKey.DSA; + } else if (alg.equals("EC")) { + keytype = PrivateKey.EC; + } else { + keytype = PrivateKey.RSA; + } + PrivateKey pk = wrapper.unwrapTemporaryPrivate(encValue, + keytype , pubKey); + + return pk; + } catch (TokenException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (NoSuchAlgorithmException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (InvalidAlgorithmParameterException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (InvalidKeyException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (Exception e) { + CMS.debug("EncryptionUnit.unwrap : Exception:"+e.toString()); + return null; + } + } + + /** + * External unwrapping. Unwraps the data using + * the transport private key. + */ + + public byte[] decryptInternalPrivate(byte wrappedKeyData[]) + throws EBaseException { + try { + CMS.debug("EncryptionUnit.decryptInternalPrivate"); + DerValue val = new DerValue(wrappedKeyData); + // val.tag == DerValue.tag_Sequence + DerInputStream in = val.data; + DerValue dSession = in.getDerValue(); + byte session[] = dSession.getOctetString(); + DerValue dPri = in.getDerValue(); + byte pri[] = dPri.getOctetString(); + + CryptoToken token = getToken(); + + // (1) unwrap the session + CMS.debug("decryptInternalPrivate(): getting key wrapper on slot:" + token.getName()); + KeyWrapper rsaWrap = token.getKeyWrapper( + KeyWrapAlgorithm.RSA); + + rsaWrap.initUnwrap(getPrivateKey(), null); + SymmetricKey sk = rsaWrap.unwrapSymmetric(session, + SymmetricKey.DES3, SymmetricKey.Usage.DECRYPT, 0); + + // (2) unwrap the pri + Cipher cipher = token.getCipherContext( + EncryptionAlgorithm.DES3_CBC_PAD); + + cipher.initDecrypt(sk, IV); + return cipher.doFinal(pri); + } catch (IllegalBlockSizeException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_DECRYPT", e.toString())); + Debug.trace("EncryptionUnit::decryptInternalPrivate " + e.toString()); + return null; + } catch (BadPaddingException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_DECRYPT", e.toString())); + Debug.trace("EncryptionUnit::decryptInternalPrivate " + e.toString()); + return null; + } catch (TokenException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_DECRYPT", e.toString())); + Debug.trace("EncryptionUnit::decryptInternalPrivate " + e.toString()); + return null; + } catch (NoSuchAlgorithmException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_DECRYPT", e.toString())); + Debug.trace("EncryptionUnit::decryptInternalPrivate " + e.toString()); + return null; + } catch (InvalidAlgorithmParameterException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_DECRYPT", e.toString())); + Debug.trace("EncryptionUnit::decryptInternalPrivate " + e.toString()); + return null; + } catch (InvalidKeyException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_DECRYPT", e.toString())); + Debug.trace("EncryptionUnit::decryptInternalPrivate " + e.toString()); + return null; + } catch (IOException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_DECRYPT", e.toString())); + Debug.trace("EncryptionUnit::decryptInternalPrivate " + e.toString()); + return null; + } catch (Exception e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_DECRYPT", e.toString())); + Debug.trace("EncryptionUnit::decryptInternalPrivate " + e.toString()); + return null; + } + } + + /** + * External unwrapping of stored symmetric key. + */ + public SymmetricKey unwrap(byte wrappedKeyData[]) + throws EBaseException { + try { + DerValue val = new DerValue(wrappedKeyData); + // val.tag == DerValue.tag_Sequence + DerInputStream in = val.data; + DerValue dSession = in.getDerValue(); + byte session[] = dSession.getOctetString(); + DerValue dPri = in.getDerValue(); + byte pri[] = dPri.getOctetString(); + + CryptoToken token = getToken(); + // (1) unwrap the session key + KeyWrapper rsaWrap = token.getKeyWrapper( + KeyWrapAlgorithm.RSA); + + rsaWrap.initUnwrap(getPrivateKey(), null); + SymmetricKey sk = rsaWrap.unwrapSymmetric(session, + SymmetricKey.DES3, SymmetricKey.Usage.UNWRAP, 0); + + // (2) unwrap the symmetric key + KeyWrapper wrapper = token.getKeyWrapper( + KeyWrapAlgorithm.DES3_CBC_PAD); + + wrapper.initUnwrap(sk, IV); + + SymmetricKey sk_ret = wrapper.unwrapSymmetric(pri, + SymmetricKey.DES3, SymmetricKey.Usage.UNWRAP, + 0); + + return sk_ret; + } catch (TokenException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + CMS.debug(e); + return null; + } catch (NoSuchAlgorithmException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (InvalidAlgorithmParameterException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (InvalidKeyException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.printStackTrace(e); + return null; + } catch (IOException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (Exception e) { + Debug.printStackTrace(e); + return null; + } + } + + /** + * Internal unwrapping. + */ + public PrivateKey unwrap_temp(byte wrappedKeyData[], PublicKey pubKey) + throws EBaseException { + return _unwrap(wrappedKeyData, pubKey, true); + } + + /** + * Internal unwrapping. + */ + public PrivateKey unwrap(byte wrappedKeyData[], PublicKey pubKey) + throws EBaseException { + return _unwrap(wrappedKeyData, pubKey, false); + } + + /** + * Internal unwrapping. + */ + private PrivateKey _unwrap(byte wrappedKeyData[], PublicKey + pubKey, boolean temporary) + throws EBaseException { + try { + DerValue val = new DerValue(wrappedKeyData); + // val.tag == DerValue.tag_Sequence + DerInputStream in = val.data; + DerValue dSession = in.getDerValue(); + byte session[] = dSession.getOctetString(); + DerValue dPri = in.getDerValue(); + byte pri[] = dPri.getOctetString(); + + CryptoToken token = getToken(); + // (1) unwrap the session + KeyWrapper rsaWrap = token.getKeyWrapper( + KeyWrapAlgorithm.RSA); + + rsaWrap.initUnwrap(getPrivateKey(), null); + SymmetricKey sk = rsaWrap.unwrapSymmetric(session, + SymmetricKey.DES3, SymmetricKey.Usage.UNWRAP, 0); + + // (2) unwrap the pri + KeyWrapper wrapper = token.getKeyWrapper( + KeyWrapAlgorithm.DES3_CBC_PAD); + + wrapper.initUnwrap(sk, IV); + + PrivateKey pk = null; + if (temporary) { + pk = wrapper.unwrapTemporaryPrivate(pri, + PrivateKey.RSA, pubKey); + } else { + pk = wrapper.unwrapPrivate(pri, + PrivateKey.RSA, pubKey); + } + return pk; + } catch (TokenException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + CMS.debug(e); + return null; + } catch (NoSuchAlgorithmException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (InvalidAlgorithmParameterException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (InvalidKeyException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.printStackTrace(e); + return null; + } catch (IOException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_UNWRAP", e.toString())); + Debug.trace("EncryptionUnit::unwrap " + e.toString()); + return null; + } catch (Exception e) { + Debug.printStackTrace(e); + return null; + } + } + + /*** + * Internal wrap, accounts for either private or symmetric key + */ + private byte[] _wrap(PrivateKey priKey, SymmetricKey symmKey) throws EBaseException { + try { + if ((priKey == null && symmKey == null) || (priKey != null && symmKey != null)) { + return null; + } + CMS.debug("EncryptionUnit.wrap interal."); + CryptoToken token = getToken(); + + // (1) generate session key + org.mozilla.jss.crypto.KeyGenerator kg = + token.getKeyGenerator(KeyGenAlgorithm.DES3); + // internalToken.getKeyGenerator(KeyGenAlgorithm.DES3); + SymmetricKey.Usage usages[] = new SymmetricKey.Usage[2]; + usages[0] = SymmetricKey.Usage.WRAP; + usages[1] = SymmetricKey.Usage.UNWRAP; + kg.setKeyUsages(usages); + kg.temporaryKeys(true); + SymmetricKey sk = kg.generate(); + CMS.debug("EncryptionUnit:wrap() session key generated on slot: " + token.getName()); + + // (2) wrap private key with session key + // KeyWrapper wrapper = internalToken.getKeyWrapper( + KeyWrapper wrapper = token.getKeyWrapper( + KeyWrapAlgorithm.DES3_CBC_PAD); + + wrapper.initWrap(sk, IV); + + byte pri[] = null; + + if ( priKey != null) { + pri = wrapper.wrap(priKey); + } else if ( symmKey != null) { + pri = wrapper.wrap(symmKey); + } + CMS.debug("EncryptionUnit:wrap() privKey wrapped"); + + // (3) wrap session with transport public + KeyWrapper rsaWrap = token.getKeyWrapper( + KeyWrapAlgorithm.RSA); + + rsaWrap.initWrap(getPublicKey(), null); + byte session[] = rsaWrap.wrap(sk); + CMS.debug("EncryptionUnit:wrap() sessin key wrapped"); + + // use MY own structure for now: + // SEQUENCE { + // encryptedSession OCTET STRING, + // encryptedPrivate OCTET STRING + // } + + DerOutputStream tmp = new DerOutputStream(); + DerOutputStream out = new DerOutputStream(); + + tmp.putOctetString(session); + tmp.putOctetString(pri); + out.write(DerValue.tag_Sequence, tmp); + + return out.toByteArray(); + } catch (TokenException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_WRAP", e.toString())); + Debug.trace("EncryptionUnit::wrap " + e.toString()); + return null; + } catch (NoSuchAlgorithmException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_WRAP", e.toString())); + Debug.trace("EncryptionUnit::wrap " + e.toString()); + return null; + } catch (CharConversionException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_WRAP", e.toString())); + Debug.trace("EncryptionUnit::wrap " + e.toString()); + return null; + } catch (InvalidAlgorithmParameterException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_WRAP", e.toString())); + Debug.trace("EncryptionUnit::wrap " + e.toString()); + return null; + } catch (InvalidKeyException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_WRAP", e.toString())); + Debug.trace("EncryptionUnit::wrap " + e.toString()); + return null; + } catch (IOException e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_WRAP", e.toString())); + Debug.trace("EncryptionUnit::wrap " + e.toString()); + return null; + } catch (Exception e) { + CMS.getLogger().log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_ENCRYPTION_WRAP", e.toString())); + Debug.trace("EncryptionUnit::wrap " + e.toString()); + return null; + } + } + + /** + * Verify the given key pair. + */ + public void verify(PublicKey publicKey, PrivateKey privateKey) throws + EBaseException { + } +} diff --git a/base/kra/src/com/netscape/kra/EnrollmentService.java b/base/kra/src/com/netscape/kra/EnrollmentService.java new file mode 100644 index 000000000..37d1aea53 --- /dev/null +++ b/base/kra/src/com/netscape/kra/EnrollmentService.java @@ -0,0 +1,872 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.cert.CertificateException; +import java.util.StringTokenizer; +import java.util.Vector; + +import netscape.security.provider.RSAPublicKey; +import netscape.security.util.BigInt; +import netscape.security.util.DerInputStream; +import netscape.security.util.DerOutputStream; +import netscape.security.util.DerValue; +import netscape.security.x509.CertificateSubjectName; +import netscape.security.x509.CertificateX509Key; +import netscape.security.x509.X509CertInfo; +import netscape.security.x509.X509Key; + +import org.mozilla.jss.asn1.ASN1Util; +import org.mozilla.jss.asn1.ASN1Value; +import org.mozilla.jss.asn1.InvalidBERException; +import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; +import org.mozilla.jss.asn1.SEQUENCE; +import org.mozilla.jss.pkix.crmf.CertReqMsg; +import org.mozilla.jss.pkix.crmf.CertRequest; +import org.mozilla.jss.pkix.crmf.PKIArchiveOptions; +import org.mozilla.jss.pkix.primitive.AVA; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authentication.AuthToken; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.dbs.keydb.IKeyRepository; +import com.netscape.certsrv.kra.EKRAException; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.kra.ProofOfArchival; +import com.netscape.certsrv.logging.AuditFormat; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.profile.IEnrollProfile; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.request.IService; +import com.netscape.certsrv.security.IStorageKeyUnit; +import com.netscape.certsrv.security.ITransportKeyUnit; +import com.netscape.certsrv.util.IStatsSubsystem; +import com.netscape.cmscore.crmf.CRMFParser; +import com.netscape.cmscore.crmf.PKIArchiveOptionsContainer; +import com.netscape.kra.ArchiveOptions; +import com.netscape.cmscore.dbs.KeyRecord; +import com.netscape.cmsutil.util.Utils; + +/** + * A class represents archival request processor. It + * passes the request to the policy processor, and + * process the request according to the policy decision. + * <P> + * If policy returns ACCEPTED, the request will be processed immediately. + * <P> + * Upon processing, the incoming user key is unwrapped with the transport key of KRA, and then wrapped with the storage + * key. The encrypted key is stored in the internal database for long term storage. + * <P> + * + * @author thomask (original) + * @author cfu (non-RSA keys; private keys secure handling); + * @version $Revision$, $Date$ + */ +public class EnrollmentService implements IService { + + // constants + public static final String CRMF_REQUEST = "CRMFRequest"; + public final static String ATTR_KEY_RECORD = "keyRecord"; + public final static String ATTR_PROOF_OF_ARCHIVAL = + "proofOfArchival"; + + // private + private IKeyRecoveryAuthority mKRA = null; + private ITransportKeyUnit mTransportUnit = null; + private IStorageKeyUnit mStorageUnit = null; + private ILogger mSignedAuditLogger = CMS.getSignedAuditLogger(); + + private final static byte EOL[] = { Character.LINE_SEPARATOR }; + private final static String LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST = + "LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_4"; + private final static String LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED = + "LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED_3"; + private final static String LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST = + "LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_4"; + private final static String LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED = + "LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED_4"; + + /** + * Constructs request processor. + * <P> + * + * @param kra key recovery authority + */ + public EnrollmentService(IKeyRecoveryAuthority kra) { + mKRA = kra; + mTransportUnit = kra.getTransportKeyUnit(); + mStorageUnit = kra.getStorageKeyUnit(); + } + + public PKIArchiveOptions toPKIArchiveOptions(byte options[]) { + ByteArrayInputStream bis = new ByteArrayInputStream(options); + PKIArchiveOptions archOpts = null; + + try { + archOpts = (PKIArchiveOptions) + (new PKIArchiveOptions.Template()).decode(bis); + } catch (Exception e) { + CMS.debug("EnrollProfile: getPKIArchiveOptions " + e.toString()); + } + return archOpts; + } + + /** + * Services an enrollment/archival request. + * <P> + * + * @param request enrollment request + * @return serving successful or not + * @exception EBaseException failed to serve + */ + public boolean serviceRequest(IRequest request) + throws EBaseException { + + IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats"); + if (statsSub != null) { + statsSub.startTiming("archival", true /* main action */); + } + + String auditMessage = null; + String auditSubjectID = auditSubjectID(); + String auditRequesterID = auditRequesterID(); + String auditArchiveID = ILogger.UNIDENTIFIED; + String auditPublicKey = ILogger.UNIDENTIFIED; + + String id = request.getRequestId().toString(); + if (id != null) { + auditArchiveID = id.trim(); + } + if (CMS.debugOn()) + CMS.debug("EnrollmentServlet: KRA services enrollment request"); + + SessionContext sContext = SessionContext.getContext(); + String agentId = (String) sContext.get(SessionContext.USER_ID); + AuthToken authToken = (AuthToken) sContext.get(SessionContext.AUTH_TOKEN); + + mKRA.log(ILogger.LL_INFO, "KRA services enrollment request"); + // unwrap user key with transport + byte unwrapped[] = null; + PKIArchiveOptionsContainer aOpts[] = null; + + String profileId = request.getExtDataInString("profileId"); + + if (profileId == null || profileId.equals("")) { + try { + aOpts = CRMFParser.getPKIArchiveOptions( + request.getExtDataInString(IRequest.HTTP_PARAMS, CRMF_REQUEST)); + } catch (IOException e) { + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_INVALID_PRIVATE_KEY")); + } + } else { + // profile-based request + PKIArchiveOptions options = (PKIArchiveOptions) + toPKIArchiveOptions( + request.getExtDataInByteArray(IEnrollProfile.REQUEST_ARCHIVE_OPTIONS)); + + aOpts = new PKIArchiveOptionsContainer[1]; + aOpts[0] = new PKIArchiveOptionsContainer(options, + 0/* not matter */); + + request.setExtData("dbStatus", "NOT_UPDATED"); + } + + for (int i = 0; i < aOpts.length; i++) { + ArchiveOptions opts = new ArchiveOptions(aOpts[i].mAO); + + if (statsSub != null) { + statsSub.startTiming("decrypt_user_key"); + } + mKRA.log(ILogger.LL_INFO, "KRA decrypts external private"); + if (CMS.debugOn()) + CMS.debug("EnrollmentService::about to decryptExternalPrivate"); + unwrapped = mTransportUnit.decryptExternalPrivate( + opts.getEncSymmKey(), + opts.getSymmAlgOID(), + opts.getSymmAlgParams(), + opts.getEncValue()); + if (statsSub != null) { + statsSub.endTiming("decrypt_user_key"); + } + if (CMS.debugOn()) + CMS.debug("EnrollmentService::finished decryptExternalPrivate"); + if (unwrapped == null) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_UNWRAP_USER_KEY")); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_INVALID_PRIVATE_KEY")); + } + + // retrieve pubic key + X509Key publicKey = getPublicKey(request, aOpts[i].mReqPos); + byte publicKeyData[] = publicKey.getEncoded(); + + if (publicKeyData == null) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_PUBLIC_NOT_FOUND")); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_INVALID_PUBLIC_KEY")); + } + + /* Bugscape #54948 - verify public and private key before archiving key */ + + if (statsSub != null) { + statsSub.startTiming("verify_key"); + } + if (verifyKeyPair(publicKeyData, unwrapped) == false) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_PUBLIC_NOT_FOUND")); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_INVALID_PUBLIC_KEY")); + } + if (statsSub != null) { + statsSub.endTiming("verify_key"); + } + + /** + * mTransportKeyUnit.verify(pKey, unwrapped); + **/ + // retrieve owner name + String owner = getOwnerName(request, aOpts[i].mReqPos); + + if (owner == null) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_OWNER_NAME_NOT_FOUND")); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_INVALID_KEYRECORD")); + } + + // + // privateKeyData ::= SEQUENCE { + // sessionKey OCTET_STRING, + // encKey OCTET_STRING, + // } + // + mKRA.log(ILogger.LL_INFO, "KRA encrypts internal private"); + if (statsSub != null) { + statsSub.startTiming("encrypt_user_key"); + } + byte privateKeyData[] = mStorageUnit.encryptInternalPrivate( + unwrapped); + if (statsSub != null) { + statsSub.endTiming("encrypt_user_key"); + } + + if (privateKeyData == null) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_WRAP_USER_KEY")); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_INVALID_PRIVATE_KEY")); + } + + // create key record + KeyRecord rec = new KeyRecord(null, publicKeyData, + privateKeyData, owner, + publicKey.getAlgorithmId().getOID().toString(), agentId); + + // we deal with RSA key only + try { + RSAPublicKey rsaPublicKey = new RSAPublicKey(publicKeyData); + + rec.setKeySize(Integer.valueOf(rsaPublicKey.getKeySize())); + } catch (InvalidKeyException e) { + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_INVALID_KEYRECORD")); + } + + // if record alreay has a serial number, yell out. + if (rec.getSerialNumber() != null) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_INVALID_SERIAL_NUMBER", + rec.getSerialNumber().toString())); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_INVALID_STATE")); + } + IKeyRepository storage = mKRA.getKeyRepository(); + BigInteger serialNo = storage.getNextSerialNumber(); + + if (serialNo == null) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_GET_NEXT_SERIAL")); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_INVALID_STATE")); + } + if (i == 0) { + rec.set(KeyRecord.ATTR_ID, serialNo); + request.setExtData(ATTR_KEY_RECORD, serialNo); + } else { + rec.set(KeyRecord.ATTR_ID + i, serialNo); + request.setExtData(ATTR_KEY_RECORD + i, serialNo); + } + + mKRA.log(ILogger.LL_INFO, "KRA adding key record " + serialNo); + if (statsSub != null) { + statsSub.startTiming("store_key"); + } + storage.addKeyRecord(rec); + if (statsSub != null) { + statsSub.endTiming("store_key"); + } + + if (CMS.debugOn()) + CMS.debug("EnrollmentService: key record 0x" + serialNo.toString(16) + + " (" + owner + ") archived"); + + mKRA.log(ILogger.LL_INFO, "key record 0x" + + serialNo.toString(16) + + " (" + owner + ") archived"); + + // for audit log + String authMgr = AuditFormat.NOAUTH; + + if (authToken != null) { + authMgr = + authToken.getInString(AuthToken.TOKEN_AUTHMGR_INST_NAME); + } + CMS.getLogger().log(ILogger.EV_AUDIT, + ILogger.S_KRA, + AuditFormat.LEVEL, + AuditFormat.FORMAT, + new Object[] { + IRequest.KEYARCHIVAL_REQUEST, + request.getRequestId(), + AuditFormat.FROMAGENT + " agentID: " + agentId, + authMgr, + "completed", + owner, + "serial number: 0x" + serialNo.toString(16) } + ); + + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.SUCCESS, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + + // store a message in the signed audit log file + auditPublicKey = auditPublicKey(rec); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED, + auditSubjectID, + ILogger.SUCCESS, + auditPublicKey); + + audit(auditMessage); + + // Xxx - should sign this proof of archival + ProofOfArchival mProof = new ProofOfArchival(serialNo, + owner, mKRA.getX500Name().toString(), + rec.getCreateTime()); + + DerOutputStream mProofOut = new DerOutputStream(); + mProof.encode(mProofOut); + if (i == 0) { + request.setExtData(ATTR_PROOF_OF_ARCHIVAL, + mProofOut.toByteArray()); + } else { + request.setExtData(ATTR_PROOF_OF_ARCHIVAL + i, + mProofOut.toByteArray()); + } + + } // for + + /* + request.delete(IEnrollProfile.REQUEST_SUBJECT_NAME); + request.delete(IEnrollProfile.REQUEST_EXTENSIONS); + request.delete(IEnrollProfile.REQUEST_VALIDITY); + request.delete(IEnrollProfile.REQUEST_KEY); + request.delete(IEnrollProfile.REQUEST_SIGNING_ALGORITHM); + request.delete(IEnrollProfile.REQUEST_LOCALE); + */ + + request.setExtData(IRequest.RESULT, IRequest.RES_SUCCESS); + + // update request + mKRA.log(ILogger.LL_INFO, "KRA updating request"); + mKRA.getRequestQueue().updateRequest(request); + + if (statsSub != null) { + statsSub.endTiming("archival"); + } + + return true; + } + + public boolean verifyKeyPair(byte publicKeyData[], byte privateKeyData[]) { + try { + DerValue publicKeyVal = new DerValue(publicKeyData); + DerInputStream publicKeyIn = publicKeyVal.data; + publicKeyIn.getSequence(0); + DerValue publicKeyDer = new DerValue(publicKeyIn.getBitString()); + DerInputStream publicKeyDerIn = publicKeyDer.data; + BigInt publicKeyModulus = publicKeyDerIn.getInteger(); + BigInt publicKeyExponent = publicKeyDerIn.getInteger(); + + DerValue privateKeyVal = new DerValue(privateKeyData); + if (privateKeyVal.tag != DerValue.tag_Sequence) + return false; + DerInputStream privateKeyIn = privateKeyVal.data; + privateKeyIn.getInteger(); + privateKeyIn.getSequence(0); + DerValue privateKeyDer = new DerValue(privateKeyIn.getOctetString()); + DerInputStream privateKeyDerIn = privateKeyDer.data; + + @SuppressWarnings("unused") + BigInt privateKeyVersion = privateKeyDerIn.getInteger(); + BigInt privateKeyModulus = privateKeyDerIn.getInteger(); + BigInt privateKeyExponent = privateKeyDerIn.getInteger(); + + if (!publicKeyModulus.equals(privateKeyModulus)) { + CMS.debug("verifyKeyPair modulus mismatch publicKeyModulus=" + + publicKeyModulus + " privateKeyModulus=" + privateKeyModulus); + return false; + } + + if (!publicKeyExponent.equals(privateKeyExponent)) { + CMS.debug("verifyKeyPair exponent mismatch publicKeyExponent=" + + publicKeyExponent + " privateKeyExponent=" + privateKeyExponent); + return false; + } + + return true; + } catch (Exception e) { + CMS.debug("verifyKeyPair error " + e); + return false; + } + } + + private static final OBJECT_IDENTIFIER PKIARCHIVEOPTIONS_OID = + new OBJECT_IDENTIFIER(new long[] { 1, 3, 6, 1, 5, 5, 7, 5, 1, 4 } + ); + + /** + * Retrieves PKIArchiveOptions from CRMF request. + * + * @param crmfBlob CRMF request + * @return PKIArchiveOptions + * @exception EBaseException failed to extrace option + */ + public static PKIArchiveOptionsContainer[] getPKIArchiveOptions(String crmfBlob) + throws EBaseException { + Vector<PKIArchiveOptionsContainer> options = new Vector<PKIArchiveOptionsContainer>(); + + if (CMS.debugOn()) + CMS.debug("EnrollmentService::getPKIArchiveOptions> crmfBlob=" + crmfBlob); + byte[] crmfBerBlob = null; + + crmfBerBlob = Utils.base64decode(crmfBlob); + ByteArrayInputStream crmfBerBlobIn = new + ByteArrayInputStream(crmfBerBlob); + SEQUENCE crmfmsgs = null; + + try { + crmfmsgs = (SEQUENCE) new + SEQUENCE.OF_Template(new + CertReqMsg.Template()).decode( + crmfBerBlobIn); + } catch (IOException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "[crmf msgs]" + e.toString())); + } catch (InvalidBERException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "[crmf msgs]" + e.toString())); + } + + for (int z = 0; z < crmfmsgs.size(); z++) { + CertReqMsg certReqMsg = (CertReqMsg) + crmfmsgs.elementAt(z); + CertRequest certReq = certReqMsg.getCertReq(); + + // try to locate PKIArchiveOption control + AVA archAva = null; + + try { + for (int i = 0; i < certReq.numControls(); i++) { + AVA ava = certReq.controlAt(i); + OBJECT_IDENTIFIER oid = ava.getOID(); + + if (oid.equals(PKIARCHIVEOPTIONS_OID)) { + archAva = ava; + break; + } + } + } catch (Exception e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "no PKIArchiveOptions found " + + e.toString())); + } + if (archAva != null) { + + ASN1Value archVal = archAva.getValue(); + ByteArrayInputStream bis = new ByteArrayInputStream(ASN1Util.encode(archVal)); + PKIArchiveOptions archOpts = null; + + try { + archOpts = (PKIArchiveOptions) + (new PKIArchiveOptions.Template()).decode(bis); + } catch (IOException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", + "[PKIArchiveOptions]" + e.toString())); + } catch (InvalidBERException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", + "[PKIArchiveOptions]" + e.toString())); + } + options.addElement(new PKIArchiveOptionsContainer(archOpts, z)); + } + } + if (options.size() == 0) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "PKIArchiveOptions found")); + } else { + PKIArchiveOptionsContainer p[] = new PKIArchiveOptionsContainer[options.size()]; + + options.copyInto(p); + return p; + } + } + + /** + * Retrieves public key from request. + * + * @param request CRMF request + * @return JSS public key + * @exception EBaseException failed to retrieve public key + */ + private X509Key getPublicKey(IRequest request, int i) throws EBaseException { + String profileId = request.getExtDataInString("profileId"); + + if (profileId != null && !profileId.equals("")) { + byte[] certKeyData = request.getExtDataInByteArray(IEnrollProfile.REQUEST_KEY); + if (certKeyData != null) { + try { + CertificateX509Key x509key = new CertificateX509Key( + new ByteArrayInputStream(certKeyData)); + + return (X509Key) x509key.get(CertificateX509Key.KEY); + } catch (Exception e1) { + CMS.debug("EnrollService: (Archival) getPublicKey " + + e1.toString()); + } + } + return null; + } + + // retrieve x509 Key from request + X509CertInfo certInfo[] = + request.getExtDataInCertInfoArray(IRequest.CERT_INFO); + CertificateX509Key pX509Key = null; + + try { + pX509Key = (CertificateX509Key) + certInfo[i].get(X509CertInfo.KEY); + } catch (IOException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_GET_PUBLIC_KEY", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", + "[" + X509CertInfo.KEY + "]" + e.toString())); + } catch (CertificateException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_GET_PUBLIC_KEY", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", + "[" + X509CertInfo.KEY + "]" + e.toString())); + } + X509Key pKey = null; + + try { + pKey = (X509Key) pX509Key.get( + CertificateX509Key.KEY); + } catch (IOException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_GET_PUBLIC_KEY", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "[" + + CertificateX509Key.KEY + "]" + e.toString())); + } + return pKey; + } + + /** + * Retrieves key's owner name from request. + * + * @param request CRMF request + * @return owner name (subject name) + * @exception EBaseException failed to retrieve public key + */ + private String getOwnerName(IRequest request, int i) + throws EBaseException { + + String profileId = request.getExtDataInString("profileId"); + + if (profileId != null && !profileId.equals("")) { + CertificateSubjectName sub = request.getExtDataInCertSubjectName( + IEnrollProfile.REQUEST_SUBJECT_NAME); + if (sub != null) { + return sub.toString(); + } + } + + X509CertInfo certInfo[] = + request.getExtDataInCertInfoArray(IRequest.CERT_INFO); + CertificateSubjectName pSub = null; + + try { + pSub = (CertificateSubjectName) + certInfo[0].get(X509CertInfo.SUBJECT); + } catch (IOException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_GET_OWNER_NAME", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "[" + + X509CertInfo.SUBJECT + "]" + e.toString())); + } catch (CertificateException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_GET_OWNER_NAME", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "[" + + X509CertInfo.SUBJECT + "]" + e.toString())); + } + String owner = pSub.toString(); + + return owner; + } + + /** + * Signed Audit Log Public Key + * + * This method is called to obtain the public key from the passed in + * "KeyRecord" for a signed audit log message. + * <P> + * + * @param rec a Key Record + * @return key string containing the certificate's public key + */ + private String auditPublicKey(KeyRecord rec) { + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + if (rec == null) { + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } + + byte rawData[] = null; + + try { + rawData = rec.getPublicKeyData(); + } catch (EBaseException e) { + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } + + String key = ""; + + // convert "rawData" into "base64Data" + if (rawData != null) { + String base64Data = null; + + base64Data = CMS.BtoA(rawData).trim(); + + // extract all line separators from the "base64Data" + StringTokenizer st = new StringTokenizer(base64Data, "\r\n"); + while (st.hasMoreTokens()) { + key += st.nextToken(); + } + } + + key = key.trim(); + + if (key.equals("")) { + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } else { + return key; + } + } + + /** + * Signed Audit Log Subject ID + * + * This method is called to obtain the "SubjectID" for + * a signed audit log message. + * <P> + * + * @return id string containing the signed audit log message SubjectID + */ + + private String auditSubjectID() { + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + String subjectID = null; + + // Initialize subjectID + SessionContext auditContext = SessionContext.getExistingContext(); + + if (auditContext != null) { + subjectID = (String) + auditContext.get(SessionContext.USER_ID); + + if (subjectID != null) { + subjectID = subjectID.trim(); + } else { + subjectID = ILogger.NONROLEUSER; + } + } else { + subjectID = ILogger.UNIDENTIFIED; + } + + return subjectID; + } + + /** + * Signed Audit Log Requester ID + * + * This method is called to obtain the "RequesterID" for + * a signed audit log message. + * <P> + * + * @return id string containing the signed audit log message RequesterID + */ + private String auditRequesterID() { + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + String requesterID = null; + + // Initialize requesterID + SessionContext auditContext = SessionContext.getExistingContext(); + + if (auditContext != null) { + requesterID = (String) + auditContext.get(SessionContext.REQUESTER_ID); + + if (requesterID != null) { + requesterID = requesterID.trim(); + } else { + requesterID = ILogger.UNIDENTIFIED; + } + } else { + requesterID = ILogger.UNIDENTIFIED; + } + + return requesterID; + } + + /** + * Signed Audit Log + * + * This method is called to store messages to the signed audit log. + * <P> + * + * @param msg signed audit log message + */ + private void audit(String msg) { + // in this case, do NOT strip preceding/trailing whitespace + // from passed-in String parameters + + if (mSignedAuditLogger == null) { + return; + } + + mSignedAuditLogger.log(ILogger.EV_SIGNED_AUDIT, + null, + ILogger.S_SIGNED_AUDIT, + ILogger.LL_SECURITY, + msg); + } +} diff --git a/base/kra/src/com/netscape/kra/KRANotify.java b/base/kra/src/com/netscape/kra/KRANotify.java new file mode 100644 index 000000000..29eaf477a --- /dev/null +++ b/base/kra/src/com/netscape/kra/KRANotify.java @@ -0,0 +1,50 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.request.ARequestNotifier; + +/** + * A class represents a KRA request queue notify. This + * object will be invoked by the request subsystem + * when a request is requested for processing. + * + * @author thomask + * @version $Revision$, $Date$ + */ +public class KRANotify extends ARequestNotifier { + private IKeyRecoveryAuthority mKRA = null; + + /** + * default constructor + */ + public KRANotify() { + super(); + } + + /** + * Creates KRA notify. + */ + public KRANotify(IKeyRecoveryAuthority kra) { + super(); + mKRA = kra; + } + + // XXX may want to do something else with mKRA ? +} diff --git a/base/kra/src/com/netscape/kra/KRAPolicy.java b/base/kra/src/com/netscape/kra/KRAPolicy.java new file mode 100644 index 000000000..aa2b2c2de --- /dev/null +++ b/base/kra/src/com/netscape/kra/KRAPolicy.java @@ -0,0 +1,78 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.ISubsystem; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.policy.IPolicyProcessor; +import com.netscape.certsrv.request.IPolicy; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.request.PolicyResult; +import com.netscape.cmscore.policy.GenericPolicyProcessor; +import com.netscape.cmscore.util.Debug; + +/** + * KRA Policy. + * + * @deprecated + * @version $Revision$, $Date$ + */ +public class KRAPolicy implements IPolicy { + IConfigStore mConfig = null; + IKeyRecoveryAuthority mKRA = null; + + public GenericPolicyProcessor mPolicies = new GenericPolicyProcessor(false); + + public KRAPolicy() { + } + + public void init(ISubsystem owner, IConfigStore config) + throws EBaseException { + mKRA = (IKeyRecoveryAuthority) owner; + mConfig = config; + mPolicies.init(mKRA, mConfig); + } + + public IPolicyProcessor getPolicyProcessor() { + return mPolicies; + } + + /** + */ + public PolicyResult apply(IRequest r) { + if (Debug.ON) + Debug.trace("KRA applies policies"); + mKRA.log(ILogger.LL_INFO, "KRA applies policies"); + PolicyResult result = mPolicies.apply(r); + + if (result.equals(PolicyResult.DEFERRED)) { + // For KRA request, there is deferred + if (Debug.ON) + Debug.trace("KRA policies return DEFERRED"); + return PolicyResult.REJECTED; + } else { + if (Debug.ON) + Debug.trace("KRA policies return ACCEPTED"); + return mPolicies.apply(r); + } + } + +} diff --git a/base/kra/src/com/netscape/kra/KRAService.java b/base/kra/src/com/netscape/kra/KRAService.java new file mode 100644 index 000000000..4858e286a --- /dev/null +++ b/base/kra/src/com/netscape/kra/KRAService.java @@ -0,0 +1,101 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.util.Hashtable; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.request.IService; +import com.netscape.cmscore.util.Debug; + +/** + * A class represents a KRA request queue service. This + * is the service object that is registered with + * the request queue. And it acts as a broker to + * distribute request into different KRA specific + * services. This service registration allows us to support + * new request easier. + * <P> + * + * @author thomask + * @version $Revision$, $Date$ + */ +public class KRAService implements IService { + + public final static String ENROLLMENT = + IRequest.ENROLLMENT_REQUEST; + public final static String RECOVERY = IRequest.KEYRECOVERY_REQUEST; + public final static String NETKEY_KEYGEN = IRequest.NETKEY_KEYGEN_REQUEST; + public final static String NETKEY_KEYRECOVERY = IRequest.NETKEY_KEYRECOVERY_REQUEST; + public final static String SECURITY_DATA_ENROLLMENT = IRequest.SECURITY_DATA_ENROLLMENT_REQUEST; + public final static String SECURITY_DATA_RECOVERY = IRequest.SECURITY_DATA_RECOVERY_REQUEST; + + + // private variables + private IKeyRecoveryAuthority mKRA = null; + private Hashtable<String, IService> mServices = new Hashtable<String, IService>(); + + /** + * Constructs KRA service. + */ + public KRAService(IKeyRecoveryAuthority kra) { + mKRA = kra; + mServices.put(ENROLLMENT, new EnrollmentService(kra)); + mServices.put(RECOVERY, new RecoveryService(kra)); + mServices.put(NETKEY_KEYGEN, new NetkeyKeygenService(kra)); + mServices.put(NETKEY_KEYRECOVERY, new TokenKeyRecoveryService(kra)); + mServices.put(SECURITY_DATA_ENROLLMENT, new SecurityDataService(kra)); + mServices.put(SECURITY_DATA_RECOVERY, new SecurityDataRecoveryService(kra)); + } + + /** + * Processes a KRA request. This method is invoked by + * request subsystem. + * + * @param r request from request subsystem + * @exception EBaseException failed to serve + */ + public boolean serviceRequest(IRequest r) throws EBaseException { + if (Debug.ON) + Debug.trace("KRA services request " + + r.getRequestId().toString()); + mKRA.log(ILogger.LL_INFO, "KRA services request " + + r.getRequestId().toString()); + IService s = mServices.get(r.getRequestType()); + + if (s == null) { + r.setExtData(IRequest.RESULT, IRequest.RES_ERROR); + r.setExtData(IRequest.ERROR, new EBaseException( + CMS.getUserMessage("CMS_BASE_INVALID_OPERATION"))); + return true; + } + try { + return s.serviceRequest(r); + } catch (EBaseException e) { + r.setExtData(IRequest.RESULT, IRequest.RES_ERROR); + r.setExtData(IRequest.ERROR, e); + // return true; + // #546508 + return false; + } + } +} diff --git a/base/kra/src/com/netscape/kra/KeyRecoveryAuthority.java b/base/kra/src/com/netscape/kra/KeyRecoveryAuthority.java new file mode 100644 index 000000000..8d8cafb84 --- /dev/null +++ b/base/kra/src/com/netscape/kra/KeyRecoveryAuthority.java @@ -0,0 +1,1785 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.StringTokenizer; +import java.util.Vector; + +import netscape.security.util.DerOutputStream; +import netscape.security.x509.CertificateChain; +import netscape.security.x509.X500Name; +import netscape.security.x509.X509CertImpl; + +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.NoSuchTokenException; +import org.mozilla.jss.crypto.CryptoToken; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authority.IAuthority; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.EPropertyNotFound; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.ISubsystem; +import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.dbs.IDBSubsystem; +import com.netscape.certsrv.dbs.keydb.IKeyRepository; +import com.netscape.certsrv.dbs.replicadb.IReplicaIDRepository; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.kra.IKeyService; +import com.netscape.certsrv.listeners.EListenersException; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.policy.IPolicyProcessor; +import com.netscape.certsrv.request.ARequestNotifier; +import com.netscape.certsrv.request.IPolicy; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.request.IRequestListener; +import com.netscape.certsrv.request.IRequestNotifier; +import com.netscape.certsrv.request.IRequestQueue; +import com.netscape.certsrv.request.IRequestScheduler; +import com.netscape.certsrv.request.IRequestSubsystem; +import com.netscape.certsrv.request.IService; +import com.netscape.certsrv.request.RequestId; +import com.netscape.certsrv.request.RequestStatus; +import com.netscape.certsrv.security.Credential; +import com.netscape.certsrv.security.IStorageKeyUnit; +import com.netscape.certsrv.security.ITransportKeyUnit; +import com.netscape.certsrv.usrgrp.IUGSubsystem; +import com.netscape.cmscore.dbs.DBSubsystem; +import com.netscape.cmscore.dbs.KeyRecord; +import com.netscape.cmscore.dbs.KeyRepository; +import com.netscape.cmscore.dbs.ReplicaIDRepository; +import com.netscape.cmscore.request.RequestSubsystem; + +/** + * A class represents an key recovery authority (KRA). A KRA + * is responsible to maintain key pairs that have been + * escrowed. It provides archive and recovery key pairs + * functionalities. + * <P> + * + * @author thomask + * @version $Revision$, $Date$ + */ +public class KeyRecoveryAuthority implements IAuthority, IKeyService, IKeyRecoveryAuthority { + + public final static String OFFICIAL_NAME = "Data Recovery Manager"; + + /** + * Internal Constants + */ + + private static final String PR_INTERNAL_TOKEN_NAME = "internal"; + private static final String PARAM_CREDS = "creds"; + private static final String PARAM_LOCK = "lock"; + private static final String PARAM_PK12 = "pk12"; + private static final String PARAM_ERROR = "error"; + private static final String PARAM_AGENT = "agent"; + + private final static String KEY_RESP_NAME = "keyRepository"; + private static final String PROP_REPLICAID_DN = "dbs.replicadn"; + + protected boolean mInitialized = false; + protected IConfigStore mConfig = null; + protected ILogger mLogger = CMS.getLogger(); + protected KRAPolicy mPolicy = null; + protected X500Name mName = null; + protected boolean mQueueRequests = false; + protected String mId = null; + protected IRequestQueue mRequestQueue = null; + protected TransportKeyUnit mTransportKeyUnit = null; + protected StorageKeyUnit mStorageKeyUnit = null; + protected Hashtable<String, Credential[]> mAutoRecovery = new Hashtable<String, Credential[]>(); + protected boolean mAutoRecoveryOn = false; + protected KeyRepository mKeyDB = null; + protected ReplicaIDRepository mReplicaRepot = null; + protected IRequestNotifier mNotify = null; + protected IRequestNotifier mPNotify = null; + protected ISubsystem mOwner = null; + protected int mRecoveryIDCounter = 0; + protected Hashtable<String, Hashtable<String, Object>> mRecoveryParams = + new Hashtable<String, Hashtable<String, Object>>(); + protected org.mozilla.jss.crypto.X509Certificate mJssCert = null; + protected CryptoToken mKeygenToken = null; + + // holds the number of bits of entropy to collect for each keygen + private int mEntropyBitsPerKeyPair = 0; + + // the number of milliseconds which it is acceptable to block while + // getting entropy - anything longer will cause a warning. + // 0 means this warning is disabled + private int mEntropyBlockWarnMilliseconds = 0; + + // for the notification listener + public IRequestListener mReqInQListener = null; + + private ILogger mSignedAuditLogger = CMS.getSignedAuditLogger(); + private final static byte EOL[] = { Character.LINE_SEPARATOR }; + private final static String SIGNED_AUDIT_AGENT_DELIMITER = ", "; + private final static String LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST = + "LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_4"; + private final static String LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED = + "LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED_3"; + private final static String LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST = + "LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_4"; + private final static String LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_ASYNC = + "LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_ASYNC_4"; + private final static String LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED = + "LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED_4"; + private final static String LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED_ASYNC = + "LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED_ASYNC_4"; + + /** + * Constructs an escrow authority. + * <P> + */ + public KeyRecoveryAuthority() { + super(); + } + + /** + * Retrieves subsystem identifier. + * + * @return subsystem id + */ + public String getId() { + return mId; + } + + /** + * Sets subsystem identifier. + * + * @param id subsystem id + * @exception EBaseException failed to set id + */ + public void setId(String id) throws EBaseException { + mId = id; + } + + /** + * @deprecated + */ + public IPolicyProcessor getPolicyProcessor() { + return mPolicy.getPolicyProcessor(); + } + + // initialize entropy collection parameters + private void initEntropy(IConfigStore config) { + mEntropyBitsPerKeyPair = 0; + mEntropyBlockWarnMilliseconds = 50; + // initialize entropy collection + IConfigStore ecs = config.getSubStore("entropy"); + if (ecs != null) { + try { + mEntropyBitsPerKeyPair = ecs.getInteger("bitsperkeypair", 0); + mEntropyBlockWarnMilliseconds = ecs.getInteger("blockwarnms", 50); + } catch (EBaseException eb) { + // ok - we deal with missing parameters above + } + } + CMS.debug("KeyRecoveryAuthority Entropy bits = " + mEntropyBitsPerKeyPair); + if (mEntropyBitsPerKeyPair == 0) { + //log(ILogger.LL_INFO, + //CMS.getLogMessage("CMSCORE_KRA_ENTROPY_COLLECTION_DISABLED")); + } else { + //log(ILogger.LL_INFO, + //CMS.getLogMessage("CMSCORE_KRA_ENTROPY_COLLECTION_ENABLED")); + CMS.debug("KeyRecoveryAuthority about to add Entropy"); + addEntropy(false); + CMS.debug("KeyRecoveryAuthority back from add Entropy"); + } + + } + + public void addEntropy(boolean logflag) { + CMS.debug("KeyRecoveryAuthority addEntropy()"); + if (mEntropyBitsPerKeyPair == 0) { + CMS.debug("KeyRecoveryAuthority returning - disabled()"); + return; + } + long start = System.currentTimeMillis(); + try { + com.netscape.cmscore.security.JssSubsystem.getInstance(). + addEntropy(mEntropyBitsPerKeyPair); + } catch (Exception e) { + CMS.debug("KeyRecoveryAuthority returning - error - see log file"); + CMS.debug("exception: " + e.getMessage()); + CMS.debug(e); + if (logflag) { + log(ILogger.LL_INFO, + CMS.getLogMessage("CMSCORE_KRA_ENTROPY_ERROR", + e.getMessage())); + } + } + long end = System.currentTimeMillis(); + long duration = end - start; + + if (mEntropyBlockWarnMilliseconds > 0 && + duration > mEntropyBlockWarnMilliseconds) { + + CMS.debug("KeyRecoveryAuthority returning - warning - entropy took too long (ms=" + + duration + ")"); + if (logflag) { + log(ILogger.LL_INFO, + CMS.getLogMessage("CMSCORE_KRA_ENTROPY_BLOCKED_WARNING", + "" + (int) duration)); + } + } + CMS.debug("KeyRecoveryAuthority returning "); + } + + /** + * Starts this subsystem. It loads and initializes all + * necessary components. This subsystem is started by + * KRASubsystem. + * <P> + * + * @param owner owner of this subsystem + * @param config configuration store for this subsystem + * @exception EBaseException failed to start subsystem + */ + public void init(ISubsystem owner, IConfigStore config) + throws EBaseException { + CMS.debug("KeyRecoveryAuthority init() begins"); + if (mInitialized) + return; + + mConfig = config; + mOwner = owner; + + // initialize policy processor + mPolicy = new KRAPolicy(); + mPolicy.init(this, mConfig.getSubStore(PROP_POLICY)); + + // create key repository + int keydb_inc = mConfig.getInteger(PROP_KEYDB_INC, 5); + + mKeyDB = new KeyRepository(getDBSubsystem(), + keydb_inc, + "ou=" + KEY_RESP_NAME + ",ou=" + + getId() + "," + + getDBSubsystem().getBaseDN()); + + // read transport key from internal database + mTransportKeyUnit = new TransportKeyUnit(); + try { + mTransportKeyUnit.init(this, mConfig.getSubStore( + PROP_TRANSPORT_KEY)); + } catch (EBaseException e) { + CMS.debug("KeyRecoveryAuthority: transport unit exception " + e.toString()); + //XXX throw e; + return; + } + + // retrieve the authority name from transport cert + try { + mJssCert = mTransportKeyUnit.getCertificate(); + X509CertImpl certImpl = new + X509CertImpl(mJssCert.getEncoded()); + + mName = (X500Name) certImpl.getSubjectDN(); + } catch (CertificateEncodingException e) { + CMS.debug("KeyRecoveryAuthority: " + e.toString()); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_LOAD_FAILED", + "transport cert " + e.toString())); + } catch (CertificateException e) { + CMS.debug("KeyRecoveryAuthority: " + e.toString()); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_LOAD_FAILED", + "transport cert " + e.toString())); + } + + // read transport key from storage key + mStorageKeyUnit = new StorageKeyUnit(); + try { + mStorageKeyUnit.init(this, + mConfig.getSubStore(PROP_STORAGE_KEY)); + } catch (EBaseException e) { + CMS.debug("KeyRecoveryAuthority: storage unit exception " + e.toString()); + throw e; + } + + // setup token for server-side key generation for user enrollments + String serverKeygenTokenName = mConfig.getString("serverKeygenTokenName", null); + if (serverKeygenTokenName == null) { + CMS.debug("serverKeygenTokenName set to nothing"); + if (mStorageKeyUnit.getToken() != null) { + try { + String storageToken = mStorageKeyUnit.getToken().getName(); + if (!storageToken.equals("internal")) { + CMS.debug("Auto set serverKeygenTokenName to " + storageToken); + serverKeygenTokenName = storageToken; + } + } catch (Exception e) { + } + } + } + if (serverKeygenTokenName == null) { + serverKeygenTokenName = "internal"; + } + if (serverKeygenTokenName.equalsIgnoreCase(PR_INTERNAL_TOKEN_NAME)) + serverKeygenTokenName = PR_INTERNAL_TOKEN_NAME; + + try { + if (serverKeygenTokenName.equalsIgnoreCase(PR_INTERNAL_TOKEN_NAME)) { + CMS.debug("KeyRecoveryAuthority: getting internal crypto token for serverkeygen"); + mKeygenToken = CryptoManager.getInstance().getInternalKeyStorageToken(); + } else { + CMS.debug("KeyRecoveryAuthority: getting HSM token for serverkeygen"); + mKeygenToken = CryptoManager.getInstance().getTokenByName(serverKeygenTokenName); + } + CMS.debug("KeyRecoveryAuthority: set up keygenToken"); + } catch (NoSuchTokenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_TOKEN_NOT_FOUND", serverKeygenTokenName)); + } catch (Exception e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CRYPTOMANAGER_UNINITIALIZED")); + } + + CMS.debug("KeyRecoveryAuthority: about to init entropy"); + initEntropy(mConfig); + CMS.debug("KeyRecoveryAuthority: completed init of entropy"); + + getLogger().log(ILogger.EV_SYSTEM, ILogger.S_KRA, + ILogger.LL_INFO, mName.toString() + " is started"); + + // setup the KRA request queue + IService service = new KRAService(this); + + mNotify = new KRANotify(this); + mPNotify = new ARequestNotifier(); + IRequestSubsystem reqSub = RequestSubsystem.getInstance(); + int reqdb_inc = mConfig.getInteger("reqdbInc", 5); + + mRequestQueue = reqSub.getRequestQueue(getId(), reqdb_inc, + mPolicy, service, mNotify, mPNotify); + + // set KeyStatusUpdateInterval to be 10 minutes if serial management is enabled. + mKeyDB.setKeyStatusUpdateInterval( + mRequestQueue.getRequestRepository(), + mConfig.getInteger("keyStatusUpdateInterval", 10 * 60)); + + // init request scheduler if configured + String schedulerClass = + mConfig.getString("requestSchedulerClass", null); + + if (schedulerClass != null) { + try { + IRequestScheduler scheduler = (IRequestScheduler) + Class.forName(schedulerClass).newInstance(); + + mRequestQueue.setRequestScheduler(scheduler); + } catch (Exception e) { + // do nothing here + } + } + initNotificationListeners(); + + String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); + if (replicaReposDN == null) { + replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); + } + + mReplicaRepot = new ReplicaIDRepository( + DBSubsystem.getInstance(), 1, replicaReposDN); + CMS.debug("Replica Repot inited"); + + } + + public CryptoToken getKeygenToken() { + return mKeygenToken; + } + + public IRequestListener getRequestInQListener() { + return mReqInQListener; + } + + public org.mozilla.jss.crypto.X509Certificate getTransportCert() { + return mJssCert; + } + + /** + * Clears up system during garbage collection. + */ + public void finalize() { + shutdown(); + } + + /** + * Starts this service. When this method is called, all + * service + * + * @exception EBaseException failed to startup this subsystem + */ + public void startup() throws EBaseException { + CMS.debug("KeyRecoveryAuthority startup() begins"); + + if (mRequestQueue != null) { + // setup administration operations if everything else is fine + mRequestQueue.recover(); + CMS.debug("KeyRecoveryAuthority startup() call request Q recover"); + + // Note that we use our instance id for registration. + // This helps us to support multiple instances + // of a subsystem within server. + + // register remote admin interface + mInitialized = true; + } else { + CMS.debug("KeyRecoveryAuthority: mRequestQueue is null, could be in preop mode"); + } + } + + /** + * Shutdowns this subsystem. + */ + public void shutdown() { + if (!mInitialized) + return; + + mTransportKeyUnit.shutdown(); + mStorageKeyUnit.shutdown(); + if (mKeyDB != null) { + mKeyDB.shutdown(); + mKeyDB = null; + } + getLogger().log(ILogger.EV_SYSTEM, ILogger.S_KRA, + ILogger.LL_INFO, mName.toString() + " is stopped"); + mInitialized = false; + } + + /** + * Retrieves the configuration store of this subsystem. + * <P> + * + * @return configuration store + */ + public IConfigStore getConfigStore() { + return mConfig; + } + + /** + * Changes the auto recovery state. + * + * @param cs list of recovery agent credentials + * @param on turn of auto recovery or not + * @return operation success or not + */ + public boolean setAutoRecoveryState(Credential cs[], boolean on) { + if (on == true) { + // check credential before enabling it + try { + getStorageKeyUnit().login(cs); + } catch (Exception e) { + return false; + } + } + // maintain in-memory variable; don't store it in config + mAutoRecoveryOn = on; + return true; + } + + /** + * Retrieves the current auto recovery state. + * + * @return enable or not + */ + public boolean getAutoRecoveryState() { + // maintain in-memory variable; don't store it in config + return mAutoRecoveryOn; + } + + /** + * Returns a list of users who are in auto + * recovery mode. + * + * @return list of user IDs that are accepted in the + * auto recovery mode + */ + public Enumeration<String> getAutoRecoveryIDs() { + return mAutoRecovery.keys(); + } + + /** + * Adds auto recovery mode to the given user id. + * + * @param id new identifier to the auto recovery mode + * @param creds list of credentials + */ + public void addAutoRecovery(String id, Credential creds[]) { + mAutoRecovery.put(id, creds); + } + + /** + * Removes auto recovery mode from the given user id. + * + * @param id id of user to be removed from auto + * recovery mode + */ + public void removeAutoRecovery(String id) { + mAutoRecovery.remove(id); + } + + /** + * Retrieves logger from escrow authority. + * + * @return logger + */ + public ILogger getLogger() { + return CMS.getLogger(); + } + + /** + * Retrieves number of required agents for + * recovery operation. + * + * @return number of required agents + * @exception EBaseException failed to retrieve info + */ + public int getNoOfRequiredAgents() throws EBaseException { + if (mConfig.getBoolean("keySplitting", false)) { + return mStorageKeyUnit.getNoOfRequiredAgents(); + } else { + int ret = -1; + ret = mConfig.getInteger("noOfRequiredRecoveryAgents", 1); + if (ret <= 0) { + throw new EBaseException("Invalid parameter noOfRequiredecoveryAgents"); + } + return ret; + } + } + + /** + * Sets number of required agents for + * recovery operation + * + * @return none + * @exception EBaseException invalid setting + */ + public void setNoOfRequiredAgents(int number) throws EBaseException { + if (mConfig.getBoolean("keySplitting")) { + mStorageKeyUnit.setNoOfRequiredAgents(number); + } else { + mConfig.putInteger("noOfRequiredRecoveryAgents", number); + } + } + + /** + * Distributed recovery. + */ + public String getRecoveryID() { + return Integer.toString(mRecoveryIDCounter++); + } + + public Hashtable<String, Object> createRecoveryParams(String recoveryID) + throws EBaseException { + Hashtable<String, Object> h = new Hashtable<String, Object>(); + + h.put(PARAM_CREDS, new Vector<Credential>()); + h.put(PARAM_LOCK, new Object()); + mRecoveryParams.put(recoveryID, h); + return h; + } + + public void destroyRecoveryParams(String recoveryID) + throws EBaseException { + mRecoveryParams.remove(recoveryID); + } + + public Hashtable<String, Object> getRecoveryParams(String recoveryID) + throws EBaseException { + return (Hashtable<String, Object>) mRecoveryParams.get(recoveryID); + } + + public void createPk12(String recoveryID, byte[] pk12) + throws EBaseException { + Hashtable<String, Object> h = getRecoveryParams(recoveryID); + + h.put(PARAM_PK12, pk12); + } + + public byte[] getPk12(String recoveryID) + throws EBaseException { + return (byte[]) getRecoveryParams(recoveryID).get(PARAM_PK12); + } + + public void createError(String recoveryID, String error) + throws EBaseException { + Hashtable<String, Object> h = getRecoveryParams(recoveryID); + + h.put(PARAM_ERROR, error); + } + + public String getError(String recoveryID) + throws EBaseException { + return (String) getRecoveryParams(recoveryID).get(PARAM_ERROR); + } + + /** + * Retrieve the current approval agents + */ + public Vector<Credential> getAppAgents( + String recoveryID) throws EBaseException { + Hashtable<String, Object> h = getRecoveryParams(recoveryID); + @SuppressWarnings("unchecked") + Vector<Credential> dc = (Vector<Credential>) h.get(PARAM_CREDS); + + return dc; + } + + /** + * Retrieves a list credentials. This puts KRA in a waiting + * mode, it never returns until all the necessary passwords + * are collected. + */ + public Credential[] getDistributedCredentials( + String recoveryID) + throws EBaseException { + Hashtable<String, Object> h = getRecoveryParams(recoveryID); + @SuppressWarnings("unchecked") + Vector<Credential> dc = (Vector<Credential>) h.get(PARAM_CREDS); + Object lock = (Object) h.get(PARAM_LOCK); + + synchronized (lock) { + while (dc.size() < getNoOfRequiredAgents()) { + CMS.debug("KeyRecoveryAuthority: cfu in synchronized lock for getDistributedCredentials"); + try { + lock.wait(); + } catch (InterruptedException e) { + } + } + Credential creds[] = new Credential[dc.size()]; + + dc.copyInto(creds); + return creds; + } + } + + /** + * Verifies credential. + */ + private void verifyCredential(Vector<Credential> creds, String uid, + String pwd) throws EBaseException { + // see if we have the uid already + + if (!mConfig.getBoolean("keySplitting")) { + // check if the uid is in the specified group + IUGSubsystem ug = (IUGSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_UG); + if (!ug.isMemberOf(uid, mConfig.getString("recoveryAgentGroup"))) { + // invalid group + throw new EBaseException(CMS.getUserMessage("CMS_KRA_CREDENTIALS_NOT_EXIST")); + } + } + + for (int i = 0; i < creds.size(); i++) { + Credential c = creds.elementAt(i); + + if (c.getIdentifier().equals(uid)) { + // duplicated uid + throw new EBaseException(CMS.getUserMessage("CMS_KRA_CREDENTIALS_EXIST")); + } + } + if (mConfig.getBoolean("keySplitting")) { + mStorageKeyUnit.checkPassword(uid, pwd); + } + } + + /** + * Adds password. + */ + public void addDistributedCredential(String recoveryID, + String uid, String pwd) throws EBaseException { + Hashtable<String, Object> h = getRecoveryParams(recoveryID); + @SuppressWarnings("unchecked") + Vector<Credential> dc = (Vector<Credential>) h.get(PARAM_CREDS); + Object lock = (Object) h.get(PARAM_LOCK); + + synchronized (lock) { + verifyCredential(dc, uid, pwd); + // verify password + dc.addElement(new Credential(uid, pwd)); + // modify status object + lock.notify(); + } + } + + /** + * Archives key. This creates a key record in the key + * repository. + * <P> + * + * <ul> + * <li>signed.audit LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST used whenever a user private key archive + * request is made (this is when the DRM receives the request) + * <li>signed.audit LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED used whenever a user private key + * archive request is processed (this is when the DRM processes the request) + * </ul> + * + * @param rec key record to be archived + * @return executed request + * @exception EBaseException failed to archive key + * @return the request + * <P> + */ + public IRequest archiveKey(KeyRecord rec) + throws EBaseException { + String auditMessage = null; + String auditSubjectID = auditSubjectID(); + String auditRequesterID = auditRequesterID(); + String auditPublicKey = auditPublicKey(rec); + String auditArchiveID = ILogger.UNIDENTIFIED; + + IRequestQueue queue = null; + IRequest r = null; + String id = null; + + // ensure that any low-level exceptions are reported + // to the signed audit log and stored as failures + try { + queue = getRequestQueue(); + + r = queue.newRequest(KRAService.ENROLLMENT); + + if (r != null) { + // overwrite "auditArchiveID" if and only if "id" != null + id = r.getRequestId().toString(); + if (id != null) { + auditArchiveID = id.trim(); + } + } + + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.SUCCESS, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + } catch (EBaseException eAudit1) { + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRequesterID, + auditArchiveID); + + audit(auditMessage); + + throw eAudit1; + } + + // ensure that any low-level exceptions are reported + // to the signed audit log and stored as failures + try { + if (r != null) { + r.setExtData(EnrollmentService.ATTR_KEY_RECORD, rec.getSerialNumber()); + queue.processRequest(r); + } + + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED, + auditSubjectID, + ILogger.SUCCESS, + auditPublicKey); + + audit(auditMessage); + } catch (EBaseException eAudit1) { + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditPublicKey); + + audit(auditMessage); + + throw eAudit1; + } + + return r; + } + + /** + * async key recovery initiation + */ + public String initAsyncKeyRecovery(BigInteger kid, X509CertImpl cert, String agent) + throws EBaseException { + + String auditPublicKey = auditPublicKey(cert); + String auditRecoveryID = "undefined"; + String auditMessage = null; + String auditSubjectID = auditSubjectID(); + + IRequestQueue queue = null; + IRequest r = null; + + try { + queue = getRequestQueue(); + r = queue.newRequest(KRAService.RECOVERY); + + r.setExtData(RecoveryService.ATTR_SERIALNO, kid); + r.setExtData(RecoveryService.ATTR_USER_CERT, cert); + // first one in the "approvingAgents" list is the initiating agent + r.setExtData(RecoveryService.ATTR_APPROVE_AGENTS, agent); + r.setRequestStatus(RequestStatus.PENDING); + queue.updateRequest(r); + auditRecoveryID = r.getRequestId().toString(); + + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_ASYNC, + auditSubjectID, + ILogger.SUCCESS, + auditRecoveryID, + auditPublicKey); + + audit(auditMessage); + } catch (EBaseException eAudit1) { + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_ASYNC, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + auditPublicKey); + + audit(auditMessage); + + throw eAudit1; + } + + //NO call to queue.processRequest(r) because it is only initiating + return r.getRequestId().toString(); + } + + /** + * is async recovery request status APPROVED - + * i.e. all required # of recovery agents approved + */ + public boolean isApprovedAsyncKeyRecovery(String reqID) + throws EBaseException { + IRequestQueue queue = null; + IRequest r = null; + + queue = getRequestQueue(); + r = queue.findRequest(new RequestId(reqID)); + if ((r.getRequestStatus() == RequestStatus.APPROVED)) { + return true; + } else { + return false; + } + } + + /** + * get async recovery request initiating agent + */ + public String getInitAgentAsyncKeyRecovery(String reqID) + throws EBaseException { + IRequestQueue queue = null; + IRequest r = null; + + queue = getRequestQueue(); + r = queue.findRequest(new RequestId(reqID)); + + String agents = r.getExtDataInString(RecoveryService.ATTR_APPROVE_AGENTS); + if (agents != null) { + int i = agents.indexOf(","); + if (i == -1) { + return agents; + } + return agents.substring(0, i); + } else { // no approvingAgents existing, can't be async recovery + CMS.debug("getInitAgentAsyncKeyRecovery: no approvingAgents in request"); + } + + return null; + } + + /** + * add async recovery agent to approving agent list of the recovery request + * record + * This method will check to see if the agent belongs to the recovery group + * first before adding. + */ + public void addAgentAsyncKeyRecovery(String reqID, String agentID) + throws EBaseException { + IRequestQueue queue = null; + IRequest r = null; + + // check if the uid is in the specified group + IUGSubsystem ug = (IUGSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_UG); + if (!ug.isMemberOf(agentID, mConfig.getString("recoveryAgentGroup"))) { + // invalid group + throw new EBaseException(CMS.getUserMessage("CMS_KRA_CREDENTIALS_NOT_EXIST")); + } + + queue = getRequestQueue(); + r = queue.findRequest(new RequestId(reqID)); + + String agents = r.getExtDataInString(RecoveryService.ATTR_APPROVE_AGENTS); + if (agents != null) { + int count = 0; + StringTokenizer st = new StringTokenizer(agents, ","); + for (; st.hasMoreTokens();) { + String a = st.nextToken(); + // first one is the initiating agent + if ((count != 0) && a.equals(agentID)) { + // duplicated uid + throw new EBaseException(CMS.getUserMessage("CMS_KRA_CREDENTIALS_EXIST")); + } + count++; + } + + // note: if count==1 and required agents is 1, it's good to add + // and it'd look like "agent1,agent1" - that's the only dup allowed + if (count <= getNoOfRequiredAgents()) { //all good, add it + r.setExtData(RecoveryService.ATTR_APPROVE_AGENTS, + agents + "," + agentID); + if (count == getNoOfRequiredAgents()) { + r.setRequestStatus(RequestStatus.APPROVED); + } else { + r.setRequestStatus(RequestStatus.PENDING); + } + queue.updateRequest(r); + } + } else { // no approvingAgents existing, can't be async recovery + CMS.debug("addAgentAsyncKeyRecovery: no approvingAgents in request. Async recovery request not initiated?"); + } + } + + /** + * Recovers key for administrators. This method is + * invoked by the agent operation of the key recovery servlet. + * <P> + * + * <ul> + * <li>signed.audit LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST used whenever a user private key recovery request is + * made (this is when the DRM receives the request) + * <li>signed.audit LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED used whenever a user private key recovery + * request is processed (this is when the DRM processes the request) + * </ul> + * + * @param kid key identifier + * @param creds list of recovery agent credentials + * @param password password of the PKCS12 package + * @param cert certficate that will be put in PKCS12 + * @param delivery file, mail or something else + * @param nickname string containing the nickname of the id cert for this + * subsystem + * @exception EBaseException failed to recover key + * @return a byte array containing the key + */ + public byte[] doKeyRecovery(BigInteger kid, + Credential creds[], String password, + X509CertImpl cert, + String delivery, String nickname, + String agent) + throws EBaseException { + String auditMessage = null; + String auditSubjectID = auditSubjectID(); + String auditRecoveryID = auditRecoveryID(); + String auditPublicKey = auditPublicKey(cert); + String auditAgents = ILogger.SIGNED_AUDIT_EMPTY_VALUE; + + IRequestQueue queue = null; + IRequest r = null; + Hashtable<String, Object> params = null; + + CMS.debug("KeyRecoveryAuthority: in synchronous doKeyRecovery()"); + // ensure that any low-level exceptions are reported + // to the signed audit log and stored as failures + try { + queue = getRequestQueue(); + r = queue.newRequest(KRAService.RECOVERY); + + // set transient parameters + params = createVolatileRequest(r.getRequestId()); + + if (mConfig.getBoolean("keySplitting")) { + params.put(RecoveryService.ATTR_AGENT_CREDENTIALS, creds); + } + params.put(RecoveryService.ATTR_TRANSPORT_PWD, password); + + r.setExtData(RecoveryService.ATTR_SERIALNO, kid); + r.setExtData(RecoveryService.ATTR_USER_CERT, cert); + if (nickname != null) { + nickname = nickname.trim(); + if (!nickname.equals("")) { + r.setExtData(RecoveryService.ATTR_NICKNAME, nickname); + } + } + // for both sync and async recovery + r.setExtData(RecoveryService.ATTR_APPROVE_AGENTS, agent); + + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST, + auditSubjectID, + ILogger.SUCCESS, + auditRecoveryID, + auditPublicKey); + + audit(auditMessage); + } catch (EBaseException eAudit1) { + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + auditPublicKey); + + audit(auditMessage); + + throw eAudit1; + } + + // ensure that any low-level exceptions are reported + // to the signed audit log and stored as failures + try { + queue.processRequest(r); + + if (r.getExtDataInString(IRequest.ERROR) == null) { + byte pkcs12[] = (byte[]) params.get( + RecoveryService.ATTR_PKCS12); + + auditAgents = auditAgents(creds); + + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.SUCCESS, + auditRecoveryID, + auditAgents); + + audit(auditMessage); + + destroyVolatileRequest(r.getRequestId()); + + return pkcs12; + } else { + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + auditAgents); + + audit(auditMessage); + + throw new EBaseException(r.getExtDataInString(IRequest.ERROR)); + } + } catch (EBaseException eAudit1) { + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + auditAgents); + + audit(auditMessage); + + throw eAudit1; + } + } + + /** + * Async Recovers key for administrators. This method is + * invoked by the agent operation of the key recovery servlet. + * <P> + * + * <ul> + * <li>signed.audit LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST used whenever a user private key recovery request is + * made (this is when the DRM receives the request) + * <li>signed.audit LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED used whenever a user private key recovery + * request is processed (this is when the DRM processes the request) + * </ul> + * + * @param requestID request id + * @param password password of the PKCS12 package + * subsystem + * @exception EBaseException failed to recover key + * @return a byte array containing the key + */ + public byte[] doKeyRecovery( + String reqID, + String password) + throws EBaseException { + String auditMessage = null; + String auditSubjectID = auditSubjectID(); + String auditRecoveryID = reqID; + String auditAgents = ILogger.SIGNED_AUDIT_EMPTY_VALUE; + + IRequestQueue queue = null; + IRequest r = null; + Hashtable<String, Object> params = null; + + CMS.debug("KeyRecoveryAuthority: in asynchronous doKeyRecovery()"); + queue = getRequestQueue(); + r = queue.findRequest(new RequestId(reqID)); + + auditAgents = + r.getExtDataInString(RecoveryService.ATTR_APPROVE_AGENTS); + + // set transient parameters + params = createVolatileRequest(r.getRequestId()); + params.put(RecoveryService.ATTR_TRANSPORT_PWD, password); + + // ensure that any low-level exceptions are reported + // to the signed audit log and stored as failures + try { + CMS.debug("KeyRecoveryAuthority: in asynchronous doKeyRecovery(), request state =" + + r.getRequestStatus().toString()); + // can only process requests in begin state + r.setRequestStatus(RequestStatus.BEGIN); + queue.processRequest(r); + + if (r.getExtDataInString(IRequest.ERROR) == null) { + byte pkcs12[] = (byte[]) params.get( + RecoveryService.ATTR_PKCS12); + + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED_ASYNC, + auditSubjectID, + ILogger.SUCCESS, + auditRecoveryID, + auditAgents); + + audit(auditMessage); + + destroyVolatileRequest(r.getRequestId()); + + return pkcs12; + } else { + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED_ASYNC, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + auditAgents); + + audit(auditMessage); + + throw new EBaseException(r.getExtDataInString(IRequest.ERROR)); + } + } catch (EBaseException eAudit1) { + // store a message in the signed audit log file + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED_ASYNC, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + auditAgents); + + audit(auditMessage); + throw eAudit1; + } + } + + /** + * Constructs a recovery request and submits it + * to the request subsystem for processing. + * + * @param kid key identifier + * @param creds list of recovery agent credentials + * @param password password of the PKCS12 package + * @param cert certficate that will be put in PKCS12 + * @param delivery file, mail or something else + * @return executed request + * @exception EBaseException failed to recover key + */ + public IRequest recoverKey(BigInteger kid, + Credential creds[], String password, + X509CertImpl cert, + String delivery) throws EBaseException { + IRequestQueue queue = getRequestQueue(); + IRequest r = queue.newRequest("recovery"); + + r.setExtData(RecoveryService.ATTR_SERIALNO, kid); + r.setExtData(RecoveryService.ATTR_TRANSPORT_PWD, password); + r.setExtData(RecoveryService.ATTR_USER_CERT, cert); + r.setExtData(RecoveryService.ATTR_DELIVERY, delivery); + queue.processRequest(r); + return r; + } + + /** + * Recovers key for end-entities. + * + * @param creds list of credentials + * @param encryptionChain certificate chain + * @param signingCert signing cert + * @param transportCert certificate to protect in-transit key + * @param ownerName owner name + * @return executed request + * @exception EBaseException failed to recover key + */ + public IRequest recoverKey(Credential creds[], CertificateChain + encryptionChain, X509CertImpl signingCert, + X509CertImpl transportCert, + X500Name ownerName) throws EBaseException { + IRequestQueue queue = getRequestQueue(); + IRequest r = queue.newRequest("recovery"); + + ByteArrayOutputStream certChainOut = new ByteArrayOutputStream(); + try { + encryptionChain.encode(certChainOut); + r.setExtData(RecoveryService.ATTR_ENCRYPTION_CERTS, + certChainOut.toByteArray()); + } catch (IOException e) { + log(ILogger.LL_FAILURE, + "Error encoding certificate chain"); + } + + r.setExtData(RecoveryService.ATTR_SIGNING_CERT, signingCert); + r.setExtData(RecoveryService.ATTR_TRANSPORT_CERT, transportCert); + + DerOutputStream ownerNameOut = new DerOutputStream(); + try { + ownerName.encode(ownerNameOut); + r.setExtData(RecoveryService.ATTR_OWNER_NAME, + ownerNameOut.toByteArray()); + } catch (IOException e) { + log(ILogger.LL_FAILURE, + "Error encoding X500Name for owner name"); + } + + queue.processRequest(r); + return r; + } + + /** + * Retrieves the storage key unit. The storage key + * is used to wrap the user key for long term + * storage. + * + * @return storage key unit. + */ + public IStorageKeyUnit getStorageKeyUnit() { + return mStorageKeyUnit; + } + + /** + * Retrieves the transport key unit. + * + * @return transport key unit + */ + public ITransportKeyUnit getTransportKeyUnit() { + return mTransportKeyUnit; + } + + /** + * Returns the name of this subsystem. This name is + * extracted from the transport certificate. + * + * @return KRA name + */ + public X500Name getX500Name() { + return mName; + } + + public String getNickName() { + return getNickname(); + } + + /** + * Returns the nickname for the id cert of this + * subsystem. + * + * @return nickname of the transport certificate + */ + public String getNickname() { + try { + return mTransportKeyUnit.getNickName(); + } catch (EBaseException e) { + return null; + } + } + + public void setNickname(String str) { + try { + mTransportKeyUnit.setNickName(str); + } catch (EBaseException e) { + } + } + + public String getNewNickName() throws EBaseException { + return mConfig.getString(PROP_NEW_NICKNAME, ""); + } + + public void setNewNickName(String name) { + mConfig.putString(PROP_NEW_NICKNAME, name); + } + + public IPolicy getPolicy() { + return mPolicy; + } + + /** + * Retrieves KRA request repository. + * <P> + * + * @return request repository + */ + public IRequestQueue getRequestQueue() { + return mRequestQueue; + } + + /** + * Retrieves the key repository. The key repository + * stores archived keys. + * <P> + */ + public IKeyRepository getKeyRepository() { + return mKeyDB; + } + + /** + * Retrieves replica repository. + * <P> + * + * @return replica repository + */ + public IReplicaIDRepository getReplicaRepository() { + return mReplicaRepot; + } + + /** + * Retrieves the DN of this escrow authority. + * <P> + * + * @return distinguished name + */ + protected String getDN() { + return getX500Name().toString(); + } + + /** + * Retrieves database connection. + */ + public IDBSubsystem getDBSubsystem() { + return DBSubsystem.getInstance(); + } + + /** + * Logs an event. + * + * @param level log level + * @param msg message to log + */ + public void log(int level, String msg) { + mLogger.log(ILogger.EV_SYSTEM, null, ILogger.S_KRA, + level, msg); + } + + /** + * Registers a request listener. + * + * @param l request listener + */ + public void registerRequestListener(IRequestListener l) { + // it's initialized. + if (mNotify != null) + mNotify.registerListener(l); + } + + public void registerPendingListener(IRequestListener l) { + mPNotify.registerListener(l); + } + + /** + * init notification related listeners - + * right now only RequestInQueue listener is available for KRA + */ + private void initNotificationListeners() { + IConfigStore nc = null; + + try { + nc = mConfig.getSubStore(PROP_NOTIFY_SUBSTORE); + if (nc != null && nc.size() > 0) { + // Initialize Request In Queue notification listener + String requestInQListenerClassName = + nc.getString("certificateIssuedListenerClassName", + "com.netscape.cms.listeners.RequestInQListener"); + + try { + mReqInQListener = (IRequestListener) Class.forName(requestInQListenerClassName).newInstance(); + mReqInQListener.init(this, nc); + } catch (Exception e1) { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_REGISTER_LISTENER", requestInQListenerClassName)); + } + } else { + log(ILogger.LL_INFO, + "No KRA notification Module configuration found"); + } + } catch (EPropertyNotFound e) { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_NOTIFY_ERROR", e.toString())); + } catch (EListenersException e) { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_NOTIFY_ERROR", e.toString())); + } catch (EBaseException e) { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_NOTIFY_ERROR", e.toString())); + } + } + + /** + * temporary accepted ras. + */ + /* code no longer used + public X500Name[] getAcceptedRAs() { + // temporary. use usr/grp for real thing. + X500Name radn = null; + String raname = null; + + try { + raname = mConfig.getString("acceptedRA", null); + if (raname != null) { + radn = new X500Name(raname); + } + } catch (IOException e) { + mLogger.log(ILogger.EV_SYSTEM, ILogger.S_KRA, + ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_INVALID_RA_NAME", raname, e.toString())); + } catch (EBaseException e) { + // ignore - set to null. + mLogger.log(ILogger.EV_SYSTEM, ILogger.S_KRA, + ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_INVALID_RA_SETUP", e.toString())); + } + return new X500Name[] { radn }; + } + */ + + public Hashtable<String, Hashtable<String, Object>> mVolatileRequests = + new Hashtable<String, Hashtable<String, Object>>(); + + /** + * Creates a request object to store attributes that + * will not be serialized. Currently, request queue + * framework will try to serialize all the attribute into + * persistent storage. Things like passwords are not + * desirable to be stored. + */ + public Hashtable<String, Object> createVolatileRequest(RequestId id) { + Hashtable<String, Object> params = new Hashtable<String, Object>(); + + mVolatileRequests.put(id.toString(), params); + return params; + } + + public Hashtable<String, Object> getVolatileRequest(RequestId id) { + return (Hashtable<String, Object>) mVolatileRequests.get(id.toString()); + } + + public void destroyVolatileRequest(RequestId id) { + mVolatileRequests.remove(id.toString()); + } + + public String getOfficialName() { + return OFFICIAL_NAME; + } + + /** + * Signed Audit Log + * + * This method is called to store messages to the signed audit log. + * <P> + * + * @param msg signed audit log message + */ + private void audit(String msg) { + // in this case, do NOT strip preceding/trailing whitespace + // from passed-in String parameters + + if (mSignedAuditLogger == null) { + return; + } + + mSignedAuditLogger.log(ILogger.EV_SIGNED_AUDIT, + null, + ILogger.S_SIGNED_AUDIT, + ILogger.LL_SECURITY, + msg); + } + + /** + * Signed Audit Log Subject ID + * + * This method is called to obtain the "SubjectID" for + * a signed audit log message. + * <P> + * + * @return id string containing the signed audit log message SubjectID + */ + private String auditSubjectID() { + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + String subjectID = null; + + // Initialize subjectID + SessionContext auditContext = SessionContext.getExistingContext(); + + if (auditContext != null) { + subjectID = (String) + auditContext.get(SessionContext.USER_ID); + + if (subjectID != null) { + subjectID = subjectID.trim(); + } else { + subjectID = ILogger.NONROLEUSER; + } + } else { + subjectID = ILogger.UNIDENTIFIED; + } + + return subjectID; + } + + /** + * Signed Audit Log Requester ID + * + * This method is called to obtain the "RequesterID" for + * a signed audit log message. + * <P> + * + * @return id string containing the signed audit log message RequesterID + */ + private String auditRequesterID() { + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + String requesterID = null; + + // Initialize requesterID + SessionContext auditContext = SessionContext.getExistingContext(); + + if (auditContext != null) { + requesterID = (String) + auditContext.get(SessionContext.REQUESTER_ID); + + if (requesterID != null) { + requesterID = requesterID.trim(); + } else { + requesterID = ILogger.UNIDENTIFIED; + } + } else { + requesterID = ILogger.UNIDENTIFIED; + } + + return requesterID; + } + + /** + * Signed Audit Log Recovery ID + * + * This method is called to obtain the "RecoveryID" for + * a signed audit log message. + * <P> + * + * @return id string containing the signed audit log message RecoveryID + */ + private String auditRecoveryID() { + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + String recoveryID = null; + + // Initialize recoveryID + SessionContext auditContext = SessionContext.getExistingContext(); + + if (auditContext != null) { + recoveryID = (String) + auditContext.get(SessionContext.RECOVERY_ID); + + if (recoveryID != null) { + recoveryID = recoveryID.trim(); + } else { + recoveryID = ILogger.UNIDENTIFIED; + } + } else { + recoveryID = ILogger.UNIDENTIFIED; + } + + return recoveryID; + } + + /** + * Signed Audit Log Public Key + * + * This method is called to obtain the public key from the passed in + * "X509Certificate" for a signed audit log message. + * <P> + * + * @param cert an X509Certificate + * @return key string containing the certificate's public key + */ + private String auditPublicKey(X509Certificate cert) { + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + if (cert == null) { + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } + + byte rawData[] = cert.getPublicKey().getEncoded(); + + // convert "rawData" into "base64Data" + if (rawData != null) { + String base64Data = CMS.BtoA(rawData).trim(); + String key = ""; + + // extract all line separators from the "base64Data" + for (int i = 0; i < base64Data.length(); i++) { + if (base64Data.substring(i, i).getBytes() != EOL) { + key += base64Data.substring(i, i); + } + } + + return key; + } + + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } + + /** + * Signed Audit Log Public Key + * + * This method is called to obtain the public key from the passed in + * "KeyRecord" for a signed audit log message. + * <P> + * + * @param rec a Key Record + * @return key string containing the certificate's public key + */ + private String auditPublicKey(KeyRecord rec) { + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + if (rec == null) { + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } + + byte rawData[] = null; + + try { + rawData = rec.getPublicKeyData(); + } catch (EBaseException e) { + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } + + String key = null; + + // convert "rawData" into "base64Data" + if (rawData != null) { + String base64Data = null; + + base64Data = CMS.BtoA(rawData).trim(); + + // extract all line separators from the "base64Data" + for (int i = 0; i < base64Data.length(); i++) { + if (base64Data.substring(i, i).getBytes() != EOL) { + key += base64Data.substring(i, i); + } + } + } + + if (key != null) { + key = key.trim(); + + if (key.equals("")) { + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } else { + return key; + } + } else { + return ILogger.SIGNED_AUDIT_EMPTY_VALUE; + } + } + + /** + * Signed Audit Agents + * + * This method is called to extract agent uids from the passed in + * "Credentials[]" and return a string of comma-separated agent uids. + * <P> + * + * @param creds array of credentials + * @return a comma-separated string of agent uids + */ + private String auditAgents(Credential creds[]) { + if (creds == null) + return null; + + // if no signed audit object exists, bail + if (mSignedAuditLogger == null) { + return null; + } + + String agents = ILogger.SIGNED_AUDIT_EMPTY_VALUE; + + String uid = null; + + for (int i = 0; i < creds.length; i++) { + uid = creds[i].getIdentifier(); + + if (uid != null) { + uid = uid.trim(); + } + + if (uid != null && + !uid.equals("")) { + + if (i == 0) { + agents = uid; + } else { + agents += SIGNED_AUDIT_AGENT_DELIMITER + uid; + } + } + } + + return agents; + } +} diff --git a/base/kra/src/com/netscape/kra/NetkeyKeygenService.java b/base/kra/src/com/netscape/kra/NetkeyKeygenService.java new file mode 100644 index 000000000..ecce2eaa9 --- /dev/null +++ b/base/kra/src/com/netscape/kra/NetkeyKeygenService.java @@ -0,0 +1,608 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import netscape.security.provider.RSAPublicKey; + +import org.mozilla.jss.crypto.Cipher; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.EncryptionAlgorithm; +import org.mozilla.jss.crypto.IVParameterSpec; +import org.mozilla.jss.crypto.KeyPairAlgorithm; +import org.mozilla.jss.crypto.KeyPairGenerator; +import org.mozilla.jss.crypto.KeyWrapAlgorithm; +import org.mozilla.jss.crypto.KeyWrapper; +import org.mozilla.jss.crypto.PQGParamGenException; +import org.mozilla.jss.crypto.PQGParams; +import org.mozilla.jss.crypto.PrivateKey; +import org.mozilla.jss.crypto.SymmetricKey; +import org.mozilla.jss.crypto.TokenException; +import org.mozilla.jss.pkcs11.PK11SymKey; +import org.mozilla.jss.pkix.crmf.PKIArchiveOptions; +import org.mozilla.jss.util.Base64OutputStream; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.dbs.keydb.IKeyRepository; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.request.IService; +import com.netscape.certsrv.security.IStorageKeyUnit; +import com.netscape.certsrv.security.ITransportKeyUnit; +import com.netscape.cmscore.dbs.KeyRecord; +import com.netscape.cmscore.util.Debug; + +/** + * A class representing keygen/archival request procesor for requests + * from netkey RAs. + * the user private key of the encryption cert is wrapped with a + * session symmetric key. The session symmetric key is wrapped with the + * storage key and stored in the internal database for long term + * storage. + * The user private key of the encryption cert is to be wrapped with the + * DES key which came in in the request wrapped with the KRA + * transport cert. The wrapped user private key is then sent back to + * the caller (netkey RA) ...netkey RA should already has kek-wrapped + * des key from the TKS. They are to be sent together back to + * the token. + * + * @author Christina Fu (cfu) + * @version $Revision$, $Date$ + */ + +public class NetkeyKeygenService implements IService { + public final static String ATTR_KEY_RECORD = "keyRecord"; + public final static String ATTR_PROOF_OF_ARCHIVAL = + "proofOfArchival"; + + // private + private final static String LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST = + "LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_4"; + private final static String LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED = + "LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED_3"; + // these need to be defined in LogMessages_en.properties later when we do this + private final static String LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST = + "LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST_3"; + private final static String LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST_PROCESSED_SUCCESS = + "LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST_PROCESSED_SUCCESS_4"; + private final static String LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST_PROCESSED_FAILURE = + "LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST_PROCESSED_FAILURE_3"; + private final static String LOGGING_SIGNED_AUDIT_PRIVATE_KEY_EXPORT_REQUEST_PROCESSED_SUCCESS = + "LOGGING_SIGNED_AUDIT_PRIVATE_KEY_EXPORT_REQUEST_PROCESSED_SUCCESS_4"; + private final static String LOGGING_SIGNED_AUDIT_PRIVATE_KEY_EXPORT_REQUEST_PROCESSED_FAILURE = + "LOGGING_SIGNED_AUDIT_PRIVATE_KEY_EXPORT_REQUEST_PROCESSED_FAILURE_4"; + private IKeyRecoveryAuthority mKRA = null; + private ITransportKeyUnit mTransportUnit = null; + private IStorageKeyUnit mStorageUnit = null; + private ILogger mSignedAuditLogger = CMS.getSignedAuditLogger(); + + /** + * Constructs request processor. + * <P> + * + * @param kra key recovery authority + */ + public NetkeyKeygenService(IKeyRecoveryAuthority kra) { + mKRA = kra; + mTransportUnit = kra.getTransportKeyUnit(); + mStorageUnit = kra.getStorageKeyUnit(); + } + + public PKIArchiveOptions toPKIArchiveOptions(byte options[]) { + ByteArrayInputStream bis = new ByteArrayInputStream(options); + PKIArchiveOptions archOpts = null; + + try { + archOpts = (PKIArchiveOptions) + (new PKIArchiveOptions.Template()).decode(bis); + } catch (Exception e) { + CMS.debug("NetkeyKeygenService: getPKIArchiveOptions " + e.toString()); + } + return archOpts; + } + + public KeyPair generateKeyPair( + KeyPairAlgorithm kpAlg, int keySize, PQGParams pqg) + throws NoSuchAlgorithmException, TokenException, InvalidAlgorithmParameterException, + InvalidParameterException, PQGParamGenException { + + CryptoToken token = mKRA.getKeygenToken(); + + CMS.debug("NetkeyKeygenService: key pair is to be generated on slot: " + token.getName()); + + /* + make it temporary so can work with HSM + netHSM works with + temporary == true + sensitive == <do not specify> + extractable == <do not specify> + LunaSA2 works with + temporary == true + sensitive == true + extractable == true + */ + KeyPairGenerator kpGen = token.getKeyPairGenerator(kpAlg); + IConfigStore config = CMS.getConfigStore(); + IConfigStore kgConfig = config.getSubStore("kra.keygen"); + boolean tp = false; + boolean sp = false; + boolean ep = false; + if (kgConfig != null) { + try { + tp = kgConfig.getBoolean("temporaryPairs", false); + sp = kgConfig.getBoolean("sensitivePairs", false); + ep = kgConfig.getBoolean("extractablePairs", false); + // by default, let nethsm work + if ((tp == false) && (sp == false) && (ep == false)) { + tp = true; + } + } catch (Exception e) { + CMS.debug("NetkeyKeygenService: kgConfig.getBoolean failed"); + // by default, let nethsm work + tp = true; + } + } else { + // by default, let nethsm work + CMS.debug("NetkeyKeygenService: cannot find config store: kra.keygen, assume temporaryPairs==true"); + tp = true; + } + /* only specified to "true" will it be set */ + if (tp == true) { + CMS.debug("NetkeyKeygenService: setting temporaryPairs to true"); + kpGen.temporaryPairs(true); + } + if (sp == true) { + CMS.debug("NetkeyKeygenService: setting sensitivePairs to true"); + kpGen.sensitivePairs(true); + } + if (ep == true) { + CMS.debug("NetkeyKeygenService: setting extractablePairs to true"); + kpGen.extractablePairs(true); + } + + if (kpAlg == KeyPairAlgorithm.DSA) { + if (pqg == null) { + kpGen.initialize(keySize); + } else { + kpGen.initialize(pqg); + } + } else { + kpGen.initialize(keySize); + } + + if (pqg == null) { + KeyPair kp = null; + synchronized (new Object()) { + CMS.debug("NetkeyKeygenService: key pair generation begins"); + kp = kpGen.genKeyPair(); + CMS.debug("NetkeyKeygenService: key pair generation done"); + mKRA.addEntropy(true); + } + return kp; + } else { + // DSA + KeyPair kp = null; + + /* no DSA for now... netkey prototype + do { + // 602548 NSS bug - to overcome it, we use isBadDSAKeyPair + kp = kpGen.genKeyPair(); + } + while (isBadDSAKeyPair(kp)); + */ + return kp; + } + } + + public KeyPair generateKeyPair(String alg, + int keySize, PQGParams pqg) throws EBaseException { + + KeyPairAlgorithm kpAlg = null; + + if (alg.equals("RSA")) + kpAlg = KeyPairAlgorithm.RSA; + else + kpAlg = KeyPairAlgorithm.DSA; + + try { + KeyPair kp = generateKeyPair(kpAlg, keySize, pqg); + + return kp; + } catch (InvalidParameterException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEYSIZE_PARAMS", + "" + keySize)); + } catch (PQGParamGenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_PQG_GEN_FAILED")); + } catch (NoSuchAlgorithmException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_ALG_NOT_SUPPORTED", + kpAlg.toString())); + } catch (TokenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_TOKEN_ERROR_1", e.toString())); + } catch (InvalidAlgorithmParameterException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_ALG_NOT_SUPPORTED", "DSA")); + } + } + + private static String base64Encode(byte[] bytes) throws IOException { + // All this streaming is lame, but Base64OutputStream needs a + // PrintStream + ByteArrayOutputStream output = new ByteArrayOutputStream(); + Base64OutputStream b64 = new Base64OutputStream(new + PrintStream(new + FilterOutputStream(output) + ) + ); + + b64.write(bytes); + b64.flush(); + + // This is internationally safe because Base64 chars are + // contained within 8859_1 + return output.toString("8859_1"); + } + + // this encrypts bytes with a symmetric key + public byte[] encryptIt(byte[] toBeEncrypted, SymmetricKey symKey, CryptoToken token, + IVParameterSpec IV) { + try { + Cipher cipher = token.getCipherContext( + EncryptionAlgorithm.DES3_CBC_PAD); + + cipher.initEncrypt(symKey, IV); + byte pri[] = cipher.doFinal(toBeEncrypted); + return pri; + } catch (Exception e) { + CMS.debug("NetkeyKeygenService:initEncrypt() threw exception: " + e.toString()); + return null; + } + + } + + /** + * Services an archival request from netkey. + * <P> + * + * @param request enrollment request + * @return serving successful or not + * @exception EBaseException failed to serve + */ + public boolean serviceRequest(IRequest request) + throws EBaseException { + String auditMessage = null; + String auditSubjectID = null; + String auditArchiveID = ILogger.UNIDENTIFIED; + byte[] wrapped_des_key; + + byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; + String iv_s = ""; + try { + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.nextBytes(iv); + } catch (Exception e) { + CMS.debug("NetkeyKeygenService.serviceRequest: " + e.toString()); + } + + IVParameterSpec algParam = new IVParameterSpec(iv); + + wrapped_des_key = null; + boolean archive = true; + PK11SymKey sk = null; + byte[] publicKeyData = null; + ; + String PubKey = ""; + + String id = request.getRequestId().toString(); + if (id != null) { + auditArchiveID = id.trim(); + } + + String rArchive = request.getExtDataInString(IRequest.NETKEY_ATTR_ARCHIVE_FLAG); + if (rArchive.equals("true")) { + archive = true; + CMS.debug("NetkeyKeygenService: serviceRequest " + "archival requested for serverSideKeyGen"); + } else { + archive = false; + CMS.debug("NetkeyKeygenService: serviceRequest " + "archival not requested for serverSideKeyGen"); + } + + String rCUID = request.getExtDataInString(IRequest.NETKEY_ATTR_CUID); + String rUserid = request.getExtDataInString(IRequest.NETKEY_ATTR_USERID); + String rKeysize = request.getExtDataInString(IRequest.NETKEY_ATTR_KEY_SIZE); + int keysize = Integer.parseInt(rKeysize); + auditSubjectID = rCUID + ":" + rUserid; + + SessionContext sContext = SessionContext.getContext(); + String agentId = ""; + if (sContext != null) { + agentId = + (String) sContext.get(SessionContext.USER_ID); + } + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST, + agentId, + ILogger.SUCCESS, + auditSubjectID); + + audit(auditMessage); + + String rWrappedDesKeyString = request.getExtDataInString(IRequest.NETKEY_ATTR_DRMTRANS_DES_KEY); + // CMS.debug("NetkeyKeygenService: received DRM-trans-wrapped DES key ="+rWrappedDesKeyString); + wrapped_des_key = com.netscape.cmsutil.util.Utils.SpecialDecode(rWrappedDesKeyString); + CMS.debug("NetkeyKeygenService: wrapped_des_key specialDecoded"); + + // get the token for generating user keys + CryptoToken keygenToken = mKRA.getKeygenToken(); + if (keygenToken == null) { + CMS.debug("NetkeyKeygenService: failed getting keygenToken"); + request.setExtData(IRequest.RESULT, Integer.valueOf(10)); + return false; + } else + CMS.debug("NetkeyKeygenService: got keygenToken"); + + if ((wrapped_des_key != null) && + (wrapped_des_key.length > 0)) { + + // unwrap the DES key + sk = (PK11SymKey) mTransportUnit.unwrap_sym(wrapped_des_key); + + /* XXX could be done in HSM*/ + KeyPair keypair = null; + + CMS.debug("NetkeyKeygenService: about to generate key pair"); + + keypair = generateKeyPair("RSA"/*alg*/, + keysize /*Integer.parseInt(len)*/, null /*pqgParams*/); + + if (keypair == null) { + CMS.debug("NetkeyKeygenService: failed generating key pair for " + rCUID + ":" + rUserid); + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST_PROCESSED_FAILURE, + agentId, + ILogger.FAILURE, + auditSubjectID); + + audit(auditMessage); + + return false; + } + CMS.debug("NetkeyKeygenService: finished generate key pair for " + rCUID + ":" + rUserid); + + try { + publicKeyData = keypair.getPublic().getEncoded(); + if (publicKeyData == null) { + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + CMS.debug("NetkeyKeygenService: failed getting publickey encoded"); + return false; + } else { + //CMS.debug("NetkeyKeygenService: public key binary length ="+ publicKeyData.length); + PubKey = base64Encode(publicKeyData); + + //CMS.debug("NetkeyKeygenService: public key length =" + PubKey.length()); + request.setExtData("public_key", PubKey); + } + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_SERVER_SIDE_KEYGEN_REQUEST_PROCESSED_SUCCESS, + agentId, + ILogger.SUCCESS, + auditSubjectID, + PubKey); + + audit(auditMessage); + + //...extract the private key handle (not privatekeydata) + java.security.PrivateKey privKey = + keypair.getPrivate(); + + if (privKey == null) { + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + CMS.debug("NetkeyKeygenService: failed getting private key"); + return false; + } else { + CMS.debug("NetkeyKeygenService: got private key"); + } + + if (sk == null) { + CMS.debug("NetkeyKeygenService: no DES key"); + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + return false; + } else { + CMS.debug("NetkeyKeygenService: received DES key"); + } + + // 3 wrapping should be done in HSM + // wrap private key with DES + KeyWrapper symWrap = + keygenToken.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD); + CMS.debug("NetkeyKeygenService: wrapper token=" + keygenToken.getName()); + CMS.debug("NetkeyKeygenService: got key wrapper"); + + CMS.debug("NetkeyKeygenService: key transport key is on slot: " + sk.getOwningToken().getName()); + symWrap.initWrap((SymmetricKey) sk, algParam); + byte wrapped[] = symWrap.wrap((PrivateKey) privKey); + /* + CMS.debug("NetkeyKeygenService: wrap called"); + CMS.debug(wrapped); + */ + /* This is for using with my decryption tool and ASN1 + decoder to see if the private key is indeed PKCS#8 format + { // cfu debug + String oFilePath = "/tmp/wrappedPrivKey.bin"; + File file = new File(oFilePath); + FileOutputStream ostream = new FileOutputStream(oFilePath); + ostream.write(wrapped); + ostream.close(); + } + */ + String wrappedPrivKeyString = /*base64Encode(wrapped);*/ + com.netscape.cmsutil.util.Utils.SpecialEncode(wrapped); + if (wrappedPrivKeyString == null) { + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + CMS.debug("NetkeyKeygenService: failed generating wrapped private key"); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_EXPORT_REQUEST_PROCESSED_FAILURE, + agentId, + ILogger.FAILURE, + auditSubjectID, + PubKey); + + audit(auditMessage); + return false; + } else { + request.setExtData("wrappedUserPrivate", wrappedPrivKeyString); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_EXPORT_REQUEST_PROCESSED_SUCCESS, + agentId, + ILogger.SUCCESS, + auditSubjectID, + PubKey); + + audit(auditMessage); + } + + iv_s = /*base64Encode(iv);*/com.netscape.cmsutil.util.Utils.SpecialEncode(iv); + request.setExtData("iv_s", iv_s); + + /* + * archival - option flag "archive" controllable by the caller - TPS + */ + if (archive) { + // + // privateKeyData ::= SEQUENCE { + // sessionKey OCTET_STRING, + // encKey OCTET_STRING, + // } + // + // mKRA.log(ILogger.LL_INFO, "KRA encrypts internal private"); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST, + agentId, + ILogger.SUCCESS, + auditSubjectID, + auditArchiveID); + + audit(auditMessage); + CMS.debug("KRA encrypts private key to put on internal ldap db"); + byte privateKeyData[] = + mStorageUnit.wrap((org.mozilla.jss.crypto.PrivateKey) privKey); + + if (privateKeyData == null) { + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + CMS.debug("NetkeyKeygenService: privatekey encryption by storage unit failed"); + return false; + } else + CMS.debug("NetkeyKeygenService: privatekey encryption by storage unit successful"); + + // create key record + KeyRecord rec = new KeyRecord(null, publicKeyData, + privateKeyData, rCUID + ":" + rUserid, + keypair.getPublic().getAlgorithm(), + agentId); + + CMS.debug("NetkeyKeygenService: got key record"); + + // we deal with RSA key only + try { + RSAPublicKey rsaPublicKey = new RSAPublicKey(publicKeyData); + + rec.setKeySize(Integer.valueOf(rsaPublicKey.getKeySize())); + } catch (InvalidKeyException e) { + request.setExtData(IRequest.RESULT, Integer.valueOf(11)); + CMS.debug("NetkeyKeygenService: failed:InvalidKeyException"); + return false; + } + //?? + IKeyRepository storage = mKRA.getKeyRepository(); + BigInteger serialNo = storage.getNextSerialNumber(); + + if (serialNo == null) { + request.setExtData(IRequest.RESULT, Integer.valueOf(11)); + CMS.debug("NetkeyKeygenService: serialNo null"); + return false; + } + CMS.debug("NetkeyKeygenService: before addKeyRecord"); + rec.set(KeyRecord.ATTR_ID, serialNo); + request.setExtData(ATTR_KEY_RECORD, serialNo); + storage.addKeyRecord(rec); + CMS.debug("NetkeyKeygenService: key archived for " + rCUID + ":" + rUserid); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_PRIVATE_KEY_ARCHIVE_REQUEST_PROCESSED, + agentId, + ILogger.SUCCESS, + PubKey); + + audit(auditMessage); + + } //if archive + + request.setExtData(IRequest.RESULT, Integer.valueOf(1)); + } catch (Exception e) { + CMS.debug("NetKeyKeygenService: " + e.toString()); + Debug.printStackTrace(e); + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + } + } else + request.setExtData(IRequest.RESULT, Integer.valueOf(2)); + + return true; + } //serviceRequest + + /** + * Signed Audit Log + * y + * This method is called to store messages to the signed audit log. + * <P> + * + * @param msg signed audit log message + */ + private void audit(String msg) { + // in this case, do NOT strip preceding/trailing whitespace + // from passed-in String parameters + + if (mSignedAuditLogger == null) { + return; + } + + mSignedAuditLogger.log(ILogger.EV_SIGNED_AUDIT, + null, + ILogger.S_SIGNED_AUDIT, + ILogger.LL_SECURITY, + msg); + } +} diff --git a/base/kra/src/com/netscape/kra/RecoveryService.java b/base/kra/src/com/netscape/kra/RecoveryService.java new file mode 100644 index 000000000..c8ecdcf5a --- /dev/null +++ b/base/kra/src/com/netscape/kra/RecoveryService.java @@ -0,0 +1,710 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.ByteArrayOutputStream; +import java.io.CharConversionException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Hashtable; + +import netscape.security.util.BigInt; +import netscape.security.util.DerInputStream; +import netscape.security.util.DerValue; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X509Key; + +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.asn1.ASN1Util; +import org.mozilla.jss.asn1.ASN1Value; +import org.mozilla.jss.asn1.BMPString; +import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.asn1.SEQUENCE; +import org.mozilla.jss.asn1.SET; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.PBEAlgorithm; +import org.mozilla.jss.crypto.PrivateKey; +import org.mozilla.jss.pkcs12.AuthenticatedSafes; +import org.mozilla.jss.pkcs12.CertBag; +import org.mozilla.jss.pkcs12.PFX; +import org.mozilla.jss.pkcs12.PasswordConverter; +import org.mozilla.jss.pkcs12.SafeBag; +import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo; +import org.mozilla.jss.pkix.primitive.PrivateKeyInfo; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authentication.AuthToken; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.dbs.keydb.IKeyRepository; +import com.netscape.certsrv.kra.EKRAException; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.logging.AuditFormat; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.request.IService; +import com.netscape.certsrv.security.Credential; +import com.netscape.certsrv.security.IStorageKeyUnit; +import com.netscape.certsrv.util.IStatsSubsystem; +import com.netscape.cmscore.dbs.KeyRecord; +import com.netscape.cmscore.util.Debug; + +/** + * A class represents recovery request processor. There + * are 2 types of recovery modes: (1) administrator or + * (2) end-entity. + * <P> + * Administrator recovery will create a PKCS12 file where stores the certificate and the recovered key. + * <P> + * End Entity recovery will send RA or CA a response where stores the recovered key. + * + * @author thomask (original) + * @author cfu (non-RSA keys; private keys secure handling); + * @version $Revision$, $Date$ + */ +public class RecoveryService implements IService { + + public static final String ATTR_NICKNAME = "nickname"; + public static final String ATTR_OWNER_NAME = "ownerName"; + public static final String ATTR_SERIALNO = "serialNumber"; + public static final String ATTR_PUBLIC_KEY_DATA = "publicKeyData"; + public static final String ATTR_PRIVATE_KEY_DATA = "privateKeyData"; + public static final String ATTR_TRANSPORT_CERT = "transportCert"; + public static final String ATTR_TRANSPORT_PWD = "transportPwd"; + public static final String ATTR_SIGNING_CERT = "signingCert"; + public static final String ATTR_PKCS12 = "pkcs12"; + public static final String ATTR_ENCRYPTION_CERTS = + "encryptionCerts"; + public static final String ATTR_AGENT_CREDENTIALS = + "agentCredentials"; + // same as encryption certs + public static final String ATTR_USER_CERT = "cert"; + public static final String ATTR_DELIVERY = "delivery"; + + // for Async Key Recovery + public static final String ATTR_APPROVE_AGENTS = "approvingAgents"; + + private IKeyRecoveryAuthority mKRA = null; + private IKeyRepository mStorage = null; + private IStorageKeyUnit mStorageUnit = null; + + /** + * Constructs request processor. + */ + public RecoveryService(IKeyRecoveryAuthority kra) { + mKRA = kra; + mStorage = mKRA.getKeyRepository(); + mStorageUnit = mKRA.getStorageKeyUnit(); + } + + /** + * Processes a recovery request. Based on the recovery mode + * (either Administrator or End-Entity), the method reads + * the key record from the database, and tried to recover the + * key with the storage key unit. + * + * @param request recovery request + * @return operation success or not + * @exception EBaseException failed to serve + */ + public boolean serviceRequest(IRequest request) throws EBaseException { + + CryptoManager cm = null; + IConfigStore config = null; + String tokName = ""; + CryptoToken ct = null; + Boolean allowEncDecrypt_recovery = false; + + try { + cm = CryptoManager.getInstance(); + config = CMS.getConfigStore(); + tokName = config.getString("kra.storageUnit.hardware", "internal"); + if (tokName.equals("internal")) { + CMS.debug("RecoveryService: serviceRequest: use internal token "); + ct = cm.getInternalCryptoToken(); + } else { + CMS.debug("RecoveryService: serviceRequest: tokenName=" + tokName); + ct = cm.getTokenByName(tokName); + } + allowEncDecrypt_recovery = config.getBoolean("kra.allowEncDecrypt.recovery", false); + } catch (Exception e) { + CMS.debug("RecoveryService exception: use internal token :" + + e.toString()); + ct = cm.getInternalCryptoToken(); + } + if (ct == null) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR" + "cannot get crypto token")); + } + + IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats"); + if (statsSub != null) { + statsSub.startTiming("recovery", true /* main action */); + } + + if (Debug.ON) + Debug.trace("KRA services recovery request"); + mKRA.log(ILogger.LL_INFO, "KRA services recovery request"); + + // byte publicKey[] = (byte[])request.get(ATTR_PUBLIC_KEY_DATA); + // X500Name owner = (X500Name)request.get(ATTR_OWNER_NAME); + + Hashtable<String, Object> params = mKRA.getVolatileRequest( + request.getRequestId()); + + if (params == null) { + // possibly we are in recovery mode + return true; + } + + // retrieve based on serial no + BigInteger serialno = request.getExtDataInBigInteger(ATTR_SERIALNO); + + mKRA.log(ILogger.LL_INFO, "KRA reading key record"); + if (statsSub != null) { + statsSub.startTiming("get_key"); + } + KeyRecord keyRecord = (KeyRecord) mStorage.readKeyRecord(serialno); + if (statsSub != null) { + statsSub.endTiming("get_key"); + } + + // see if the certificate matches the key + byte pubData[] = keyRecord.getPublicKeyData(); + X509Certificate x509cert = + request.getExtDataInCert(ATTR_USER_CERT); + byte inputPubData[] = x509cert.getPublicKey().getEncoded(); + + if (inputPubData.length != pubData.length) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_PUBLIC_KEY_LEN")); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_PUBLIC_KEY_NOT_MATCHED")); + } + for (int i = 0; i < pubData.length; i++) { + if (pubData[i] != inputPubData[i]) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_PUBLIC_KEY_LEN")); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_PUBLIC_KEY_NOT_MATCHED")); + } + } + + boolean isRSA = true; + String keyAlg = x509cert.getPublicKey().getAlgorithm(); + if (keyAlg != null) { + CMS.debug("RecoveryService: publicKey alg =" + keyAlg); + if (!keyAlg.equals("RSA")) + isRSA = false; + } + + // Unwrap the archived private key + byte privateKeyData[] = null; + X509Certificate transportCert = + request.getExtDataInCert(ATTR_TRANSPORT_CERT); + + if (transportCert == null) { + if (statsSub != null) { + statsSub.startTiming("recover_key"); + } + + PrivateKey privKey = null; + if (allowEncDecrypt_recovery == true) { + privateKeyData = recoverKey(params, keyRecord); + } else { + privKey = recoverKey(params, keyRecord, isRSA); + } + if (statsSub != null) { + statsSub.endTiming("recover_key"); + } + + if ((isRSA == true) && (allowEncDecrypt_recovery == true)) { + if (statsSub != null) { + statsSub.startTiming("verify_key"); + } + // verifyKeyPair() is RSA-centric + if (verifyKeyPair(pubData, privateKeyData) == false) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_PUBLIC_NOT_FOUND")); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_INVALID_PUBLIC_KEY")); + } + if (statsSub != null) { + statsSub.endTiming("verify_key"); + } + } + + if (statsSub != null) { + statsSub.startTiming("create_p12"); + } + if (allowEncDecrypt_recovery == true) { + createPFX(request, params, privateKeyData); + } else { + createPFX(request, params, privKey, ct); + } + if (statsSub != null) { + statsSub.endTiming("create_p12"); + } + } else { + + if (CMS.getConfigStore().getBoolean("kra.keySplitting")) { + Credential creds[] = (Credential[]) + params.get(ATTR_AGENT_CREDENTIALS); + mKRA.getStorageKeyUnit().login(creds); + } + if (statsSub != null) { + statsSub.startTiming("unwrap_key"); + } + mKRA.getStorageKeyUnit().unwrap( + keyRecord.getPrivateKeyData(), null); // throw exception on error + if (statsSub != null) { + statsSub.endTiming("unwrap_key"); + } + + if (CMS.getConfigStore().getBoolean("kra.keySplitting")) { + mKRA.getStorageKeyUnit().logout(); + } + } + mKRA.log(ILogger.LL_INFO, "key " + + serialno.toString() + + " recovered"); + + // for audit log + String authMgr = AuditFormat.NOAUTH; + String initiative = AuditFormat.FROMUSER; + SessionContext sContext = SessionContext.getContext(); + + if (sContext != null) { + String agentId = + (String) sContext.get(SessionContext.USER_ID); + + initiative = AuditFormat.FROMAGENT + " agentID: " + agentId; + AuthToken authToken = (AuthToken) sContext.get(SessionContext.AUTH_TOKEN); + + if (authToken != null) { + authMgr = + authToken.getInString(AuthToken.TOKEN_AUTHMGR_INST_NAME); + } + } + CMS.getLogger().log(ILogger.EV_AUDIT, + ILogger.S_KRA, + AuditFormat.LEVEL, + AuditFormat.FORMAT, + new Object[] { + IRequest.KEYRECOVERY_REQUEST, + request.getRequestId(), + initiative, + authMgr, + "completed", + ((X509CertImpl) x509cert).getSubjectDN(), + "serial number: 0x" + serialno.toString(16) } + ); + + if (statsSub != null) { + statsSub.endTiming("recovery"); + } + + return true; + } + + /* + * verifyKeyPair()- RSA-centric key verification + */ + public boolean verifyKeyPair(byte publicKeyData[], byte privateKeyData[]) { + try { + DerValue publicKeyVal = new DerValue(publicKeyData); + DerInputStream publicKeyIn = publicKeyVal.data; + publicKeyIn.getSequence(0); + DerValue publicKeyDer = new DerValue(publicKeyIn.getBitString()); + DerInputStream publicKeyDerIn = publicKeyDer.data; + BigInt publicKeyModulus = publicKeyDerIn.getInteger(); + BigInt publicKeyExponent = publicKeyDerIn.getInteger(); + + DerValue privateKeyVal = new DerValue(privateKeyData); + if (privateKeyVal.tag != DerValue.tag_Sequence) + return false; + DerInputStream privateKeyIn = privateKeyVal.data; + privateKeyIn.getInteger(); + privateKeyIn.getSequence(0); + DerValue privateKeyDer = new DerValue(privateKeyIn.getOctetString()); + DerInputStream privateKeyDerIn = privateKeyDer.data; + + @SuppressWarnings("unused") + BigInt privateKeyVersion = privateKeyDerIn.getInteger(); + BigInt privateKeyModulus = privateKeyDerIn.getInteger(); + BigInt privateKeyExponent = privateKeyDerIn.getInteger(); + + if (!publicKeyModulus.equals(privateKeyModulus)) { + CMS.debug("verifyKeyPair modulus mismatch publicKeyModulus=" + + publicKeyModulus + " privateKeyModulus=" + privateKeyModulus); + return false; + } + + if (!publicKeyExponent.equals(privateKeyExponent)) { + CMS.debug("verifyKeyPair exponent mismatch publicKeyExponent=" + + publicKeyExponent + " privateKeyExponent=" + privateKeyExponent); + return false; + } + + return true; + } catch (Exception e) { + CMS.debug("verifyKeyPair error " + e); + return false; + } + } + + /** + * Recovers key. (using unwrapping/wrapping on token) + * - used when allowEncDecrypt_recovery is false + */ + public synchronized PrivateKey recoverKey(Hashtable<String, Object> request, KeyRecord keyRecord, boolean isRSA) + throws EBaseException { + + if (!isRSA) { + CMS.debug("RecoverService: recoverKey: currently, non-RSA keys are not supported when allowEncDecrypt_ is false"); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", "key type not supported")); + } + try { + if (CMS.getConfigStore().getBoolean("kra.keySplitting")) { + Credential creds[] = (Credential[]) + request.get(ATTR_AGENT_CREDENTIALS); + + mStorageUnit.login(creds); + } + + /* wrapped retrieve session key and private key */ + DerValue val = new DerValue(keyRecord.getPrivateKeyData()); + DerInputStream in = val.data; + DerValue dSession = in.getDerValue(); + byte session[] = dSession.getOctetString(); + DerValue dPri = in.getDerValue(); + byte pri[] = dPri.getOctetString(); + + /* debug */ + byte publicKeyData[] = keyRecord.getPublicKeyData(); + PublicKey pubkey = null; + try { + pubkey = X509Key.parsePublicKey(new DerValue(publicKeyData)); + } catch (Exception e) { + CMS.debug("RecoverService: after parsePublicKey:" + e.toString()); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", "pubic key parsing failure")); + } + byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; + PrivateKey privKey = + mStorageUnit.unwrap( + session, + keyRecord.getAlgorithm(), + iv, + pri, + (PublicKey) pubkey); + + if (privKey == null) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_PRIVATE_KEY_NOT_FOUND")); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", + "private key unwrapping failure")); + } + if (CMS.getConfigStore().getBoolean("kra.keySplitting")) { + mStorageUnit.logout(); + } + return privKey; + } catch (Exception e) { + CMS.debug("RecoverService: recoverKey() failed with allowEncDecrypt_recovery=false:" + e.toString()); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", + "recoverKey() failed with allowEncDecrypt_recovery=false:" + e.toString())); + } + } + + /** + * Creates a PFX (PKCS12) file. (the unwrapping/wrapping way) + * - used when allowEncDecrypt_recovery is false + * + * @param request CRMF recovery request + * @param priKey private key handle + * @exception EBaseException failed to create P12 file + */ + public void createPFX(IRequest request, Hashtable<String, Object> params, + PrivateKey priKey, CryptoToken ct) throws EBaseException { + CMS.debug("RecoverService: createPFX() allowEncDecrypt_recovery=false"); + try { + // create p12 + X509Certificate x509cert = + request.getExtDataInCert(ATTR_USER_CERT); + String pwd = (String) params.get(ATTR_TRANSPORT_PWD); + + // add certificate + mKRA.log(ILogger.LL_INFO, "KRA adds certificate to P12"); + CMS.debug("RecoverService: createPFX() adds certificate to P12"); + SEQUENCE encSafeContents = new SEQUENCE(); + ASN1Value cert = new OCTET_STRING(x509cert.getEncoded()); + String nickname = request.getExtDataInString(ATTR_NICKNAME); + + if (nickname == null) { + nickname = x509cert.getSubjectDN().toString(); + } + byte localKeyId[] = createLocalKeyId(x509cert); + SET certAttrs = createBagAttrs( + nickname, localKeyId); + // attributes: user friendly name, Local Key ID + SafeBag certBag = new SafeBag(SafeBag.CERT_BAG, + new CertBag(CertBag.X509_CERT_TYPE, cert), + certAttrs); + + encSafeContents.addElement(certBag); + + // add key + mKRA.log(ILogger.LL_INFO, "KRA adds key to P12"); + CMS.debug("RecoverService: createPFX() adds key to P12"); + org.mozilla.jss.util.Password pass = new + org.mozilla.jss.util.Password( + pwd.toCharArray()); + + SEQUENCE safeContents = new SEQUENCE(); + PasswordConverter passConverter = new + PasswordConverter(); + byte salt[] = { 0x01, 0x01, 0x01, 0x01 }; + + ASN1Value key = EncryptedPrivateKeyInfo.createPBE( + PBEAlgorithm.PBE_SHA1_DES3_CBC, + pass, salt, 1, passConverter, priKey, ct); + + SET keyAttrs = createBagAttrs( + x509cert.getSubjectDN().toString(), + localKeyId); + + SafeBag keyBag = new SafeBag( + SafeBag.PKCS8_SHROUDED_KEY_BAG, key, + keyAttrs); // ?? + + safeContents.addElement(keyBag); + + // build contents + AuthenticatedSafes authSafes = new + AuthenticatedSafes(); + + authSafes.addSafeContents( + safeContents + ); + authSafes.addSafeContents( + encSafeContents + ); + + // authSafes.addEncryptedSafeContents( + // authSafes.DEFAULT_KEY_GEN_ALG, + // pass, null, 1, + // encSafeContents); + PFX pfx = new PFX(authSafes); + + pfx.computeMacData(pass, null, 5); // ?? + ByteArrayOutputStream fos = new + ByteArrayOutputStream(); + + pfx.encode(fos); + pass.clear(); + + // put final PKCS12 into volatile request + params.put(ATTR_PKCS12, fos.toByteArray()); + } catch (Exception e) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_CONSTRUCT_P12", e.toString())); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_PKCS12_FAILED_1", e.toString())); + } + + // update request + mKRA.getRequestQueue().updateRequest(request); + } + + /** + * Recovers key. + * - used when allowEncDecrypt_recovery is true + */ + public synchronized byte[] recoverKey(Hashtable<String, Object> request, KeyRecord keyRecord) + throws EBaseException { + if (CMS.getConfigStore().getBoolean("kra.keySplitting")) { + Credential creds[] = (Credential[]) + request.get(ATTR_AGENT_CREDENTIALS); + + mStorageUnit.login(creds); + } + mKRA.log(ILogger.LL_INFO, "KRA decrypts internal private"); + byte privateKeyData[] = + mStorageUnit.decryptInternalPrivate( + keyRecord.getPrivateKeyData()); + + if (CMS.getConfigStore().getBoolean("kra.keySplitting")) { + mStorageUnit.logout(); + } + if (privateKeyData == null) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_PRIVATE_KEY_NOT_FOUND")); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", "no private key")); + } + return privateKeyData; + } + + /** + * Creates a PFX (PKCS12) file. + * - used when allowEncDecrypt_recovery is true + * + * @param request CRMF recovery request + * @param priData decrypted private key (PrivateKeyInfo) + * @exception EBaseException failed to create P12 file + */ + public void createPFX(IRequest request, Hashtable<String, Object> params, + byte priData[]) throws EBaseException { + CMS.debug("RecoverService: createPFX() allowEncDecrypt_recovery=true"); + try { + // create p12 + X509Certificate x509cert = + request.getExtDataInCert(ATTR_USER_CERT); + String pwd = (String) params.get(ATTR_TRANSPORT_PWD); + + // add certificate + mKRA.log(ILogger.LL_INFO, "KRA adds certificate to P12"); + SEQUENCE encSafeContents = new SEQUENCE(); + ASN1Value cert = new OCTET_STRING(x509cert.getEncoded()); + String nickname = request.getExtDataInString(ATTR_NICKNAME); + + if (nickname == null) { + nickname = x509cert.getSubjectDN().toString(); + } + byte localKeyId[] = createLocalKeyId(x509cert); + SET certAttrs = createBagAttrs( + nickname, localKeyId); + // attributes: user friendly name, Local Key ID + SafeBag certBag = new SafeBag(SafeBag.CERT_BAG, + new CertBag(CertBag.X509_CERT_TYPE, cert), + certAttrs); + + encSafeContents.addElement(certBag); + + // add key + mKRA.log(ILogger.LL_INFO, "KRA adds key to P12"); + org.mozilla.jss.util.Password pass = new + org.mozilla.jss.util.Password( + pwd.toCharArray()); + + SEQUENCE safeContents = new SEQUENCE(); + PasswordConverter passConverter = new + PasswordConverter(); + byte salt[] = { 0x01, 0x01, 0x01, 0x01 }; + PrivateKeyInfo pki = (PrivateKeyInfo) + ASN1Util.decode(PrivateKeyInfo.getTemplate(), + priData); + ASN1Value key = EncryptedPrivateKeyInfo.createPBE( + PBEAlgorithm.PBE_SHA1_DES3_CBC, + pass, salt, 1, passConverter, pki); + SET keyAttrs = createBagAttrs( + x509cert.getSubjectDN().toString(), + localKeyId); + SafeBag keyBag = new SafeBag( + SafeBag.PKCS8_SHROUDED_KEY_BAG, key, + keyAttrs); // ?? + + safeContents.addElement(keyBag); + + // build contents + AuthenticatedSafes authSafes = new + AuthenticatedSafes(); + + authSafes.addSafeContents( + safeContents + ); + authSafes.addSafeContents( + encSafeContents + ); + + // authSafes.addEncryptedSafeContents( + // authSafes.DEFAULT_KEY_GEN_ALG, + // pass, null, 1, + // encSafeContents); + PFX pfx = new PFX(authSafes); + + pfx.computeMacData(pass, null, 5); // ?? + ByteArrayOutputStream fos = new + ByteArrayOutputStream(); + + pfx.encode(fos); + pass.clear(); + + // put final PKCS12 into volatile request + params.put(ATTR_PKCS12, fos.toByteArray()); + } catch (Exception e) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_CONSTRUCT_P12", e.toString())); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_PKCS12_FAILED_1", e.toString())); + } + + // update request + mKRA.getRequestQueue().updateRequest(request); + } + + /** + * Creates local key identifier. + */ + public byte[] createLocalKeyId(X509Certificate cert) + throws EBaseException { + try { + // SHA1 hash of the X509Cert der encoding + byte certDer[] = cert.getEncoded(); + + // XXX - should use JSS + MessageDigest md = MessageDigest.getInstance("SHA"); + + md.update(certDer); + return md.digest(); + } catch (CertificateEncodingException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_CREAT_KEY_ID", e.toString())); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_KEYID_FAILED_1", e.toString())); + } catch (NoSuchAlgorithmException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_CREAT_KEY_ID", e.toString())); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_KEYID_FAILED_1", e.toString())); + } + } + + /** + * Creates bag attributes. + */ + public SET createBagAttrs(String nickName, byte localKeyId[]) + throws EBaseException { + try { + SET attrs = new SET(); + SEQUENCE nickNameAttr = new SEQUENCE(); + + nickNameAttr.addElement(SafeBag.FRIENDLY_NAME); + SET nickNameSet = new SET(); + + nickNameSet.addElement(new BMPString(nickName)); + nickNameAttr.addElement(nickNameSet); + attrs.addElement(nickNameAttr); + SEQUENCE localKeyAttr = new SEQUENCE(); + + localKeyAttr.addElement(SafeBag.LOCAL_KEY_ID); + SET localKeySet = new SET(); + + localKeySet.addElement(new OCTET_STRING(localKeyId)); + localKeyAttr.addElement(localKeySet); + attrs.addElement(localKeyAttr); + return attrs; + } catch (CharConversionException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_CREAT_KEY_BAG", e.toString())); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_KEYBAG_FAILED_1", e.toString())); + } + } +} diff --git a/base/kra/src/com/netscape/kra/SecurityDataRecoveryService.java b/base/kra/src/com/netscape/kra/SecurityDataRecoveryService.java new file mode 100644 index 000000000..f96ece890 --- /dev/null +++ b/base/kra/src/com/netscape/kra/SecurityDataRecoveryService.java @@ -0,0 +1,388 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.ByteArrayOutputStream; +import java.io.CharConversionException; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Hashtable; +import java.util.Random; + +import javax.crypto.spec.RC2ParameterSpec; + +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.Cipher; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.EncryptionAlgorithm; +import org.mozilla.jss.crypto.IVParameterSpec; +import org.mozilla.jss.crypto.KeyGenerator; +import org.mozilla.jss.crypto.KeyWrapAlgorithm; +import org.mozilla.jss.crypto.KeyWrapper; +import org.mozilla.jss.crypto.PBEAlgorithm; +import org.mozilla.jss.crypto.PBEKeyGenParams; +import org.mozilla.jss.crypto.SymmetricKey; +import org.mozilla.jss.crypto.TokenException; +import org.mozilla.jss.pkcs12.PasswordConverter; +import org.mozilla.jss.pkcs7.ContentInfo; +import org.mozilla.jss.pkcs7.EncryptedContentInfo; +import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier; +import org.mozilla.jss.pkix.primitive.PBEParameter; +import org.mozilla.jss.util.Password; + +import com.netscape.certsrv.kra.EKRAException; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.request.IService; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.security.IStorageKeyUnit; +import com.netscape.certsrv.security.ITransportKeyUnit; +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.dbs.keydb.IKeyRecord; +import com.netscape.certsrv.dbs.keydb.IKeyRepository; +import com.netscape.cms.servlet.request.KeyRequestResource; +import com.netscape.cmscore.dbs.KeyRecord; +import com.netscape.cmsutil.util.Utils; + +/** + * This implementation services SecurityData Recovery requests. + * <p> + * + * @version $Revision$, $Date$ + */ +@SuppressWarnings("deprecation") +public class SecurityDataRecoveryService implements IService { + + private IKeyRecoveryAuthority mKRA = null; + + private IKeyRepository mStorage = null; + private IStorageKeyUnit mStorageUnit = null; + private ITransportKeyUnit mTransportUnit = null; + + public static final String ATTR_SERIALNO = "serialNumber"; + public static final String ATTR_KEY_RECORD = "keyRecord"; + + public SecurityDataRecoveryService(IKeyRecoveryAuthority kra) { + mKRA = kra; + mStorage = mKRA.getKeyRepository(); + mStorageUnit = mKRA.getStorageKeyUnit(); + mTransportUnit = mKRA.getTransportKeyUnit(); + + } + + /** + * Performs the service (such as certificate generation) + * represented by this request. + * <p> + * + * @param request + * The SecurityData recovery request that needs service. The service may use + * attributes stored in the request, and may update the + * values, or store new ones. + * @return + * an indication of whether this request is still pending. + * 'false' means the request will wait for further notification. + * @exception EBaseException indicates major processing failure. + */ + public boolean serviceRequest(IRequest request) + throws EBaseException { + + //Pave the way for allowing generated IV vector + byte iv[]= null; + byte iv_default[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; + byte iv_in[] = null; + + Hashtable<String, Object> params = mKRA.getVolatileRequest( + request.getRequestId()); + + if (params == null) { + CMS.debug("Can't get volatile params."); + throw new EBaseException("Can't obtain volatile params!"); + } + + BigInteger serialno = request.getExtDataInBigInteger(ATTR_SERIALNO); + + request.setExtData(ATTR_KEY_RECORD, serialno); + + byte[] wrappedPassPhrase = null; + byte[] wrappedSessKey = null; + + String transWrappedSessKeyStr = (String) params.get(IRequest.SECURITY_DATA_TRANS_SESS_KEY); + if (transWrappedSessKeyStr != null) { + wrappedSessKey = Utils.base64decode(transWrappedSessKeyStr); + } + + String sessWrappedPassPhraseStr = (String) params.get(IRequest.SECURITY_DATA_SESS_PASS_PHRASE); + if (sessWrappedPassPhraseStr != null) { + wrappedPassPhrase = Utils.base64decode(sessWrappedPassPhraseStr); + } + + String ivInStr = (String) params.get(IRequest.SECURITY_DATA_IV_STRING_IN); + if (ivInStr != null) { + iv_in = Utils.base64decode(ivInStr); + } + + if (transWrappedSessKeyStr == null && sessWrappedPassPhraseStr == null) { + //We may be in recovery case where no params were initially submitted. + return false; + } + + //Create the return IV if needed. + iv = new byte[8]; + + try { + Random rnd = new Random(); + rnd.nextBytes(iv); + } catch (Exception e) { + iv = iv_default; + } + + String ivStr = Utils.base64encode(iv); + + KeyRecord keyRecord = (KeyRecord) mStorage.readKeyRecord(serialno); + + SymmetricKey unwrappedSess = null; + + String dataType = (String) keyRecord.get(IKeyRecord.ATTR_DATA_TYPE); + SymmetricKey symKey = null; + byte[] unwrappedSecData = null; + if (dataType.equals(KeyRequestResource.SYMMETRIC_KEY_TYPE)) { + symKey = recoverSymKey(keyRecord); + } else if (dataType.equals(KeyRequestResource.PASS_PHRASE_TYPE)) { + unwrappedSecData = recoverSecurityData(keyRecord); + } + + CryptoToken ct = mTransportUnit.getToken(); + + byte[] key_data = null; + String pbeWrappedData = null; + + if (sessWrappedPassPhraseStr != null) { //We have a trans wrapped pass phrase, we will be doing PBE packaging + byte[] unwrappedPass = null; + Password pass = null; + + try { + unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.DECRYPT); + Cipher decryptor = ct.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD); + decryptor.initDecrypt(unwrappedSess, new IVParameterSpec(iv_in)); + unwrappedPass = decryptor.doFinal(wrappedPassPhrase); + String passStr = new String(unwrappedPass, "UTF-8"); + + pass = new Password(passStr.toCharArray()); + passStr = null; + + if (dataType.equals(KeyRequestResource.SYMMETRIC_KEY_TYPE)) { + pbeWrappedData = createEncryptedContentInfo(ct, symKey, null, + pass); + } else if (dataType.equals(KeyRequestResource.PASS_PHRASE_TYPE)) { + pbeWrappedData = createEncryptedContentInfo(ct, null, unwrappedSecData, + pass); + } + + params.put(IRequest.SECURITY_DATA_PASS_WRAPPED_DATA, pbeWrappedData); + + } catch (Exception e) { + throw new EBaseException("Can't unwrap pass phase! " + e.toString()); + } finally { + if ( pass != null) { + pass.clear(); + } + + if ( unwrappedPass != null) { + java.util.Arrays.fill(unwrappedPass, (byte) 0); + } + } + + } else { // No trans wrapped pass phrase, return session wrapped data. + if (dataType.equals(KeyRequestResource.SYMMETRIC_KEY_TYPE)) { + //wrap the key with session key + try { + unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.WRAP); + KeyWrapper wrapper = ct.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD); + wrapper.initWrap(unwrappedSess, new IVParameterSpec(iv)); + key_data = wrapper.wrap(symKey); + } catch (Exception e) { + throw new EBaseException("Can't wrap symmetric key! " + e.toString()); + } + + } else if (dataType.equals(KeyRequestResource.PASS_PHRASE_TYPE)) { + try { + unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.ENCRYPT); + Cipher encryptor = ct.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD); + if (encryptor != null) { + encryptor.initEncrypt(unwrappedSess, new IVParameterSpec(iv)); + key_data = encryptor.doFinal(unwrappedSecData); + } else { + throw new IOException("Failed to create cipher"); + } + } catch (Exception e) { + throw new EBaseException("Can't wrap pass phrase!"); + } + } + + String wrappedKeyData = Utils.base64encode(key_data); + params.put(IRequest.SECURITY_DATA_SESS_WRAPPED_DATA, wrappedKeyData); + params.put(IRequest.SECURITY_DATA_IV_STRING_OUT, ivStr); + + } + return false; + } + + public SymmetricKey recoverSymKey(KeyRecord keyRecord) + throws EBaseException { + + try { + SymmetricKey symKey = + mStorageUnit.unwrap( + keyRecord.getPrivateKeyData()); + + if (symKey == null) { + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", + "symmetric key unwrapping failure")); + } + + return symKey; + } catch (Exception e) { + + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", + "recoverSymKey() " + e.toString())); + } + } + + public byte[] recoverSecurityData(KeyRecord keyRecord) + throws EBaseException { + + byte[] decodedData = null; + + try { + decodedData = mStorageUnit.decryptInternalPrivate( + keyRecord.getPrivateKeyData()); + + if (decodedData == null) { + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", + "security data unwrapping failure")); + } + + return decodedData; + } catch (Exception e) { + + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", + "recoverSecurityData() " + e.toString())); + } + } + + //ToDo: This might fit in JSS. + private static EncryptedContentInfo + createEncryptedContentInfoPBEOfSymmKey(PBEAlgorithm keyGenAlg, Password password, byte[] salt, + int iterationCount, + KeyGenerator.CharToByteConverter charToByteConverter, + SymmetricKey symKey, CryptoToken token) + throws CryptoManager.NotInitializedException, NoSuchAlgorithmException, + InvalidKeyException, InvalidAlgorithmParameterException, TokenException, + CharConversionException { + + if (!(keyGenAlg instanceof PBEAlgorithm)) { + throw new NoSuchAlgorithmException("Key generation algorithm" + + " is not a PBE algorithm"); + } + PBEAlgorithm pbeAlg = (PBEAlgorithm) keyGenAlg; + + KeyGenerator kg = token.getKeyGenerator(keyGenAlg); + PBEKeyGenParams pbekgParams = new PBEKeyGenParams( + password, salt, iterationCount); + if (charToByteConverter != null) { + kg.setCharToByteConverter(charToByteConverter); + } + kg.initialize(pbekgParams); + SymmetricKey key = kg.generate(); + + EncryptionAlgorithm encAlg = pbeAlg.getEncryptionAlg(); + AlgorithmParameterSpec params = null; + if (encAlg.getParameterClass().equals(IVParameterSpec.class)) { + params = new IVParameterSpec(kg.generatePBE_IV()); + } else if (encAlg.getParameterClass().equals( + RC2ParameterSpec.class)) { + params = new RC2ParameterSpec(key.getStrength(), + kg.generatePBE_IV()); + } + + KeyWrapper wrapper = token.getKeyWrapper( + KeyWrapAlgorithm.DES3_CBC_PAD); + wrapper.initWrap(key, params); + byte encrypted[] = wrapper.wrap(symKey); + + PBEParameter pbeParam = new PBEParameter(salt, iterationCount); + AlgorithmIdentifier encAlgID = new AlgorithmIdentifier( + keyGenAlg.toOID(), pbeParam); + + EncryptedContentInfo encCI = new EncryptedContentInfo( + ContentInfo.DATA, + encAlgID, + new OCTET_STRING(encrypted)); + + return encCI; + + } + + private static String createEncryptedContentInfo(CryptoToken ct, SymmetricKey symKey, byte[] securityData, + Password password) + throws EBaseException { + + EncryptedContentInfo cInfo = null; + String retData = null; + PBEAlgorithm keyGenAlg = PBEAlgorithm.PBE_SHA1_DES3_CBC; + + byte[] encoded = null; + try { + PasswordConverter passConverter = new + PasswordConverter(); + byte salt[] = { 0x01, 0x01, 0x01, 0x01 }; + if (symKey != null) { + + cInfo = createEncryptedContentInfoPBEOfSymmKey(keyGenAlg, password, salt, + 1, + passConverter, + symKey, ct); + + } else if (securityData != null) { + + cInfo = EncryptedContentInfo.createPBE(keyGenAlg, password, salt, 1, passConverter, securityData); + } + + if(cInfo == null) { + throw new EBaseException("Can't create a PBE wrapped EncryptedContentInfo!"); + } + + ByteArrayOutputStream oStream = new ByteArrayOutputStream(); + cInfo.encode(oStream); + encoded = oStream.toByteArray(); + retData = Utils.base64encode(encoded); + + } catch (Exception e) { + throw new EBaseException("Can't create a PBE wrapped EncryptedContentInfo! " + e.toString()); + } + + return retData; + } + +} diff --git a/base/kra/src/com/netscape/kra/SecurityDataService.java b/base/kra/src/com/netscape/kra/SecurityDataService.java new file mode 100644 index 000000000..fa009dac9 --- /dev/null +++ b/base/kra/src/com/netscape/kra/SecurityDataService.java @@ -0,0 +1,171 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.math.BigInteger; +import org.mozilla.jss.crypto.SymmetricKey; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.profile.IEnrollProfile; +import com.netscape.certsrv.request.IService; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.security.IStorageKeyUnit; +import com.netscape.certsrv.security.ITransportKeyUnit; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.dbs.keydb.IKeyRecord; +import com.netscape.certsrv.dbs.keydb.IKeyRepository; +import com.netscape.certsrv.apps.CMS; +import com.netscape.cms.servlet.request.KeyRequestResource; +import com.netscape.cmscore.dbs.KeyRecord; +import com.netscape.cmsutil.util.Utils; + +/** + * This implementation implements SecurityData archival operations. + * <p> + * + * @version $Revision$, $Date$ + */ +public class SecurityDataService implements IService { + + private final static String DEFAULT_OWNER = "IPA Agent"; + public final static String ATTR_KEY_RECORD = "keyRecord"; + private final static String STATUS_ACTIVE = "active"; + + private IKeyRecoveryAuthority mKRA = null; + private ITransportKeyUnit mTransportUnit = null; + private IStorageKeyUnit mStorageUnit = null; + + public SecurityDataService(IKeyRecoveryAuthority kra) { + mKRA = kra; + mTransportUnit = kra.getTransportKeyUnit(); + mStorageUnit = kra.getStorageKeyUnit(); + } + + /** + * Performs the service of archiving Security Data. + * represented by this request. + * <p> + * + * @param request + * The request that needs service. The service may use + * attributes stored in the request, and may update the + * values, or store new ones. + * @return + * an indication of whether this request is still pending. + * 'false' means the request will wait for further notification. + * @exception EBaseException indicates major processing failure. + */ + public boolean serviceRequest(IRequest request) + throws EBaseException { + String id = request.getRequestId().toString(); + String clientId = request.getExtDataInString(IRequest.SECURITY_DATA_CLIENT_ID); + String wrappedSecurityData = request.getExtDataInString(IEnrollProfile.REQUEST_ARCHIVE_OPTIONS); + String dataType = request.getExtDataInString(IRequest.SECURITY_DATA_TYPE); + + CMS.debug("SecurityDataService.serviceRequest. Request id: " + id); + CMS.debug("SecurityDataService.serviceRequest wrappedSecurityData: " + wrappedSecurityData); + + String owner = getOwnerName(request); + + //Check here even though restful layer checks for this. + if(wrappedSecurityData == null || clientId == null || dataType == null) { + throw new EBaseException("Bad data in SecurityDataService.serviceRequest"); + } + //We need some info from the PKIArchiveOptions wrapped security data + + byte[] encoded = Utils.base64decode(wrappedSecurityData); + + ArchiveOptions options = ArchiveOptions.toArchiveOptions(encoded); + + //Check here just in case a null ArchiveOptions makes it this far + if(options == null) { + throw new EBaseException("Problem decofing PKIArchiveOptions."); + } + + String algStr = options.getSymmAlgOID(); + + SymmetricKey securitySymKey = null; + byte[] securityData = null; + + String keyType = null; + if (dataType.equals(KeyRequestResource.SYMMETRIC_KEY_TYPE)) { + // Symmetric Key + keyType = KeyRequestResource.SYMMETRIC_KEY_TYPE; + securitySymKey = mTransportUnit.unwrap_symmetric(options.getEncSymmKey(), + options.getSymmAlgOID(), + options.getSymmAlgParams(), + options.getEncValue()); + + } else if (dataType.equals(KeyRequestResource.PASS_PHRASE_TYPE)) { + keyType = KeyRequestResource.PASS_PHRASE_TYPE; + securityData = mTransportUnit.decryptExternalPrivate(options.getEncSymmKey(), + options.getSymmAlgOID(), + options.getSymmAlgParams(), + options.getEncValue()); + + } + + byte[] publicKey = null; + byte privateSecurityData[] = null; + + if (securitySymKey != null) { + privateSecurityData = mStorageUnit.wrap(securitySymKey); + } else if (securityData != null) { + privateSecurityData = mStorageUnit.encryptInternalPrivate(securityData); + } else { // We have no data. + throw new EBaseException("Failed to create security data to archive!"); + } + // create key record + KeyRecord rec = new KeyRecord(null, publicKey, + privateSecurityData, owner, + algStr, owner); + + rec.set(IKeyRecord.ATTR_CLIENT_ID, clientId); + + //Now we need a serial number for our new key. + + if (rec.getSerialNumber() != null) { + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_STATE")); + } + + IKeyRepository storage = mKRA.getKeyRepository(); + BigInteger serialNo = storage.getNextSerialNumber(); + + if (serialNo == null) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_GET_NEXT_SERIAL")); + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_STATE")); + } + + rec.set(KeyRecord.ATTR_ID, serialNo); + rec.set(KeyRecord.ATTR_DATA_TYPE, keyType); + rec.set(KeyRecord.ATTR_STATUS, STATUS_ACTIVE); + request.setExtData(ATTR_KEY_RECORD, serialNo); + + CMS.debug("KRA adding Security Data key record " + serialNo); + + storage.addKeyRecord(rec); + + return true; + + } + //ToDo: return real owner with auth + private String getOwnerName(IRequest request) { + return DEFAULT_OWNER; + } +}
\ No newline at end of file diff --git a/base/kra/src/com/netscape/kra/StorageKeyUnit.java b/base/kra/src/com/netscape/kra/StorageKeyUnit.java new file mode 100644 index 000000000..c956bf8d8 --- /dev/null +++ b/base/kra/src/com/netscape/kra/StorageKeyUnit.java @@ -0,0 +1,978 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.CharConversionException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.crypto.BadPaddingException; +import org.mozilla.jss.crypto.Cipher; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.EncryptionAlgorithm; +import org.mozilla.jss.crypto.IllegalBlockSizeException; +import org.mozilla.jss.crypto.KeyGenerator; +import org.mozilla.jss.crypto.KeyWrapAlgorithm; +import org.mozilla.jss.crypto.KeyWrapper; +import org.mozilla.jss.crypto.ObjectNotFoundException; +import org.mozilla.jss.crypto.PBEAlgorithm; +import org.mozilla.jss.crypto.PBEKeyGenParams; +import org.mozilla.jss.crypto.PrivateKey; +import org.mozilla.jss.crypto.SymmetricKey; +import org.mozilla.jss.crypto.TokenCertificate; +import org.mozilla.jss.crypto.TokenException; +import org.mozilla.jss.crypto.X509Certificate; +import org.mozilla.jss.util.Password; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.ISubsystem; +import com.netscape.certsrv.kra.EKRAException; +import com.netscape.certsrv.kra.IJoinShares; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.kra.IShare; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.security.Credential; +import com.netscape.certsrv.security.IStorageKeyUnit; +import com.netscape.cmsutil.util.Utils; + +/** + * A class represents a storage key unit. Currently, this + * is implemented with cryptix, the final implementation + * should be built on JSS/HCL. + * + * @author thomask + * @version $Revision$, $Date$ + */ +public class StorageKeyUnit extends EncryptionUnit implements + ISubsystem, IStorageKeyUnit { + + private IConfigStore mConfig = null; + + // private RSAPublicKey mPublicKey = null; + // private RSAPrivateKey mPrivateKey = null; + + private IConfigStore mStorageConfig = null; + private IKeyRecoveryAuthority mKRA = null; + private String mTokenFile = null; + private X509Certificate mCert = null; + private CryptoManager mManager = null; + private CryptoToken mToken = null; + private PrivateKey mPrivateKey = null; + private byte mPrivateKeyData[] = null; + private boolean mKeySplitting = false; + + private static final String PROP_N = "n"; + private static final String PROP_M = "m"; + private static final String PROP_UID = "uid"; + private static final String PROP_SHARE = "share"; + private static final String PROP_HARDWARE = "hardware"; + private static final String PROP_LOGOUT = "logout"; + public static final String PROP_NICKNAME = "nickName"; + public static final String PROP_KEYDB = "keydb"; + public static final String PROP_CERTDB = "certdb"; + public static final String PROP_MN = "mn"; + + /** + * Constructs this token. + */ + public StorageKeyUnit() { + super(); + } + + /** + * Retrieves subsystem identifier. + */ + public String getId() { + return "storageKeyUnit"; + } + + /** + * Sets subsystem identifier. Once the system is + * loaded, system identifier cannot be changed + * dynamically. + */ + public void setId(String id) throws EBaseException { + throw new EBaseException(CMS.getUserMessage("CMS_INVALID_OPERATION")); + } + + /** + * return true if byte arrays are equal, false otherwise + */ + private boolean byteArraysMatch(byte a[], byte b[]) { + if (a == null || b == null) { + return false; + } + if (a.length != b.length) { + return false; + } + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + /** + * Initializes this subsystem. + */ + public void init(ISubsystem owner, IConfigStore config) + throws EBaseException { + mKRA = (IKeyRecoveryAuthority) owner; + mConfig = config; + + mKeySplitting = owner.getConfigStore().getBoolean("keySplitting", false); + + try { + mManager = CryptoManager.getInstance(); + mToken = getToken(); + } catch (org.mozilla.jss.CryptoManager.NotInitializedException e) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_STORAGE_INIT", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString())); + } + + if (mConfig.getString(PROP_HARDWARE, null) != null) { + System.setProperty("cms.skip_token", mConfig.getString(PROP_HARDWARE)); + + // The strategy here is to read all the certs in the token + // and cycle through them until we find one that matches the + // kra-cert.db file + + if (mKeySplitting) { + + byte certFileData[] = null; + try { + File certFile = new File( + mConfig.getString(PROP_CERTDB)); + + certFileData = new byte[ + (Long.valueOf(certFile.length())).intValue()]; + FileInputStream fi = new FileInputStream(certFile); + + fi.read(certFileData); + fi.close(); + + // pick up cert by nickName + + } catch (IOException e) { + mKRA.log(ILogger.LL_INFO, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_READ_CERT", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString())); + } + + try { + X509Certificate certs[] = + getToken().getCryptoStore().getCertificates(); + for (int i = 0; i < certs.length; i++) { + if (byteArraysMatch(certs[i].getEncoded(), certFileData)) { + mCert = certs[i]; + } + } + if (mCert == null) { + mKRA.log(ILogger.LL_FAILURE, + "Storage Cert could not be initialized. No cert in token matched kra-cert file"); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", "mCert == null")); + } else { + mKRA.log(ILogger.LL_INFO, "Using Storage Cert " + mCert.getSubjectDN()); + } + } catch (CertificateEncodingException e) { + mKRA.log(ILogger.LL_FAILURE, "Error encoding cert "); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString())); + } catch (TokenException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_READ_CERT", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString())); + } + } + + } else { + + // read certificate from file + byte certData[] = null; + + try { + if (mKeySplitting) { + File certFile = new File( + mConfig.getString(PROP_CERTDB)); + + certData = new byte[ + (Long.valueOf(certFile.length())).intValue()]; + FileInputStream fi = new FileInputStream(certFile); + + fi.read(certData); + fi.close(); + + // pick up cert by nickName + mCert = mManager.findCertByNickname( + config.getString(PROP_NICKNAME)); + + } else { + mCert = mManager.findCertByNickname( + config.getString(PROP_NICKNAME)); + } + } catch (IOException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_READ_CERT", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString())); + } catch (TokenException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_READ_CERT", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString())); + } catch (ObjectNotFoundException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_READ_CERT", e.toString())); + // XXX - this import wont work + try { + mCert = mManager.importCertPackage(certData, + "kraStorageCert"); + } catch (Exception ex) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_IMPORT_CERT", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", ex.toString())); + } + } + + if (mKeySplitting) { + // read private key from the file + try { + File priFile = new File(mConfig.getString(PROP_KEYDB)); + + mPrivateKeyData = new byte[ + (Long.valueOf(priFile.length())).intValue()]; + FileInputStream fi = new FileInputStream(priFile); + + fi.read(mPrivateKeyData); + fi.close(); + } catch (IOException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_READ_PRIVATE", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", e.toString())); + } + } + + } + + if (mKeySplitting) { + // open internal data storage configuration + mTokenFile = mConfig.getString(PROP_MN); + try { + // read m, n and no of identifier + mStorageConfig = CMS.createFileConfigStore(mTokenFile); + } catch (EBaseException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_READ_MN", + e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_OPERATION")); + + } + } + + try { + if (mCert == null) { + CMS.debug("mCert is null...retrieving " + config.getString(PROP_NICKNAME)); + mCert = mManager.findCertByNickname( + config.getString(PROP_NICKNAME)); + CMS.debug("mCert = " + mCert); + } + } catch (Exception e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_READ_CERT", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString())); + } + + } + + /** + * Starts up this subsystem. + */ + public void startup() throws EBaseException { + } + + /** + * Shutdowns this subsystem. + */ + public void shutdown() { + } + + /** + * Returns the configuration store of this token. + */ + public IConfigStore getConfigStore() { + return mConfig; + } + + public static SymmetricKey buildSymmetricKeyWithInternalStorage( + String pin) throws EBaseException { + try { + return buildSymmetricKey(CryptoManager.getInstance().getInternalKeyStorageToken(), pin); + } catch (Exception e) { + return null; + } + } + + /** + * Builds symmetric key from the given password. + */ + public static SymmetricKey buildSymmetricKey(CryptoToken token, + String pin) throws EBaseException { + try { + + Password pass = new Password(pin.toCharArray()); + KeyGenerator kg = null; + + kg = token.getKeyGenerator( + PBEAlgorithm.PBE_SHA1_DES3_CBC); + byte salt[] = { 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01 }; + PBEKeyGenParams kgp = new PBEKeyGenParams(pass, + salt, 5); + + pass.clear(); + kg.initialize(kgp); + return kg.generate(); + } catch (TokenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "buildSymmetricKey:" + + e.toString())); + } catch (NoSuchAlgorithmException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "buildSymmetricKey:" + + e.toString())); + } catch (InvalidAlgorithmParameterException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "buildSymmetricKey:" + + e.toString())); + } catch (CharConversionException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "buildSymmetricKey:" + + e.toString())); + } + } + + /** + * Unwraps the storage key with the given symmetric key. + */ + public PrivateKey unwrapStorageKey(CryptoToken token, + SymmetricKey sk, byte wrapped[], + PublicKey pubKey) + throws EBaseException { + try { + + CMS.debug("StorageKeyUnit.unwrapStorageKey."); + + KeyWrapper wrapper = token.getKeyWrapper( + KeyWrapAlgorithm.DES3_CBC_PAD); + + wrapper.initUnwrap(sk, IV); + + // XXX - it does not like the public key that is + // not a crypto X509Certificate + PrivateKey pk = wrapper.unwrapTemporaryPrivate(wrapped, + PrivateKey.RSA, pubKey); + + return pk; + } catch (TokenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "unwrapStorageKey:" + + e.toString())); + } catch (NoSuchAlgorithmException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "unwrapStorageKey:" + + e.toString())); + } catch (InvalidKeyException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "unwrapStorageKey:" + + e.toString())); + } catch (InvalidAlgorithmParameterException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "unwrapStorageKey:" + + e.toString())); + } + } + + /** + * Used by config-cert. + */ + public byte[] wrapStorageKey(CryptoToken token, + SymmetricKey sk, PrivateKey pri) + throws EBaseException { + CMS.debug("StorageKeyUnit.wrapStorageKey."); + try { + // move public & private to config/storage.dat + // delete private key + KeyWrapper wrapper = token.getKeyWrapper( + KeyWrapAlgorithm.DES3_CBC_PAD); + + // next to randomly generate a symmetric + // password + + wrapper.initWrap(sk, IV); + return wrapper.wrap(pri); + } catch (TokenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "wrapStorageKey:" + + e.toString())); + } catch (NoSuchAlgorithmException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "wrapStorageKey:" + + e.toString())); + } catch (InvalidKeyException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "wrapStorageKey:" + + e.toString())); + } catch (InvalidAlgorithmParameterException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + "wrapStorageKey:" + + e.toString())); + } + } + + /** + * Logins to this token. + */ + public void login(String pin) throws EBaseException { + if (mConfig.getString(PROP_HARDWARE, null) != null) { + try { + getToken().login(new Password(pin.toCharArray())); + PrivateKey pk[] = getToken().getCryptoStore().getPrivateKeys(); + + for (int i = 0; i < pk.length; i++) { + if (arraysEqual(pk[i].getUniqueID(), + ((TokenCertificate) mCert).getUniqueID())) { + mPrivateKey = pk[i]; + } + } + } catch (Exception e) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_STORAGE_LOGIN", e.toString())); + } + + } else { + try { + SymmetricKey sk = buildSymmetricKey(mToken, pin); + + mPrivateKey = unwrapStorageKey(mToken, sk, + mPrivateKeyData, getPublicKey()); + } catch (Exception e) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_STORAGE_LOGIN", e.toString())); + } + if (mPrivateKey == null) { + mPrivateKey = getPrivateKey(); + } + } + } + + /** + * Logins to this token. + */ + public void login(Credential creds[]) + throws EBaseException { + String pwd = constructPassword(creds); + + login(pwd); + } + + /** + * Logout from this token. + */ + public void logout() { + try { + if (mConfig.getString(PROP_HARDWARE, null) != null) { + if (mConfig.getBoolean(PROP_LOGOUT, false)) { + getToken().logout(); + } + } + } catch (Exception e) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_STORAGE_LOGOUT", e.toString())); + + } + mPrivateKey = null; + } + + /** + * Returns a list of recovery agent identifiers. + */ + public Enumeration<String> getAgentIdentifiers() { + Vector<String> v = new Vector<String>(); + + for (int i = 0;; i++) { + try { + String uid = + mStorageConfig.getString(PROP_UID + i); + + if (uid == null) + break; + v.addElement(uid); + } catch (EBaseException e) { + break; + } + } + return v.elements(); + } + + /** + * Changes agent password. + */ + public boolean changeAgentPassword(String id, String oldpwd, + String newpwd) throws EBaseException { + // locate the id(s) + for (int i = 0;; i++) { + try { + String uid = + mStorageConfig.getString(PROP_UID + i); + + if (uid == null) + break; + if (id.equals(uid)) { + byte share[] = decryptShareWithInternalStorage(mStorageConfig.getString(PROP_SHARE + i), oldpwd); + + mStorageConfig.putString(PROP_SHARE + i, + encryptShareWithInternalStorage( + share, newpwd)); + mStorageConfig.commit(false); + return true; + } + } catch (Exception e) { + break; + } + } + return false; + } + + /** + * Changes the m out of n recovery schema. + */ + public boolean changeAgentMN(int new_n, int new_m, + Credential oldcreds[], + Credential newcreds[]) + throws EBaseException { + + if (new_n != newcreds.length) { + throw new EKRAException(CMS.getUserMessage("CMS_KRA_INVALID_N")); + } + + // XXX - verify and construct original password + String secret = constructPassword(oldcreds); + + // XXX - remove extra configuration + for (int j = new_n; j < getNoOfAgents(); j++) { + mStorageConfig.remove(PROP_UID + j); + mStorageConfig.remove(PROP_SHARE + j); + } + + // XXX - split pwd into n pieces + byte shares[][] = new byte[newcreds.length][]; + + IShare s = null; + try { + String className = mConfig.getString("share_class", + "com.netscape.cms.shares.OldShare"); + s = (IShare) Class.forName(className).newInstance(); + } catch (Exception e) { + CMS.debug("Loading Shares error " + e); + } + if (s == null) { + CMS.debug("Share plugin is not found"); + return false; + } + + try { + s.initialize(secret.getBytes(), new_m); + } catch (Exception e) { + CMS.debug("Failed to initialize Share plugin"); + return false; + } + + for (int i = 0; i < newcreds.length; i++) { + byte share[] = s.createShare(i + 1); + + shares[i] = share; + } + + // store the new shares into configuration + mStorageConfig.putInteger(PROP_N, new_n); + mStorageConfig.putInteger(PROP_M, new_m); + for (int i = 0; i < newcreds.length; i++) { + mStorageConfig.putString(PROP_UID + i, + newcreds[i].getIdentifier()); + // use password to encrypt shares... + mStorageConfig.putString(PROP_SHARE + i, + encryptShareWithInternalStorage(shares[i], + newcreds[i].getPassword())); + } + + try { + mStorageConfig.commit(false); + return true; + } catch (EBaseException e) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_CHANGE_MN", e.toString())); + } + return false; + } + + /** + * Returns number of recovery agents. + */ + public int getNoOfAgents() throws EBaseException { + return mStorageConfig.getInteger(PROP_N); + } + + /** + * Returns number of recovery agents required for + * recovery operation. + */ + public int getNoOfRequiredAgents() throws EBaseException { + return mStorageConfig.getInteger(PROP_M); + } + + public void setNoOfRequiredAgents(int number) { + mStorageConfig.putInteger(PROP_M, number); + } + + public CryptoToken getInternalToken() { + try { + return CryptoManager.getInstance().getInternalKeyStorageToken(); + } catch (Exception e) { + return null; + } + } + + public CryptoToken getToken() { + try { + if (mConfig.getString(PROP_HARDWARE, null) != null) { + return mManager.getTokenByName(mConfig.getString(PROP_HARDWARE)); + } else { + return CryptoManager.getInstance().getInternalKeyStorageToken(); + } + } catch (Exception e) { + return null; + } + } + + /** + * Returns the certificate blob. + */ + public PublicKey getPublicKey() { + // NEED to move this key into internal storage token. + return mCert.getPublicKey(); + } + + public PrivateKey getPrivateKey() { + + if (!mKeySplitting) { + try { + PrivateKey pk[] = getToken().getCryptoStore().getPrivateKeys(); + for (int i = 0; i < pk.length; i++) { + if (arraysEqual(pk[i].getUniqueID(), + ((TokenCertificate) mCert).getUniqueID())) { + return pk[i]; + } + } + } catch (TokenException e) { + } + return null; + } else { + return mPrivateKey; + } + } + + /** + * Verifies the integrity of the given key pairs. + */ + public void verify(byte publicKey[], PrivateKey privateKey) + throws EBaseException { + // XXX + } + + public String encryptShareWithInternalStorage( + byte share[], String pwd) + throws EBaseException { + try { + return encryptShare(CryptoManager.getInstance().getInternalKeyStorageToken(), share, pwd); + } catch (Exception e) { + return null; + } + } + + /** + * Protectes the share with the given password. + */ + public String encryptShare(CryptoToken token, + byte share[], String pwd) + throws EBaseException { + try { + CMS.debug("StorageKeyUnit.encryptShare"); + Cipher cipher = token.getCipherContext( + EncryptionAlgorithm.DES3_CBC_PAD); + SymmetricKey sk = StorageKeyUnit.buildSymmetricKey(token, pwd); + + cipher.initEncrypt(sk, IV); + byte prev[] = preVerify(share); + byte enc[] = cipher.doFinal(prev); + + return Utils.base64encode(enc).trim(); + } catch (NoSuchAlgorithmException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + e.toString())); + } catch (TokenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + e.toString())); + } catch (InvalidKeyException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + e.toString())); + } catch (InvalidAlgorithmParameterException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + e.toString())); + } catch (BadPaddingException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + e.toString())); + } catch (IllegalBlockSizeException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_KEY_1", + e.toString())); + } + } + + public static byte[] preVerify(byte share[]) { + byte data[] = new byte[share.length + 2]; + + data[0] = 0; + data[1] = 0; + for (int i = 0; i < share.length; i++) { + data[2 + i] = share[i]; + } + return data; + } + + public static boolean verifyShare(byte share[]) { + if (share[0] == 0 && share[1] == 0) { + return true; + } else { + return false; + } + } + + public static byte[] postVerify(byte share[]) { + byte data[] = new byte[share.length - 2]; + + for (int i = 2; i < share.length; i++) { + data[i - 2] = share[i]; + } + return data; + } + + public void checkPassword(String userid, String pwd) throws EBaseException { + for (int i = 0;; i++) { + String uid = null; + + try { + uid = mStorageConfig.getString(PROP_UID + i); + if (uid == null) + break; + } catch (Exception e) { + break; + } + if (uid.equals(userid)) { + byte data[] = decryptShareWithInternalStorage( + mStorageConfig.getString(PROP_SHARE + i), + pwd); + if (data == null) { + throw new EBaseException(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + return; + } + } + throw new EBaseException(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + + } + + public byte[] decryptShareWithInternalStorage( + String encoding, String pwd) + throws EBaseException { + try { + return decryptShare(CryptoManager.getInstance().getInternalKeyStorageToken(), encoding, pwd); + } catch (Exception e) { + return null; + } + } + + /** + * Decrypts shares with the given password. + */ + public byte[] decryptShare(CryptoToken token, + String encoding, String pwd) + throws EBaseException { + try { + CMS.debug("StorageKeyUnit.decryptShare"); + byte share[] = CMS.AtoB(encoding); + Cipher cipher = token.getCipherContext( + EncryptionAlgorithm.DES3_CBC_PAD); + SymmetricKey sk = StorageKeyUnit.buildSymmetricKey( + token, pwd); + + cipher.initDecrypt(sk, IV); + byte dec[] = cipher.doFinal(share); + + if (dec == null || !verifyShare(dec)) { + // invalid passwod + throw new EBaseException(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + return postVerify(dec); + } catch (OutOfMemoryError e) { + // XXX - this happens in cipher.doFinal when + // the given share is not valid (the password + // given from the agent is not correct). + // Actulla, cipher.doFinal should return + // something better than this! + // + // e.printStackTrace(); + // + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_PASSWORD", + e.toString())); + } catch (TokenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_PASSWORD", + e.toString())); + } catch (NoSuchAlgorithmException e) { + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_PASSWORD", + e.toString())); + } catch (InvalidKeyException e) { + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_PASSWORD", + e.toString())); + } catch (InvalidAlgorithmParameterException e) { + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_PASSWORD", + e.toString())); + } catch (IllegalBlockSizeException e) { + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_PASSWORD", + e.toString())); + } catch (BadPaddingException e) { + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_PASSWORD", + e.toString())); + } + } + + /** + * Reconstructs password from recovery agents. + */ + private String constructPassword(Credential creds[]) + throws EBaseException { + // sort the credential according to the order in + // configuration file + Hashtable<String, byte[]> v = new Hashtable<String, byte[]>(); + + for (int i = 0;; i++) { + String uid = null; + + try { + uid = mStorageConfig.getString(PROP_UID + i); + if (uid == null) + break; + } catch (Exception e) { + break; + } + for (int j = 0; j < creds.length; j++) { + if (uid.equals(creds[j].getIdentifier())) { + byte pwd[] = decryptShareWithInternalStorage( + mStorageConfig.getString( + PROP_SHARE + i), + creds[j].getPassword()); + if (pwd == null) { + throw new EBaseException(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + v.put(Integer.toString(i), pwd); + break; + } + } + } + + if (v.size() < 0) { + throw new EBaseException(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + + if (v.size() != creds.length) { + throw new EBaseException(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + + IJoinShares j = null; + try { + String className = mConfig.getString("joinshares_class", + "com.netscape.cms.shares.OldJoinShares"); + j = (IJoinShares) Class.forName(className).newInstance(); + } catch (Exception e) { + CMS.debug("JoinShares error " + e); + } + if (j == null) { + CMS.debug("JoinShares plugin is not found"); + throw new EBaseException(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + + try { + j.initialize(v.size()); + } catch (Exception e) { + CMS.debug("Failed to initialize JoinShares"); + throw new EBaseException(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + Enumeration<String> e = v.keys(); + + while (e.hasMoreElements()) { + String next = e.nextElement(); + + j.addShare(Integer.parseInt(next) + 1, + (byte[]) v.get(next)); + } + try { + byte secret[] = j.recoverSecret(); + String pwd = new String(secret); + + return pwd; + } catch (Exception ee) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_STORAGE_RECONSTRUCT", e.toString())); + throw new EBaseException(CMS.getUserMessage("CMS_KRA_INVALID_PASSWORD", + ee.toString())); + } + } + + public static boolean arraysEqual(byte[] bytes, byte[] ints) { + if (bytes == null || ints == null) { + return false; + } + + if (bytes.length != ints.length) { + return false; + } + + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] != ints[i]) { + return false; + } + } + return true; + } + +} diff --git a/base/kra/src/com/netscape/kra/TokenKeyRecoveryService.java b/base/kra/src/com/netscape/kra/TokenKeyRecoveryService.java new file mode 100644 index 000000000..7575ea9f4 --- /dev/null +++ b/base/kra/src/com/netscape/kra/TokenKeyRecoveryService.java @@ -0,0 +1,627 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.security.SecureRandom; +import java.util.Hashtable; + +import netscape.security.util.BigInt; +import netscape.security.util.DerInputStream; +import netscape.security.util.DerValue; + +import org.mozilla.jss.crypto.Cipher; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.EncryptionAlgorithm; +import org.mozilla.jss.crypto.IVParameterSpec; +import org.mozilla.jss.crypto.SymmetricKey; +import org.mozilla.jss.pkcs11.PK11SymKey; +import org.mozilla.jss.util.Base64OutputStream; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.dbs.keydb.IKeyRepository; +import com.netscape.certsrv.kra.EKRAException; +import com.netscape.certsrv.kra.IKeyRecoveryAuthority; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.request.IRequest; +import com.netscape.certsrv.request.IService; +import com.netscape.certsrv.security.IStorageKeyUnit; +import com.netscape.certsrv.security.ITransportKeyUnit; +import com.netscape.cmscore.dbs.KeyRecord; +import com.netscape.cmsutil.util.Cert; + +/** + * A class represents recovery request processor. + * + * @author Christina Fu (cfu) + * @version $Revision$, $Date$ + */ +public class TokenKeyRecoveryService implements IService { + + public static final String ATTR_NICKNAME = "nickname"; + public static final String ATTR_OWNER_NAME = "ownerName"; + public static final String ATTR_PUBLIC_KEY_DATA = "publicKeyData"; + public static final String ATTR_PRIVATE_KEY_DATA = "privateKeyData"; + public static final String ATTR_TRANSPORT_CERT = "transportCert"; + public static final String ATTR_TRANSPORT_PWD = "transportPwd"; + public static final String ATTR_SIGNING_CERT = "signingCert"; + public static final String ATTR_PKCS12 = "pkcs12"; + public static final String ATTR_ENCRYPTION_CERTS = + "encryptionCerts"; + public static final String ATTR_AGENT_CREDENTIALS = + "agentCredentials"; + // same as encryption certs + public static final String ATTR_USER_CERT = "cert"; + public static final String ATTR_DELIVERY = "delivery"; + + private IKeyRecoveryAuthority mKRA = null; + private IKeyRepository mStorage = null; + private IStorageKeyUnit mStorageUnit = null; + private ITransportKeyUnit mTransportUnit = null; + + private final static String LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST = + "LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_4"; + + private final static String LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED = + "LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED_4"; + private ILogger mSignedAuditLogger = CMS.getSignedAuditLogger(); + + /** + * Constructs request processor. + */ + public TokenKeyRecoveryService(IKeyRecoveryAuthority kra) { + mKRA = kra; + mStorage = mKRA.getKeyRepository(); + mStorageUnit = mKRA.getStorageKeyUnit(); + mTransportUnit = kra.getTransportKeyUnit(); + } + + /** + * Process the HTTP request. + * + * @param s The URL to decode + */ + protected String URLdecode(String s) { + if (s == null) + return null; + ByteArrayOutputStream out = new ByteArrayOutputStream(s.length()); + + for (int i = 0; i < s.length(); i++) { + int c = (int) s.charAt(i); + + if (c == '+') { + out.write(' '); + } else if (c == '%') { + int c1 = Character.digit(s.charAt(++i), 16); + int c2 = Character.digit(s.charAt(++i), 16); + + out.write((char) (c1 * 16 + c2)); + } else { + out.write(c); + } + } // end for + return out.toString(); + } + + public static String normalizeCertStr(String s) { + String val = ""; + + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '\\') { + i++; + continue; + } else if (s.charAt(i) == '\\') { + i++; + continue; + } else if (s.charAt(i) == '"') { + continue; + } else if (s.charAt(i) == ' ') { + continue; + } + val += s.charAt(i); + } + return val; + } + + private static String base64Encode(byte[] bytes) throws IOException { + // All this streaming is lame, but Base64OutputStream needs a + // PrintStream + ByteArrayOutputStream output = new ByteArrayOutputStream(); + Base64OutputStream b64 = new Base64OutputStream(new + PrintStream(new + FilterOutputStream(output) + ) + ); + + b64.write(bytes); + b64.flush(); + + // This is internationally safe because Base64 chars are + // contained within 8859_1 + return output.toString("8859_1"); + } + + // this encrypts bytes with a symmetric key + public byte[] encryptIt(byte[] toBeEncrypted, SymmetricKey symKey, CryptoToken token, + IVParameterSpec IV) { + try { + Cipher cipher = token.getCipherContext( + EncryptionAlgorithm.DES3_CBC_PAD); + + cipher.initEncrypt(symKey, IV); + byte pri[] = cipher.doFinal(toBeEncrypted); + return pri; + } catch (Exception e) { + CMS.debug("initEncrypt() threw exception: " + e.toString()); + return null; + } + + } + + /** + * Processes a recovery request. The method reads + * the key record from the database, and tries to recover the + * key with the storage key unit. Once recovered, it wraps it + * with desKey + * In the params + * - cert is used for recovery record search + * - cuid may be used for additional validation check + * - userid may be used for additional validation check + * - wrappedDesKey is used for wrapping recovered private key + * + * @param request recovery request + * @return operation success or not + * @exception EBaseException failed to serve + */ + public boolean serviceRequest(IRequest request) throws EBaseException { + String auditMessage = null; + String auditSubjectID = null; + String auditRecoveryID = ILogger.UNIDENTIFIED; + String iv_s = ""; + + CMS.debug("KRA services token key recovery request"); + + byte[] wrapped_des_key; + + byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; + try { + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.nextBytes(iv); + } catch (Exception e) { + CMS.debug("TokenKeyRecoveryService.serviceRequest: " + e.toString()); + } + + String id = request.getRequestId().toString(); + if (id != null) { + auditRecoveryID = id.trim(); + } + SessionContext sContext = SessionContext.getContext(); + String agentId = ""; + if (sContext != null) { + agentId = + (String) sContext.get(SessionContext.USER_ID); + } + + Hashtable<String, Object> params = mKRA.getVolatileRequest( + request.getRequestId()); + + if (params == null) { + // possibly we are in recovery mode + CMS.debug("getVolatileRequest params null"); + // return true; + } + + wrapped_des_key = null; + + PK11SymKey sk = null; + + String rCUID = request.getExtDataInString(IRequest.NETKEY_ATTR_CUID); + String rUserid = request.getExtDataInString(IRequest.NETKEY_ATTR_USERID); + String rWrappedDesKeyString = request.getExtDataInString(IRequest.NETKEY_ATTR_DRMTRANS_DES_KEY); + auditSubjectID = rCUID + ":" + rUserid; + + CMS.debug("TokenKeyRecoveryService: received DRM-trans-wrapped des key =" + rWrappedDesKeyString); + wrapped_des_key = com.netscape.cmsutil.util.Utils.SpecialDecode(rWrappedDesKeyString); + CMS.debug("TokenKeyRecoveryService: wrapped_des_key specialDecoded"); + + if ((wrapped_des_key != null) && + (wrapped_des_key.length > 0)) { + + // unwrap the des key + sk = (PK11SymKey) mTransportUnit.unwrap_encrypt_sym(wrapped_des_key); + + if (sk == null) { + CMS.debug("TokenKeyRecoveryService: no des key"); + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + } else { + CMS.debug("TokenKeyRecoveryService: received des key"); + } + } else { + CMS.debug("TokenKeyRecoveryService: not receive des key"); + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } + + // retrieve based on Certificate + String cert_s = request.getExtDataInString(ATTR_USER_CERT); + if (cert_s == null) { + CMS.debug("TokenKeyRecoveryService: not receive cert"); + request.setExtData(IRequest.RESULT, Integer.valueOf(3)); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } + + String cert = normalizeCertStr(cert_s); + java.security.cert.X509Certificate x509cert = null; + try { + x509cert = (java.security.cert.X509Certificate) Cert.mapCert(cert); + if (x509cert == null) { + CMS.debug("cert mapping failed"); + request.setExtData(IRequest.RESULT, Integer.valueOf(5)); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } + } catch (IOException e) { + CMS.debug("TokenKeyRecoveryService: mapCert failed"); + request.setExtData(IRequest.RESULT, Integer.valueOf(6)); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } + + try { + /* + CryptoToken internalToken = + CryptoManager.getInstance().getInternalKeyStorageToken(); + */ + CryptoToken token = mStorageUnit.getToken(); + CMS.debug("TokenKeyRecoveryService: got token slot:" + token.getName()); + IVParameterSpec algParam = new IVParameterSpec(iv); + + Cipher cipher = token.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD); + + KeyRecord keyRecord = null; + CMS.debug("KRA reading key record"); + try { + keyRecord = (KeyRecord) mStorage.readKeyRecord(cert); + if (keyRecord != null) + CMS.debug("read key record"); + else { + CMS.debug("key record not found"); + request.setExtData(IRequest.RESULT, Integer.valueOf(8)); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } + } catch (Exception e) { + com.netscape.cmscore.util.Debug.printStackTrace(e); + request.setExtData(IRequest.RESULT, Integer.valueOf(9)); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } + + // see if the owner name matches (cuid:userid) -XXX need make this optional + String owner = keyRecord.getOwnerName(); + CMS.debug("TokenKeyRecoveryService: owner name on record =" + owner); + CMS.debug("TokenKeyRecoveryService: owner name from TPS =" + rCUID + ":" + rUserid); + if (owner != null) { + if (owner.equals(rCUID + ":" + rUserid)) { + CMS.debug("TokenKeyRecoveryService: owner name matches"); + } else { + CMS.debug("TokenKeyRecoveryService: owner name mismatches"); + } + } + + // see if the certificate matches the key + byte pubData[] = keyRecord.getPublicKeyData(); + byte inputPubData[] = x509cert.getPublicKey().getEncoded(); + + if (inputPubData.length != pubData.length) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_PUBLIC_KEY_LEN")); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_PUBLIC_KEY_NOT_MATCHED")); + } + + for (int i = 0; i < pubData.length; i++) { + if (pubData[i] != inputPubData[i]) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_PUBLIC_KEY_LEN")); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_PUBLIC_KEY_NOT_MATCHED")); + } + } + + // Unwrap the archived private key + byte privateKeyData[] = null; + privateKeyData = recoverKey(params, keyRecord); + if (privateKeyData == null) { + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + CMS.debug("TokenKeyRecoveryService: failed getting private key"); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } + CMS.debug("TokenKeyRecoveryService: got private key...about to verify"); + + iv_s = /*base64Encode(iv);*/com.netscape.cmsutil.util.Utils.SpecialEncode(iv); + request.setExtData("iv_s", iv_s); + + CMS.debug("request.setExtData: iv_s: " + iv_s); + + /* LunaSA returns data with padding which we need to remove */ + ByteArrayInputStream dis = new ByteArrayInputStream(privateKeyData); + DerValue dv = new DerValue(dis); + byte p[] = dv.toByteArray(); + int l = p.length; + CMS.debug("length different data length=" + l + + " real length=" + privateKeyData.length); + if (l != privateKeyData.length) { + privateKeyData = p; + } + + if (verifyKeyPair(pubData, privateKeyData) == false) { + mKRA.log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_KRA_PUBLIC_NOT_FOUND")); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + throw new EKRAException( + CMS.getUserMessage("CMS_KRA_INVALID_PUBLIC_KEY")); + } else { + CMS.debug("TokenKeyRecoveryService: private key verified with public key"); + } + + //encrypt and put in private key + cipher.initEncrypt(sk, algParam); + byte wrapped[] = cipher.doFinal(privateKeyData); + + String wrappedPrivKeyString = + com.netscape.cmsutil.util.Utils.SpecialEncode(wrapped); + if (wrappedPrivKeyString == null) { + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + CMS.debug("TokenKeyRecoveryService: failed generating wrapped private key"); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } else { + CMS.debug("TokenKeyRecoveryService: got private key data wrapped"); + request.setExtData("wrappedUserPrivate", + wrappedPrivKeyString); + request.setExtData(IRequest.RESULT, Integer.valueOf(1)); + CMS.debug("TokenKeyRecoveryService: key for " + rCUID + ":" + rUserid + " recovered"); + } + + //convert and put in the public key + String b64PKey = base64Encode(pubData); + + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST, + auditSubjectID, + ILogger.SUCCESS, + auditRecoveryID, + b64PKey); + + audit(auditMessage); + + if (b64PKey == null) { + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + CMS.debug("TokenKeyRecoveryService: failed getting publickey encoded"); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.FAILURE, + auditRecoveryID, + agentId); + + audit(auditMessage); + return false; + } else { + CMS.debug("TokenKeyRecoveryService: got publicKeyData b64 = " + + b64PKey); + } + request.setExtData("public_key", b64PKey); + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_KEY_RECOVERY_REQUEST_PROCESSED, + auditSubjectID, + ILogger.SUCCESS, + auditRecoveryID, + agentId); + + audit(auditMessage); + + return true; + + } catch (Exception e) { + CMS.debug("TokenKeyRecoveryService: " + e.toString()); + request.setExtData(IRequest.RESULT, Integer.valueOf(4)); + } + + return true; + } + + public boolean verifyKeyPair(byte publicKeyData[], byte privateKeyData[]) { + try { + DerValue publicKeyVal = new DerValue(publicKeyData); + DerInputStream publicKeyIn = publicKeyVal.data; + publicKeyIn.getSequence(0); + DerValue publicKeyDer = new DerValue(publicKeyIn.getBitString()); + DerInputStream publicKeyDerIn = publicKeyDer.data; + BigInt publicKeyModulus = publicKeyDerIn.getInteger(); + BigInt publicKeyExponent = publicKeyDerIn.getInteger(); + + DerValue privateKeyVal = new DerValue(privateKeyData); + if (privateKeyVal.tag != DerValue.tag_Sequence) + return false; + DerInputStream privateKeyIn = privateKeyVal.data; + privateKeyIn.getInteger(); + privateKeyIn.getSequence(0); + DerValue privateKeyDer = new DerValue(privateKeyIn.getOctetString()); + DerInputStream privateKeyDerIn = privateKeyDer.data; + + @SuppressWarnings("unused") + BigInt privateKeyVersion = privateKeyDerIn.getInteger(); // consume stream + BigInt privateKeyModulus = privateKeyDerIn.getInteger(); + BigInt privateKeyExponent = privateKeyDerIn.getInteger(); + + if (!publicKeyModulus.equals(privateKeyModulus)) { + CMS.debug("verifyKeyPair modulus mismatch publicKeyModulus=" + + publicKeyModulus + " privateKeyModulus=" + privateKeyModulus); + return false; + } + + if (!publicKeyExponent.equals(privateKeyExponent)) { + CMS.debug("verifyKeyPair exponent mismatch publicKeyExponent=" + + publicKeyExponent + " privateKeyExponent=" + privateKeyExponent); + return false; + } + + return true; + } catch (Exception e) { + CMS.debug("verifyKeyPair error " + e); + return false; + } + } + + /** + * Recovers key. + */ + public synchronized byte[] recoverKey(Hashtable<String, Object> request, KeyRecord keyRecord) + throws EBaseException { + /* + Credential creds[] = (Credential[]) + request.get(ATTR_AGENT_CREDENTIALS); + + mStorageUnit.login(creds); + */ + CMS.debug("KRA decrypts internal private"); + byte privateKeyData[] = + mStorageUnit.decryptInternalPrivate( + keyRecord.getPrivateKeyData()); + /* + mStorageUnit.logout(); + */ + if (privateKeyData == null) { + mKRA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_KRA_PRIVATE_KEY_NOT_FOUND")); + throw new EKRAException(CMS.getUserMessage("CMS_KRA_RECOVERY_FAILED_1", "no private key")); + } + return privateKeyData; + } + + /** + * Signed Audit Log + * y + * This method is called to store messages to the signed audit log. + * <P> + * + * @param msg signed audit log message + */ + private void audit(String msg) { + // in this case, do NOT strip preceding/trailing whitespace + // from passed-in String parameters + + if (mSignedAuditLogger == null) { + return; + } + + mSignedAuditLogger.log(ILogger.EV_SIGNED_AUDIT, + null, + ILogger.S_SIGNED_AUDIT, + ILogger.LL_SECURITY, + msg); + } + +} diff --git a/base/kra/src/com/netscape/kra/TransportKeyUnit.java b/base/kra/src/com/netscape/kra/TransportKeyUnit.java new file mode 100644 index 000000000..90ac2120f --- /dev/null +++ b/base/kra/src/com/netscape/kra/TransportKeyUnit.java @@ -0,0 +1,195 @@ +// --- 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) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.kra; + +import java.security.PublicKey; + +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.ObjectNotFoundException; +import org.mozilla.jss.crypto.PrivateKey; +import org.mozilla.jss.crypto.Signature; +import org.mozilla.jss.crypto.SignatureAlgorithm; +import org.mozilla.jss.crypto.TokenException; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.ISubsystem; +import com.netscape.certsrv.security.ITransportKeyUnit; +import com.netscape.cmsutil.util.Cert; + +/** + * A class represents the transport key pair. This key pair + * is used to protected EE's private key in transit. + * + * @author thomask + * @version $Revision$, $Date$ + */ +public class TransportKeyUnit extends EncryptionUnit implements + ISubsystem, ITransportKeyUnit { + + public static final String PROP_NICKNAME = "nickName"; + public static final String PROP_SIGNING_ALGORITHM = "signingAlgorithm"; + + // private RSAPublicKey mPublicKey = null; + // private RSAPrivateKey mPrivateKey = null; + private IConfigStore mConfig = null; + private org.mozilla.jss.crypto.X509Certificate mCert = null; + private CryptoManager mManager = null; + + /** + * Constructs this token. + */ + public TransportKeyUnit() { + super(); + } + + /** + * Retrieves subsystem identifier. + */ + public String getId() { + return "transportKeyUnit"; + } + + /** + * Sets subsystem identifier. + */ + public void setId(String id) throws EBaseException { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_OPERATION")); + } + + /** + * Initializes this subsystem. + */ + public void init(ISubsystem owner, IConfigStore config) + throws EBaseException { + mConfig = config; + try { + mManager = CryptoManager.getInstance(); + mCert = mManager.findCertByNickname(getNickName()); + String algo = config.getString("signingAlgorithm", "SHA256withRSA"); + + // #613795 - initialize this; otherwise JSS is not happy + CryptoToken token = getToken(); + SignatureAlgorithm sigalg = Cert.mapAlgorithmToJss(algo); + Signature signer = token.getSignatureContext(sigalg); + signer.initSign(getPrivateKey()); + + } catch (org.mozilla.jss.CryptoManager.NotInitializedException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INTERNAL_ERROR", e.toString())); + + } catch (TokenException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INTERNAL_ERROR", e.toString())); + } catch (ObjectNotFoundException e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INTERNAL_ERROR", e.toString())); + } catch (Exception e) { + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INTERNAL_ERROR", e.toString())); + } + } + + public CryptoToken getInternalToken() { + try { + return CryptoManager.getInstance().getInternalKeyStorageToken(); + } catch (Exception e) { + return null; + } + } + + public CryptoToken getToken() { + // 390148: returning the token that owns the private + // key. + return getPrivateKey().getOwningToken(); + } + + /** + * Starts up this subsystem. + */ + public void startup() throws EBaseException { + } + + /** + * Shutdowns this subsystem. + */ + public void shutdown() { + } + + /** + * Returns the configuration store of this token. + */ + public IConfigStore getConfigStore() { + return mConfig; + } + + public String getNickName() throws EBaseException { + return mConfig.getString(PROP_NICKNAME); + } + + public void setNickName(String str) throws EBaseException { + mConfig.putString(PROP_NICKNAME, str); + } + + public String getSigningAlgorithm() throws EBaseException { + return mConfig.getString(PROP_SIGNING_ALGORITHM); + } + + public void setSigningAlgorithm(String str) throws EBaseException { + mConfig.putString(PROP_SIGNING_ALGORITHM, str); + } + + /** + * Logins to this token. + */ + public void login(String pin) throws EBaseException { + } + + /** + * Logout from this token. + */ + public void logout() { + } + + /** + * Retrieves public key. + */ + public org.mozilla.jss.crypto.X509Certificate getCertificate() { + return mCert; + } + + public PublicKey getPublicKey() { + return mCert.getPublicKey(); + } + + public PrivateKey getPrivateKey() { + try { + return mManager.findPrivKeyByCert(mCert); + } catch (TokenException e) { + return null; + } catch (ObjectNotFoundException e) { + return null; + } + } + + /** + * Verifies the integrity of the given key pair. + */ + public void verify(byte publicKey[], PrivateKey privateKey) + throws EBaseException { + // XXX + } +} |