package com.opentrust.spi.pdf;
import java.util.ArrayList;
import java.util.List;
import org.bouncycastle.asn1.ess.ESSCertID;
import org.bouncycastle.asn1.ess.ESSCertIDv2;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import com.opentrust.spi.logger.Channel;
import com.opentrust.spi.logger.SPILogger;
public class PAdESHelper {
private static SPILogger log = SPILogger.getLogger("PDFSIGN");
/**
* Checks whether this PDF signature conforms to ISO 32000-1
* return a list of error messages, or empty list when no error
*/
public static List<String> isISO32000_1Conformant(PDFEnvelopedSignature pdfSignature, boolean fastFail, boolean excludePAdESV3Overrides) {
List<String> conformanceReport = new ArrayList<String>();
//FIXME : implement. For now, mainly covered in the building process of PDFEnvelopedSignature object,
// which would fail with bouncycastle.CMSException if signature was not ISO 32000-1 compliant
//ISO32000_1, 12.8.1, Signature dictionary 'Cert' entry:
// If SubFilter is adbe.pkcs7.detached or adbe.pkcs7.sha1, this entry shall not be used, and the certificate chain shall be put in the PKCS#7 envelope in Contents.
String subFilter = pdfSignature.getSubFilter();
if((PDFEnvelopedSignature.SF_ADBE_PKCS7_DETACHED.equals(subFilter) || PDFEnvelopedSignature.SF_ADBE_PKCS7_SHA1.equals(subFilter))
&& pdfSignature.getDictionaryCert()!=null) {
addErrorToReport(conformanceReport, "Signature dictionay 'Cert' entry should not be used with subFilter '%1$s'", subFilter);
if(fastFail) return conformanceReport;
}
return conformanceReport;
}
/**
* Checks whether this PDF signature conforms to PAdES 'v2' (or 'basic' or 'cms')
* Refers to ETSI TS 102 778-2
*/
public static List<String> isPAdESBasicConformant(PDFEnvelopedSignature pdfSignature, boolean fastFail) {
// 5.1.a - PAdES basic comes on top of ISO 32000
List<String> conformanceReport = isISO32000_1Conformant(pdfSignature, fastFail, false);
if(fastFail && !conformanceReport.isEmpty()) return conformanceReport;
// 5.1.b - ByteRange shall be the entire file, excluding the PDF signature
// (other 5.1.b requirements are covered by parsing performed in iText AcroFields class)
if(pdfSignature.getByteRange()[0]!=0 || pdfSignature.getRevisionLength()!=(pdfSignature.getByteRange()[2]+pdfSignature.getByteRange()[3])) {
addErrorToReport(conformanceReport, "Signature does not cover the whole document");
if(fastFail) return conformanceReport;
}
// 5.1.c - PDF signature is PKCS#7
// (other 5.1.c requirements are covered by parsing performed in iText AcroFields class)
if(pdfSignature.getSubFilter()==null || PDFEnvelopedSignature.SF_ADBE_X509_RSA_SHA1.equals(pdfSignature.getSubFilter())) {
addErrorToReport(conformanceReport, "Signature not a PKCS#7 (subFilter '%1$s')", pdfSignature.getSubFilter());
if(fastFail) return conformanceReport;
}
// (5.1.d - PKCS#7 conforms to RFC 2315 : covered by spi-cms module)
// 5.1.d - PKCS#7 shall include signing certificate
if(pdfSignature.getSigningCertificate()==null) {
addErrorToReport(conformanceReport, "PKCS#7 does not contain signer Certificate");
if(fastFail) return conformanceReport;
}
// 5.1.e - Timestamping and revocation information should be included in the PDF signature
//TODO : log warning if no TS & revocation info are found (revocation info for the chain)
// 5.1.f - Any revocation information shall be a signed attribute
//FIXME : see if we should consider the signature invalid, or only log a warning, when revocation info is found as unsigned property ?
// 5.1.g - Use of RFC 3281 attribute certificates is not recommended
//TODO : log warning if such attribute certificates are found
// 5.1.h - The shall only be one SignerInfo : already covered in PDFEnvelopedSignature constructor)
// 5.2.a - ?
// 5.2.b - Only two values supported for SubFilter
if(!PDFEnvelopedSignature.SF_ADBE_PKCS7_DETACHED.equals(pdfSignature.getSubFilter()) && !!PDFEnvelopedSignature.SF_ADBE_PKCS7_SHA1.equals(pdfSignature.getSubFilter())) {
addErrorToReport(conformanceReport, "Signature subFilter '%1$s' not supported", pdfSignature.getSubFilter());
if(fastFail) return conformanceReport;
}
// 5.3.a - Verify that the document digest matches that in the signature : dealt with outside this module (spi-dssl for instance, by calling pdfSignature.verify())
// 5.3.b - Validate the path of certificates... : dealt with outside this module (spi-dssl for instance)
// 5.4 & 5.5 & 5.6 : dealt with outside this module (not yet for 5.6)
return conformanceReport;
}
/**
* Checks whether this PDF signature conforms to PAdES 'v3' BES
* Refers to ETSI TS 102 778-3
*/
public static List<String> isPAdESBESConformant(PDFEnvelopedSignature pdfSignature, boolean fastFail) {
return isPAdESBESConformant(pdfSignature, fastFail, false);
}
/**
* Checks whether this PDF signature conforms to PAdES 'v3' BES
* Refers to ETSI TS 102 778-3
*/
private static List<String> isPAdESBESConformant(PDFEnvelopedSignature pdfSignature, boolean fastFail, boolean isForEPES) {
// 4.2.a - ISO 32000-1 except where overridden by PAdES v3
List<String> conformanceReport = isISO32000_1Conformant(pdfSignature, fastFail, true);
// 4.2.b - DER-encoded CMS SignedData object in the key Content of the signature dictionary : covered by parsing performed in iText AcroFields class + 4.2.e
// 4.2.c - ByteRange shall be the entire file, excluding the PDF signature
if(pdfSignature.getByteRange()[0]!=0 || pdfSignature.getRevisionLength()!=(pdfSignature.getByteRange()[2]+pdfSignature.getByteRange()[3])) {
addErrorToReport(conformanceReport, "Signature does not cover the whole document");
if(fastFail) return conformanceReport;
}
// 4.2.d - ISO 32000-1, 12.8.3.2 & 12.8.3.3 do not apply
// 4.2.e - SubFilter shall be ETSI.CAdES.detached
if(!PDFEnvelopedSignature.SF_ETSI_CADES_DETACHED.equals(pdfSignature.getSubFilter())) {
addErrorToReport(conformanceReport, "Signature subFilter '%1$s' not supported", pdfSignature.getSubFilter());
if(fastFail) return conformanceReport;
}
// 4.2.f - ?
// 4.2.g - signature dictionary shall not contain Cert entry
if(pdfSignature.getDictionaryCert()!=null) {
addErrorToReport(conformanceReport, "Signature shall not contain Cert entry");
if(fastFail) return conformanceReport;
}
// 4.2.h - Unsigned signature attributes may be ignored : OK...
// 4.2.i - A timestamp should be applied immediately after the signature is created : OK...
// 4.3 - only a single SignerInfo : already covered in PDFEnvelopedSignature constructor
// 4.4.1 - content-type attribute mandatory with value "id-data"
// content-type attribute presence and equality to eContentInfo.contentype already a requirement of CMS RFC : covered by CMSSignature class
if(!PKCSObjectIdentifiers.data.equals(pdfSignature.getContentTypeAttribute())) {
addErrorToReport(conformanceReport, "content-type attribute should be '%1$s' but is '%2$s'", PKCSObjectIdentifiers.data, pdfSignature.getCmsContentType());
if(fastFail) return conformanceReport;
}
// 4.4.2 - message-digest attribute shall be used
if(pdfSignature.getDigest()==null) {
addErrorToReport(conformanceReport, "message-digest attribute is missing");
if(fastFail) return conformanceReport;
}
// 4.4.3 - signing-certificate (when SHA-1 is used) or signing-certificate-v2 (when any other than SHA-1 is used) shall be used
ESSCertID signingCertificateAttribute = pdfSignature.getSigningCertificateAttribute();
ESSCertIDv2 signingCertificateV2Attribute = pdfSignature.getSigningCertificateV2Attribute();
if(signingCertificateAttribute==null && signingCertificateV2Attribute==null) {
addErrorToReport(conformanceReport, "both signing-certificate and signing-certificate-v2 attributes are missing");
if(fastFail) return conformanceReport;
} else if(signingCertificateAttribute!=null && !"SHA1".equals(pdfSignature.getDataDigestAlgorithm())){
addErrorToReport(conformanceReport, "signing-certificate should not be used with "+pdfSignature.getDataDigestAlgorithm());
if(fastFail) return conformanceReport;
} else if(signingCertificateV2Attribute!=null && "SHA1".equals(pdfSignature.getDataDigestAlgorithm())){
addErrorToReport(conformanceReport, "signing-certificate-v2 should not be used with SHA1");
if(fastFail) return conformanceReport;
}
// 4.5.1 - (see EPES validation)
// 4.5.2 - signature-time-stamp should be present
//TODO : log warning if no TS ?
// 4.5.3 - signing-time attribute shall not be used
if(pdfSignature.getCmsSignDate()!=null) {
addErrorToReport(conformanceReport, "signing-time CMS attribute shall not be used");
if(fastFail) return conformanceReport;
}
// 4.5.4 - counter-signature attribute shall not be used
if(pdfSignature.getCounterSignatures()!=null && pdfSignature.getCounterSignatures().size()!=0) {
addErrorToReport(conformanceReport, "counter-signature CMS attribute shall not be used");
if(fastFail) return conformanceReport;
}
// 4.5.5 - content-reference attribute shall not be used
if(pdfSignature.getContentReferenceAttribute()!=null) {
addErrorToReport(conformanceReport, "content-reference CMS attribute shall not be used");
if(fastFail) return conformanceReport;
}
// 4.5.6 - content-identifier attribute shall not be used
if(pdfSignature.getContentIdentifierAttribute()!=null) {
addErrorToReport(conformanceReport, "content-identifier CMS attribute shall not be used");
if(fastFail) return conformanceReport;
}
// 4.5.7 - content-hints attribute shall not be used
if(pdfSignature.getContentHintsAttribute()!=null) {
addErrorToReport(conformanceReport, "content-hints CMS attribute shall not be used");
if(fastFail) return conformanceReport;
}
// 4.5.8 - commitment-type-indication may be present for EPES. Shall not be present for BES
if(!isForEPES && pdfSignature.getCommitmentTypeIndicationAttribute()!=null) {
addErrorToReport(conformanceReport, "commitment-type-indication CMS attribute shall not be used");
if(fastFail) return conformanceReport;
}
// 4.5.9 - signer-location attribute shall not be present
if(pdfSignature.getSignerLocationAttribute()!=null) {
addErrorToReport(conformanceReport, "signer-location CMS attribute shall not be used");
if(fastFail) return conformanceReport;
}
//4.7 - extensions dictionary
//FIXME : check extensions dictionary contains ESIC/1.7/3
return conformanceReport;
}
/**
* Checks whether this PDF signature conforms to PAdES 'v3' EPES
* Refers to ETSI TS 102 778-3
*/
public static List<String> isPAdESEPESConformant(PDFEnvelopedSignature pdfSignature, boolean fastFail) {
List<String> conformanceReport = isPAdESBESConformant(pdfSignature, fastFail, true);
// 4.5.1 - signature-policy-identifier attribute shall be present
if(pdfSignature.getSignaturePolicyIdentifierAttribute()==null) {
addErrorToReport(conformanceReport, "signature-policy-identifier attribute is missing");
if(fastFail) return conformanceReport;
}
return conformanceReport;
}
private static void addErrorToReport(List<String> conformanceReport, String error, Object... params) {
String formattedString = String.format(error, params);
log.debug(Channel.TECH, formattedString);
conformanceReport.add(formattedString);
}
}