/*************************************************************************
* *
* SignServer: The OpenSource Automated Signing Server *
* *
* 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.signserver.client.cli.defaultimpl;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import javax.xml.ws.soap.SOAPFaultException;
import org.apache.commons.cli.*;
import org.apache.log4j.Logger;
import org.signserver.cli.spi.AbstractCommand;
import org.signserver.cli.spi.CommandFailureException;
import org.signserver.cli.spi.IllegalCommandArgumentsException;
import org.signserver.common.AccessDeniedException;
import org.signserver.common.AuthorizationRequiredException;
import org.signserver.common.CryptoTokenOfflineException;
import org.signserver.common.IllegalRequestException;
import org.signserver.common.SignServerException;
import org.signserver.protocol.ws.client.SignServerWSClientFactory;
/**
* Command Line Interface (CLI) for signing documents.
*
* @author Markus KilÄs
* @version $Id: SignDocumentCommand.java 3029 2012-11-22 10:27:09Z netmackan $
*/
public class SignDocumentCommand extends AbstractCommand {
/** Logger for this class. */
private static final Logger LOG = Logger.getLogger(SignDocumentCommand.class);
/** ResourceBundle with internationalized StringS. */
private static final ResourceBundle TEXTS = ResourceBundle.getBundle(
"org/signserver/client/cli/defaultimpl/ResourceBundle");
private static final String DEFAULT_CLIENTWS_WSDL_URL = "/signserver/ClientWSService/ClientWS?wsdl";
/** System-specific new line characters. **/
private static final String NL = System.getProperty("line.separator");
/** The name of this command. */
private static final String COMMAND = "signdocument";
/** Option WORKERID. */
public static final String WORKERID = "workerid";
/** Option WORKERNAME. */
public static final String WORKERNAME = "workername";
/** Option DATA. */
public static final String DATA = "data";
/** Option HOST. */
public static final String HOST = "host";
/** Option INFILE. */
public static final String INFILE = "infile";
/** Option OUTFILE. */
public static final String OUTFILE = "outfile";
/** Option PORT. */
public static final String PORT = "port";
public static final String SERVLET = "servlet";
/** Option PROTOCOL. */
public static final String PROTOCOL = "protocol";
/** Option USERNAME. */
public static final String USERNAME = "username";
/** Option PASSWORD. */
public static final String PASSWORD = "password";
/** Option PDFPASSWORD. */
public static final String PDFPASSWORD = "pdfpassword";
/** The command line options. */
private static final Options OPTIONS;
/**
* Protocols that can be used for accessing SignServer.
*/
public static enum Protocol {
/** The SignServerWS interface. */
WEBSERVICES,
/** The ClientWS interface. */
CLIENTWS,
/** The HTTP interface. */
HTTP
}
static {
OPTIONS = new Options();
OPTIONS.addOption(WORKERID, true,
TEXTS.getString("WORKERID_DESCRIPTION"));
OPTIONS.addOption(WORKERNAME, true,
TEXTS.getString("WORKERNAME_DESCRIPTION"));
OPTIONS.addOption(DATA, true,
TEXTS.getString("DATA_DESCRIPTION"));
OPTIONS.addOption(INFILE, true,
TEXTS.getString("INFILE_DESCRIPTION"));
OPTIONS.addOption(OUTFILE, true,
TEXTS.getString("OUTFILE_DESCRIPTION"));
OPTIONS.addOption(HOST, true,
TEXTS.getString("HOST_DESCRIPTION"));
OPTIONS.addOption(PORT, true,
TEXTS.getString("PORT_DESCRIPTION"));
OPTIONS.addOption(SERVLET, true,
TEXTS.getString("SERVLET_DESCRIPTION"));
OPTIONS.addOption(PROTOCOL, true,
TEXTS.getString("PROTOCOL_DESCRIPTION"));
OPTIONS.addOption(USERNAME, true,
TEXTS.getString("USERNAME_DESCRIPTION"));
OPTIONS.addOption(PASSWORD, true,
TEXTS.getString("PASSWORD_DESCRIPTION"));
OPTIONS.addOption(PDFPASSWORD, true,
TEXTS.getString("PDFPASSWORD_DESCRIPTION"));
for (Option option : KeyStoreOptions.getKeyStoreOptions()) {
OPTIONS.addOption(option);
}
}
/** ID of worker who should perform the operation. */
private int workerId;
/** Name of worker who should perform the operation. */
private String workerName;
/** Data to sign. */
private String data;
/** Hostname or IP address of the SignServer host. */
private String host;
/** TCP port number of the SignServer host. */
private Integer port;
private String servlet = "/signserver/process";
/** File to read the data from. */
private File inFile;
/** File to read the signed data to. */
private File outFile;
/** Protocol to use for contacting SignServer. */
private Protocol protocol = Protocol.HTTP;
private String username;
private String password;
private String pdfPassword;
private KeyStoreOptions keyStoreOptions = new KeyStoreOptions();
@Override
public String getDescription() {
return "Request a document to be signed by SignServer";
}
@Override
public String getUsages() {
StringBuilder footer = new StringBuilder();
footer.append(NL)
.append("Sample usages:").append(NL)
.append("a) ").append(COMMAND).append(" -workername XMLSigner -data \"\"").append(NL)
.append("b) ").append(COMMAND).append(" -workername XMLSigner -infile /tmp/document.xml").append(NL)
.append("c) ").append(COMMAND).append(" -workerid 2 -data \"\" -truststore truststore.jks -truststorepwd changeit").append(NL)
.append("d) ").append(COMMAND).append(" -workerid 2 -data \"\" -keystore superadmin.jks -keystorepwd foo123").append(NL);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
final HelpFormatter formatter = new HelpFormatter();
PrintWriter pw = new PrintWriter(bout);
formatter.printHelp(pw, HelpFormatter.DEFAULT_WIDTH, "signdocument <-workername WORKERNAME | -workerid WORKERID> [options]", getDescription(), OPTIONS, HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, footer.toString());
pw.close();
return bout.toString();
}
/**
* Reads all the options from the command line.
*
* @param line The command line to read from
*/
private void parseCommandLine(final CommandLine line) {
if (line.hasOption(WORKERNAME)) {
workerName = line.getOptionValue(WORKERNAME, null);
}
if (line.hasOption(WORKERID)) {
workerId = Integer.parseInt(line.getOptionValue(WORKERID, null));
}
host = line.getOptionValue(HOST, KeyStoreOptions.DEFAULT_HOST);
if (line.hasOption(PORT)) {
port = Integer.parseInt(line.getOptionValue(PORT));
}
if (line.hasOption(SERVLET)) {
servlet = line.getOptionValue(SERVLET, null);
}
if (line.hasOption(DATA)) {
data = line.getOptionValue(DATA, null);
}
if (line.hasOption(INFILE)) {
inFile = new File(line.getOptionValue(INFILE, null));
}
if (line.hasOption(OUTFILE)) {
outFile = new File(line.getOptionValue(OUTFILE, null));
}
if (line.hasOption(PROTOCOL)) {
protocol = Protocol.valueOf(line.getOptionValue(
PROTOCOL, null));
// if the protocol is WS and -servlet is not set, override the servlet URL
// with the default one for the WS servlet
if (Protocol.WEBSERVICES.equals(protocol) &&
!line.hasOption(SERVLET)) {
servlet = SignServerWSClientFactory.DEFAULT_WSDL_URL;
}
if ((Protocol.CLIENTWS.equals(protocol)) &&
!line.hasOption(SERVLET)) {
servlet = DEFAULT_CLIENTWS_WSDL_URL;
}
}
if (line.hasOption(USERNAME)) {
username = line.getOptionValue(USERNAME, null);
}
if (line.hasOption(PASSWORD)) {
password = line.getOptionValue(PASSWORD, null);
}
if (line.hasOption(PDFPASSWORD)) {
pdfPassword = line.getOptionValue(PDFPASSWORD, null);
}
keyStoreOptions.parseCommandLine(line);
}
/**
* Checks that all mandadory options are given.
*/
private void validateOptions() throws IllegalCommandArgumentsException {
if (workerName == null && workerId == 0) {
throw new IllegalCommandArgumentsException(
"Missing -workername or -workerid");
} else if (data == null && inFile == null) {
throw new IllegalArgumentException("Missing -data or -infile");
}
keyStoreOptions.validateOptions();
}
/**
* Creates a DocumentSigner using the choosen protocol.
*
* @return a DocumentSigner using the choosen protocol
* @throws MalformedURLException in case an URL can not be constructed
* using the given host and port
*/
private DocumentSigner createSigner() throws MalformedURLException {
final DocumentSigner signer;
keyStoreOptions.setupHTTPS();
if (port == null) {
if (keyStoreOptions.isUsePrivateHTTPS()) {
port = KeyStoreOptions.DEFAULT_PRIVATE_HTTPS_PORT;
} else if (keyStoreOptions.isUseHTTPS()) {
port = KeyStoreOptions.DEFAULT_PUBLIC_HTTPS_PORT;
} else {
port = KeyStoreOptions.DEFAULT_HTTP_PORT;
}
}
switch (protocol) {
case WEBSERVICES: {
LOG.debug("Using SignServerWS as procotol");
final String workerIdOrName;
if (workerId == 0) {
workerIdOrName = workerName;
} else {
workerIdOrName = String.valueOf(workerId);
}
signer = new WebServicesDocumentSigner(
host,
port,
servlet,
workerIdOrName,
keyStoreOptions.isUseHTTPS(),
username, password,
pdfPassword);
break;
}
case CLIENTWS: {
LOG.debug("Using ClientWS as procotol");
final String workerIdOrName;
if (workerId == 0) {
workerIdOrName = workerName;
} else {
workerIdOrName = String.valueOf(workerId);
}
signer = new ClientWSDocumentSigner(
host,
port,
servlet,
workerIdOrName,
keyStoreOptions.isUseHTTPS(),
username, password,
pdfPassword);
break;
}
case HTTP:
default: {
LOG.debug("Using HTTP as procotol");
final URL url = new URL(keyStoreOptions.isUseHTTPS() ? "https" : "http", host, port, servlet);
if (workerId == 0) {
signer = new HTTPDocumentSigner(url, workerName, username, password, pdfPassword);
} else {
signer = new HTTPDocumentSigner(url, workerId, username, password, pdfPassword);
}
}
}
return signer;
}
/**
* Execute the signing operation.
*/
private void run() {
FileInputStream fin = null;
try {
final byte[] bytes;
Map requestContext = new HashMap();
if (inFile == null) {
bytes = data.getBytes();
} else {
requestContext.put("FILENAME", inFile.getName());
fin = new FileInputStream(inFile);
bytes = new byte[(int) inFile.length()];
fin.read(bytes);
}
OutputStream out = null;
try {
if (outFile == null) {
out = System.out;
} else {
out = new FileOutputStream(outFile);
}
createSigner().sign(bytes, out, requestContext);
} finally {
if (out != null && out != System.out) {
out.close();
}
}
} catch (FileNotFoundException ex) {
LOG.error(MessageFormat.format(TEXTS.getString("FILE_NOT_FOUND:"),
ex.getLocalizedMessage()));
} catch (IllegalRequestException ex) {
LOG.error(ex.getLocalizedMessage());
} catch (CryptoTokenOfflineException ex) {
LOG.error(ex.getLocalizedMessage());
} catch (SignServerException ex) {
LOG.error(ex.getLocalizedMessage());
} catch (SOAPFaultException ex) {
if (ex.getCause() instanceof AuthorizationRequiredException) {
final AuthorizationRequiredException authEx =
(AuthorizationRequiredException) ex.getCause();
LOG.error("Authorization required: " + authEx.getMessage());
} else if (ex.getCause() instanceof AccessDeniedException) {
final AccessDeniedException authEx =
(AccessDeniedException) ex.getCause();
LOG.error("Access denied: " + authEx.getMessage());
}
LOG.error(ex);
} catch (IOException ex) {
LOG.error(ex);
} finally {
if (fin != null) {
try {
fin.close();
} catch (IOException ex) {
LOG.error("Error closing file", ex);
}
}
}
}
@Override
public int execute(String[] args) throws IllegalCommandArgumentsException, CommandFailureException {
try {
// Parse the command line
parseCommandLine(new GnuParser().parse(OPTIONS, args));
validateOptions();
run();
return 0;
} catch (ParseException ex) {
throw new IllegalCommandArgumentsException(ex.getMessage());
}
}
}