/*************************************************************************
* *
* EJBCA: The OpenSource Certificate Authority *
* *
* This software is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
package org.ejbca.ui.cli.batch;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import org.ejbca.core.model.AlgorithmConstants;
import org.ejbca.core.model.InternalResources;
import org.ejbca.core.model.SecConst;
import org.ejbca.core.model.keyrecovery.KeyRecoveryData;
import org.ejbca.core.model.log.Admin;
import org.ejbca.core.model.ra.UserAdminConstants;
import org.ejbca.core.model.ra.UserDataConstants;
import org.ejbca.core.model.ra.UserDataVO;
import org.ejbca.ui.cli.BaseCommand;
import org.ejbca.ui.cli.ErrorAdminCommandException;
import org.ejbca.util.CertTools;
import org.ejbca.util.CryptoProviderTools;
import org.ejbca.util.keystore.KeyTools;
import org.ejbca.util.keystore.P12toPEM;
/**
* This class generates keys and request certificates for all users with status NEW. The result is
* generated PKCS12, JKS or PEM-files.
*
* @version $Id: BatchMakeP12.java 11818 2011-04-26 11:14:44Z jeklund $
*/
public class BatchMakeP12 extends BaseCommand {
/** Internal localization of logs and errors */
private static final InternalResources intres = InternalResources.getInstance();
BatchToolProperties props = new BatchToolProperties();
/**
* Where created P12-files are stored, default p12
*/
private String mainStoreDir = "";
private final Admin admin = new Admin(Admin.TYPE_BATCHCOMMANDLINE_USER);
private Boolean usekeyrecovery = null;
public String getMainCommand() {
return null;
}
public String getSubCommand() {
return "batch";
}
public String getDescription() {
return "Generate keys and certificates for all users with status NEW";
}
public void execute(String[] args) throws ErrorAdminCommandException {
try {
String username = null;
String directory = getHomeDir() + "p12";
for (int i = 1; i < args.length; i++) {
if ("-?".equalsIgnoreCase(args[i]) || "--help".equalsIgnoreCase(args[i])) {
getLogger().info("Description: " + getDescription());
getLogger().info("Usage: " + getCommand() + " [username] [-dir directory]");
getLogger().info(" username: the name of the user to generate the key.");
getLogger().info(" If omitted, keys will be generated for all users with status NEW or FAILED");
getLogger().info(" directory: the name of the directory to store the keys to");
System.exit(1); // NOPMD
} else if ("-dir".equalsIgnoreCase(args[i])) {
directory = args[++i];
} else {
username = args[i];
}
}
if (username == null) {
getLogger().info("Use '" + getSubCommand() + " --help' for additional options.");
}
// Bouncy Castle security provider
CryptoProviderTools.installBCProviderIfNotAvailable();
// Create subdirectory 'p12' if it does not exist
File dir = new File(directory).getCanonicalFile();
dir.mkdir();
setMainStoreDir(directory);
String iMsg = intres.getLocalizedMessage("batch.generateindir", dir);
getLogger().info(iMsg);
if (username != null) {
createUser(username);
} else {
// Make P12 for all NEW users in local DB
createAllNew();
// Make P12 for all FAILED users in local DB
createAllFailed();
// Make P12 for all KEYRECOVERABLE users in local DB
createAllKeyRecover();
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1); // NOPMD
}
}
protected Admin getAdmin() {
return admin;
}
private boolean getUseKeyRecovery() {
if (usekeyrecovery == null) {
usekeyrecovery = ejb.getGlobalConfigurationSession().getCachedGlobalConfiguration(getAdmin()).getEnableKeyRecovery();
}
return usekeyrecovery;
}
/**
* Gets full CA-certificate chain.
*
* @return Certificate[]
*/
private Certificate[] getCACertChain(int caid) {
getLogger().trace(">getCACertChain()");
Certificate[] chain = (Certificate[]) ejb.getSignSession().getCertificateChain(getAdmin(), caid).toArray(new Certificate[0]);
getLogger().trace("<getCACertChain()");
return chain;
}
/**
* Sets the location where generated P12-files will be stored, full name
* will be: mainStoreDir/username.p12.
*
* @param dir
* existing directory
*/
public void setMainStoreDir(String dir) {
mainStoreDir = dir;
}
/**
* Stores keystore.
*
* @param ks
* KeyStore
* @param username
* username, the owner of the keystore
* @param kspassword
* the password used to protect the peystore
* @param createJKS
* if a jks should be created
* @param createPEM
* if pem files should be created
* @throws IOException
* if directory to store keystore cannot be created
*/
private void storeKeyStore(KeyStore ks, String username, String kspassword, boolean createJKS, boolean createPEM) throws IOException, KeyStoreException,
UnrecoverableKeyException, NoSuchAlgorithmException, NoSuchProviderException, CertificateException {
if (getLogger().isTraceEnabled()) {
getLogger().trace(">storeKeyStore: ks=" + ks.toString() + ", username=" + username);
}
// Where to store it?
if (mainStoreDir == null) {
throw new IOException("Can't find directory to store keystore in.");
}
if (!new File(mainStoreDir).exists()) {
new File(mainStoreDir).mkdir();
getLogger().info("Directory '" + mainStoreDir + "' did not exist and was created.");
}
String keyStoreFilename = mainStoreDir + "/" + username;
if (createJKS) {
keyStoreFilename += ".jks";
} else {
keyStoreFilename += ".p12";
}
// If we should also create PEM-files, do that
if (createPEM) {
String PEMfilename = mainStoreDir + "/pem";
P12toPEM p12topem = new P12toPEM(ks, kspassword, true);
p12topem.setExportPath(PEMfilename);
p12topem.createPEM();
} else {
FileOutputStream os = new FileOutputStream(keyStoreFilename);
ks.store(os, kspassword.toCharArray());
}
getLogger().debug("Keystore stored in " + keyStoreFilename);
if (getLogger().isTraceEnabled()) {
getLogger().trace("<storeKeyStore: ks=" + ks.toString() + ", username=" + username);
}
}
/**
* Creates files for a user, sends request to CA, receives reply and creates
* P12.
*
* @param username
* username
* @param password
* user's password
* @param id
* of CA used to issue the keystore certificates
* @param rsaKeys
* a previously generated RSA keypair
* @param createJKS
* if a jks should be created
* @param createPEM
* if pem files should be created
* @param savekeys
* if generated keys should be saved in db (key recovery)
* @param orgCert
* if an original key recovered cert should be reused, null
* indicates generate new cert.
* @throws Exception
* if the certificate is not an X509 certificate
* @throws Exception
* if the CA-certificate is corrupt
* @throws Exception
* if verification of certificate or CA-cert fails
* @throws Exception
* if keyfile (generated by ourselves) is corrupt
*/
private void createUser(String username, String password, int caid, KeyPair rsaKeys, boolean createJKS, boolean createPEM, boolean savekeys,
X509Certificate orgCert) throws Exception {
if (getLogger().isTraceEnabled()) {
getLogger().trace(">createUser: username=" + username);
}
X509Certificate cert = null;
if (orgCert != null) {
cert = orgCert;
boolean finishUser = ejb.getCAAdminSession().getCAInfo(getAdmin(), caid).getFinishUser();
if (finishUser) {
UserDataVO userdata = ejb.getUserAdminSession().findUser(admin, username);
ejb.getAuthenticationSession().finishUser(userdata);
}
} else {
// Create self signed certificate, because ECDSA keys are not
// serializable
String sigAlg = AlgorithmConstants.SIGALG_SHA1_WITH_RSA;
if (props.getKeyAlg().equals("ECDSA")) {
sigAlg = AlgorithmConstants.SIGALG_SHA256_WITH_ECDSA;
} else if (props.getKeyAlg().equals("DSA")) {
sigAlg = AlgorithmConstants.SIGALG_SHA1_WITH_DSA;
}
X509Certificate selfcert = CertTools.genSelfCert("CN=selfsigned", 1, null, rsaKeys.getPrivate(), rsaKeys.getPublic(), sigAlg, false);
cert = (X509Certificate) ejb.getSignSession().createCertificate(getAdmin(), username, password, selfcert);
}
// System.out.println("issuer " + CertTools.getIssuerDN(cert) + ", " +
// cert.getClass().getName());
// Make a certificate chain from the certificate and the CA-certificate
Certificate[] cachain = getCACertChain(caid);
// Verify CA-certificate
if (CertTools.isSelfSigned((X509Certificate) cachain[cachain.length - 1])) {
try {
// Make sure we have BC certs, otherwise SHA256WithRSAAndMGF1
// will not verify (at least not as of jdk6)
Certificate cacert = CertTools.getCertfromByteArray(cachain[cachain.length - 1].getEncoded());
cacert.verify(cacert.getPublicKey());
// cachain[cachain.length - 1].verify(cachain[cachain.length -
// 1].getPublicKey());
} catch (GeneralSecurityException se) {
String errMsg = intres.getLocalizedMessage("batch.errorrootnotverify");
throw new Exception(errMsg);
}
} else {
String errMsg = intres.getLocalizedMessage("batch.errorrootnotselfsigned");
throw new Exception(errMsg);
}
// Verify that the user-certificate is signed by our CA
try {
// Make sure we have BC certs, otherwise SHA256WithRSAAndMGF1 will
// not verify (at least not as of jdk6)
Certificate cacert = CertTools.getCertfromByteArray(cachain[0].getEncoded());
Certificate usercert = CertTools.getCertfromByteArray(cert.getEncoded());
usercert.verify(cacert.getPublicKey());
} catch (GeneralSecurityException se) {
String errMsg = intres.getLocalizedMessage("batch.errorgennotverify");
throw new Exception(errMsg);
}
if (getUseKeyRecovery() && savekeys) {
// Save generated keys to database.
ejb.getKeyRecoverySession().addKeyRecoveryData(getAdmin(), cert, username, rsaKeys);
}
// Use CN if as alias in the keystore, if CN is not present use username
String alias = CertTools.getPartFromDN(CertTools.getSubjectDN(cert), "CN");
if (alias == null) {
alias = username;
}
// Store keys and certificates in keystore.
KeyStore ks = null;
if (createJKS) {
ks = KeyTools.createJKS(alias, rsaKeys.getPrivate(), password, cert, cachain);
} else {
ks = KeyTools.createP12(alias, rsaKeys.getPrivate(), cert, cachain);
}
storeKeyStore(ks, username, password, createJKS, createPEM);
String iMsg = intres.getLocalizedMessage("batch.createkeystore", username);
getLogger().info(iMsg);
if (getLogger().isTraceEnabled()) {
getLogger().trace("<createUser: username=" + username);
}
}
/**
* Recovers or generates new keys for the user and generates keystore
*
* @param data
* user data for user
* @param createJKS
* if a jks should be created
* @param createPEM
* if pem files should be created
* @param keyrecoverflag
* if we should try to revoer already existing keys
* @throws Exception
* If something goes wrong...
*/
private void processUser(UserDataVO data, boolean createJKS, boolean createPEM, boolean keyrecoverflag) throws Exception {
KeyPair rsaKeys = null;
X509Certificate orgCert = null;
if (getUseKeyRecovery() && keyrecoverflag) {
boolean reusecertificate = ejb.getEndEntityProfileSession().getEndEntityProfile(getAdmin(), data.getEndEntityProfileId()).getReUseKeyRecoveredCertificate();
// Recover Keys
KeyRecoveryData recoveryData = ejb.getKeyRecoverySession().keyRecovery(getAdmin(), data.getUsername(), data.getEndEntityProfileId());
if (reusecertificate) {
ejb.getKeyRecoverySession().unmarkUser(getAdmin(), data.getUsername());
}
if (recoveryData != null) {
rsaKeys = recoveryData.getKeyPair();
if (reusecertificate) {
orgCert = (X509Certificate) recoveryData.getCertificate();
}
} else {
String errMsg = intres.getLocalizedMessage("batch.errornokeyrecoverydata", data.getUsername());
throw new Exception(errMsg);
}
} else {
rsaKeys = KeyTools.genKeys(props.getKeySpec(), props.getKeyAlg());
}
// Get certificate for user and create keystore
if (rsaKeys != null) {
createUser(data.getUsername(), data.getPassword(), data.getCAId(), rsaKeys, createJKS, createPEM, !keyrecoverflag && data.getKeyRecoverable(),
orgCert);
}
}
private boolean doCreate(UserDataVO data, int status) throws Exception {
boolean ret = false;
// get users Token Type.
int tokentype = data.getTokenType();
boolean createJKS = (tokentype == SecConst.TOKEN_SOFT_JKS);
boolean createPEM = (tokentype == SecConst.TOKEN_SOFT_PEM);
boolean createP12 = (tokentype == SecConst.TOKEN_SOFT_P12);
// Only generate supported tokens
if (createP12 || createPEM || createJKS) {
if (status == UserDataConstants.STATUS_KEYRECOVERY) {
String iMsg = intres.getLocalizedMessage("batch.retrieveingkeys", data.getUsername());
getLogger().info(iMsg);
} else {
String iMsg = intres.getLocalizedMessage("batch.generatingkeys", data.getUsername());
getLogger().info(iMsg);
}
processUser(data, createJKS, createPEM, (status == UserDataConstants.STATUS_KEYRECOVERY));
// If all was OK, users status is set to GENERATED by the
// signsession when the user certificate is created.
// If status is still NEW, FAILED or KEYRECOVER though, it means we
// should set it back to what it was before, probably it had a
// request counter
// meaning that we should not reset the clear text password yet.
UserDataVO vo = ejb.getUserAdminSession().findUser(getAdmin(), data.getUsername());
if ((vo.getStatus() == UserDataConstants.STATUS_NEW) || (vo.getStatus() == UserDataConstants.STATUS_FAILED)
|| (vo.getStatus() == UserDataConstants.STATUS_KEYRECOVERY)) {
ejb.getUserAdminSession().setClearTextPassword(getAdmin(), data.getUsername(), data.getPassword());
} else {
// Delete clear text password, if we are not letting status be
// the same as originally
ejb.getUserAdminSession().setClearTextPassword(getAdmin(), data.getUsername(), null);
}
ret = true;
String iMsg = intres.getLocalizedMessage("batch.generateduser", data.getUsername());
getLogger().info(iMsg);
} else {
getLogger().debug("Cannot batchmake browser generated token for user (wrong tokentype)- " + data.getUsername());
}
return ret;
}
/**
* Creates keystore-files for all users with status NEW in the local
* database.
*
* @throws Exception
* if something goes wrong...
*/
public void createAllNew() throws Exception {
getLogger().trace(">createAllNew");
String iMsg = intres.getLocalizedMessage("batch.generatingallstatus", "NEW");
getLogger().info(iMsg);
createAllWithStatus(UserDataConstants.STATUS_NEW);
getLogger().trace("<createAllNew");
}
/**
* Creates P12-files for all users with status FAILED in the local database.
*
* @throws Exception
* if something goes wrong...
*/
public void createAllFailed() throws Exception {
getLogger().trace(">createAllFailed");
String iMsg = intres.getLocalizedMessage("batch.generatingallstatus", "FAILED");
getLogger().info(iMsg);
createAllWithStatus(UserDataConstants.STATUS_FAILED);
getLogger().trace("<createAllFailed");
}
/**
* Creates P12-files for all users with status KEYRECOVER in the local
* database.
*
* @throws Exception
* if something goes wrong...
*/
public void createAllKeyRecover() throws Exception {
if (getUseKeyRecovery()) {
getLogger().trace(">createAllKeyRecover");
String iMsg = intres.getLocalizedMessage("batch.generatingallstatus", "KEYRECOVER");
getLogger().info(iMsg);
createAllWithStatus(UserDataConstants.STATUS_KEYRECOVERY);
getLogger().trace("<createAllKeyRecover");
}
}
/**
* Creates P12-files for all users with status in the local database.
*
* @param status
* @throws Exception
* if something goes wrong...
*/
public void createAllWithStatus(int status) throws Exception {
if (getLogger().isTraceEnabled()) {
getLogger().trace(">createAllWithStatus: " + status);
}
CryptoProviderTools.installBCProviderIfNotAvailable(); // If this is invoked directly
ArrayList<UserDataVO> result = new ArrayList<UserDataVO>();
boolean stopnow = false;
do {
for(UserDataVO data : ejb.getUserAdminSession().findAllBatchUsersByStatusWithLimit(status)) {
if (data.getTokenType() == SecConst.TOKEN_SOFT_JKS || data.getTokenType() == SecConst.TOKEN_SOFT_PEM
|| data.getTokenType() == SecConst.TOKEN_SOFT_P12) {
result.add(data);
}
}
String iMsg = intres.getLocalizedMessage("batch.generatingnoofusers", Integer.valueOf(result.size()));
getLogger().info(iMsg);
int failcount = 0;
int successcount = 0;
if (result.size() > 0) {
if (result.size() < UserAdminConstants.MAXIMUM_QUERY_ROWCOUNT) {
stopnow = true;
}
String failedusers = "";
String successusers = "";
for(UserDataVO data : result) {
if ((data.getPassword() != null) && (data.getPassword().length() > 0)) {
try {
if (doCreate(data, status)) {
successusers += (":" + data.getUsername());
successcount++;
}
} catch (Exception e) {
// If things went wrong set status to FAILED
String errMsg = intres.getLocalizedMessage("batch.errorsetstatus", "FAILED");
getLogger().error(errMsg, e);
getLogger().debug("batch.errorsetstatus", e);
failedusers += (":" + data.getUsername());
failcount++;
if (status == UserDataConstants.STATUS_KEYRECOVERY) {
ejb.getUserAdminSession().setUserStatus(getAdmin(), data.getUsername(), UserDataConstants.STATUS_KEYRECOVERY);
} else {
ejb.getUserAdminSession().setUserStatus(getAdmin(), data.getUsername(), UserDataConstants.STATUS_FAILED);
}
}
} else {
iMsg = intres.getLocalizedMessage("batch.infonoclearpwd", data.getUsername());
getLogger().info(iMsg);
}
}
if (failedusers.length() > 0) {
String errMsg = intres.getLocalizedMessage("batch.errorbatchfailed", Integer.valueOf(failcount), Integer.valueOf(successcount), failedusers);
throw new Exception(errMsg);
}
iMsg = intres.getLocalizedMessage("batch.success", Integer.valueOf(successcount), successusers);
getLogger().info(iMsg);
}
} while ((result.size() > 0) && !stopnow);
if (getLogger().isTraceEnabled()) {
getLogger().trace("<createAllWithStatus: " + status);
}
}
/**
* Creates P12-files for one user in the local database.
*
* @param username
* username
* @throws Exception
* if the user does not exist or something goes wrong during
* generation
*/
public void createUser(String username) throws Exception {
if (getLogger().isTraceEnabled()) {
getLogger().trace(">createUser(" + username + ")");
}
UserDataVO data = ejb.getUserAdminSession().findUser(getAdmin(), username);
if (data == null) {
getLogger().error(intres.getLocalizedMessage("batch.errorunknown", username));
return;
}
int status = data.getStatus();
if ((data != null) && (data.getPassword() != null) && (data.getPassword().length() > 0)) {
if ((status == UserDataConstants.STATUS_NEW) || ((status == UserDataConstants.STATUS_KEYRECOVERY) && getUseKeyRecovery())) {
try {
doCreate(data, status);
} catch (Exception e) {
// If things went wrong set status to FAILED
String errMsg = intres.getLocalizedMessage("batch.errorsetstatus", "FAILED");
getLogger().error(errMsg, e);
if (status == UserDataConstants.STATUS_KEYRECOVERY) {
ejb.getUserAdminSession().setUserStatus(getAdmin(), data.getUsername(), UserDataConstants.STATUS_KEYRECOVERY);
} else {
ejb.getUserAdminSession().setUserStatus(getAdmin(), data.getUsername(), UserDataConstants.STATUS_FAILED);
}
errMsg = intres.getLocalizedMessage("batch.errorbatchfaileduser", username);
throw new Exception(errMsg);
}
} else {
String errMsg = intres.getLocalizedMessage("batch.errorbatchfaileduser", username);
getLogger().error(errMsg);
throw new Exception(errMsg);
}
}
if (getLogger().isTraceEnabled()) {
getLogger().trace(">createUser(" + username + ")");
}
}
/**
* Return environment variable EJBCA_HOME or an empty string if the variable
* isn't set.
*
* @return Environment variable EJBCA_HOME
*/
private static String getHomeDir() {
String ejbcaHomeDir = System.getenv("EJBCA_HOME");
if (ejbcaHomeDir == null) {
ejbcaHomeDir = "";
} else if (!ejbcaHomeDir.endsWith("/") && !ejbcaHomeDir.endsWith("\\")) {
ejbcaHomeDir += File.separatorChar;
}
return ejbcaHomeDir;
}
}