/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS
* IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.icepdf.core.pobjects.acroform.signature;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.jce.provider.X509CertParser;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.x509.util.StreamParsingException;
import org.icepdf.core.io.SeekableInput;
import org.icepdf.core.pobjects.Name;
import org.icepdf.core.pobjects.acroform.SignatureDictionary;
import org.icepdf.core.pobjects.acroform.SignatureFieldDictionary;
import org.icepdf.core.pobjects.acroform.signature.certificates.CertificateVerifier;
import org.icepdf.core.pobjects.acroform.signature.exceptions.CertificateVerificationException;
import org.icepdf.core.pobjects.acroform.signature.exceptions.RevocationVerificationException;
import org.icepdf.core.pobjects.acroform.signature.exceptions.SelfSignedVerificationException;
import org.icepdf.core.pobjects.acroform.signature.exceptions.SignatureIntegrityException;
import org.icepdf.core.util.Defs;
import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* PKCS#1 and PKCS#7 are fairly close from a verification point of view so we'll use this class for common
* functionality between PKCS#1 and PKCS#7.
*/
public abstract class AbstractPkcsValidator implements SignatureValidator {
private static final Logger logger =
Logger.getLogger(AbstractPkcsValidator.class.toString());
private static String caCertLocation = "/lib/security/cacerts";
static {
String javaHome = Defs.sysProperty("java.home");
caCertLocation = Defs.sysProperty("org.icepdf.core.signatures.caCertPath", javaHome + caCertLocation);
}
// data object descriptor codes.
public static final String ID_DATA_OBJECT_IDENTIFIER = PKCSObjectIdentifiers.data.getId();
public static final String ID_SIGNED_DATA_OBJECT_IDENTIFIER = PKCSObjectIdentifiers.signedData.getId();
public static final String ID_ENVELOPED_DATA_OBJECT_IDENTIFIER = PKCSObjectIdentifiers.envelopedData.getId();
public static final String ID_DIGESTED_DATA_OBJECT_IDENTIFIER = PKCSObjectIdentifiers.digestedData.getId();
public static final String ID_ENCRYPTED_DATA_OBJECT_IDENTIFIER = PKCSObjectIdentifiers.encryptedData.getId();
private static final String ALGORITHM_WITH = "with";
// signature dictionary of signature do verify
protected SignatureFieldDictionary signatureFieldDictionary;
// signer certificate
protected Collection<Certificate> certificateChain;
protected X509Certificate signerCertificate;
// digests used for verification
protected String digestAlgorithmIdentifier;
protected String signatureAlgorithmIdentifier;
// PKCS
protected ASN1Set signedAttributesSequence;
protected byte[] encapsulatedContentInfoData;
protected byte[] messageDigest;
protected byte[] signatureValue;
// validity checks.
private boolean isSignedDataModified = true;
private boolean isDocumentDataModified;
private boolean isSignaturesCoverDocumentLength;
private boolean isCertificateChainTrusted;
private boolean isCertificateDateValid = true;
private boolean isRevocation;
private boolean isSelfSigned;
// todo impelement singer time check.
private boolean isSignerTimeValid;
private boolean isEmbeddedTimeStamp;
// last time validate call was made.
private Date lastVerified;
protected boolean initialized;
public AbstractPkcsValidator(SignatureFieldDictionary signatureFieldDictionary) throws SignatureIntegrityException {
this.signatureFieldDictionary = signatureFieldDictionary;
if (!initialized) {
init();
}
}
/**
* Runs a series of tests to try and determine the validity o
*
* @throws SignatureIntegrityException
*/
public abstract void validate() throws SignatureIntegrityException;
protected void announceSignatureType(SignatureDictionary signatureDictionary) {
if (logger.isLoggable(Level.FINE)) {
Name preferredHandler = signatureDictionary.getFilter();
Name encoding = signatureDictionary.getSubFilter();
if (logger.isLoggable(Level.FINER)) {
logger.finer("Signature Handler: " + preferredHandler);
logger.finer(" Encoding: " + encoding);
logger.finer("Starting Validation");
}
}
}
/**
* SignedData ::= SEQUENCE {
* 0, version CMSVersion,
* 1, digestAlgorithms DigestAlgorithmIdentifiers,
* 2, encapContentInfo EncapsulatedContentInfo,
* 3, certificateChain [0] IMPLICIT CertificateSet OPTIONAL,
* 4, crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
* 5, signerInfos SignerInfos }
* <br>
* DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
* SignerInfos ::= SET OF SignerInfo
*/
protected void parseSignerData(ASN1Sequence signedData, byte[] cmsData) throws SignatureIntegrityException {
// digest algorithms ID, not currently using them but useful for debug.
if (logger.isLoggable(Level.FINER)) {
// should always be 1.
int cmsVersion = ((ASN1Integer) signedData.getObjectAt(0)).getValue().intValue();
logger.finest("CMS version: " + cmsVersion);
Enumeration<ASN1Sequence> enumeration = ((ASN1Set) signedData.getObjectAt(1)).getObjects();
while (enumeration.hasMoreElements()) {
String objectId = ((ASN1ObjectIdentifier) enumeration.nextElement().getObjectAt(0)).getId();
try {
String digestAlgorithmName = AlgorithmIdentifier.getDigestAlgorithmName(objectId);
MessageDigest tmp = AlgorithmIdentifier.getDigestInstance(objectId, null);
logger.finest("DigestAlgorithmIdentifiers: " + digestAlgorithmName + " " + objectId);
logger.finest(tmp.toString());
} catch (Throwable ex) {
logger.log(Level.WARNING, "Error finding iod: " + objectId, ex);
}
}
}
/**
* EncapsulatedContentInfo ::= SEQUENCE {
* eContentType ContentType,
* eContent [0] EXPLICIT OCTET STRING OPTIONAL }
*
* ContentType ::= OBJECT IDENTIFIER
*/
encapsulatedContentInfoData = null;
ASN1Sequence encapsulatedContentInfo = (ASN1Sequence) signedData.getObjectAt(2);
// grab just the first definitions, as we are looking for encapuslated data for PKCS7.sha1.
if (encapsulatedContentInfo.size() >= 2) {
// should still be iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 1 ...
ASN1ObjectIdentifier eObjectIdentifier = (ASN1ObjectIdentifier) encapsulatedContentInfo.getObjectAt(0);
String eObjectIdentifierId = eObjectIdentifier.getId();
if (logger.isLoggable(Level.FINER)) {
logger.finest("EncapsulatedContentInfo: " + eObjectIdentifierId + " " +
Pkcs7Validator.getObjectIdName(eObjectIdentifierId));
}
// should be octets encode as pkcs#7
ASN1OctetString eContent = (ASN1OctetString) ((ASN1TaggedObject) encapsulatedContentInfo.getObjectAt(1))
.getObject();
// shows up in pkcs7.sha1 only
encapsulatedContentInfoData = eContent.getOctets();
if (logger.isLoggable(Level.FINER)) {
logger.finest("EncapsulatedContentInfo Data " + eContent.toString());
}
} else if (encapsulatedContentInfo.size() == 1) {
if (logger.isLoggable(Level.FINER)) {
ASN1ObjectIdentifier eObjectIdentifier = (ASN1ObjectIdentifier) encapsulatedContentInfo.getObjectAt(0);
String eObjectIdentifierId = eObjectIdentifier.getId();
logger.finest("EncapsulatedContentInfo size is 1: " + eObjectIdentifierId + " " +
Pkcs7Validator.getObjectIdName(eObjectIdentifierId));
}
}
// grab the signer info.
ASN1Sequence signerInfo = parseCertificateData(cmsData, signedData);
// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
digestAlgorithmIdentifier = ((ASN1ObjectIdentifier)
((ASN1Sequence) signerInfo.getObjectAt(2)).getObjectAt(0)).getId();
// signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
// signedAttrs is optional so we look for the occurrence
//
// SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
//
// Attribute ::= SEQUENCE {
// attrType OBJECT IDENTIFIER,
// attrValues SET OF AttributeValue }
//
// AttributeValue ::= ANY
// SignatureValue ::= OCTET STRING
int nextEntry = 3;
messageDigest = null;
ASN1TaggedObject signedAttributes;
signedAttributesSequence = null;
if (signerInfo.getObjectAt(nextEntry) instanceof ASN1TaggedObject) {
signedAttributes = (ASN1TaggedObject) signerInfo.getObjectAt(nextEntry);
signedAttributesSequence = ASN1Set.getInstance(signedAttributes, false);
for (int i = 0, max = signedAttributesSequence.size(); i < max; ++i) {
// attribute type/value pair.
ASN1Sequence attributePair = (ASN1Sequence) signedAttributesSequence.getObjectAt(i);
// mainly just looking for the message digest.
if (((ASN1ObjectIdentifier) attributePair.getObjectAt(0)).getId().equals(
PKCSObjectIdentifiers.pkcs_9_at_messageDigest.getId())) {
ASN1Set set = (ASN1Set) attributePair.getObjectAt(1);
messageDigest = ((ASN1OctetString) set.getObjectAt(0)).getOctets();
}
// try and pull out the signing time.
// currently not using this time.
// if (((ASN1ObjectIdentifier) attributePair.getObjectAt(0)).getId().equals(
// PKCSObjectIdentifiers.pkcs_9_at_signingTime.getId())) {
// ASN1Set set = (ASN1Set) attributePair.getObjectAt(1);
// ASN1UTCTime signerTime = ((ASN1UTCTime) set.getObjectAt(0));
// try {
// // see if the signer time matches the certificate validity times.
// System.out.println(" SignatureSigner Time " + signerTime.getDate());
// } catch (ParseException e) {
// e.printStackTrace();
// }
// }
// more attributes to come.
}
if (messageDigest == null) {
throw new SignatureIntegrityException("Message Digest can nut be null");
}
++nextEntry;
}
// signatureAlgorithm SignatureAlgorithmIdentifier,
signatureAlgorithmIdentifier = ((ASN1ObjectIdentifier) ((ASN1Sequence) signerInfo.getObjectAt(nextEntry))
.getObjectAt(0)).getId();
nextEntry++;
// signature SignatureValue
signatureValue = ((ASN1OctetString) signerInfo.getObjectAt(nextEntry)).getOctets();
nextEntry++;
// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL
// once again optional so we check to see if the entry is available.
if (nextEntry < signerInfo.size() && signerInfo.getObjectAt(nextEntry) instanceof ASN1TaggedObject) {
ASN1TaggedObject unsignedAttributes = (ASN1TaggedObject) signerInfo.getObjectAt(nextEntry);
ASN1Set unsignedAttributeSequence = ASN1Set.getInstance(unsignedAttributes, false);
AttributeTable attributeTable = new AttributeTable(unsignedAttributeSequence);
Attribute timeStamp = attributeTable.get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
if (timeStamp != null && timeStamp.getAttrValues().size() > 0) {
ASN1Set attributeValues = timeStamp.getAttrValues();
ASN1Sequence tokenSequence = ASN1Sequence.getInstance(attributeValues.getObjectAt(0));
ContentInfo contentInfo = ContentInfo.getInstance(tokenSequence);
// if we can parse it we call it good, so cert has a embedded time but we don't do any validation on it
try {
new TimeStampToken(contentInfo);
isEmbeddedTimeStamp = true;
} catch (Throwable e1) {
throw new SignatureIntegrityException("Valid TimeStamp could now be created");
}
}
}
}
private ASN1Sequence parseCertificateData(byte[] cmsData, ASN1Sequence signedData) throws SignatureIntegrityException {
// Next two entries are optional.
// 3, certificateChain [0] IMPLICIT CertificateSet OPTIONAL,
// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
// Most of our example seem to have what looks like a CertificateSet but I haven't had much luck finding
// a specific format to follow ot parse out the data.
// CertificateSet is defined as:<br>
// The CertificateSet type provides a set of certificateChain. It is
// intended that the set be sufficient to contain certification paths
// from a recognized "root" or "top-level certification authority" to
// all of the sender certificateChain with which the set is associated.
// However, there may be more certificateChain than necessary, or there MAY
// be fewer than necessary.
// <br>
// The precise meaning of a "certification path" is outside the scope of
// this document. However, [PROFILE] provides a definition for X.509
// certificateChain. Some applications may impose upper limits on the
// length of a certification path; others may enforce certain
// relationships between the subjects and issuers of certificateChain within
// a certification path.
// <br>
// Object tmp = signedData.getObjectAt(3);
// the certificateChain
X509CertParser x509CertParser = new X509CertParser();
x509CertParser.engineInit(new ByteArrayInputStream(cmsData));
try {
certificateChain = x509CertParser.engineReadAll();
} catch (StreamParsingException e) {
logger.log(Level.WARNING, "Error parsing certificate data: ", e);
throw new SignatureIntegrityException("Error parsing certificate data ");
}
/**
* SignerInfo ::= SEQUENCE {
* 0, version CMSVersion,
* 1, sid SignerIdentifier,
* 2, digestAlgorithm DigestAlgorithmIdentifier,
* signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
* signatureAlgorithm SignatureAlgorithmIdentifier,
* signature SignatureValue,
* unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
*/
// the signerInfos is going to be the last entry in the sequence.
ASN1Set signerInfos = (ASN1Set) signedData.getObjectAt(signedData.size() - 1);
// and we only need the first entry, as enveloped signatures aren't found in PDF land.
ASN1Sequence signerInfo = (ASN1Sequence) signerInfos.getObjectAt(0);
// If the SignerIdentifier is the CHOICE issuerAndSerialNumber, then the version MUST be 1.
// If the SignerIdentifier is subjectKeyIdentifier, then the version MUST be 3.
int signerVersion = ((ASN1Integer) signerInfo.getObjectAt(0)).getValue().intValue();
/**
* SignerIdentifier ::= CHOICE {
* issuerAndSerialNumber IssuerAndSerialNumber,
* subjectKeyIdentifier [0] SubjectKeyIdentifier }
*
* SubjectKeyIdentifier ::= OCTET STRING
*/
ASN1Sequence issuerAndSerialNumber = (ASN1Sequence) signerInfo.getObjectAt(1);
signerCertificate = null;
if (signerVersion == 1) {
// parse out the issue and SerialNumber.
X500Principal issuer;
try {
issuer = new X500Principal(issuerAndSerialNumber.getObjectAt(0).toASN1Primitive().getEncoded());
} catch (IOException e1) {
logger.warning("Could not create X500 Principle data ");
throw new SignatureIntegrityException("Could not create X500 Principle data");
}
BigInteger serialNumber = ((ASN1Integer) issuerAndSerialNumber.getObjectAt(1)).getValue();
signerCertificate = null;
// signer cert should always be the first in the list.
for (Object element : certificateChain) {
X509Certificate certificate = (X509Certificate) element;
if (certificate.getIssuerX500Principal().equals(issuer) &&
serialNumber.equals(certificate.getSerialNumber())) {
signerCertificate = certificate;
break;
} else {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Certificate and issuer could not be verified as the same entity.");
}
}
}
} else if (signerVersion == 3) {
// SubjectKeyIdentifier ::= OCTET STRING
// ASN1Primitive subjectKeyIdentifier = issuerAndSerialNumber.getObjectAt(0).toASN1Primitive();
throw new IllegalStateException("Singer version 3 not supported");
}
return signerInfo;
}
/**
* The CMS associates a content type identifier with a content. The syntax MUST have ASN.1 type ContentInfo:
* ContentInfo ::= SEQUENCE {
* contentType ContentType,
* content [0] EXPLICIT ANY DEFINED BY contentType }
* ContentType ::= OBJECT IDENTIFIER
*/
protected ASN1Sequence captureSignedData(byte[] cmsData)
throws SignatureIntegrityException {
ASN1Sequence cmsSequence = buildASN1Primitive(cmsData);
if (cmsSequence == null || cmsSequence.getObjectAt(0) == null) {
throw new SignatureIntegrityException("ContentInfo does not contain content type.");
}
/**
* id-data OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 1 }
* Currently not doing anything with this but we may need it at a later date to support different signed data.
* But we are looking pkcs7 variants.
*/
ASN1ObjectIdentifier objectIdentifier = (ASN1ObjectIdentifier) cmsSequence.getObjectAt(0);
if (objectIdentifier == null ||
!(ID_DATA_OBJECT_IDENTIFIER.equals(objectIdentifier.getId()) ||
ID_DIGESTED_DATA_OBJECT_IDENTIFIER.equals(objectIdentifier.getId()) ||
ID_ENCRYPTED_DATA_OBJECT_IDENTIFIER.equals(objectIdentifier.getId()) ||
ID_ENVELOPED_DATA_OBJECT_IDENTIFIER.equals(objectIdentifier.getId()) ||
ID_SIGNED_DATA_OBJECT_IDENTIFIER.equals(objectIdentifier.getId()))) {
logger.warning("ANSI object id is not a valid PKCS7 identifier " + objectIdentifier);
throw new SignatureIntegrityException("ANSI object id is not a valid PKCS7 identifier");
} else {
logger.finest("Object identifier: " + objectIdentifier.getId() + " " +
Pkcs7Validator.getObjectIdName(objectIdentifier.getId()));
}
if (!ID_SIGNED_DATA_OBJECT_IDENTIFIER.equals(objectIdentifier.getId())) {
throw new SignatureIntegrityException("ANSI.1 object must be of type Signed Data");
}
// Signed-data content type -- start of parsing
return (ASN1Sequence) ((ASN1TaggedObject) cmsSequence.getObjectAt(1)).getObject();
}
// Verify that the signature is indeed correct and verify the public key is a match.
protected boolean verifySignedAttributes(String provider, X509Certificate signerCertificate,
byte[] signatureValue,
String signatureAlgorithmIdentifier,
String digestAlgorithmIdentifier,
byte[] attr) throws SignatureIntegrityException {
try {
Signature signature = createSignature(signerCertificate.getPublicKey(), provider,
signatureAlgorithmIdentifier, digestAlgorithmIdentifier);
signature.update(attr);
return signature.verify(signatureValue);
} catch (InvalidKeyException e) {
throw new SignatureIntegrityException(e);
} catch (NoSuchAlgorithmException e) {
throw new SignatureIntegrityException(e);
} catch (SignatureException e) {
throw new SignatureIntegrityException(e);
}
}
// Creates a signature object for the given ID's and verifies the public key.
protected Signature createSignature(PublicKey publicKey, String provider,
String signatureAlgorithmIdentifier, String digestAlgorithmIdentifier)
throws InvalidKeyException, NoSuchAlgorithmException {
String encryptionAlgorithmName = AlgorithmIdentifier.getEncryptionAlgorithmName(signatureAlgorithmIdentifier);
String digestAlgorithmName = AlgorithmIdentifier.getDigestAlgorithmName(digestAlgorithmIdentifier);
String digestAlgorithm = digestAlgorithmName + ALGORITHM_WITH + encryptionAlgorithmName;
logger.finest("DigestAlgorithm " + digestAlgorithm);
Signature signature;
if (provider != null) {
try {
signature = Signature.getInstance(digestAlgorithm, provider);
} catch (NoSuchProviderException e) {
signature = Signature.getInstance(digestAlgorithm);
}
} else {
signature = Signature.getInstance(digestAlgorithm);
}
signature.initVerify(publicKey);
return signature;
}
/**
* Takes the DER-encoded PKCS#1 binary data or PKCS#7 binary data object and reads it into an
* Abstract Syntax Notation One (ASNI.1) object.
*
* @return ASN1Sequence representing the Cryptographic Message Syntax (CMS), null if data stream
* could not be loaded
*/
protected ASN1Sequence buildASN1Primitive(byte[] cmsData) {
try {
// setup the
ASN1InputStream abstractSyntaxNotationStream = new ASN1InputStream(new ByteArrayInputStream(cmsData));
ASN1Primitive pkcs = abstractSyntaxNotationStream.readObject();
if (pkcs instanceof ASN1Sequence) {
if (logger.isLoggable(Level.FINER)) {
logger.finest("ASN1Sequence found starting sequence processing. ");
}
return (ASN1Sequence) pkcs;
} else if (logger.isLoggable(Level.FINER)) {
logger.finest("ASN1Sequence was not found backing out. ");
}
} catch (IOException e) {
logger.log(Level.WARNING, "ASN1 stream could not be read.", e);
}
return null;
}
/**
* Gets a descriptive name for the given ANSI.1 object identifier number.
*
* @param objectId object id to lookup against know list of PKCS#7 id's.
* @return string describing the hard to read object id.
*/
protected static String getObjectIdName(String objectId) {
if (ID_DATA_OBJECT_IDENTIFIER.equals(objectId)) {
return "ID Data Object Identifier";
} else if (ID_SIGNED_DATA_OBJECT_IDENTIFIER.equals(objectId)) {
return "ID Signed Data Object Identifier";
} else if (ID_ENVELOPED_DATA_OBJECT_IDENTIFIER.equals(objectId)) {
return "ID Enveloped Data Object Identifier";
} else if (ID_ENCRYPTED_DATA_OBJECT_IDENTIFIER.equals(objectId)) {
return "ID Encrypted Data Object Identifier";
} else if (ID_DIGESTED_DATA_OBJECT_IDENTIFIER.equals(objectId)) {
return "ID Digested Data Object Identifier";
}
return "Unknown";
}
/**
* Validates the document against the data in the signatureDictionary.
*
* @throws SignatureIntegrityException
*/
protected void validateDocument() throws SignatureIntegrityException {
SignatureDictionary signatureDictionary = signatureFieldDictionary.getSignatureDictionary();
Signature signature;
MessageDigest messageDigestAlgorithm;
MessageDigest eConMessageDigestAlgorithm;
try {
String provider = signatureDictionary.getFilter().getName();
messageDigestAlgorithm = AlgorithmIdentifier.getDigestInstance(
digestAlgorithmIdentifier, provider);
eConMessageDigestAlgorithm = AlgorithmIdentifier.getDigestInstance(
digestAlgorithmIdentifier, provider);
signature = createSignature(signerCertificate.getPublicKey(), provider,
signatureAlgorithmIdentifier, digestAlgorithmIdentifier);
PublicKey publicKey = signerCertificate.getPublicKey();
if (logger.isLoggable(Level.FINER)) {
logger.finest("Certificate: \n" + signerCertificate.toString());
logger.finest("Public Key: \n" + publicKey);
}
} catch (NoSuchProviderException e1) {
logger.log(Level.WARNING, "No such provider found ", e1);
return;
} catch (NoSuchAlgorithmException e1) {
logger.log(Level.WARNING, "No such algorithm found ", e1);
return;
} catch (InvalidKeyException e1) {
logger.log(Level.WARNING, "Invalid key ", e1);
return;
}
// let digest the data.
ArrayList<Integer> byteRange = signatureFieldDictionary.getSignatureDictionary().getByteRange();
SeekableInput documentInput = signatureFieldDictionary.getLibrary().getDocumentInput();
documentInput.beginThreadAccess();
try {
long totalLength = documentInput.getLength();
long digestedLength = byteRange.get(2) + byteRange.get(3);
// this doesn't mean the signature has been tampered with just that there are subsequent modification
// or signatures added after this signature.
if (digestedLength < totalLength) {
isDocumentDataModified = true;
}
documentInput.seekAbsolute(byteRange.get(0));
byte[] firstSection = new byte[byteRange.get(1)];
documentInput.read(firstSection);
messageDigestAlgorithm.update(firstSection);
documentInput.seekAbsolute(byteRange.get(2));
byte[] secondSection = new byte[byteRange.get(3)];
documentInput.read(secondSection);
messageDigestAlgorithm.update(secondSection);
} catch (IOException e) {
throw new SignatureIntegrityException(e);
} finally {
documentInput.endThreadAccess();
}
// setup the compare
try {
// RFC3852 - The result of the message digest calculation process depends on whether the signedAttrs field
// is present. When the field is absent, the result is just the message digest of the content as described
// above. When the field is present, however, the result is the message digest of the complete DER encoding
// of the SignedAttrs value contained in the signedAttrs field.
byte[] documentDigestBytes = messageDigestAlgorithm.digest();
if (signedAttributesSequence != null) {
boolean encapsulatedDigestCheck = true;
boolean verifyEncContentInfoData = true;
if (encapsulatedContentInfoData != null) {
verifyEncContentInfoData = Arrays.equals(documentDigestBytes, encapsulatedContentInfoData);
eConMessageDigestAlgorithm.update(encapsulatedContentInfoData);
encapsulatedDigestCheck = Arrays.equals(eConMessageDigestAlgorithm.digest(), messageDigest);
}
boolean nonEncapsulatedDigestCheck = Arrays.equals(documentDigestBytes, messageDigest);
// When the field is present, however, the result is the message digest of the complete DER encoding of
// the SignedAttrs value contained in the signedAttrs field
boolean isSignatureValid =
verifySignedAttributes(signatureDictionary.getFilter().getName(), signerCertificate, signatureValue,
signatureAlgorithmIdentifier,
digestAlgorithmIdentifier,
signedAttributesSequence.getEncoded(ASN1Encoding.DER));
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Encapsulated Digest verified: " + encapsulatedDigestCheck);
logger.finest("Non-encapsulated Digest verified: " + nonEncapsulatedDigestCheck);
logger.finest("Signature verified: " + isSignatureValid);
logger.finest("Encapsulated data verified: " + verifyEncContentInfoData);
}
// verify the attributes.
if ((encapsulatedDigestCheck || nonEncapsulatedDigestCheck) && verifyEncContentInfoData) {
isSignedDataModified = false;
}
} else {
if (encapsulatedContentInfoData != null) {
signature.update(messageDigestAlgorithm.digest());
}
boolean nonEncapsulatedDigestCheck = Arrays.equals(documentDigestBytes, messageDigest);
if (nonEncapsulatedDigestCheck) {
isSignedDataModified = false;
}
}
lastVerified = new Date();
} catch (SignatureException e) {
throw new SignatureIntegrityException(e);
} catch (IOException e) {
throw new SignatureIntegrityException(e);
}
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
java.io.FileInputStream fis = null;
try {
fis = new java.io.FileInputStream(caCertLocation);
trustStore.load(fis, null);
} finally {
if (fis != null) {
fis.close();
}
}
// cert validation
X509Certificate[] cers = certificateChain.toArray(new X509Certificate[0]);
ArrayList<X509Certificate> trusted = new ArrayList<X509Certificate>(trustStore.size());
Enumeration<String> aliases = trustStore.aliases();
while (aliases.hasMoreElements()) {
trusted.add((X509Certificate) trustStore.getCertificate(aliases.nextElement()));
}
CertificateVerifier.verifyCertificate(cers[0], trusted);
isCertificateChainTrusted = true;
isCertificateDateValid = true;
lastVerified = new Date();
} catch (CertificateExpiredException e) {
logger.log(Level.FINEST, "Certificate chain could not be validated, certificate is expired", e);
isCertificateDateValid = false;
} catch (SelfSignedVerificationException e) {
logger.log(Level.FINEST, "Certificate chain could not be validated, signature is self singed.", e);
isSelfSigned = true;
} catch (CertificateVerificationException e) {
logger.log(Level.FINEST, "Certificate chain could not be validated. ", e);
isCertificateChainTrusted = false;
} catch (RevocationVerificationException e) {
logger.log(Level.FINEST, "Certificate chain could not be validated, certificate has been revoked.", e);
isRevocation = true;
} catch (IOException e) {
logger.log(Level.FINEST, "Error locating trusted keystore .", e);
isCertificateChainTrusted = false;
} catch (CertificateException e) {
logger.log(Level.FINEST, "Certificate exception.", e);
isCertificateChainTrusted = false;
} catch (Throwable e) {
logger.log(Level.FINEST, "Error validation certificate chain.", e);
isCertificateChainTrusted = false;
}
}
public boolean checkByteRange() throws SignatureIntegrityException {
if (signatureFieldDictionary == null) {
return false;
}
ArrayList<Integer> byteRange = signatureFieldDictionary.getSignatureDictionary().getByteRange();
SeekableInput documentInput = signatureFieldDictionary.getLibrary().getDocumentInput();
documentInput.beginThreadAccess();
try {
long totalLength = documentInput.getLength();
long digestedLength = byteRange.get(2) + byteRange.get(3);
// this doesn't mean the signature has been tampered with just that there are subsequent modification
// or signatures added after this signature.
if (digestedLength == totalLength) {
return true;
}
} catch (IOException e) {
throw new SignatureIntegrityException(e);
} finally {
documentInput.endThreadAccess();
}
return false;
}
/**
* Gets the certificate used to sing the document. The signature principle matches the certificates
* principle in other words.
*
* @return signer certificate.
*/
public X509Certificate getSignerCertificate() {
return signerCertificate;
}
/**
* Gets the certificate chain associated with this signature.
*
* @return certificate chain of one or more certificates.
*/
public Collection<Certificate> getCertificateChain() {
return certificateChain;
}
/**
* Date that validation process was last executed.
*
* @return Date last validation cycle was executed
*/
public Date getLastValidated() {
return lastVerified;
}
/**
* Indicates if the singed data section specified by a signature has been modified. This indicates the document
* has been tampered with.
*
* @return true if singed data has been altered, false otherwise.
*/
public boolean isSignedDataModified() {
return isSignedDataModified;
}
/**
* Indicates that data after the signature definition has been been modified. This is most likely do to another
* signature being added to the document or some form or page manipulation. However it is possible that
* an major update has been appended to the document.
*
* @return true if the document has been modified outside the byte range of the signature.
*/
public boolean isDocumentDataModified() {
return isDocumentDataModified;
}
public boolean isSignaturesCoverDocumentLength() {
return isSignaturesCoverDocumentLength;
}
public void setSignaturesCoverDocumentLength(boolean signaturesCoverDocumentLength) {
isSignaturesCoverDocumentLength = signaturesCoverDocumentLength;
}
/**
* Indicates the certificate chain has been validated against keystore of trusted certificates.
*
* @return true if the certificate chain has been validated, false otherwise.
*/
public boolean isCertificateChainTrusted() {
return isCertificateChainTrusted;
}
/**
* Indicates if the signing certificate or a certificate in the chain is on a revocation list.
*
* @return true if the certy have been revoked, false otherwise.
*/
public boolean isRevocation() {
return isRevocation;
}
/**
* Indicates the signature was self singed and the certificate can not be trusted.
*
* @return true if self signed, false otherwise.
*/
public boolean isSelfSigned() {
return isSelfSigned;
}
/**
* Indicates if a certificate data has been marked as invalid. This generally means that a certificate
* has expired.
*
* @return true if the certificate data is valid, otherwise false.
*/
public boolean isCertificateDateValid() {
return isCertificateDateValid;
}
public boolean isEmbeddedTimeStamp() {
return isEmbeddedTimeStamp;
}
/**
* Will always return fals, timestamps are currently not validated.
*
* @return false.
*/
public boolean isSignerTimeValid() {
return false;
}
}