package com.opentrust.spi.pdf;
import java.awt.Color;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.cert.CRL;
import java.security.cert.Certificate;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.ocsp.OCSPResp;
import org.bouncycastle.tsp.TimeStampResponse;
import org.bouncycastle.util.encoders.Hex;
import com.keynectis.sequoia.ca.crypto.utils.OIDUtils;
import com.keynectis.sequoia.ca.crypto.utils.PKCS12File;
import com.keynectis.sequoia.security.clients.interfaces.IOCSPClient;
import com.keynectis.sequoia.security.clients.interfaces.ITspClient;
import com.opentrust.spi.cms.CMSForPAdESBasicGenerator;
import com.opentrust.spi.cms.CMSForPAdESEnhancedGenerator;
import com.opentrust.spi.cms.CMSGenerator;
import com.opentrust.spi.cms.CMSSignedDataWrapper;
import com.opentrust.spi.cms.helpers.OCSPResponse;
import com.opentrust.spi.crypto.CRLHelper;
import com.opentrust.spi.crypto.CertificateHelper;
import com.opentrust.spi.crypto.CryptoConstants.AlgorithmID;
import com.opentrust.spi.crypto.DigestHelper;
import com.opentrust.spi.crypto.ExceptionHandler;
import com.opentrust.spi.logger.Channel;
import com.opentrust.spi.logger.SPILogger;
import com.opentrust.spi.pdf.PdfSignParameters.OCSPParameters;
import com.opentrust.spi.pdf.PdfSignParameters.PAdESParameters;
import com.opentrust.spi.pdf.PdfSignParameters.SignatureLayoutParameters;
import com.opentrust.spi.pdf.PdfSignParameters.TimestampingParameters;
import com.opentrust.spi.tsp.TimestampToken;
import com.spilowagie.text.DocumentException;
import com.spilowagie.text.Font;
import com.spilowagie.text.FontFactory;
import com.spilowagie.text.Image;
import com.spilowagie.text.Rectangle;
import com.spilowagie.text.pdf.AcroFields;
import com.spilowagie.text.pdf.BaseFont;
import com.spilowagie.text.pdf.PdfArray;
import com.spilowagie.text.pdf.PdfDate;
import com.spilowagie.text.pdf.PdfDeveloperExtension;
import com.spilowagie.text.pdf.PdfDictionary;
import com.spilowagie.text.pdf.PdfIndirectReference;
import com.spilowagie.text.pdf.PdfName;
import com.spilowagie.text.pdf.PdfNumber;
import com.spilowagie.text.pdf.PdfObject;
import com.spilowagie.text.pdf.PdfReader;
import com.spilowagie.text.pdf.PdfSignatureAppearance;
import com.spilowagie.text.pdf.PdfStamper;
import com.spilowagie.text.pdf.PdfStream;
import com.spilowagie.text.pdf.PdfString;
import com.spilowagie.text.pdf.PdfWriter;
import com.spilowagie.text.pdf.RandomAccessFileOrArray;
//TODO : check if there is a fileID in the document to sign. If no fileID -> no fileID in outgoing file. If fileID, use it, along with signDate, to build the new fileID
public class PDFSign {
public final static boolean enablePositionCheck = false;
public static String PRODUCED_BY = "OpenTrust SPI";
public static void setPRODUCED_BY(String pRODUCED_BY) {
PRODUCED_BY = pRODUCED_BY;
}
private static SPILogger log = SPILogger.getLogger("PDFSIGN");
private static int CONTENT_SIZE = 0x2502;
private static int TIMESTAMP_SIZE = 0x2502;
//private static OCSPResponderManager ocspResponderManager = OCSPResponderManager.getInstance();
protected IOCSPClient ocspClient;
protected ITspClient tspClient;
/*
static IOCSPClient defaultOCSPClient;
static ITspClient defaultTspClient;
*/
private static PdfName DOCTIMESTAMP= new PdfName("DocTimeStamp");
static PdfName DSS= new PdfName("DSS");
static PdfName CERTS= new PdfName("Certs");
static PdfName CRLS= new PdfName("CRLs");
static PdfName OCSPS= new PdfName("OCSPs");
static PdfName VRI= new PdfName("VRI");
static PdfName CRL= new PdfName("CRL");
static PdfName OCSP= new PdfName("OCSP");
static {
FontFactory.register("/fonts/DejaVuSerif.ttf");
FontFactory.register("/fonts/DejaVuSans.ttf");
FontFactory.register("/fonts/DejaVuSansMono.ttf");
//TODO : not using -Bold and -Oblique fonts, this apparently not being necessary
//TODO : use something like ClassHelper.getClassesForPackage() to retrieve all ttf resource files in /fonts
}
/****************** BEGIN SIGN METHODS ************************/
public static SignReturn sign(String provider, PdfReader reader, OutputStream out, File tmpFile, PrivateKey priv,
Certificate[] certificateChain, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters)
throws SPIException {
try {
Certificate signerCert = null;
if (certificateChain != null & certificateChain.length != 0)
signerCert = certificateChain[0];
PdfStamper stp = prepareSign(reader, out, tmpFile, signerCert, parameters);
PdfSignatureAppearance sap = stp.getSignatureAppearance();
SignResult signResult = cms_sign(provider, sap.getRangeStream(), priv, certificateChain, parameters, crls,
ocspResponseEncoded);
return signAfterPresignWithEncodedP7(sap, signResult.getEncodedPkcs7(), parameters,
signResult.getTimestampToken());
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
public static SignReturn sign(String provider, PdfReader reader, OutputStream out, File tmpFile, String keyStoreFileName, String password, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try
{
PKCS12File p12 = new PKCS12File(keyStoreFileName, password);
/*
KeyStore keyStore = KeyStoreHelper.load(keyStoreFileName, password);
String alias = KeyStoreHelper.getDefaultAlias(keyStore);
Certificate[] chain = (Certificate[]) keyStore.getCertificateChain(alias);
PrivateKey priv = (PrivateKey) (keyStore.getKey(alias, password.toCharArray()));
*/
Certificate[] chain = p12.getChain();
PrivateKey priv = p12.mPrivateKey;
return sign(provider, reader, out, tmpFile, priv, chain, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
public static SignReturn sign(String provider, InputStream original_pdf, OutputStream out, PrivateKey priv, Certificate[] certificateChain, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
log.debug(Channel.TECH, "Signing document using parameters %1$s", parameters);
try {
PdfReader reader = new PdfReader(original_pdf);
return sign(provider, reader, out, null, priv, certificateChain, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
// Use for large Files
public static SignReturn sign(String provider, String original_pdf_name, OutputStream out, File tmpFile, PrivateKey priv, Certificate[] certificateChain, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
log.debug(Channel.TECH, "Signing document using parameters %1$s", parameters);
try {
PdfReader reader = new PdfReader(new RandomAccessFileOrArray(original_pdf_name), null);
return sign(provider, reader, out, tmpFile, priv, certificateChain, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
public static SignReturn sign(String provider, File original_pdf, OutputStream out, PrivateKey priv, Certificate[] certificateChain, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try {
return sign(provider, new FileInputStream(original_pdf), out, priv, certificateChain, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
public static SignReturn sign(String provider, File original_pdf, OutputStream out, String keyStoreFileName, String password, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
log.debug(Channel.TECH, "Signing document retrieving certificate from keystore '%1$s' and using parameters %2$s", keyStoreFileName, parameters);
try {
return sign(provider, new FileInputStream(original_pdf), out, keyStoreFileName, password, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
public static SignReturn sign(String provider, InputStream original_pdf, OutputStream out,
String keyStoreFileName, String password, CRL[] crls, OCSPResponse[] ocspResponseEncoded,
PdfSignParameters parameters) throws SPIException {
log.debug(Channel.TECH, "Signing document retrieving certificate from keystore '%1$s' and using parameters %2$s", keyStoreFileName, parameters);
try {
PdfReader reader = new PdfReader(original_pdf);
return sign(provider, reader, out, null, keyStoreFileName, password, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
// Use for large Files
public static SignReturn sign(String provider, String original_pdf_name,
OutputStream out, File tmpFile, String keyStoreFileName, String password, CRL[] crls,
OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException
{
log.debug(Channel.TECH, "Signing document retrieving certificate from keystore '%1$s' and using parameters %2$s", keyStoreFileName, parameters);
try {
PdfReader reader = new PdfReader(new RandomAccessFileOrArray(original_pdf_name), null);
return sign(provider, reader, out, tmpFile, keyStoreFileName, password, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e, "reading file " + original_pdf_name);
}
return null;
}
/****************** END SIGN METHODS ************************/
/****************** BEGIN PRESIGN METHODS ************************/
/**
* preSign
* Usage : call preSign + build and sign CMS using returned data as data to sign + send CMS-encoded bytes as encodedPkcs7 parameter to method signAfterPresignWithEncodedP7
*/
public static PresignReturn preSign(InputStream original_pdf, PdfSignParameters parameters) throws SPIException {
return preSign(original_pdf, null, null, null, parameters);
}
//For large PDF
public static PresignReturn preSign(String original_pdf_name, File tmpFile, PdfSignParameters parameters) throws SPIException {
return preSign(original_pdf_name, tmpFile, null, null, null, parameters);
}
protected static PresignReturn preSign(PdfReader original_pdf, OutputStream output_pdf, File tmpFile, Certificate signerCert, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
log.debug(Channel.TECH, "Presigning document using parameters %1$s", parameters);
PresignReturn retour = null;
try {
PdfStamper stp = prepareSign(original_pdf, output_pdf, tmpFile, signerCert, parameters);
PdfSignatureAppearance sap = stp.getSignatureAppearance();
retour = new PresignReturn(sap.getRangeStream(), sap.getFieldName(), sap);
String dataHashAlgo = parameters.getDataHashAlgorithm();
if(dataHashAlgo!=null) {
// TODO : raise a wrong usage exception when dataHashAlgo==null && tmpFile!=null
retour.setHashToSign(DigestHelper.getDigest(retour.getDataToSign(), parameters.getDataHashAlgorithm()));
log.debug(Channel.TECH, "presign returning hash : %1$s", retour.getHashToSign());
}
// dataToSign stream is about to be closed, we set it to null
if(tmpFile!=null) retour.dataToSign = null;
sap.closeStreams();
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return retour;
}
public static PresignReturn preSign(InputStream original_pdf, Certificate signerCert, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
return preSign(original_pdf, null, signerCert, crls, ocspResponseEncoded, parameters);
}
//Use for large PDF
public static PresignReturn preSign(String original_pdf_name, File tmpFile, Certificate signerCert, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
return preSign(original_pdf_name, null, tmpFile, signerCert, crls, ocspResponseEncoded, parameters);
}
public static PresignReturn preSign(InputStream original_pdf, OutputStream output_pdf, Certificate signerCert, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try {
log.debug(Channel.TECH, "preSign for normal size pdf");
PdfReader reader = new PdfReader(original_pdf);
return preSign(reader, output_pdf, null, signerCert, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
//Use for large PDF
public static PresignReturn preSign(String original_pdf_name, OutputStream output_pdf, File tmpFile, Certificate signerCert, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try {
log.debug(Channel.TECH, "preSign for large size pdf");
PdfReader reader = new PdfReader(new RandomAccessFileOrArray(original_pdf_name), null);
return preSign(reader, output_pdf, tmpFile, signerCert, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
/****************** END PRESIGN METHODS ************************/
/****************** BEGIN PRESIGNFORRAWSIGNATURE METHODS ************************/
/**
* preSignForRawSignature
* Usage : call preSignForRawSignature + sign returned data +
* - either send raw signature bytes as rawSignature parameter to method signAfterPresignWithRawSignature, along with returned encoded PKCS#7
* - or insert raw signature in returned PKCS#7, and send it as encodedPkcs7 parameter to signAfterPresignWithEncodedPkcs7
*/
protected static PresignReturnForRawSignature preSignForRawSignature(PdfReader original_pdf, File tmpFile, Certificate[] certs, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
log.debug(Channel.TECH, "Presigning document before raw signature using parameters %1$s", parameters);
PresignReturnForRawSignature retour = null;
try {
PdfStamper stp = prepareSign(original_pdf, null, tmpFile, certs[0], parameters);
PdfSignatureAppearance sap = stp.getSignatureAppearance();
String dataHashAlgo = parameters.getDataHashAlgorithm();
if(dataHashAlgo==null) throw new IllegalArgumentException("digestAlgo required for preSignForRawSignature");
// Computing data to sign hash
MessageDigest messageDigest = MessageDigest.getInstance(dataHashAlgo);
DigestInputStream dis = new DigestInputStream(sap.getRangeStream(),messageDigest);
byte[] buf = new byte[1024];
while (dis.read(buf, 0, buf.length) != -1);
sap.closeStreams();
// Building CMS
// FIXME : use PadesBasicCMS class or Pades....etc
PDFEnvelopedSignature otp7 = new PDFEnvelopedSignature(dis.getMessageDigest().digest(), certs, crls, ocspResponseEncoded, parameters.getDataHashAlgorithm(), BouncyCastleProvider.PROVIDER_NAME, "dummy".getBytes(), null, "RSA", parameters.getSigningTime()!=null?parameters.getSigningTime().getTime():null);
byte[] encodedP7 = otp7.getEncodedPKCS7(null, null);
retour = new PresignReturnForRawSignature(otp7.getSignedAttrs(), encodedP7, sap.getFieldName());
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return retour;
}
public static PresignReturnForRawSignature preSignForRawSignature(InputStream original_pdf, Certificate[] certs, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try {
log.debug(Channel.TECH, "PresignReturnForRawSignature for normal size pdf");
PdfReader reader = new PdfReader(original_pdf);
return preSignForRawSignature(reader, null, certs, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
//Use for large PDF
public static PresignReturnForRawSignature preSignForRawSignature(String original_pdf_name, File tmpFile, Certificate[] certs, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try {
log.debug(Channel.TECH, "PresignReturnForRawSignature for large size pdf");
PdfReader reader = new PdfReader(new RandomAccessFileOrArray(original_pdf_name), null);
return preSignForRawSignature(reader, tmpFile, certs, crls, ocspResponseEncoded, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
/****************** END PRESIGNFORRAWSIGNATURE METHODS ************************/
/****************** BEGIN signAfterPresignWithRawSignature METHODS ************************/
/**
* signAfterPresignWithRawSignature
* Usage : call preSignForRawSignature + sign returned data + send raw signature bytes as rawSignature parameter to method signAfterPresignWithRawSignature, along with returned encoded PKCS#7
*/
public static SignReturn signAfterPresignWithRawSignature(InputStream original_pdf, OutputStream out, byte[] rawSignature, byte[] encodedUnsignedPkcs7, Certificate signerCert, PdfSignParameters parameters) throws SPIException {
try {
return signAfterPresignWithRawSignature(new PdfReader(original_pdf), out, null, rawSignature, encodedUnsignedPkcs7, signerCert, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
protected static SignReturn signAfterPresignWithRawSignature(PdfReader original_pdf, OutputStream out,
File tmpFile, byte[] rawSignature, byte[] encodedUnsignedPkcs7, Certificate signerCert,
PdfSignParameters parameters) throws SPIException {
try {
log.debug(Channel.TECH, "Signing document with incoming raw signature, using parameters %1$s", parameters);
// TODO : see if we can use something like
// CMSSignedData.replaceSigners instead, to start from the unsigned
// p7 and only change its signaturevalue
CMSSignedDataWrapper unsignedCmsSignature = new CMSSignedDataWrapper(encodedUnsignedPkcs7);
CRL[] crlList = null;
if (unsignedCmsSignature.getCRLs() != null)
crlList = unsignedCmsSignature.getCRLs().toArray(new CRL[] {});
OCSPResponse[] ocspResponseEncoded = unsignedCmsSignature.getOCSPResponses().toArray(new OCSPResponse[] {});
String dataDigestAlgorithm = unsignedCmsSignature.getDataDigestAlgorithm();
try
{
DERObjectIdentifier oid = new DERObjectIdentifier(dataDigestAlgorithm);
dataDigestAlgorithm = OIDUtils.getName(oid);
}
catch(Exception e) {}
PDFEnvelopedSignature signedotp7 = new PDFEnvelopedSignature(unsignedCmsSignature.getDigestAttribute(),
unsignedCmsSignature.getSignatureCertificateInfo().toArray(new Certificate[] {}), crlList,
ocspResponseEncoded, dataDigestAlgorithm,
BouncyCastleProvider.PROVIDER_NAME, rawSignature, null,
unsignedCmsSignature.getSignatureAlgorithm(), unsignedCmsSignature.getSigningTime());
byte[] p7 = signedotp7.getEncodedPKCS7();
TimestampingParameters tsParams = parameters.getTimeStampParams();
if (tsParams != null) {
// fetch timestamp and add it to p7
CMSSignedDataWrapper cmsSignature = new CMSSignedDataWrapper(p7);
PDFEnvelopedSignature.addTSToCMS(cmsSignature, tsParams.getTimeStampDigestAlgo(), tsParams.getTspClient());
p7 = cmsSignature.getEncoded();
}
return signAfterPresignWithEncodedP7(original_pdf, out, tmpFile, p7, signerCert, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
//Use for large PDF
public static SignReturn signAfterPresignWithRawSignature(String original_pdf_name, OutputStream out, File tmpFile, byte[] rawSignature, byte[] encodedUnsignedPkcs7, Certificate signerCert, PdfSignParameters parameters) throws SPIException {
try {
return signAfterPresignWithRawSignature(new PdfReader(new RandomAccessFileOrArray(original_pdf_name), null), out, tmpFile, rawSignature, encodedUnsignedPkcs7, signerCert, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
/****************** END signAfterPresignWithRawSignature METHODS ************************/
/****************** BEGIN signAfterPresignWithEncodedP7 METHODS ************************/
/**
* signAfterPresignWithEncodedP7
* Usage : call preSign + build and sign CMS using returned data as data to sign + send CMS-encoded bytes as encodedPkcs7 parameter to method signAfterPresignWithEncodedP7
*
* tspToken, when provided, is already inside encodedPkcs7 and results from using parameters.getTimeStampParams()
*/
protected static SignReturn signAfterPresignWithEncodedP7(PdfSignatureAppearance sap, byte[] encodedPkcs7,
PdfSignParameters parameters, TimestampToken tspToken) throws SPIException {
SignReturn retour = null;
log.debug(Channel.TECH, "Signing document with incoming CMS-encoded signature, using parameters %1$s",
parameters);
try {
PdfDictionary dic2 = new PdfDictionary();
// We re-compute the size that was allocated in the original pdf to
// receive the PKCS#7, and compare it with actual pkcs#7 data size
// TODO adapt to CRL size
int content_size = CONTENT_SIZE;
if (parameters != null) {
if (parameters.getSignatureContainerSize() > 0)
content_size = parameters.getSignatureContainerSize();
int ts_size = TIMESTAMP_SIZE;
if (parameters.getTimeStampContainerSize() > 0)
ts_size = parameters.getTimeStampContainerSize();
if (parameters.isAllocateTimeStampContainer() || parameters.getTimeStampParams() != null) {
log.debug(Channel.TECH, "adding %1$s to content_size(%2$s) because of timestamping", ts_size,
content_size);
content_size += ts_size;
}
}
byte out[] = new byte[content_size];
log.debug(Channel.TECH, "adding P7 in PDF. Size in bytes is %1$s and signblock size is %2$s",
encodedPkcs7.length, out.length);
if (encodedPkcs7.length > out.length)
throw new SPIException(
"Signature size (%1$s) is bigger than expected (%2$s). Please change input estimated size or configuration default signblock size.",
encodedPkcs7.length, out.length);
System.arraycopy(encodedPkcs7, 0, out, 0, encodedPkcs7.length);
dic2.put(PdfName.CONTENTS, new PdfString(out).setHexWriting(true));
sap.close(dic2);
CMSSignedDataWrapper p7 = new CMSSignedDataWrapper(encodedPkcs7);
retour = new SignReturn(sap.getFieldName(), p7.getSignatureValue(), tspToken);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return retour;
}
protected static SignReturn signAfterPresignWithEncodedP7(PdfReader original_pdf, OutputStream out, File tmpFile, byte[] encodedPkcs7, Certificate certs, PdfSignParameters parameters) throws SPIException {
return signAfterPresignWithEncodedP7(original_pdf, out, tmpFile, encodedPkcs7, certs, parameters, null);
}
protected static SignReturn signAfterPresignWithEncodedP7(PdfReader original_pdf, OutputStream out, File tmpFile, byte[] encodedPkcs7, Certificate signerCert, PdfSignParameters parameters, TimestampToken tspToken) throws SPIException {
SignReturn retour = null;
log.debug(Channel.TECH, "Signing document with incoming CMS-encoded signature, using parameters %1$s", parameters);
try {
//Parsing incoming encodedPkcs7
CMSSignedDataWrapper p7 = new CMSSignedDataWrapper(encodedPkcs7);
if(p7.getSigningTime()!=null) {
Calendar cmsSigningTime = Calendar.getInstance();
cmsSigningTime.setTime(p7.getSigningTime());
log.debug(Channel.TECH, "Signing time found in CMS : %1$s", cmsSigningTime);
if(parameters.getSigningTime()!=null) {
if(!parameters.getSigningTime().equals(cmsSigningTime)) {
log.debug(Channel.TECH, "Signing time found in CMS (%1$s) is different from signing time given as input parameter (%2$s). Input parameter will be used.", cmsSigningTime, parameters.getSigningTime());
} else {
log.debug(Channel.TECH, "Signing time found in CMS (%1$s) is identical to signing time given as input parameter (%2$s).", cmsSigningTime, parameters.getSigningTime());
}
} else {
log.debug(Channel.TECH, "Using signing time found in CMS : %1$s", cmsSigningTime);
parameters.setSigningTime(cmsSigningTime);
}
}
PdfStamper stp = prepareSign(original_pdf, out, tmpFile, signerCert, parameters);
PdfSignatureAppearance sap = stp.getSignatureAppearance();
retour = signAfterPresignWithEncodedP7(sap, encodedPkcs7, parameters, tspToken);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return retour;
}
public static SignReturn signAfterPresignWithEncodedP7(InputStream original_pdf, OutputStream out, byte[] encodedPkcs7, Certificate signerCert, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try {
PdfReader reader = new PdfReader(original_pdf);
return signAfterPresignWithEncodedP7(reader, out, null, encodedPkcs7, signerCert, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
// Use for large PDF
public static SignReturn signAfterPresignWithEncodedP7(String original_pdf_name, OutputStream out, File tmpFile, byte[] encodedPkcs7, Certificate signerCert, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try {
PdfReader reader = new PdfReader(new RandomAccessFileOrArray(original_pdf_name), null);
return signAfterPresignWithEncodedP7(reader, out, tmpFile, encodedPkcs7, signerCert, parameters);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
public static SignReturn signAfterPresignWithEncodedP7(PdfSignatureAppearance sap, byte[] encodedPkcs7, Certificate signerCert, CRL[] crls, OCSPResponse[] ocspResponseEncoded, PdfSignParameters parameters) throws SPIException {
try {
return signAfterPresignWithEncodedP7(sap, encodedPkcs7, parameters, null);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
/****************** END signAfterPresignWithEncodedP7 METHODS ************************/
/****************** BEGIN cms_sign METHODS ************************/
/**
* cms_sign :
* adbe.pkcs7.detached signing for PDF, by providing data or digestData (=data digested with parameters.dataHashAlgorithm)
*/
public static SignResult cms_sign(byte[] digestData, PrivateKey priv, Certificate[] certChain, PdfSignParameters parameters, CRL[] crls, OCSPResponse[] ocspResponseEncoded) throws Exception {
return cms_sign(null, null, digestData, priv, certChain, parameters, crls, ocspResponseEncoded);
}
public static SignResult cms_sign(byte[] digestData, String keyStoreFileName, String password, PdfSignParameters parameters, CRL[] crls, OCSPResponse[] ocspResponseEncoded) throws Exception {
return cms_sign(null, digestData, keyStoreFileName, password, parameters, crls, ocspResponseEncoded);
}
public static SignResult cms_sign(String provider, InputStream data, PrivateKey priv, Certificate[] certChain,
PdfSignParameters parameters, CRL[] crls, OCSPResponse[] ocspResponseEncoded) throws Exception {
return cms_sign(provider, data, null, priv, certChain, parameters, crls, ocspResponseEncoded);
}
public static SignResult cms_sign(InputStream data, String keyStoreFileName, String password,
PdfSignParameters parameters, CRL[] crls, OCSPResponse[] ocspResponseEncoded) throws Exception {
return cms_sign(data, null, keyStoreFileName, password, parameters, crls, ocspResponseEncoded);
}
private static SignResult cms_sign(InputStream data, byte[] digestData, String keyStoreFileName, String password,
PdfSignParameters parameters, CRL[] crls, OCSPResponse[] ocspResponseEncoded) throws Exception {
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(new FileInputStream(keyStoreFileName), password.toCharArray());
String ALIAS = ks.aliases().nextElement();
java.security.cert.Certificate[] certChain = ks.getCertificateChain(ALIAS);
PrivateKey priv = (PrivateKey) (ks.getKey(ALIAS, password.toCharArray()));
return cms_sign(null, data, digestData, priv, certChain, parameters, crls, ocspResponseEncoded);
}
private static SignResult cms_sign(String provider, InputStream data, byte[] digestData, PrivateKey priv,
Certificate[] certChain, PdfSignParameters parameters, CRL[] crls, OCSPResponse[] ocspResponseEncoded)
throws Exception {
IOCSPClient ocspClient = parameters.ocspClient;
TimestampingParameters timeStampParams = parameters.getTimeStampParams();
ITspClient tspClient = timeStampParams != null ? timeStampParams.getTspClient() : null;
Collection<OCSPResponse> ocspResponses = new ArrayList<OCSPResponse>();
List<OCSPParameters> ocspParamsList = parameters.getOCSPParams();
if (ocspResponseEncoded != null)
ocspResponses = Arrays.asList(ocspResponseEncoded);
else if (ocspParamsList != null)
{
for (OCSPParameters ocspParams : ocspParamsList) {
log.debug(Channel.TECH, "Requests a new OCSP Response");
OCSPResp status = parameters.ocspClient.getRawStatus((X509Certificate) ocspParams.getTargetCertificate(),
(X509Certificate) ocspParams.getIssuerCertificate());
ocspResponses.add(new OCSPResponse(status.getEncoded()));
/*
OCSPResponder ocspResponder =
ocspResponderManager.getOCSPResponder
(ocspParams.getOcspResponderId()); OCSPResponse
ocspResponseFresh =
ocspResponder.getOCSPResponse(ocspParams.getTargetCertificate
(), ocspParams.getIssuerCertificate());
ocspResponses.add(ocspResponseFresh);
*/
}
}
String dataHashAlgorithm = parameters.getDataHashAlgorithm();
String digestAlgOID = AlgorithmID.valueOfTag(dataHashAlgorithm).getOID();
byte[] encodedPkcs7 = null;
PAdESParameters padesParameters = parameters.getPadesParameters();
boolean isPAdesEnhancedLevel = padesParameters != null ?
padesParameters.isPadesEnhancedLevel() : false;
CMSGenerator generator = null;
Certificate signatureCertificate = certChain[0];
List<Certificate> certStore = certChain != null ? Arrays.asList(certChain) : null;
List<java.security.cert.CRL> signedCrls = crls != null ? Arrays.asList(crls) : null;
if (isPAdesEnhancedLevel) {
generator = new CMSForPAdESEnhancedGenerator(provider, signatureCertificate, priv,
certStore, digestAlgOID,
signedCrls, ocspResponses);
CMSForPAdESEnhancedGenerator padesGenerator = (CMSForPAdESEnhancedGenerator) generator;
padesGenerator.setPolicyIdentifierParams(padesParameters.getPolicyIdentifierParams());
padesGenerator.setClaimedAttribute(padesParameters.getClaimedAttributesOID(),
padesParameters.getClaimedAttributes());
if (padesParameters.getCertifiedAttribute() != null)
padesGenerator.setCertifiedAttribute(padesParameters.getCertifiedAttribute().getEncoded());
// FIXME : if commitment-type and no signature-policy-identifier,
// implicitly set commitment-type in reason entry ?
if (padesParameters.getCommitmentTypeId() != null)
padesGenerator.setCommitmentTypeId(padesParameters.getCommitmentTypeId());
TimestampingParameters contentTimeStampParams = padesParameters.getContentTimeStampParams();
if (contentTimeStampParams != null) {
if (data != null) {
byte[] digest = DigestHelper.getDigest(data, dataHashAlgorithm);
ITspClient tspClient2 = contentTimeStampParams.tspClient;
byte[] tsp = tspClient2.getRawTsp(digest, dataHashAlgorithm);
TimeStampResponse response = new TimeStampResponse(tsp);
padesGenerator.setContentTimeStamp(response.getTimeStampToken().getEncoded());
digestData = digest;
data = null;
} // FIXME : implement else
}
} else {
Date signingTime = parameters.getSigningTime().getTime();
generator = new CMSForPAdESBasicGenerator(provider, signatureCertificate, priv,
certStore, signingTime, digestAlgOID, signedCrls, ocspResponses);
}
if (data != null) {
encodedPkcs7 = generator.signContent(data, false);
} else {
encodedPkcs7 = generator.signReference(digestData);
}
if (tspClient == null)
return new SignResult(encodedPkcs7, null);
TimestampingParameters tsParams = timeStampParams;
CMSSignedDataWrapper cmsSignature = new CMSSignedDataWrapper(encodedPkcs7);
TimestampToken timeStampToken = PDFEnvelopedSignature.addTSToCMS(cmsSignature, tsParams.getTimeStampDigestAlgo(), tspClient);
return new SignResult(cmsSignature.getEncoded(), timeStampToken);
}
/****************** END cms_sign METHODS ************************/
public static CMSSignedDataWrapper cms_sign(String digestAlgo, byte[] digest, PrivateKey priv,
Certificate[] certChain, PdfSignParameters parameters, CRL[] crls, OCSPResponse[] ocspResponseEncoded)
throws Exception {
IOCSPClient ocspClient = parameters.ocspClient;
TimestampingParameters timeStampParams = parameters.getTimeStampParams();
ITspClient tspClient = timeStampParams != null ? timeStampParams.getTspClient() : null;
Collection<OCSPResponse> ocspResponses = new ArrayList<OCSPResponse>();
List<OCSPParameters> ocspParamsList = parameters.getOCSPParams();
if (ocspResponseEncoded != null)
ocspResponses = Arrays.asList(ocspResponseEncoded);
else if (ocspParamsList != null) {
for (OCSPParameters ocspParams : ocspParamsList) {
log.debug(Channel.TECH, "Requests a new OCSP Response");
OCSPResp status = parameters.ocspClient.getRawStatus(
(X509Certificate) ocspParams.getTargetCertificate(),
(X509Certificate) ocspParams.getIssuerCertificate());
ocspResponses.add(new OCSPResponse(status.getEncoded()));
}
}
String dataHashAlgorithm = parameters.getDataHashAlgorithm();
String digestAlgOID = AlgorithmID.valueOfTag(dataHashAlgorithm).getOID();
byte[] encodedPkcs7 = null;
PAdESParameters padesParameters = parameters.getPadesParameters();
boolean isPAdesEnhancedLevel = padesParameters != null ? padesParameters.isPadesEnhancedLevel() : false;
CMSGenerator generator = null;
Certificate signatureCertificate = certChain[0];
List<Certificate> certStore = certChain != null ? Arrays.asList(certChain) : null;
List<java.security.cert.CRL> signedCrls = crls != null ? Arrays.asList(crls) : null;
if (isPAdesEnhancedLevel) {
generator = new CMSForPAdESEnhancedGenerator(null, signatureCertificate, priv, certStore, digestAlgOID,
signedCrls, ocspResponses);
CMSForPAdESEnhancedGenerator padesGenerator = (CMSForPAdESEnhancedGenerator) generator;
padesGenerator.setPolicyIdentifierParams(padesParameters.getPolicyIdentifierParams());
padesGenerator.setClaimedAttribute(padesParameters.getClaimedAttributesOID(),
padesParameters.getClaimedAttributes());
if (padesParameters.getCertifiedAttribute() != null)
padesGenerator.setCertifiedAttribute(padesParameters.getCertifiedAttribute().getEncoded());
// FIXME : if commitment-type and no signature-policy-identifier,
// implicitly set commitment-type in reason entry ?
if (padesParameters.getCommitmentTypeId() != null)
padesGenerator.setCommitmentTypeId(padesParameters.getCommitmentTypeId());
TimestampingParameters contentTimeStampParams = padesParameters.getContentTimeStampParams();
if (contentTimeStampParams != null) {
ITspClient tspClient2 = contentTimeStampParams.tspClient;
byte[] tsp = tspClient2.getRawTsp(digest, dataHashAlgorithm);
TimeStampResponse response = new TimeStampResponse(tsp);
padesGenerator.setContentTimeStamp(response.getTimeStampToken().getEncoded());
}
} else {
Date signingTime = parameters.getSigningTime().getTime();
generator = new CMSForPAdESBasicGenerator(null, signatureCertificate, priv, certStore, signingTime,
digestAlgOID, signedCrls, ocspResponses);
}
encodedPkcs7 = generator.signReference(digest);
CMSSignedDataWrapper cmsSignature = new CMSSignedDataWrapper(encodedPkcs7);
if (tspClient == null)
return cmsSignature;
TimestampingParameters tsParams = timeStampParams;
PDFEnvelopedSignature.addTSToCMS(cmsSignature, tsParams.getTimeStampDigestAlgo(), tspClient);
return cmsSignature;
}
/****************** BEGIN LTV Timestamp METHODS ************************/
//FIXME : other addLTVTimestamp methods (as many as sign methods ?)
public static SignReturn addLTVTimestamp(InputStream fileInputStream, OutputStream fileOutputStream,
TimestampingParameters timestampingParameters) throws SPIException {
try {
PdfReader reader = new PdfReader(fileInputStream);
PdfStamper stp = prepareDocTimeStamp(reader, fileOutputStream, null, timestampingParameters);
PdfSignatureAppearance sap = stp.getSignatureAppearance();
/*
TimeStampProcessor timeStampProcessor = TimeStampProcessorFactory.getInstance().
getTimeStampProcessor(timestampingParameters.getTimeStampServerURL(), timestampingParameters.getTimeStampPolicyOID(), timestampingParameters.getTimeStampDigestAlgo(), true);
byte[] tsResponse = timeStampProcessor.timestamp(sap.getRangeStream());
TimestampToken timestampToken = TimestampTokenManagerFactory.getInstance(BouncyCastleProvider.PROVIDER_NAME).getTimeStampToken(tsResponse);
*/
ITspClient tspClient = timestampingParameters.getTspClient();
String digestAlgo = timestampingParameters.getTimeStampDigestAlgo();
byte [] digest = DigestHelper.getDigest(sap.getRangeStream(), digestAlgo);
byte[] tsp = tspClient.getRawTsp(digest, digestAlgo);
TimeStampResponse response = new TimeStampResponse(tsp);
tsp = response.getTimeStampToken().getEncoded();
return signAfterPresignWithEncodedP7(sap, tsp, null, null);
} catch (Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
/****************** END LTV Timestamp METHODS ************************/
/****************** BEGIN LTV DSS METHODS
* @throws SPIException ************************/
//Adds a LTV DocumentSecurityStore dictionary
public static void addLTVDSS(InputStream fileInputStream, OutputStream fileOutputStream, List<Certificate> certsList, List<CRL> crlsList, List<OCSPResponse> ocspsList) throws SPIException {
addLTVDSSWithVRI(fileInputStream, fileOutputStream, certsList, crlsList, ocspsList, null);
}
// Adds a LTV DocumentSecurityStore dictionary including VRI dictionaries if requested using vriClrs or vriOCSPs lists
// All clrs or ocsps in VRI lists will also be added to the main DSS lists
// Caller should make sure that provided CRLs or OCSPs are not already in the signatures' P7 data
public static void addLTVDSSWithVRI(InputStream fileInputStream, OutputStream fileOutputStream, List<Certificate> certsList, List<CRL> crlsList, List<OCSPResponse> ocspsList, List<VRIData> vriData) throws SPIException {
try {
PdfReader reader = new PdfReader(fileInputStream);
PdfStamper stp = new PdfStamper(reader, fileOutputStream, '\0', true);
PdfWriter writer = stp.getWriter();
AcroFields af = reader.getAcroFields();
PdfDictionary catalog = reader.getCatalog();
stp.markUsed(catalog);
PdfDictionary dss = new PdfDictionary();
PdfArray dssCerts = new PdfArray();
PdfArray dssCrls = new PdfArray();
PdfArray dssOcsps = new PdfArray();
if(vriData!=null) {
PdfDictionary vrim = new PdfDictionary();
for (VRIData vriDat : vriData) {
PdfArray vriCert = new PdfArray();
PdfArray vriCrl = new PdfArray();
PdfArray vriOcsp = new PdfArray();
PdfDictionary vri = new PdfDictionary();
if(vriDat.certsList!=null) {
for (Certificate certObject : vriDat.certsList) {
addIndirectBytesToArrays(writer, ((X509Certificate) certObject).getEncoded(), vriCert, dssCerts);
}
}
if(vriDat.crlsList!=null) {
for (CRL crlObject : vriDat.crlsList) {
addIndirectBytesToArrays(writer, ((X509CRL) crlObject).getEncoded(), vriCrl, dssCrls);
}
}
if(vriDat.ocspsList!=null) {
for (OCSPResponse ocspObject : vriDat.ocspsList) {
addIndirectBytesToArrays(writer, ocspObject.getEncoded(), vriOcsp, dssOcsps);
}
}
if (vriCert.size() > 0)
vri.put(PdfName.CERT, addObjectAsIndirectRef(writer, vriCert));
if (vriCrl.size() > 0)
vri.put(CRL, addObjectAsIndirectRef(writer, vriCrl));
if (vriOcsp.size() > 0)
vri.put(OCSP, addObjectAsIndirectRef(writer, vriOcsp));
vrim.put(getVRIKey(af.getSignatureDictionary(vriDat.signatureName)), addObjectAsIndirectRef(writer, vri));
// Not TU or TS because "not recommended" by ETSI TS 102 778-4 V1.1.2
}
dss.put(VRI, addObjectAsIndirectRef(writer, vrim));
}
if(certsList!=null) {
for (Certificate certObject : certsList) {
addIndirectBytesToArrays(writer, ((X509Certificate) certObject).getEncoded(), dssCerts);
}
}
if(crlsList!=null) {
for (CRL crlObject : crlsList) {
addIndirectBytesToArrays(writer, ((X509CRL) crlObject).getEncoded(), dssCrls);
}
}
if(ocspsList!=null) {
for (OCSPResponse ocspObject : ocspsList) {
addIndirectBytesToArrays(writer, ocspObject.getEncoded(), dssOcsps);
}
}
if (dssCerts.size() > 0)
dss.put(CERTS, addObjectAsIndirectRef(writer, dssCerts));
if (dssCrls.size() > 0)
dss.put(CRLS, addObjectAsIndirectRef(writer, dssCrls));
if (dssOcsps.size() > 0)
dss.put(OCSPS, addObjectAsIndirectRef(writer, dssOcsps));
catalog.put(DSS, addObjectAsIndirectRef(writer, dss));
stp.close();
} catch(Exception e) {
ExceptionHandler.handle(e);
}
}
private static void addIndirectBytesToArrays(PdfWriter writer, byte[] bytes, PdfArray... arrays) throws IOException {
PdfStream ps = new PdfStream(bytes);
ps.flateCompress();
PdfIndirectReference iref = addObjectAsIndirectRef(writer, ps);
for(PdfArray array : arrays) {
array.add(iref);
}
}
private static PdfIndirectReference addObjectAsIndirectRef(PdfWriter writer, PdfObject object) throws IOException {
return writer.addToBody(object, false).getIndirectReference();
}
//The key of each entry in [the VRI] dictionary is the base-16-encoded (uppercase) SHA1 digest of the signature to which it applies
//For a Time-stamp's signature [the signature to digest] is the bytes of the Time-stamp itself since the Time-stamp token is a signed data object
protected static PdfName getVRIKey(PdfDictionary dic) throws SPIException {
try {
PdfString contents = dic.getAsString(PdfName.CONTENTS);
byte[] sigContent = contents.getOriginalBytes();
if (PdfName.ETSI_RFC3161.equals(PdfReader.getPdfObject(dic.get(PdfName.SUBFILTER)))) {
sigContent = getSigContentForTS(sigContent);
}
return getVRIKey(sigContent);
} catch(Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
protected static byte[] getSigContentForTS(byte[] sigContent) throws Exception {
//FIXME : find which version should be used
return sigContent;
// ASN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(sigContent));
// DERObject pkcs = din.readObject();
// return pkcs.getEncoded();
}
protected static PdfName getVRIKey(byte[] sigContent) throws SPIException {
try {
byte[] sigContentDigest = DigestHelper.getDigest(sigContent, "SHA1");
return new PdfName(new String(Hex.encode(sigContentDigest)).toUpperCase());
} catch(Exception e) {
ExceptionHandler.handle(e);
}
return null;
}
public static class VRIData {
protected String signatureName;
protected List<Certificate> certsList = new ArrayList<Certificate>();
protected List<CRL> crlsList = new ArrayList<CRL>();
protected List<OCSPResponse> ocspsList = new ArrayList<OCSPResponse>();
public VRIData(String signatureName, List<Certificate> certsList, List<CRL> crlsList, List<OCSPResponse> ocspsList) {
this.signatureName = signatureName;
this.certsList = certsList;
this.crlsList = crlsList;
this.ocspsList = ocspsList;
}
public void addCertificate(Certificate cert) {
certsList.add(cert);
}
public void addCRL(CRL crl) {
crlsList.add(crl);
}
public void addOCSP(OCSPResponse ocsp) {
ocspsList.add(ocsp);
}
}
public static class VRIValidationData {
protected PdfName signatureVRIKey;
protected List<CertificateWithRevision> certsList = new ArrayList<CertificateWithRevision>();
protected List<CRLWithRevision> crlsList = new ArrayList<CRLWithRevision>();
protected List<OCSPResponseWithRevision> ocspsList = new ArrayList<OCSPResponseWithRevision>();
protected VRIValidationData(PdfName signatureVRIKey) {
this.signatureVRIKey = signatureVRIKey;
}
public void setCertificates(List<CertificateWithRevision> certsList) {
this.certsList = certsList;
}
protected void setCRLs(List<CRLWithRevision> crlsList) {
this.crlsList = crlsList;
}
protected void setOCSPs(List<OCSPResponseWithRevision> ocspsList) {
this.ocspsList = ocspsList;
}
public List<CertificateWithRevision> getCertsList() {
return certsList;
}
public List<CRLWithRevision> getCrlsList() {
return crlsList;
}
public List<OCSPResponseWithRevision> getOcspsList() {
return ocspsList;
}
}
public static class ValidationData {
protected List<CertificateWithRevision> certsList = new ArrayList<CertificateWithRevision>();
protected List<CRLWithRevision> crlsList = new ArrayList<CRLWithRevision>();
protected List<OCSPResponseWithRevision> ocspsList = new ArrayList<OCSPResponseWithRevision>();
protected Map<PdfName, VRIValidationData> vriList = new HashMap<PdfName, VRIValidationData>();
protected ValidationData(){}
public void setCertificates(List<CertificateWithRevision> certsList) {
this.certsList = certsList;
}
protected void setCRLs(List<CRLWithRevision> crlsList) {
this.crlsList = crlsList;
}
protected void setOCSPs(List<OCSPResponseWithRevision> ocspsList) {
this.ocspsList = ocspsList;
}
protected void addVRI(VRIValidationData vri) {
vriList.put(vri.signatureVRIKey, vri);
}
public List<CertificateWithRevision> getCertsList() {
return certsList;
}
public List<CRLWithRevision> getCrlsList() {
return crlsList;
}
public List<OCSPResponseWithRevision> getOcspsList() {
return ocspsList;
}
public VRIValidationData getVriData(PDFEnvelopedSignature signature) throws SPIException {
byte[] sigContent = signature.getCONTENTSContent();
try {
if(signature.getDocTimeStampValue()!=null) {
sigContent = getSigContentForTS(sigContent);
}
} catch(Exception e) {
ExceptionHandler.handle(e);
}
return vriList.get(getVRIKey(sigContent));
}
}
public static interface ObjectWithRevision {
public int getRevision();
}
public static interface ObjectWithRevisionAbstractFactory<ConcreteObjectWithRevision extends ObjectWithRevision> {
public ConcreteObjectWithRevision createObjectWithRevision(byte[] octets, int revision) throws Exception;
}
public static class CertificateWithRevisionFactory implements ObjectWithRevisionAbstractFactory<CertificateWithRevision> {
@Override
public CertificateWithRevision createObjectWithRevision(byte[] octets, int revision) throws Exception {
Certificate cert = CertificateHelper.getCertificate(octets);
log.debug(Channel.TECH, "certificate found in dictionary");
return new CertificateWithRevision(cert, revision);
}
}
public static class CRLWithRevisionFactory implements ObjectWithRevisionAbstractFactory<CRLWithRevision> {
@Override
public CRLWithRevision createObjectWithRevision(byte[] octets, int revision) throws Exception {
CRL crl = CRLHelper.getCRL(octets);
log.debug(Channel.TECH, "CRL found in dictionary");
return new CRLWithRevision(crl, revision);
}
}
public static class OCSPResponseWithRevisionFactory implements ObjectWithRevisionAbstractFactory<OCSPResponseWithRevision> {
@Override
public OCSPResponseWithRevision createObjectWithRevision(byte[] octets, int revision) throws Exception {
//OCSPResponse ocsp = OCSPResponseFactory.getInstance().getOCSPResponse(octets);
OCSPResponse ocsp = new OCSPResponse(octets);
log.debug(Channel.TECH, "OCSP response found in dictionary");
return new OCSPResponseWithRevision(ocsp, revision);
}
}
public static class CertificateWithRevision implements ObjectWithRevision {
private final Certificate cert;
private final int revision;
@Override
public int getRevision() {
return revision;
}
public Certificate getCertificate() {
return cert;
}
public CertificateWithRevision(Certificate cert, int revision) {
this.cert = cert;
this.revision = revision;
}
}
public static class CRLWithRevision implements ObjectWithRevision {
private final CRL crl;
private final int revision;
@Override
public int getRevision() {
return revision;
}
public CRL getCRL() {
return crl;
}
public CRLWithRevision(CRL crl, int revision) {
this.crl = crl;
this.revision = revision;
}
}
public static class OCSPResponseWithRevision implements ObjectWithRevision {
private final OCSPResponse ocspR;
private final int revision;
@Override
public int getRevision() {
return revision;
}
public OCSPResponse getOCSPResponse() {
return ocspR;
}
public OCSPResponseWithRevision(OCSPResponse ocspR, int revision) {
this.ocspR = ocspR;
this.revision = revision;
}
}
/****************** END LTV DSS METHODS ************************/
//FIXME : avoid copies between prepareDocTimeStamp & prepareSign
protected static PdfStamper prepareDocTimeStamp(PdfReader reader, OutputStream fout, File tmpFile, TimestampingParameters timestampingParameters) throws Exception {
PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', tmpFile, true);
stp.addDeveloperExtension(new PdfDeveloperExtension(new PdfName("ESIC"), PdfWriter.PDF_VERSION_1_7, 1));
PdfSignatureAppearance sap = stp.getSignatureAppearance();
sap.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.TYPE, DOCTIMESTAMP);
dic.put(PdfName.FILTER, PdfSignatureAppearance.WINCER_SIGNED);
dic.put(PdfName.SUBFILTER, PdfName.ETSI_RFC3161);
PdfDictionary buildDic = new PdfDictionary();
PdfDictionary buildDataDic = new PdfDictionary();
buildDataDic.put(new PdfName("Name"), PdfSignatureAppearance.WINCER_SIGNED);
buildDic.put(new PdfName("Filter"), buildDataDic);
buildDataDic = new PdfDictionary();
buildDataDic.put(new PdfName("Name"), new PdfName(PRODUCED_BY));
buildDic.put(new PdfName("App"), buildDataDic);
dic.put(new PdfName("Prop_Build"), buildDic);
sap.setCryptoDictionary(dic);
LinkedHashMap exc = new LinkedHashMap();
//TODO implement content size calculation base on CRL size (and OCSP response size)
int content_size = CONTENT_SIZE;
int hexblock_size = content_size*2+2;
log.debug(Channel.TECH, "Prepared signing with content_size=%1$s bytes (%2$s real bytes once hex-encoded)", content_size, hexblock_size);
exc.put(PdfName.CONTENTS, new Integer(hexblock_size));
sap.preClose(exc);
if(log.isDebugEnabled(Channel.TECH)) {
log.debug(Channel.TECH, "PDF Prepared for signature. ByteRange is %1$s", Arrays.toString(sap.getRange()));
}
return stp;
}
protected static PdfStamper prepareSign(PdfReader reader, OutputStream fout, File tmpFile, Certificate signerCert, PdfSignParameters parameters) throws Exception {
//FIXME : check that this new signature won't break existing certification signatures (with DocMDP transform)
Calendar presignDate = parameters.getSigningTime();
// '\0' means we keep the same Acrobat version as the one in incoming
// file (Acrobat 7 -> Acrobat 7, Acrobat 8 -> Acrobat 8, etc)
// first null is because we're not going to create an output file in
// this method
// second null means signature will be created in memory, not in a temporary file
//TODO : force version to 1.7 when PAdES enhanced or LTV ? Doesn't it break existing signatures ? Adobe Pro X doesn't change PDF version, and produces documents with for instance PdfVersion=1.4 and extension.BaseVersion=1.7 ...
PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', tmpFile, parameters.isCreateNewRevision());
boolean isPadesEnhancedLevel = false;
// PAdES v3 : extension dictionary
PAdESParameters padesParams = parameters.getPadesParameters();
isPadesEnhancedLevel = padesParams!=null && padesParams.isPadesEnhancedLevel();
if(isPadesEnhancedLevel)
stp.addDeveloperExtension(new PdfDeveloperExtension(new PdfName("ESIC"), PdfWriter.PDF_VERSION_1_7, 2));
// Adobe Pro X seems to add <</ADBE<</BaseVersion/1.7/ExtensionLevel 8>>>>
PdfSignatureAppearance sap = stp.getSignatureAppearance();
String signatureName = parameters.getSignatureName();
if(signatureName != null && signatureName.length() == 0)
signatureName = null;
if(signatureName!=null) {
if(!parameters.isSignatureAlreadyExists() && reader.getAcroFields().getField(signatureName)!=null)
throw new SPIIllegalDataException("A PDF field with the same name (%1$s) already exists in the document", signatureName);
sap.setFieldName(signatureName);
}
if (parameters.isSignatureAlreadyExists()) {
//FIXME : check that the new signature conforms to 'seed values' for this field
if(signatureName==null) throw new SPIIllegalDataException("Already existing signatures cannot have empty signature names");
AcroFields af = reader.getAcroFields();
AcroFields.Item item = af.getFieldItem(signatureName);
if (item == null) throw new SPIIllegalDataException("The field %1$s does not exist.",signatureName);
PdfDictionary merged = item.getMerged(0);
if(merged.get(PdfName.V)!=null) throw new SPIIllegalDataException("The field %1$s is not empty.",signatureName);
sap.setVisibleSignature(signatureName);
}
sap.setCertificationLevel(parameters.getCertifLevel());
if (parameters.isVisible()) { // This means SignatureLayoutParameters is not null
log.debug(Channel.TECH, "Visible signature");
SignatureLayoutParameters signatureLayoutParameters = parameters.getSignatureLayoutParameters();
if (!parameters.isSignatureAlreadyExists()) {
// bottom left point
float x1 = signatureLayoutParameters.getX1();
float y1 = signatureLayoutParameters.getY1();
// upper right
float x2 = signatureLayoutParameters.getX2();
float y2 = signatureLayoutParameters.getY2();
int pageNbr = signatureLayoutParameters.getPageNbr();
if (reader.getNumberOfPages() < pageNbr || pageNbr <= 0)
throw new SPIIllegalDataException("Invalid page number: %1$s", pageNbr);
// check that the signature rectangle is contained in the page
if (enablePositionCheck)
checkSignaturePosition(reader, x1, y1, x2, y2, pageNbr);
sap.setVisibleSignature(new Rectangle(x1, y1, x2, y2), pageNbr, signatureName);
}
String description = signatureLayoutParameters.getDescription();
if (description != null) sap.setLayer2Text(description);
else if(signerCert==null) throw new SPIIllegalArgumentException("No description and no signer certificate provided. Default description cannot be created.");
sap.setRunDirection(signatureLayoutParameters.getRunDirection());
byte[] backgroundImage = signatureLayoutParameters.getBackgroundImage();
if(backgroundImage!=null && backgroundImage.length > 0) {
Image itextBackgroundImage;
try {
itextBackgroundImage = Image.getInstance(backgroundImage);
}
catch (Exception e) {
throw new SPIIllegalDataException("Failed parsing PDF background image: "+e.getMessage(), e);
}
sap.setImage(itextBackgroundImage);
sap.setImageScale(signatureLayoutParameters.getBackgroundImageScale());
}
int sigRenderMode = signatureLayoutParameters.getSigRenderMode();
byte[] signatureImage = null;
if(sigRenderMode==PdfSignatureAppearance.SignatureRenderGraphicAndDescription) {
signatureImage = signatureLayoutParameters.getSignatureImage();
if(signatureImage!=null && signatureImage.length > 0) {
Image itextImage;
try {
itextImage = Image.getInstance(signatureImage);
}
catch (Exception e) {
throw new SPIIllegalDataException("Failed parsing PDF signature image: "+e.getMessage(), e);
}
sap.setSignatureGraphic(itextImage);
} else throw new SPIIllegalDataException("Cannot use graphic+description signature render mode without providing a graphic");
} else if(sigRenderMode==PdfSignatureAppearance.SignatureRenderNameAndDescription) {
if(signerCert==null) throw new SPIIllegalArgumentException("Cannot use name+description signature render mode without providing the signer certificate");
}
sap.setAcro6Layers(true);
sap.setRender(sigRenderMode);
int fontFamily = signatureLayoutParameters.getFontFamily();
int fontStyle = signatureLayoutParameters.getFontStyle();
float fontSize= signatureLayoutParameters.getFontSize();
Color fontColor = signatureLayoutParameters.getFontColor();
if(parameters.isKeepPDFACompliance()) {
// Test if images with transparency are used, which would break PDF/A compliance
if(backgroundImage!=null) {
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(backgroundImage));
if(bi.getTransparency()!=Transparency.OPAQUE) throw new SPIIllegalArgumentException("Cannot use background image with transparency when trying to keep PDF/A compliance");
}
if(signatureImage!=null) {
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(signatureImage));
if(bi.getTransparency()!=Transparency.OPAQUE) throw new SPIIllegalArgumentException("Cannot use signature image with transparency when trying to keep PDF/A compliance");
}
// Embed a look-alike font
//TODO : see if we could reuse an embedded font instead of a replacement (embedded fully OR with all the characters we need), see PDFFontHelper
String replacementFont = "dejavusans"; // fontFamily==Font.HELVETICA
if(fontFamily==Font.COURIER) replacementFont = "dejavusansmono";
else if(fontFamily==Font.TIMES_ROMAN) replacementFont = "dejavuserif";
//TODO : no 'SYMBOL' or 'ZAPFDINGBATS' replacement, using 'dejavusans'
/*for (String f : (Set<String>)FontFactory.getRegisteredFonts()) {
System.out.println("registered font : "+f);;
}*/
Font font = FontFactory.getFont(replacementFont, BaseFont.CP1252, BaseFont.EMBEDDED, fontSize, fontStyle, fontColor);
sap.setLayer2Font(font);
} else {
sap.setLayer2Font(new Font(fontFamily,fontSize,fontStyle, fontColor));
}
} else {
log.debug(Channel.TECH, "Invisible signature");
}
// seems only useful for visible signatures when no layer2Text (description) has been set. And in this case only certs is used
sap.setCrypto(null, new Certificate[] {signerCert}, null, null);
String reason = parameters.getReason();
String location = parameters.getLocation();
String contact = parameters.getContact();
PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.FT, PdfName.SIG);
dic.put(PdfName.FILTER, parameters.getFilter());
if(isPadesEnhancedLevel) dic.put(PdfName.SUBFILTER, new PdfName(PDFEnvelopedSignature.SF_ETSI_CADES_DETACHED));
else dic.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
dic.put(PdfName.M, new PdfDate(presignDate));
// We do not add a dic.put(PdfName.NAME, "XXX")) because it should only
// be used when "it is not possible to extract the name from the
// signature" (PDF reference), which is not the case here
//if (!StringHelper.isNullOrEmpty(location))
if (location != null && location.length() > 0)
dic.put(PdfName.LOCATION, new PdfString(location, PdfObject.TEXT_UNICODE)); // sap.setLocation doesn't work if a criptodictionary is specified
if (reason != null && reason.length() > 0)
dic.put(PdfName.REASON, new PdfString(reason, PdfObject.TEXT_UNICODE)); // sap.setReason doesn't work if a criptodictionary is specified
if (contact != null && contact.length() > 0)
dic.put(PdfName.CONTACTINFO, new PdfString(contact, PdfObject.TEXT_UNICODE)); // sap.setContact doesn't work if a criptodictionary is specified
PdfDictionary buildDic = new PdfDictionary();
PdfDictionary buildDataDic = new PdfDictionary();
buildDataDic.put(new PdfName("Name"), parameters.getFilter());
buildDic.put(new PdfName("Filter"), buildDataDic);
buildDataDic = new PdfDictionary();
buildDataDic.put(new PdfName("Name"), new PdfName(PRODUCED_BY));
//TODO : see if version can be retrieved from com.opentrust.spi.securityserver.install.ProductInfo
//TODO : see if we can also add SPI platform ID
//buildDataDic.put(new PdfName("REx"), new PdfString("2.2.1", PdfObject.TEXT_UNICODE));
buildDic.put(new PdfName("App"), buildDataDic);
dic.put(new PdfName("Prop_Build"), buildDic);
if (parameters.getCertifLevel() > 0) {
// By changing TRANSFORMMETHOD parameters and the transformparams
// dictionary, it is possible to sign parts only of the document.
// See TransformMethod = DocMDP, UR, FieldMDP or Identity in PDF reference
// we only use DocMDP for now
PdfDictionary transformParams = new PdfDictionary();
transformParams.put(PdfName.P, new PdfNumber(parameters.getCertifLevel()));
transformParams.put(PdfName.V, new PdfName("1.2")); // 1.2 is the default value for the "DocMDP transform parameters dictionary version". "1.2" is the value to be used for PDF 1.5 and later
transformParams.put(PdfName.TYPE, PdfName.TRANSFORMPARAMS);
PdfDictionary reference = new PdfDictionary();
reference.put(PdfName.TRANSFORMMETHOD, PdfName.DOCMDP);
reference.put(PdfName.TYPE, PdfName.SIGREF);
reference.put(PdfName.TRANSFORMPARAMS, transformParams);
PdfArray types = new PdfArray();
types.add(reference);
dic.put(PdfName.REFERENCE, types);
}
sap.setCryptoDictionary(dic);
sap.setSignDate(presignDate);
LinkedHashMap exc = new LinkedHashMap();
//TODO implement content size calculation base on CRL size (and OCSP response size)
int content_size = CONTENT_SIZE;
if(parameters.getSignatureContainerSize()>0) content_size = parameters.getSignatureContainerSize();
int ts_size = TIMESTAMP_SIZE;
if(parameters.getTimeStampContainerSize()>0) ts_size = parameters.getTimeStampContainerSize();
if(parameters.isAllocateTimeStampContainer() || parameters.getTimeStampParams()!=null) {
log.debug(Channel.TECH, "adding %1$s to content_size(%2$s) because of timestamping", ts_size, content_size);
content_size += ts_size;
}
int hexblock_size = content_size*2+2;
log.debug(Channel.TECH, "Prepared signing with content_size=%1$s bytes (%2$s real bytes once hex-encoded)", content_size, hexblock_size);
exc.put(PdfName.CONTENTS, new Integer(hexblock_size));
sap.preClose(exc);
if(log.isDebugEnabled(Channel.TECH)) {
log.debug(Channel.TECH, "PDF Prepared for signature. ByteRange is %1$s", Arrays.toString(sap.getRange()));
}
return stp;
}
private static void checkSignaturePosition(PdfReader reader, float x1, float y1, float x2, float y2, int pageNbr)
throws Exception {
Rectangle pageRectangle = reader.getPageSizeWithRotation(pageNbr);
Rectangle signatureRectangle = new Rectangle(x1, y1, x2, y2);
if (!pageRectangle.contains(signatureRectangle))
{
SPIIllegalDataException exception = new SPIIllegalDataException(
"pikachu was here Given signature rectangle %s doesn't fit into page rectangle: %s",
signatureRectangle.getCoordinates(), pageRectangle.getCoordinates());
throw exception;
}
}
public static class SignResult {
private final byte[] encodedPkcs7;
private final TimestampToken timestampToken;
public SignResult(byte[] encodedPkcs7, TimestampToken timestampToken) {
this.encodedPkcs7 = encodedPkcs7;
this.timestampToken = timestampToken;
}
public byte[] getEncodedPkcs7() {
return encodedPkcs7;
}
public TimestampToken getTimestampToken() {
return timestampToken;
}
}
public static class SignReturn {
private final TimestampToken timestampToken;
private final byte[] pkcs7SignatureValue;
private final String signatureName;
public SignReturn(String signatureName, byte[] pkcs7SignatureValue, TimestampToken timestampToken) {
super();
this.signatureName = signatureName;
this.pkcs7SignatureValue=pkcs7SignatureValue;
this.timestampToken=timestampToken;
}
/**
* null if no timestamp was added
*/
public TimestampToken getTimestampToken() {
return timestampToken;
}
public byte[] getPkcs7SignatureValue() {
return pkcs7SignatureValue;
}
public String getSignatureName() {
return signatureName;
}
}
public static class PresignReturn {
private InputStream dataToSign;
private final String signatureName;
private byte[] hashToSign;
private final PdfSignatureAppearance pdfSignatureAppearance;
public PresignReturn(InputStream dataToSign, String signatureName) {
this(dataToSign, signatureName, null);
}
public PresignReturn(InputStream dataToSign, String signatureName, PdfSignatureAppearance pdfSignatureAppearance) {
super();
this.dataToSign = dataToSign;
this.signatureName = signatureName;
this.pdfSignatureAppearance = pdfSignatureAppearance;
}
public InputStream getDataToSign() {
return dataToSign;
}
public String getSignatureName() {
return signatureName;
}
public byte[] getHashToSign() {
return hashToSign;
}
public void setHashToSign(byte[] hashToSign) {
this.hashToSign = hashToSign;
}
public PdfSignatureAppearance getPdfSignatureAppearance() {
return pdfSignatureAppearance;
}
}
public static class PresignReturnForRawSignature {
private final String signatureName;
private final byte[] hashToSign;
private final byte[] encodedPkcs7WithoutSignature;
public PresignReturnForRawSignature(byte[] hashToSign, byte[] encodedPkcs7WithoutSignature, String signatureName) {
super();
this.hashToSign = hashToSign;
this.encodedPkcs7WithoutSignature = encodedPkcs7WithoutSignature;
this.signatureName = signatureName;
}
public String getSignatureName() {
return signatureName;
}
public byte[] getHashToSign() {
return hashToSign;
}
public byte[] getEncodedPkcs7WithoutSignature() {
return encodedPkcs7WithoutSignature;
}
}
/**
* Flatten a PDF document (convert cryptographic signature field into flat image).
*
* @param pdfIs input stream containing the PDF document. The Stream is read to the end but not closed.
* @param flattenPdfOs output stream containing the flattened PDF document
* @throws IOException
* @throws DocumentException
*/
public static void flattenPdf(InputStream pdfIs, OutputStream flattenPdfOs) throws IOException, DocumentException {
PdfReader pdfReader = new PdfReader(pdfIs);
PdfStamper pdfStamper = new PdfStamper(pdfReader, flattenPdfOs);
pdfStamper.setFormFlattening(true);
pdfStamper.close();
pdfReader.close();
}
}