/*******************************************************************************
* Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria
* PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
* joint initiative of the Federal Chancellery Austria and Graz University of
* Technology.
*
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
* http://www.osor.eu/eupl/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
******************************************************************************/
package at.gv.egiz.pdfas.cli;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import javax.activation.DataSource;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import at.gv.egiz.pdfas.common.exceptions.PDFASError;
import at.gv.egiz.pdfas.common.utils.StreamUtils;
import at.gv.egiz.pdfas.lib.api.ByteArrayDataSource;
import at.gv.egiz.pdfas.lib.api.Configuration;
import at.gv.egiz.pdfas.lib.api.PdfAs;
import at.gv.egiz.pdfas.lib.api.PdfAsFactory;
import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner;
import at.gv.egiz.pdfas.lib.api.sign.SignParameter;
import at.gv.egiz.pdfas.lib.api.sign.SignResult;
import at.gv.egiz.pdfas.lib.api.verify.VerifyParameter;
import at.gv.egiz.pdfas.lib.api.verify.VerifyParameter.SignatureVerificationLevel;
import at.gv.egiz.pdfas.lib.api.verify.VerifyResult;
import at.gv.egiz.pdfas.moa.MOAConnector;
import at.gv.egiz.pdfas.sigs.pades.PAdESSigner;
import at.gv.egiz.pdfas.sigs.pades.PAdESSignerKeystore;
import at.gv.egiz.sl.util.BKUSLConnector;
public class Main {
public static final String CLI_ARG_MODE_SHORT = "m";
public static final String CLI_ARG_MODE = "mode";
public static final String CLI_ARG_HELP_SHORT = "h";
public static final String CLI_ARG_HELP = "help";
public static final String CLI_ARG_CONNECTOR_SHORT = "c";
public static final String CLI_ARG_CONNECTOR = "connector";
public static final String CLI_ARG_OUTPUT_SHORT = "o";
public static final String CLI_ARG_OUTPUT = "output";
public static final String CLI_ARG_PROFILE_SHORT = "p";
public static final String CLI_ARG_PROFILE = "profile";
public static final String CLI_ARG_POSITION_SHORT = "pos";
public static final String CLI_ARG_POSITION = "position";
public static final String CLI_ARG_CONF_SHORT = "conf";
public static final String CLI_ARG_CONF = "configuration";
public static final String CLI_ARG_DEPLOY_SHORT = "d";
public static final String CLI_ARG_DEPLOY = "deploy";
public static final String CLI_ARG_VERIFY_WHICH_SHORT = "vw";
public static final String CLI_ARG_VERIFY_WHICH = "verify_which";
public static final String CLI_ARG_VERIFY_LEVEL_SHORT = "vl";
public static final String CLI_ARG_VERIFY_LEVEL = "verify_level";
public static final String CLI_ARG_VERIFY_LEVEL_OPTION_FULL = "full";
public static final String CLI_ARG_VERIFY_LEVEL_OPTION_INT_ONLY = "intOnly";
public static final String CLI_ARG_KEYSTORE_FILE_SHORT = "ksf";
public static final String CLI_ARG_KEYSTORE_FILE = "ks_file";
public static final String CLI_ARG_KEYSTORE_ALIAS = "ks_alias";
public static final String CLI_ARG_KEYSTORE_ALIAS_SHORT = "ksa";
public static final String CLI_ARG_KEYSTORE_TYPE = "ks_type";
public static final String CLI_ARG_KEYSTORE_TYPE_SHORT = "kst";
public static final String CLI_ARG_KEYSTORE_STOREPASS = "ks_storepass";
public static final String CLI_ARG_KEYSTORE_STOREPASS_SHORT = "kssp";
public static final String CLI_ARG_KEYSTORE_KEYPASS = "ks_keypass";
public static final String CLI_ARG_KEYSTORE_KEYPASS_SHORT = "kskp";
public static final String STANDARD_CONFIG_LOCATION = System
.getProperty("user.home") + "/.pdfas/";
public static final String STANDARD_POSITION_STRING = "x:auto;y:auto;w:auto;p:auto;f:0";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private static Options createOptions() {
Options cliOptions = new Options();
Option modeOption = new Option(CLI_ARG_MODE_SHORT, CLI_ARG_MODE, true,
"Mode of operation (sign | verify)");
// modeOption.setRequired(true);
cliOptions.addOption(modeOption);
Option deployOption = new Option(CLI_ARG_DEPLOY_SHORT, CLI_ARG_DEPLOY,
false, "Deploys default configuration");
// modeOption.setRequired(true);
cliOptions.addOption(deployOption);
Option helpOption = new Option(CLI_ARG_HELP_SHORT, CLI_ARG_HELP, false,
"Shows this help message");
cliOptions.addOption(helpOption);
Option connectorOption = new Option(CLI_ARG_CONNECTOR_SHORT,
CLI_ARG_CONNECTOR, true,
"Connector to use (bku | ks (Keystore) | moa)");
cliOptions.addOption(connectorOption);
Option keystoreFileOption = new Option(CLI_ARG_KEYSTORE_FILE_SHORT,
CLI_ARG_KEYSTORE_FILE, true, "Software keystore file");
cliOptions.addOption(keystoreFileOption);
Option keystoreTypeOption = new Option(CLI_ARG_KEYSTORE_TYPE_SHORT,
CLI_ARG_KEYSTORE_TYPE, true,
"Software keystore type (PKCS12 | JKS ...)");
cliOptions.addOption(keystoreTypeOption);
Option keystoreAliasOption = new Option(CLI_ARG_KEYSTORE_ALIAS_SHORT,
CLI_ARG_KEYSTORE_ALIAS, true, "Key Alias in keystore");
cliOptions.addOption(keystoreAliasOption);
Option keystoreStorePassOption = new Option(
CLI_ARG_KEYSTORE_STOREPASS_SHORT, CLI_ARG_KEYSTORE_STOREPASS,
true, "Password for keystore");
cliOptions.addOption(keystoreStorePassOption);
Option keystoreKeyPassOption = new Option(
CLI_ARG_KEYSTORE_KEYPASS_SHORT, CLI_ARG_KEYSTORE_KEYPASS, true,
"Password for key");
cliOptions.addOption(keystoreKeyPassOption);
Option profileOption = new Option(CLI_ARG_PROFILE_SHORT,
CLI_ARG_PROFILE, true, "Signature profile to use");
cliOptions.addOption(profileOption);
Option positionOption = new Option(
CLI_ARG_POSITION_SHORT,
CLI_ARG_POSITION,
true,
"Position string: position has the format [x:x_algo];[y:y_algo];[w:w_algo][p:p_algo];[f:f_algo];[r:r_algo]");
cliOptions.addOption(positionOption);
Option confOption = new Option(CLI_ARG_CONF_SHORT, CLI_ARG_CONF, true,
"Configuration file to use");
cliOptions.addOption(confOption);
Option verifywhichOption = new Option(
CLI_ARG_VERIFY_WHICH_SHORT,
CLI_ARG_VERIFY_WHICH,
true,
"[optional] zero based number of the signature to be verified. If omitted, all signatures are verified.");
cliOptions.addOption(verifywhichOption);
Option verifyLevelOption = new Option(
CLI_ARG_VERIFY_LEVEL_SHORT,
CLI_ARG_VERIFY_LEVEL,
true,
"[optional] Verification Level Full certificate verification, or only integrity Verification ("
+ CLI_ARG_VERIFY_LEVEL_OPTION_FULL
+ " | "
+ CLI_ARG_VERIFY_LEVEL_OPTION_INT_ONLY + ")");
cliOptions.addOption(verifyLevelOption);
Option outputOption = new Option(CLI_ARG_OUTPUT_SHORT, CLI_ARG_OUTPUT,
true, "The output file");
cliOptions.addOption(outputOption);
return cliOptions;
}
private static void usage() {
HelpFormatter formater = new HelpFormatter();
formater.printHelp("pdf-as [OPTIONS] ", createOptions());
}
public static void main(String[] args) {
// create the command line parser
CommandLineParser parser = new GnuParser();
ModeOfOperation mode = ModeOfOperation.INVALID;
try {
CommandLine cli = parser.parse(createOptions(), args);
if (cli.hasOption(CLI_ARG_DEPLOY_SHORT)) {
PdfAsFactory.deployDefaultConfiguration(new File(
STANDARD_CONFIG_LOCATION));
System.out.println("Configuration was deployed to: "
+ STANDARD_CONFIG_LOCATION);
}
if (cli.hasOption(CLI_ARG_MODE_SHORT)) {
String modevalue = cli.getOptionValue(CLI_ARG_MODE_SHORT);
if (modevalue.toLowerCase().trim().equals("sign")) {
mode = ModeOfOperation.SIGN;
} else if (modevalue.toLowerCase().trim().equals("verify")) {
mode = ModeOfOperation.VERIFY;
} else {
throw new ParseException("Invalid value for option "
+ CLI_ARG_MODE_SHORT + ": " + modevalue);
}
}
if (cli.hasOption(CLI_ARG_HELP_SHORT)) {
usage();
System.exit(0);
}
if (mode == ModeOfOperation.INVALID
&& !cli.hasOption(CLI_ARG_DEPLOY_SHORT)) {
usage();
} else if (mode == ModeOfOperation.SIGN) {
perform_sign(cli);
} else if (mode == ModeOfOperation.VERIFY) {
perform_verify(cli);
}
} catch (ParseException e) {
System.err.println("Invalid arguments: " + e.getMessage());
usage();
System.exit(-1);
} catch (PDFASError e) {
System.err.println("PDF-AS Error: [" + e.getCode() + "]"
+ e.getMessage());
Iterator> infoIt = e.getProcessInformations()
.entrySet().iterator();
while (infoIt.hasNext()) {
Entry infoEntry = infoIt.next();
logger.debug("Process Information: {} = {}",
infoEntry.getKey(), infoEntry.getValue());
}
e.printStackTrace(System.err);
System.exit(-1);
} catch (Throwable e) {
System.err.println("Unhandled PDF-AS Error: " + e.getMessage());
e.printStackTrace(System.err);
System.exit(-1);
}
}
private static void deployConfigIfNotexisting() {
File configurationLocation = new File(STANDARD_CONFIG_LOCATION);
try {
if (!configurationLocation.exists()) {
PdfAsFactory.deployDefaultConfiguration(configurationLocation);
}
} catch (Exception e) {
System.out.println("Failed to deploy default confiuration to "
+ configurationLocation.getAbsolutePath());
e.printStackTrace();
}
}
private static void perform_sign(CommandLine cli) throws Exception {
String configurationFile = null;
if (cli.hasOption(CLI_ARG_CONF_SHORT)) {
configurationFile = cli.getOptionValue(CLI_ARG_CONF_SHORT);
} else {
configurationFile = STANDARD_CONFIG_LOCATION;
deployConfigIfNotexisting();
}
String positionString = null;
if (cli.hasOption(CLI_ARG_POSITION_SHORT)) {
positionString = cli.getOptionValue(CLI_ARG_POSITION_SHORT);
} else {
positionString = null;
}
String profilID = null;
if (cli.hasOption(CLI_ARG_PROFILE_SHORT)) {
profilID = cli.getOptionValue(CLI_ARG_PROFILE_SHORT);
}
String outputFile = null;
if (cli.hasOption(CLI_ARG_OUTPUT_SHORT)) {
outputFile = cli.getOptionValue(CLI_ARG_OUTPUT_SHORT);
}
String connector = null;
if (cli.hasOption(CLI_ARG_CONNECTOR_SHORT)) {
connector = cli.getOptionValue(CLI_ARG_CONNECTOR_SHORT);
}
String pdfFile = null;
pdfFile = cli.getArgs()[cli.getArgs().length - 1];
File inputFile = new File(pdfFile);
if (!inputFile.exists()) {
throw new Exception("Input file does not exists");
}
if (outputFile == null) {
if (pdfFile.endsWith(".pdf")) {
outputFile = pdfFile.subSequence(0,
pdfFile.length() - ".pdf".length())
+ "_signed.pdf";
} else {
outputFile = pdfFile + "_signed.pdf";
}
}
File outputPdfFile = new File(outputFile);
DataSource dataSource = new ByteArrayDataSource(
StreamUtils.inputStreamToByteArray(new FileInputStream(
inputFile)));
PdfAs pdfAs = null;
pdfAs = PdfAsFactory.createPdfAs(new File(configurationFile));
Configuration configuration = pdfAs.getConfiguration();
FileOutputStream fos = new FileOutputStream(outputPdfFile, false);
SignParameter signParameter = PdfAsFactory.createSignParameter(
configuration, dataSource, fos);
String id = UUID.randomUUID().toString();
signParameter.setTransactionId(id);
System.out.println("Transaction: " + id);
IPlainSigner slConnector = null;
if (connector != null) {
if (connector.equalsIgnoreCase("bku")) {
slConnector = new PAdESSigner(new BKUSLConnector(configuration));
} else if (connector.equalsIgnoreCase("moa")) {
slConnector = new PAdESSigner(new MOAConnector(configuration));
} else if (connector.equalsIgnoreCase("ks")) {
String keystoreFilename = null;
String keystoreAlias = null;
String keystoreType = null;
String keystoreStorepass = null;
String keystoreKeypass = null;
if (cli.hasOption(CLI_ARG_KEYSTORE_FILE_SHORT)) {
keystoreFilename = cli
.getOptionValue(CLI_ARG_KEYSTORE_FILE_SHORT);
}
if (cli.hasOption(CLI_ARG_KEYSTORE_ALIAS_SHORT)) {
keystoreAlias = cli
.getOptionValue(CLI_ARG_KEYSTORE_ALIAS_SHORT);
}
if (cli.hasOption(CLI_ARG_KEYSTORE_TYPE_SHORT)) {
keystoreType = cli
.getOptionValue(CLI_ARG_KEYSTORE_TYPE_SHORT);
}
if (cli.hasOption(CLI_ARG_KEYSTORE_STOREPASS_SHORT)) {
keystoreStorepass = cli
.getOptionValue(CLI_ARG_KEYSTORE_STOREPASS_SHORT);
}
if (cli.hasOption(CLI_ARG_KEYSTORE_KEYPASS_SHORT)) {
keystoreKeypass = cli
.getOptionValue(CLI_ARG_KEYSTORE_KEYPASS_SHORT);
}
if (keystoreFilename == null) {
throw new Exception(
"You need to provide a keystore file if using ks connector");
}
if (keystoreAlias == null) {
throw new Exception(
"You need to provide a key alias if using ks connector");
}
if (keystoreType == null) {
keystoreType = "PKCS12";
System.out.println("Defaulting to " + keystoreType
+ " keystore type.");
}
if (keystoreStorepass == null) {
keystoreStorepass = "";
}
if (keystoreKeypass == null) {
keystoreKeypass = "";
}
slConnector = new PAdESSignerKeystore(keystoreFilename,
keystoreAlias, keystoreStorepass, keystoreKeypass,
keystoreType);
}
}
if (slConnector == null) {
slConnector = new PAdESSigner(new BKUSLConnector(configuration));
}
signParameter.setPlainSigner(slConnector);
signParameter.setDataSource(dataSource);
signParameter.setSignaturePosition(positionString);
signParameter.setSignatureProfileId(profilID);
System.out.println("Starting signature for " + pdfFile);
System.out.println("Selected signature Profile " + profilID);
SignResult result = null;
try {
result = pdfAs.sign(signParameter);
} finally {
if (result != null) {
Iterator> infoIt = result
.getProcessInformations().entrySet().iterator();
while (infoIt.hasNext()) {
Entry infoEntry = infoIt.next();
logger.debug("Process Information: {} = {}",
infoEntry.getKey(), infoEntry.getValue());
}
}
}
fos.close();
System.out.println("Signed document " + outputFile);
}
private static void perform_verify(CommandLine cli) throws Exception {
String configurationFile = null;
if (cli.hasOption(CLI_ARG_CONF_SHORT)) {
configurationFile = cli.getOptionValue(CLI_ARG_CONF_SHORT);
} else {
configurationFile = STANDARD_CONFIG_LOCATION;
deployConfigIfNotexisting();
}
int which = -1;
if (cli.hasOption(CLI_ARG_VERIFY_WHICH_SHORT)) {
String whichValue = cli.getOptionValue(CLI_ARG_VERIFY_WHICH_SHORT);
which = Integer.parseInt(whichValue);
}
SignatureVerificationLevel lvl = SignatureVerificationLevel.FULL_VERIFICATION;
if (cli.hasOption(CLI_ARG_VERIFY_LEVEL_SHORT)) {
String levelValue = cli.getOptionValue(CLI_ARG_VERIFY_LEVEL_SHORT);
if (levelValue.equals(CLI_ARG_VERIFY_LEVEL_OPTION_FULL)) {
lvl = SignatureVerificationLevel.FULL_VERIFICATION;
} else if (levelValue.equals(CLI_ARG_VERIFY_LEVEL_OPTION_INT_ONLY)) {
lvl = SignatureVerificationLevel.INTEGRITY_ONLY_VERIFICATION;
} else {
System.out.println("Invalid value for verification Level: "
+ levelValue);
System.out.println("Allowed values are: "
+ CLI_ARG_VERIFY_LEVEL_OPTION_FULL + ", "
+ CLI_ARG_VERIFY_LEVEL_OPTION_INT_ONLY);
throw new Exception("Invalid value for verification Level: "
+ levelValue);
}
}
String confOutputFile = null;
if (cli.hasOption(CLI_ARG_OUTPUT_SHORT)) {
confOutputFile = cli.getOptionValue(CLI_ARG_OUTPUT_SHORT);
}
String pdfFile = null;
pdfFile = cli.getArgs()[cli.getArgs().length - 1];
File inputFile = new File(pdfFile);
if (!inputFile.exists()) {
throw new Exception("Input file does not exists");
}
DataSource dataSource = new ByteArrayDataSource(
StreamUtils.inputStreamToByteArray(new FileInputStream(
inputFile)));
PdfAs pdfAs = null;
pdfAs = PdfAsFactory.createPdfAs(new File(configurationFile));
Configuration configuration = pdfAs.getConfiguration();
VerifyParameter verifyParameter = PdfAsFactory.createVerifyParameter(
configuration, dataSource);
verifyParameter.setSignatureVerificationLevel(lvl);
verifyParameter.setDataSource(dataSource);
verifyParameter.setConfiguration(configuration);
verifyParameter.setWhichSignature(which);
List results = pdfAs.verify(verifyParameter);
Iterator resultIterator = results.iterator();
int idx = 0;
while (resultIterator.hasNext()) {
VerifyResult verifyResult = resultIterator.next();
dumpVerifyResult(verifyResult, pdfFile, idx, confOutputFile);
idx++;
}
}
private static void dumpVerifyResult(VerifyResult verifyResult,
String inputFile, int idx, String confOutputFile) {
System.out.println("Verification Result:");
System.out.println("\tValue Check: "
+ verifyResult.getValueCheckCode().getMessage() + " ["
+ verifyResult.getValueCheckCode().getCode() + "]");
System.out.println("\tCertificate Check: "
+ verifyResult.getCertificateCheck().getMessage() + " ["
+ verifyResult.getCertificateCheck().getCode() + "]");
System.out.println("\tQualified Certificate: "
+ verifyResult.isQualifiedCertificate());
System.out.println("\tVerification done: "
+ verifyResult.isVerificationDone());
try {
if (verifyResult.isVerificationDone()
&& verifyResult.getValueCheckCode().getCode() == 0) {
String outputFile = null;
if (confOutputFile == null) {
if (inputFile.endsWith(".pdf")) {
outputFile = inputFile.subSequence(0,
inputFile.length() - ".pdf".length())
+ "_verified_" + idx + ".pdf";
} else {
outputFile = inputFile + "_verified_" + idx + ".pdf";
}
} else {
if (confOutputFile.endsWith(".pdf")) {
outputFile = confOutputFile.subSequence(0,
confOutputFile.length() - ".pdf".length())
+ "_" + idx + ".pdf";
} else {
outputFile = confOutputFile + "_" + idx + ".pdf";
}
}
File outputPdfFile = new File(outputFile);
FileOutputStream fos = new FileOutputStream(outputPdfFile,
false);
fos.write(verifyResult.getSignatureData());
fos.close();
System.out.println("\tSigned PDF: " + outputFile);
}
} catch (Exception e) {
System.out.println("\tFailed to save signed PDF! ["
+ e.getMessage() + "]");
e.printStackTrace();
}
}
}