package com.opentrust.spi.pdf;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.jce.X509Principal;
import com.opentrust.spi.crypto.CertificateHelper;
import com.opentrust.spi.helpers.DateHelper;
import com.opentrust.spi.helpers.StringHelper;
import com.opentrust.spi.logger.Channel;
import com.opentrust.spi.logger.SPILogger;
public class PDFSignaturesHelper {
private final static SPILogger logger = SPILogger.getLogger("PDFSIGN");
// BEGIN HELPER METHODS
// static File getTempPDFFile() {
// try {
// File localRoot = getTempPDFFolder();
// File tempFile = File.createTempFile("tmp_spipdf_", ".pdf", localRoot);
// return tempFile;
// } catch(Exception e) {
// ExceptionHandler.handleNoThrow(e,"Could not create tmp pdf file");
// }
// return null;
// }
// static File getTempPDFFolder() {
// try {
// String localRootPath = Config.getConfig().getStringProperty(CommonProperties.COM_LOCAL_TMPDIR);
// File localRoot = new File(localRootPath+File.separatorChar+"pdf");
// if (localRoot.exists())
// FileHelper.canUseDirectory(localRoot.getAbsolutePath(),
// "rw");
// else
// localRoot.mkdirs();
// return localRoot;
// } catch(Exception e) {
// ExceptionHandler.handleNoThrow(e,"Could not create tmp pdf folder");
// }
// return null;
// }
public static String buildSignatureAppearanceDescription(String descriptionTemplate,
X509Certificate signerCertificate,
Calendar signingTime,
String location, String reason, String contact) throws SPIIllegalArgumentException {
String result = descriptionTemplate;
// First deal with potential variable replacements in description
if (descriptionTemplate != null && descriptionTemplate.contains("$")) {
logger.debug(Channel.TECH, "Building PDF appearance description, template is %1$s",
descriptionTemplate);
// Variable syntax is: $variable_name or $variable_name[custom_param1;custom_param2;...]
Map<String,String> tokens = new HashMap<String,String>();
// List of supported variable names in description
final String VARNAME_SIGNER_DN = "signer_dn";
final String VARNAME_SIGNER_NAME = "signer_name";
final String VARNAME_SIGNING_TIME = "signing_time";
final String VARNAME_LOCATION = "location";
final String VARNAME_REASON = "reason";
final String VARNAME_CONTACT = "contact";
// Fill variable values to be used for replacement
if(signingTime!=null) {
String signingTimeStr = DateHelper.toSimpleLongString(signingTime.getTime());
tokens.put(VARNAME_SIGNING_TIME, signingTimeStr);
}
else {
tokens.put(VARNAME_SIGNING_TIME, null); // so that when signing_time variable is used without signing time provided, an exception is thrown
}
if(signerCertificate!=null) {
X500Principal signer = signerCertificate.getSubjectX500Principal();
String signerName = signer.getName();
tokens.put(VARNAME_SIGNER_DN, signerName);
tokens.put(VARNAME_SIGNER_NAME, getCNOrDefaultForPrincipal(signer));
} else {
tokens.put(VARNAME_SIGNER_DN,null); // so that when signer_dn variable is used without input certificate, an exception is thrown
tokens.put(VARNAME_SIGNER_NAME, null); // so that when signer_name variable is used without input certificate, an exception is thrown
}
tokens.put(VARNAME_LOCATION, location);
tokens.put(VARNAME_REASON, reason);
tokens.put(VARNAME_CONTACT, contact);
//Iterate over variables found in description to perform replacement
Pattern variablePattern = Pattern.compile("\\$("+StringHelper.join(tokens.keySet().iterator(), "|")
+")(\\[([^\\]]*)\\])?");
Matcher variableMatcher = variablePattern.matcher(descriptionTemplate);
StringBuffer sb = new StringBuffer();
while(variableMatcher.find()) {
String variableName = variableMatcher.group(1);
String replacement = tokens.get(variableName);
String variableOptionalParams = variableMatcher.group(3);
if (variableOptionalParams != null) {
if (VARNAME_SIGNING_TIME.equals(variableName) && signingTime != null ) {
// Use custom-formatted signing_time instead of toSimpleLongString signing_time
try {
String[] params = variableOptionalParams.split(";");
String customDateFormat = params[0];
String customTimezone = params.length > 1 ? params[1].trim() : null;
String customLocale = params.length > 2 ? params[2].trim() : null;
SimpleDateFormat dateFormat = customLocale != null ?
new SimpleDateFormat(customDateFormat, new Locale(customLocale)) : new SimpleDateFormat(customDateFormat);
if (customTimezone != null)
dateFormat.setTimeZone(TimeZone.getTimeZone(customTimezone.trim()));
replacement = dateFormat.format(signingTime.getTime());
} catch(Throwable e) {
throw new SPIIllegalArgumentException(e,
"Could not recognize valid signing time date format '%1$s' in description parameter: %2$s",
variableOptionalParams, e.toString());
}
}
}
if(replacement==null) {
if (VARNAME_SIGNER_DN.equals(variableName) || VARNAME_SIGNER_NAME.equals(variableName))
throw new SPIIllegalArgumentException("Attempt to use certificate-related variable in signature description without providing the signer certificate");
else if (VARNAME_SIGNING_TIME.equals(variableName))
throw new SPIIllegalArgumentException("Attempt to use signing time variable in signature description without provided signing time value");
else
throw new SPIIllegalArgumentException("Attempt to use %1$s variable in signature description without providing the corresponding value",
variableName);
}
else {
//We need to escape $ character in replacement string, otherwise appendReplacement will fail
replacement = replacement.replaceAll("\\$", "\\\\\\$");
}
variableMatcher.appendReplacement(sb, replacement);
}
variableMatcher.appendTail(sb);
result = sb.toString();
logger.debug(Channel.TECH, "Final PDF appearance description value is %1$s", result);
}
else {
logger.debug(Channel.TECH, "Building PDF appearance description, no variable replacement needed in appearance description");
}
return result;
}
// copied from admin-web, ACBean class. TODO : put this in a helper package
private static String getCNOrDefaultForPrincipal(X500Principal principal) {
String name = null;
try {
name = CertificateHelper.getCNFromDN(principal.getName());
} catch (IllegalArgumentException e) {}
if(name == null || name.length() == 0) {
try {
X509Principal p=new X509Principal(principal.getEncoded());
name = (String)p.getValues().lastElement();
} catch(Exception e) {
name = "";
}
}
return name;
}
// END HELPER METHODS
}