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