From 76eca860d5d87b78156d1478306e8efab0c2c9e1 Mon Sep 17 00:00:00 2001 From: Christina Fu Date: Wed, 18 Oct 2017 14:27:03 -0700 Subject: [PATCH] Ticket #2604 RFE: shared token storage and retrieval mechanism This patch provides the implementation for the SharedSecret class that implements the SharedToken interface to support CMC enrollment and revocation based on shared secret between client and server. This patch also contains a new tool, CMCSharedToken that can be used to take a passphrase as input and produce the secret data that is to go into its respective ldap entry. The secret data takes on the following format that has been long adopted by the KRA key/secret archival features: SEQUENCE { encryptedSession OCTET STRING, encryptedPrivate OCTET STRING } See design at: https://pki.fedoraproject.org/wiki/PKI_10.5_CMC_Shared_Token Additionally, this patch also addressed the ReqID inconsistency issue in the CMC revocation area in CMCOutputTemplate.java Change-Id: I1bbf34d9d30e7fe43b89bfb6e1b471756f2d649b --- base/ca/shared/conf/CS.cfg | 3 +- .../certsrv/authentication/ISharedToken.java | 10 +- .../certsrv/dbs/certdb/ICertRecord.java | 2 + .../com/netscape/cmstools/CMCSharedToken.java | 320 +++++++++++++ base/java-tools/templates/CMakeLists.txt | 1 + .../cms/authentication/SharedSecret.java | 435 +++++++++++++++++- .../cms/profile/common/EnrollProfile.java | 104 +++-- .../cms/servlet/common/CMCOutputTemplate.java | 37 +- specs/pki-core.spec | 1 + 9 files changed, 836 insertions(+), 77 deletions(-) create mode 100644 base/java-tools/src/com/netscape/cmstools/CMCSharedToken.java diff --git a/base/ca/shared/conf/CS.cfg b/base/ca/shared/conf/CS.cfg index b873e040ccb..c1321c86b21 100644 --- a/base/ca/shared/conf/CS.cfg +++ b/base/ca/shared/conf/CS.cfg @@ -177,6 +177,7 @@ auths.impl.UserPwdDirAuth.class=com.netscape.cms.authentication.UserPwdDirAuthen auths.impl.TokenAuth.class=com.netscape.cms.authentication.TokenAuthentication auths.impl.FlatFileAuth.class=com.netscape.cms.authentication.FlatFileAuth auths.impl.SessionAuthentication.class=com.netscape.cms.authentication.SessionAuthentication +auths.impl.SharedToken.class=com.netscape.cms.authentication.SharedSecret auths.instance.TokenAuth.pluginName=TokenAuth auths.instance.AgentCertAuth.agentGroup=Certificate Manager Agents auths.instance.AgentCertAuth.pluginName=AgentCertAuth @@ -735,8 +736,6 @@ ca.publish.rule.instance.LdapXCertRule.predicate= ca.publish.rule.instance.LdapXCertRule.publisher=LdapCrossCertPairPublisher ca.publish.rule.instance.LdapXCertRule.type=xcert cmc.popLinkWitnessRequired=false -#cmc.revokeCert.sharedSecret.class=com.netscape.cms.authentication.SharedSecret -#cmc.sharedSecret.class=com.netscape.cms.authentication.SharedSecret cmc.token=internal cms.passwordlist=internaldb,replicationdb cms.password.ignore.publishing.failure=true diff --git a/base/common/src/com/netscape/certsrv/authentication/ISharedToken.java b/base/common/src/com/netscape/certsrv/authentication/ISharedToken.java index 84b02440416..b33ae7b76bd 100644 --- a/base/common/src/com/netscape/certsrv/authentication/ISharedToken.java +++ b/base/common/src/com/netscape/certsrv/authentication/ISharedToken.java @@ -16,6 +16,7 @@ // All rights reserved. // --- END COPYRIGHT BLOCK --- package com.netscape.certsrv.authentication; +import com.netscape.certsrv.base.EBaseException; import java.math.BigInteger; @@ -27,9 +28,12 @@ public interface ISharedToken { // support for id_cmc_identification - public String getSharedToken(String identification); + public String getSharedToken(String identification) + throws EBaseException; - public String getSharedToken(PKIData cmcData); + public String getSharedToken(PKIData cmcData) + throws EBaseException; - public String getSharedToken(BigInteger serialnum); + public String getSharedToken(BigInteger serialnum) + throws EBaseException; } diff --git a/base/common/src/com/netscape/certsrv/dbs/certdb/ICertRecord.java b/base/common/src/com/netscape/certsrv/dbs/certdb/ICertRecord.java index 65db57e70d9..d0d0748b944 100644 --- a/base/common/src/com/netscape/certsrv/dbs/certdb/ICertRecord.java +++ b/base/common/src/com/netscape/certsrv/dbs/certdb/ICertRecord.java @@ -55,6 +55,8 @@ public interface ICertRecord extends IDBObj { public static final String META_CRMF_REQID = "crmfReqId"; public static final String META_CHALLENGE_PHRASE = "challengePhrase"; public static final String META_PROFILE_ID = "profileId"; + // for supporting CMC shared-secret based revocation + public static final String META_REV_SHRTOK = "revShrTok"; public final static String STATUS_VALID = "VALID"; public final static String STATUS_INVALID = "INVALID"; diff --git a/base/java-tools/src/com/netscape/cmstools/CMCSharedToken.java b/base/java-tools/src/com/netscape/cmstools/CMCSharedToken.java new file mode 100644 index 00000000000..4705eee435f --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/CMCSharedToken.java @@ -0,0 +1,320 @@ +// --- 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) 2017 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmstools; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.PosixParser; +import org.apache.commons.io.FileUtils; +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.EncryptionAlgorithm; +import org.mozilla.jss.crypto.IVParameterSpec; +import org.mozilla.jss.crypto.KeyGenAlgorithm; +import org.mozilla.jss.crypto.KeyWrapAlgorithm; +import org.mozilla.jss.crypto.ObjectNotFoundException; +import org.mozilla.jss.crypto.PrivateKey; +import org.mozilla.jss.crypto.SymmetricKey; +import org.mozilla.jss.crypto.X509Certificate; + +import com.netscape.cmsutil.crypto.CryptoUtil; +import com.netscape.cmsutil.util.Cert; +import com.netscape.cmsutil.util.Utils; + +import netscape.security.util.DerInputStream; +import netscape.security.util.DerOutputStream; +import netscape.security.util.DerValue; + +/** + * A command-line utility used to take a passphrase as an input and + * generate an encrypted entry for ldap entry + * + *
+ * IMPORTANT:  The issuance protection certificate file needs to be created to
+ * contain the certificate in its PEM format.
+ * 
+ *

+ * @author cfu + */ +public class CMCSharedToken { + public boolean verbose = false; + + public static Options createOptions() { + + Options options = new Options(); + + Option option = new Option("d", true, "Security database location"); + option.setArgName("database"); + options.addOption(option); + + option = new Option("h", true, "Security token name"); + option.setArgName("token"); + options.addOption(option); + + option = new Option("o", true, "Output file to store base-64 secret data"); + option.setArgName("output"); + options.addOption(option); + + option = new Option("p", true, "passphrase"); + option.setArgName("passphrase"); + options.addOption(option); + + option = new Option("b", true, "PEM issuance protection certificate"); + option.setArgName("issuance protection cert"); + options.addOption(option); + + option = new Option("n", true, "Issuance Protection certificate nickname"); + option.setArgName("issuance protection cert nickname"); + options.addOption(option); + + options.addOption("v", "verbose", false, "Run in verbose mode."); + options.addOption(null, "help", false, "Show help message."); + + return options; + } + + public static void printHelp() { + + System.out.println("Usage: CMCSharedToken [OPTIONS]"); + System.out.println(" If the issuance protection cert was previously imported into the"); + System.out.println(" nss database, then -n can be used instead of -b "); + System.out.println(); + System.out.println("Options:"); + System.out.println(" -d Security database location (default: current directory)"); + System.out.println(" -h Security token name (default: internal)"); + System.out.println(" -p CMC enrollment passphrase (put in \"\" if containing spaces)"); + System.out.println(" Use either -b OR -n below"); + System.out.println(" -b PEM issuance protection certificate"); + System.out.println(" -n issuance protection certificate nickname"); + System.out.println("To store the base-64 secret data, the following options are required:"); + System.out.println(" -o Output file to store base-64 secret data"); + System.out.println(); + System.out.println(" -v, --verbose Run in verbose mode."); + System.out.println(" --help Show help message."); + System.out.println(); + } + + + public static void printError(String message) { + System.err.println("ERROR: " + message); + System.err.println("Try 'CMCSharedToken --help' for more information."); + } + + /* + * used for isVerificationMode only + */ + public static java.security.PrivateKey getPrivateKey(String tokenName, String nickname) + throws Exception { + + X509Certificate cert = getCertificate(tokenName, nickname); + if (cert != null) + System.out.println("getPrivateKey: got cert"); + + return CryptoManager.getInstance().findPrivKeyByCert(cert); + } + + public static X509Certificate getCertificate(String tokenName, + String nickname) throws Exception { + CryptoManager manager = CryptoManager.getInstance(); + CryptoToken token = CryptoUtil.getKeyStorageToken(tokenName); + + StringBuffer certname = new StringBuffer(); + + if (!token.equals(manager.getInternalKeyStorageToken())) { + certname.append(tokenName); + certname.append(":"); + } + certname.append(nickname); + try { + return manager.findCertByNickname(certname.toString()); + } catch (ObjectNotFoundException e) { + throw new IOException("Certificate not found"); + } + } + + public static void main(String args[]) throws Exception { + boolean isVerificationMode = false; // developer debugging only + + Options options = createOptions(); + CommandLine cmd = null; + + try { + CommandLineParser parser = new PosixParser(); + cmd = parser.parse(options, args); + + } catch (Exception e) { + printError(e.getMessage()); + System.exit(1); + } + + if (cmd.hasOption("help")) { + printHelp(); + System.exit(0); + } + + boolean verbose = cmd.hasOption("v"); + + String databaseDir = cmd.getOptionValue("d", "."); + String passphrase = cmd.getOptionValue("p"); + if (passphrase == null) { + printError("Missing passphrase"); + System.exit(1); + } + if (verbose) { + System.out.println("passphrase String = " + passphrase); + System.out.println("passphrase UTF-8 bytes = "); + System.out.println(Arrays.toString(passphrase.getBytes("UTF-8"))); + } + String tokenName = cmd.getOptionValue("h"); + + String issuanceProtCertFilename = cmd.getOptionValue("b"); + String issuanceProtCertNick = cmd.getOptionValue("n"); + String output = cmd.getOptionValue("o"); + + try { + CryptoManager.initialize(databaseDir); + + CryptoManager manager = CryptoManager.getInstance(); + + CryptoToken token = CryptoUtil.getKeyStorageToken(tokenName); + tokenName = token.getName(); + manager.setThreadToken(token); + X509Certificate issuanceProtCert = null; + if (issuanceProtCertFilename != null) { + if (verbose) System.out.println("Loading issuance protection certificate"); + String encoded = FileUtils.readFileToString(new File(issuanceProtCertFilename)); + encoded = Cert.normalizeCertStrAndReq(encoded); + encoded = Cert.stripBrackets(encoded); + byte[] issuanceProtCertData = Utils.base64decode(encoded); + + issuanceProtCert = manager.importCACertPackage(issuanceProtCertData); + if (verbose) System.out.println("issuance protection certificate imported"); + } else { + // must have issuance protection cert nickname if file not provided + if (verbose) System.out.println("Getting cert by nickname: " + issuanceProtCertNick); + if (issuanceProtCertNick == null) { + System.out.println("Invallid command: either nickname or PEM file must be provided for Issuance Protection Certificate"); + System.exit(1); + } + issuanceProtCert = getCertificate(tokenName, issuanceProtCertNick); + } + + EncryptionAlgorithm encryptAlgorithm = EncryptionAlgorithm.AES_128_CBC_PAD; + KeyWrapAlgorithm wrapAlgorithm = KeyWrapAlgorithm.RSA; + + if (verbose) System.out.println("Generating session key"); + SymmetricKey sessionKey = CryptoUtil.generateKey( + token, + KeyGenAlgorithm.AES, + 128, + null, + true); + + if (verbose) System.out.println("Encrypting passphrase"); + byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; + byte[] secret_data = CryptoUtil.encryptUsingSymmetricKey( + token, + sessionKey, + passphrase.getBytes("UTF-8"), + encryptAlgorithm, + new IVParameterSpec(iv)); + + if (verbose) System.out.println("Wrapping session key with issuance protection cert"); + byte[] issuanceProtWrappedSessionKey = CryptoUtil.wrapUsingPublicKey( + token, + issuanceProtCert.getPublicKey(), + sessionKey, + wrapAlgorithm); + + // final_data takes this format: + // SEQUENCE { + // encryptedSession OCTET STRING, + // encryptedPrivate OCTET STRING + // } + + DerOutputStream tmp = new DerOutputStream(); + + tmp.putOctetString(issuanceProtWrappedSessionKey); + tmp.putOctetString(secret_data); + DerOutputStream out = new DerOutputStream(); + out.write(DerValue.tag_Sequence, tmp); + + byte[] final_data = out.toByteArray(); + String final_data_b64 = Utils.base64encode(final_data); + if (final_data_b64 != null) { + System.out.println("\nEncrypted Secret Data:"); + System.out.println(final_data_b64); + } else + System.out.println("Failed to produce final data"); + + if (output != null) { + System.out.println("\nStoring Base64 secret data into " + output); + try (FileWriter fout = new FileWriter(output)) { + fout.write(final_data_b64); + } + } + + if (isVerificationMode) { // developer use only + PrivateKey wrappingKey = null; + if (issuanceProtCertNick != null) + wrappingKey = (org.mozilla.jss.crypto.PrivateKey) getPrivateKey(tokenName, issuanceProtCertNick); + else + wrappingKey = CryptoManager.getInstance().findPrivKeyByCert(issuanceProtCert); + + System.out.println("\nVerification begins..."); + byte[] wrapped_secret_data = Utils.base64decode(final_data_b64); + DerValue wrapped_val = new DerValue(wrapped_secret_data); + // val.tag == DerValue.tag_Sequence + DerInputStream wrapped_in = wrapped_val.data; + DerValue wrapped_dSession = wrapped_in.getDerValue(); + byte wrapped_session[] = wrapped_dSession.getOctetString(); + System.out.println("wrapped session key retrieved"); + DerValue wrapped_dPassphrase = wrapped_in.getDerValue(); + byte wrapped_passphrase[] = wrapped_dPassphrase.getOctetString(); + System.out.println("wrapped passphrase retrieved"); + + SymmetricKey ver_session = CryptoUtil.unwrap(token, SymmetricKey.AES, 128, SymmetricKey.Usage.UNWRAP, wrappingKey, wrapped_session, wrapAlgorithm); + byte[] ver_passphrase = CryptoUtil.decryptUsingSymmetricKey(token, new IVParameterSpec(iv), wrapped_passphrase, + ver_session, EncryptionAlgorithm.AES_128_CBC_PAD); + + String ver_spassphrase = new String(ver_passphrase, "UTF-8"); + + System.out.println("ver_passphrase String = " + ver_spassphrase); + System.out.println("ver_passphrase UTF-8 bytes = "); + System.out.println(Arrays.toString(ver_spassphrase.getBytes("UTF-8"))); + + if (ver_spassphrase.equals(passphrase)) + System.out.println("Verification success!"); + else + System.out.println("Verification failure! ver_spassphrase="+ ver_spassphrase); + } + + } catch (Exception e) { + if (verbose) e.printStackTrace(); + printError(e.getMessage()); + System.exit(1); + } + } +} diff --git a/base/java-tools/templates/CMakeLists.txt b/base/java-tools/templates/CMakeLists.txt index 1c422f4ab64..373ef9a3a05 100644 --- a/base/java-tools/templates/CMakeLists.txt +++ b/base/java-tools/templates/CMakeLists.txt @@ -9,6 +9,7 @@ set(PKI_COMMANDS CMCRequest CMCResponse CMCRevoke + CMCSharedToken CRMFPopClient ExtJoiner GenExtKeyUsage diff --git a/base/server/cms/src/com/netscape/cms/authentication/SharedSecret.java b/base/server/cms/src/com/netscape/cms/authentication/SharedSecret.java index 736110731ac..cf69975a633 100644 --- a/base/server/cms/src/com/netscape/cms/authentication/SharedSecret.java +++ b/base/server/cms/src/com/netscape/cms/authentication/SharedSecret.java @@ -12,33 +12,448 @@ // 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. +// (C) 2017 Red Hat, Inc. // All rights reserved. // --- END COPYRIGHT BLOCK --- package com.netscape.cms.authentication; import java.math.BigInteger; +// ldap java sdk +import java.util.Enumeration; +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.EncryptionAlgorithm; +import org.mozilla.jss.crypto.IVParameterSpec; +import org.mozilla.jss.crypto.KeyWrapAlgorithm; +import org.mozilla.jss.crypto.PrivateKey; +import org.mozilla.jss.crypto.SymmetricKey; import org.mozilla.jss.pkix.cmc.PKIData; +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authentication.AuthToken; +import com.netscape.certsrv.authentication.EInvalidCredentials; +import com.netscape.certsrv.authentication.IAuthCredentials; import com.netscape.certsrv.authentication.ISharedToken; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.IExtendedPluginInfo; +import com.netscape.certsrv.base.MetaInfo; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.dbs.certdb.ICertRecord; +import com.netscape.certsrv.dbs.certdb.ICertificateRepository; +import com.netscape.certsrv.ldap.ILdapConnFactory; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.cmsutil.crypto.CryptoUtil; +import com.netscape.cmsutil.util.Utils; -public class SharedSecret implements ISharedToken { +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPSearchResults; +import netscape.ldap.LDAPv2; +import netscape.security.util.DerInputStream; +import netscape.security.util.DerValue; + +/** + * SharedSecret provides methods to retrieve shared secrets between users and + * the server. It is primarily developed to support CMC Shared Secret-based + * authentication for enrollment and revocation, but does not + * preclude usages that conform to the same mechanism and storage format. + * + * @author cfu + * + */ +public class SharedSecret extends DirBasedAuthentication + implements ISharedToken { + /* + * required credentials to authenticate. Though for this + * special impl it will be unused. + */ + public static final String CRED_ShrTok = "shrTok"; + protected static String[] mRequiredCreds = { CRED_ShrTok}; + + protected static final String PROP_DNPATTERN = "dnpattern"; + protected static final String PROP_LDAPSTRINGATTRS = "ldapStringAttributes"; + protected static final String PROP_LDAPBYTEATTRS = "ldapByteAttributes"; + protected static final String PROP_LDAP_BOUND_CONN = "ldapBoundConn"; + protected static final String PROP_LDAP_BOUND_TAG = "ldapauth.bindPWPrompt"; + + //public static final String PROP_REMOVE_SharedToken = "removeShrTok"; + public static final String PROP_SharedToken_ATTR = "shrTokAttr"; + + //public static final boolean DEF_REMOVE_SharedToken = false; + public static final String DEF_SharedToken_ATTR = "shrTok"; + public KeyWrapAlgorithm wrapAlgorithm = KeyWrapAlgorithm.RSA; + + /* Holds configuration parameters accepted by this implementation. + * This list is passed to the configuration console so configuration + * for instances of this implementation can be configured through the + * console. + */ + protected static String[] mConfigParams = + new String[] { //PROP_REMOVE_SharedToken, + PROP_SharedToken_ATTR, + PROP_DNPATTERN, + PROP_LDAPSTRINGATTRS, + PROP_LDAPBYTEATTRS, + "ldap.ldapconn.host", + "ldap.ldapconn.port", + "ldap.ldapconn.secureConn", + "ldap.ldapconn.version", + "ldap.ldapauth.bindDN", + "ldap.ldapauth.bindPWPrompt", + "ldap.ldapauth.clientCertNickname", + "ldap.ldapauth.authtype", + "ldap.basedn", + "ldap.minConns", + "ldap.maxConns", + }; + + static { + //mExtendedPluginInfo.add( + //PROP_REMOVE_SharedToken + ";boolean;SEE DOCUMENTATION for shared token removal"); + mExtendedPluginInfo.add( + PROP_SharedToken_ATTR + ";string;directory attribute to use for pin (default 'pin')"); + mExtendedPluginInfo.add( + "ldap.ldapauth.bindDN;string;DN to bind. " + + "For example 'CN=SharedToken User'"); + mExtendedPluginInfo.add( + "ldap.ldapauth.bindPWPrompt;password;Enter password used to bind as " + + "the above user"); + mExtendedPluginInfo.add( + "ldap.ldapauth.clientCertNickname;string;If you want to use " + + "SSL client auth to the directory, set the client " + + "cert nickname here"); + mExtendedPluginInfo.add( + "ldap.ldapauth.authtype;choice(BasicAuth,SslClientAuth),required;" + + "How to bind to the directory (for pin removal only)"); + mExtendedPluginInfo.add(IExtendedPluginInfo.HELP_TEXT + + ";Authenticate the username, password and pin provided " + + "by the user against an LDAP directory. Works with the " + + "Dir/ShrTok Based Enrollment HTML form"); + mExtendedPluginInfo.add(IExtendedPluginInfo.HELP_TOKEN + + ";configuration-authrules-uidpwdpindirauth"); + + } + + //protected boolean mRemoveShrTok = DEF_REMOVE_SharedToken; + protected String mShrTokAttr = DEF_SharedToken_ATTR; + private ILdapConnFactory shrTokLdapFactory = null; + private IConfigStore shrTokLdapConfigStore = null; + + private PrivateKey issuanceProtPrivKey = null; + protected CryptoManager cm = null; + protected CryptoToken tmpToken = null; + protected byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; + EncryptionAlgorithm encryptAlgorithm = EncryptionAlgorithm.AES_128_CBC_PAD; + ICertificateRepository certRepository = null; public SharedSecret() { + super(); + } + + public void init(String name, String implName, IConfigStore config) + throws EBaseException { + String method = "SharedSecret.init: "; + String msg = ""; + CMS.debug(method + " begins."); + super.init(name, implName, config); + //TODO later: + //mRemoveShrTok = + // config.getBoolean(PROP_REMOVE_SharedToken, DEF_REMOVE_SharedToken); + mShrTokAttr = + config.getString(PROP_SharedToken_ATTR, DEF_SharedToken_ATTR); + if (mShrTokAttr == null) { + msg = method + "shrTokAttr null"; + CMS.debug(msg); + throw new EBaseException(msg); + } + if (mShrTokAttr.equals("")) { + mShrTokAttr = DEF_SharedToken_ATTR; + } + + initLdapConn(config); + + ICertificateAuthority authority = + (ICertificateAuthority) CMS.getSubsystem(CMS.SUBSYSTEM_CA); + issuanceProtPrivKey = authority.getIssuanceProtPrivKey(); + if (issuanceProtPrivKey != null) + CMS.debug(method + "got issuanceProtPrivKey"); + else { + msg = method + "issuanceProtPrivKey null"; + CMS.debug(msg); + throw new EBaseException(msg); + } + certRepository = authority.getCertificateRepository(); + if (certRepository == null) { + msg = method + "certRepository null"; + CMS.debug(msg); + throw new EBaseException(msg); + } + + try { + cm = CryptoManager.getInstance(); + } catch (Exception e) { + msg = method + e.toString(); + CMS.debug(msg); + throw new EBaseException(msg); + } + tmpToken = cm.getInternalKeyStorageToken(); + if (tmpToken == null) { + msg = method + "tmpToken null"; + CMS.debug(msg); + throw new EBaseException(msg); + } + + CMS.debug(method + " ends."); + } + + /** + * initLadapConn initializes ldap connection for shared token based + * CMC enrollment. + */ + public void initLdapConn(IConfigStore config) + throws EBaseException { + String method = "SharedSecret.initLdapConn"; + String msg = ""; + + shrTokLdapConfigStore = config.getSubStore("ldap"); + if (shrTokLdapConfigStore == null) { + msg = method + "config substore ldap null"; + CMS.debug(msg); + throw new EBaseException(msg); + } + shrTokLdapFactory = CMS.getLdapBoundConnFactory("SharedSecret"); + if (shrTokLdapFactory == null) { + msg = method + "CMS.getLdapBoundConnFactory returned null for SharedSecret"; + CMS.debug(msg); + throw new EBaseException(msg); + } + shrTokLdapFactory.init(shrTokLdapConfigStore); + } + + /** + * getSharedToken(String identification) provides + * support for id_cmc_identification shared secret based enrollment + * + * Note: caller should clear the memory for the returned token + * after each use + */ + public String getSharedToken(String identification) + throws EBaseException { + String method = "SharedSecret.getSharedToken(String identification): "; + String msg = ""; + CMS.debug(method + "begins."); + + LDAPConnection shrTokLdapConnection = null; + LDAPSearchResults res = null; + LDAPEntry entry = null; + + try { + CMS.debug(method + + "searching for identification =" + + identification + "; mShrTokAttr =" + mShrTokAttr); + // get shared token + shrTokLdapConnection = shrTokLdapFactory.getConn(); + if (shrTokLdapConnection == null) { + msg = method + "shrTokLdapConnection is null!!"; + CMS.debug(msg); + throw new EBaseException(msg); + } + + // get user dn. + String userdn = null; + res = shrTokLdapConnection.search(mBaseDN, + LDAPv2.SCOPE_SUB, "(uid=" + identification + ")", null, false); + if (res == null) { + msg = method + "shrTokLdapConnection.search returns null!!"; + CMS.debug(msg); + throw new EBaseException(msg); + } + + if (res.hasMoreElements()) { + entry = (LDAPEntry) res.nextElement(); + + userdn = entry.getDN(); + } else { + log(ILogger.LL_SECURITY, CMS.getLogMessage("CMS_AUTH_USER_NOT_EXIST", identification)); + msg = method + "ldap search result contains nothing"; + CMS.debug(msg); + throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + if (userdn == null) { + msg = method + "ldap entry found userdn null!!"; + CMS.debug(msg); + throw new EBaseException(msg); + } + + res = shrTokLdapConnection.search(userdn, LDAPv2.SCOPE_BASE, + "(objectclass=*)", new String[] { mShrTokAttr }, false); + if (res != null && res.hasMoreElements()) { + entry = (LDAPEntry) res.nextElement(); + } else { + msg = method + "no entry returned for " + identification; + CMS.debug(msg); + throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + + LDAPAttribute shrTokAttr = entry.getAttribute(mShrTokAttr); + + if (shrTokAttr == null) { + CMS.debug(method + "no shared token attribute found"); + throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + + @SuppressWarnings("unchecked") + Enumeration shrTokValues = shrTokAttr.getByteValues(); + + if (!shrTokValues.hasMoreElements()) { + CMS.debug(method + "no shared token attribute values found"); + throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + byte[] entryShrTok = shrTokValues.nextElement(); + if (entryShrTok == null) { + CMS.debug(method + "no shared token value found"); + throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); + } + CMS.debug(method + " got entryShrTok"); + + String shrSecret = decryptShrTokData(new String(entryShrTok)); + CMS.debug(method + "returning"); + return shrSecret; + } catch (Exception e) { + CMS.debug(method + " exception: " + e.toString()); + throw new EBaseException(method + e.toString()); + } finally { + if (shrTokLdapConnection != null) + shrTokLdapFactory.returnConn(shrTokLdapConnection); + } + } + + /** + * decryptShrTokData decrypts data with the following format: + * SEQUENCE { + * encryptedSession OCTET STRING, + * encryptedPrivate OCTET STRING + * } + * @param data_s + * @return + */ + private String decryptShrTokData(String data_s) { + String method = "SharedSecret.decryptShrTokData: "; + String msg = ""; + try { + byte[] wrapped_secret_data = Utils.base64decode(data_s); + DerValue wrapped_val = new DerValue(wrapped_secret_data); + // val.tag == DerValue.tag_Sequence + DerInputStream wrapped_in = wrapped_val.data; + DerValue wrapped_dSession = wrapped_in.getDerValue(); + byte wrapped_session[] = wrapped_dSession.getOctetString(); + CMS.debug(method + "wrapped session key retrieved"); + DerValue wrapped_dPassphrase = wrapped_in.getDerValue(); + byte wrapped_passphrase[] = wrapped_dPassphrase.getOctetString(); + CMS.debug(method + "wrapped passphrase retrieved"); + + SymmetricKey ver_session = CryptoUtil.unwrap(tmpToken, SymmetricKey.AES, 128, SymmetricKey.Usage.UNWRAP, + issuanceProtPrivKey, wrapped_session, wrapAlgorithm); + byte[] ver_passphrase = CryptoUtil.decryptUsingSymmetricKey(tmpToken, new IVParameterSpec(iv), + wrapped_passphrase, + ver_session, EncryptionAlgorithm.AES_128_CBC_PAD); + + String ver_spassphrase = new String(ver_passphrase, "UTF-8"); + return ver_spassphrase; + } catch (Exception e) { + CMS.debug(method + e.toString()); + return null; + } } - // support for id_cmc_identification - public String getSharedToken(String identification) { - return "testing"; + /** + * unsupported + */ + public String getSharedToken(PKIData cmcdata) + throws EBaseException { + String method = "SharedSecret.getSharedToken(PKIData cmcdata): "; + String msg = ""; + throw new EBaseException(method + msg); } - public String getSharedToken(PKIData cmcdata) { - return "testing"; + /** + * getSharedToken(BigInteger serial) retrieves the shared secret data + * from CA's internal certificate db based on serial number to revoke shared + * secret based revocation + * Note that unlike the shared token attribute for enrollment, the metaInfo + * attribute for shared token in revocatoiin is not configurable. + * + * Note: caller should clear the memory for the returned token + * after each use + */ + public String getSharedToken(BigInteger serial) + throws EBaseException { + String method = "SharedSecret.getSharedToken(BigInteger serial): "; + String msg = ""; + + ICertRecord record = null; + try { + record = certRepository.readCertificateRecord(serial); + } catch (EBaseException ee) { + CMS.debug(method + "Exception: " + ee.toString()); + msg = method + "cert record not found"; + CMS.debug(msg); + throw ee; + } + + MetaInfo metaInfo = (MetaInfo) record.get(ICertRecord.ATTR_META_INFO); + if (metaInfo == null) { + msg = "cert record metaInfo not found"; + CMS.debug(method + msg); + throw new EBaseException(method + msg); + } + String shrTok_s = (String) metaInfo.get(ICertRecord.META_REV_SHRTOK); + if (shrTok_s == null) { + msg = "shrTok not found in metaInfo"; + CMS.debug(method + msg); + throw new EBaseException(method + msg); + } + + String shrSecret = decryptShrTokData(shrTok_s); + CMS.debug(method + "returning"); + return shrSecret; } - public String getSharedToken(BigInteger serial) { - return "testing"; + /** + * unsupported + * This is an unconventional authentication plugin implementation that + * does not support authenticate() + */ + protected String authenticate(LDAPConnection conn, + IAuthCredentials authCreds, + AuthToken token) + throws EBaseException { + String method = "SharedSecret:authenticate: "; + //unused + throw new EBaseException(method + " unsupported to be called this way."); } - + + /** + * Returns a list of configuration parameter names. + * The list is passed to the configuration console so instances of + * this implementation can be configured through the console. + * + * @return String array of configuration parameter names. + */ + public String[] getConfigParams() { + return (mConfigParams); + } + + /** + * Returns array of required credentials for this authentication manager. + * + * @return Array of required credentials. + */ + public String[] getRequiredCreds() { + return mRequiredCreds; + } + } diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java index edefb2083a1..2168efb8efb 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java @@ -102,6 +102,7 @@ import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.IAuthManager; +import com.netscape.certsrv.authentication.IAuthSubsystem; import com.netscape.certsrv.authentication.IAuthToken; import com.netscape.certsrv.authentication.ISharedToken; import com.netscape.certsrv.authority.IAuthority; @@ -832,7 +833,7 @@ public TaggedRequest[] parseCMC(Locale locale, String certreq, boolean donePOI) ident_s = (UTF8String) (ASN1Util.decode(UTF8String.getTemplate(), ASN1Util.encode(ident.elementAt(0)))); } - if (ident == null && ident_s == null) { + if (ident_s == null) { msg = " id_cmc_identification contains invalid content"; CMS.debug(method + msg); SEQUENCE bpids = getRequestBpids(reqSeq); @@ -1358,13 +1359,18 @@ private boolean verifyPOPLinkWitness( } boolean sharedSecretFound = true; - String configName = "cmc.sharedSecret.class"; + String configName = "SharedToken"; String sharedSecret = null; - ISharedToken tokenClass = CMS.getSharedTokenClass(configName); - if (tokenClass == null) { - CMS.debug(method + " Failed to retrieve shared secret plugin class"); - sharedSecretFound = false; - } else { + try { + IAuthSubsystem authSS = (IAuthSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTH); + + IAuthManager sharedTokenAuth = authSS.getAuthManager(configName); + if (sharedTokenAuth == null) { + CMS.debug(method + " Failed to retrieve shared secret authentication plugin class"); + sharedSecretFound = false; + } + ISharedToken tokenClass = (ISharedToken) sharedTokenAuth; + if (ident_string != null) { sharedSecret = tokenClass.getSharedToken(ident_string); } else { @@ -1372,6 +1378,10 @@ private boolean verifyPOPLinkWitness( } if (sharedSecret == null) sharedSecretFound = false; + + } catch (Exception e) { + CMS.debug(e); + return false; } INTEGER reqId = null; @@ -1631,42 +1641,43 @@ private boolean verifyIdentityProofV2( return false; } - String configName = "cmc.sharedSecret.class"; - ISharedToken tokenClass = CMS.getSharedTokenClass(configName); + try { + String configName = "SharedToken"; + IAuthSubsystem authSS = (IAuthSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTH); - if (tokenClass == null) { - msg = " Failed to retrieve shared secret plugin class"; - CMS.debug(method + msg); - auditMessage = CMS.getLogMessage( - AuditEvent.CMC_PROOF_OF_IDENTIFICATION, - auditAttemptedCred, - ILogger.FAILURE, - method + msg); - audit(auditMessage); - return false; - } + IAuthManager sharedTokenAuth = authSS.getAuthManager(configName); + if (sharedTokenAuth == null) { + msg = " Failed to retrieve shared secret authentication plugin class"; + CMS.debug(method + msg); + auditMessage = CMS.getLogMessage( + AuditEvent.CMC_PROOF_OF_IDENTIFICATION, + auditAttemptedCred, + ILogger.FAILURE, + method + msg); + audit(auditMessage); + return false; + } + ISharedToken tokenClass = (ISharedToken) sharedTokenAuth; - String token = null; - if (ident_string != null) { - auditAttemptedCred = ident_string; - token = tokenClass.getSharedToken(ident_string); - } else - token = tokenClass.getSharedToken(mCMCData); + String token = null; + if (ident_string != null) { + auditAttemptedCred = ident_string; + token = tokenClass.getSharedToken(ident_string); + } else + token = tokenClass.getSharedToken(mCMCData); - if (token == null) { - msg = " Failed to retrieve shared secret"; - CMS.debug(method + msg); - auditMessage = CMS.getLogMessage( - AuditEvent.CMC_PROOF_OF_IDENTIFICATION, - auditAttemptedCred, - ILogger.FAILURE, - method + msg); - audit(auditMessage); - return false; - } + if (token == null) { + msg = " Failed to retrieve shared secret"; + CMS.debug(method + msg); + auditMessage = CMS.getLogMessage( + AuditEvent.CMC_PROOF_OF_IDENTIFICATION, + auditAttemptedCred, + ILogger.FAILURE, + method + msg); + audit(auditMessage); + return false; + } - // CMS.debug(method + "Shared Secret returned by tokenClass:" + token); - try { IdentityProofV2 idV2val = (IdentityProofV2) (ASN1Util.decode(IdentityProofV2.getTemplate(), ASN1Util.encode(vals.elementAt(0)))); @@ -1695,11 +1706,10 @@ private boolean verifyIdentityProofV2( String auditSubjectID = null; if (verified) { - auditSubjectID = (String) - sessionContext.get(SessionContext.USER_ID); - CMS.debug(method + "current auditSubjectID was:"+ auditSubjectID); + auditSubjectID = (String) sessionContext.get(SessionContext.USER_ID); + CMS.debug(method + "current auditSubjectID was:" + auditSubjectID); CMS.debug(method + "identity verified. Updating auditSubjectID"); - CMS.debug(method + "updated auditSubjectID is:"+ ident_string); + CMS.debug(method + "updated auditSubjectID is:" + ident_string); auditSubjectID = ident_string; sessionContext.put(SessionContext.USER_ID, auditSubjectID); @@ -1740,18 +1750,22 @@ private boolean verifyIdentityProof( String configName = "cmc.sharedSecret.class"; ISharedToken tokenClass = CMS.getSharedTokenClass(configName); if (tokenClass == null) { - CMS.debug(method + " Failed to retrieve shared secret plugin class"); + CMS.debug(method + " Failed to retrieve shared secret authentication plugin class"); return false; } - String token = tokenClass.getSharedToken(mCMCData); OCTET_STRING ostr = null; + String token = null; try { + token = tokenClass.getSharedToken(mCMCData); ostr = (OCTET_STRING) (ASN1Util.decode(OCTET_STRING.getTemplate(), ASN1Util.encode(vals.elementAt(0)))); } catch (InvalidBERException e) { CMS.debug(method + "Failed to decode the byte value."); return false; + } catch (Exception e) { + CMS.debug(method + "exception: " + e.toString()); + return false; } byte[] b = ostr.toByteArray(); byte[] text = ASN1Util.encode(reqSeq); diff --git a/base/server/cms/src/com/netscape/cms/servlet/common/CMCOutputTemplate.java b/base/server/cms/src/com/netscape/cms/servlet/common/CMCOutputTemplate.java index 3c133d88d3a..7a2e360edfe 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/common/CMCOutputTemplate.java +++ b/base/server/cms/src/com/netscape/cms/servlet/common/CMCOutputTemplate.java @@ -70,6 +70,8 @@ import org.mozilla.jss.pkix.primitive.Name; import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authentication.IAuthManager; +import com.netscape.certsrv.authentication.IAuthSubsystem; import com.netscape.certsrv.authentication.ISharedToken; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.SessionContext; @@ -982,6 +984,7 @@ private int processRevokeRequestControl(TaggedAttribute attr, String auditSerialNumber = null; String auditReasonNum = null; RequestStatus auditApprovalStatus = RequestStatus.REJECTED; + String auditReqID = null; if (attr != null) { INTEGER attrbpid = attr.getBodyPartID(); @@ -1069,13 +1072,13 @@ private int processRevokeRequestControl(TaggedAttribute attr, revoke = true; } else { //use shared secret; request unsigned CMS.debug(method + "checking shared secret"); - // check shared secret - //TODO: remember to provide one-time-use when working - // on shared token - ISharedToken tokenClass = - CMS.getSharedTokenClass("cmc.revokeCert.sharedSecret.class"); - if (tokenClass == null) { - CMS.debug(method + " Failed to retrieve shared secret plugin class"); + + String configName = "SharedToken"; + IAuthSubsystem authSS = (IAuthSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTH); + + IAuthManager sharedTokenAuth = authSS.getAuthManager(configName); + if (sharedTokenAuth == null) { + CMS.debug(method + " Failed to retrieve shared secret authentication plugin class"); OtherInfo otherInfo = new OtherInfo(OtherInfo.FAIL, new INTEGER(OtherInfo.INTERNAL_CA_ERROR), null, null); SEQUENCE failed_bpids = new SEQUENCE(); @@ -1087,9 +1090,9 @@ private int processRevokeRequestControl(TaggedAttribute attr, controlSeq.addElement(tagattr); return bpid; } + ISharedToken tokenClass = (ISharedToken) sharedTokenAuth; - String sharedSecret = - sharedSecret = tokenClass.getSharedToken(revokeSerial); + String sharedSecret = tokenClass.getSharedToken(revokeSerial); if (sharedSecret == null) { CMS.debug("CMCOutputTemplate: shared secret not found."); @@ -1127,7 +1130,7 @@ private int processRevokeRequestControl(TaggedAttribute attr, audit(new CertStatusChangeRequestProcessedEvent( auditSubjectID, ILogger.FAILURE, - auditRequesterID, + auditReqID, auditSerialNumber, auditRequestType, auditReasonNum, @@ -1161,7 +1164,7 @@ record = repository.readCertificateRecord(revokeSerial); } if (record.getStatus().equals(ICertRecord.STATUS_REVOKED)) { - CMS.debug("CMCOutputTemplate: The certificate is already revoked."); + CMS.debug("CMCOutputTemplate: The certificate is already revoked:" + auditSerialNumber); SEQUENCE success_bpids = new SEQUENCE(); success_bpids.addElement(attrbpid); cmcStatusInfoV2 = new CMCStatusInfoV2(CMCStatusInfoV2.SUCCESS, @@ -1172,11 +1175,10 @@ record = repository.readCertificateRecord(revokeSerial); controlSeq.addElement(tagattr); return bpid; } - X509CertImpl impl = record.getCertificate(); X500Name certPrincipal = (X500Name) impl.getSubjectDN(); - auditSubjectID = certPrincipal.getCommonName(); + auditSubjectID = certPrincipal.toString(); // in case of user-signed request, check if signer // principal matches that of the revoking cert @@ -1198,7 +1200,7 @@ record = repository.readCertificateRecord(revokeSerial); audit(new CertStatusChangeRequestProcessedEvent( auditSubjectID, ILogger.FAILURE, - auditRequesterID, + auditReqID, auditSerialNumber, auditRequestType, auditReasonNum, @@ -1230,6 +1232,7 @@ record = repository.readCertificateRecord(revokeSerial); revCertImpls[0] = revCertImpl; IRequestQueue queue = ca.getRequestQueue(); IRequest revReq = queue.newRequest(IRequest.REVOCATION_REQUEST); + auditReqID = revReq.getRequestId().toString(); revReq.setExtData(IRequest.CERT_INFO, revCertImpls); revReq.setExtData(IRequest.REVOKED_REASON, Integer.valueOf(reason.toInt())); @@ -1259,7 +1262,7 @@ record = repository.readCertificateRecord(revokeSerial); audit(new CertStatusChangeRequestProcessedEvent( auditSubjectID, ILogger.FAILURE, - auditRequesterID, + auditReqID, auditSerialNumber, auditRequestType, auditReasonNum, @@ -1291,7 +1294,7 @@ record = repository.readCertificateRecord(revokeSerial); audit(new CertStatusChangeRequestProcessedEvent( auditSubjectID, ILogger.SUCCESS, - auditRequesterID, + auditReqID, auditSerialNumber, auditRequestType, auditReasonNum, @@ -1310,7 +1313,7 @@ record = repository.readCertificateRecord(revokeSerial); audit(new CertStatusChangeRequestProcessedEvent( auditSubjectID, ILogger.FAILURE, - auditRequesterID, + auditReqID, auditSerialNumber, auditRequestType, auditReasonNum, diff --git a/specs/pki-core.spec b/specs/pki-core.spec index ffe001abecd..0e6e7fb7ee7 100644 --- a/specs/pki-core.spec +++ b/specs/pki-core.spec @@ -1271,6 +1271,7 @@ fi %{_bindir}/CMCRequest %{_bindir}/CMCResponse %{_bindir}/CMCRevoke +%{_bindir}/CMCSharedToken %{_bindir}/CRMFPopClient %{_bindir}/DRMTool %{_bindir}/ExtJoiner