com.itextpdf.signatures.PdfPKCS7 Maven / Gradle / Ivy
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2023 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
package com.itextpdf.signatures;
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
import com.itextpdf.commons.bouncycastle.asn1.IASN1Encodable;
import com.itextpdf.commons.bouncycastle.asn1.IASN1EncodableVector;
import com.itextpdf.commons.bouncycastle.asn1.IASN1Enumerated;
import com.itextpdf.commons.bouncycastle.asn1.IASN1InputStream;
import com.itextpdf.commons.bouncycastle.asn1.IASN1ObjectIdentifier;
import com.itextpdf.commons.bouncycastle.asn1.IASN1OctetString;
import com.itextpdf.commons.bouncycastle.asn1.IASN1OutputStream;
import com.itextpdf.commons.bouncycastle.asn1.IASN1Primitive;
import com.itextpdf.commons.bouncycastle.asn1.IASN1Sequence;
import com.itextpdf.commons.bouncycastle.asn1.IASN1Set;
import com.itextpdf.commons.bouncycastle.asn1.IASN1TaggedObject;
import com.itextpdf.commons.bouncycastle.asn1.IDEROctetString;
import com.itextpdf.commons.bouncycastle.asn1.IDERSequence;
import com.itextpdf.commons.bouncycastle.asn1.IDERSet;
import com.itextpdf.commons.bouncycastle.asn1.cms.IAttribute;
import com.itextpdf.commons.bouncycastle.asn1.cms.IAttributeTable;
import com.itextpdf.commons.bouncycastle.asn1.cms.IContentInfo;
import com.itextpdf.commons.bouncycastle.asn1.esf.ISignaturePolicyIdentifier;
import com.itextpdf.commons.bouncycastle.asn1.ess.IESSCertID;
import com.itextpdf.commons.bouncycastle.asn1.ess.IESSCertIDv2;
import com.itextpdf.commons.bouncycastle.asn1.ess.ISigningCertificate;
import com.itextpdf.commons.bouncycastle.asn1.ess.ISigningCertificateV2;
import com.itextpdf.commons.bouncycastle.asn1.ocsp.IBasicOCSPResponse;
import com.itextpdf.commons.bouncycastle.asn1.ocsp.IOCSPObjectIdentifiers;
import com.itextpdf.commons.bouncycastle.asn1.pkcs.IPKCSObjectIdentifiers;
import com.itextpdf.commons.bouncycastle.asn1.pkcs.IRSASSAPSSParams;
import com.itextpdf.commons.bouncycastle.asn1.tsp.IMessageImprint;
import com.itextpdf.commons.bouncycastle.asn1.tsp.ITSTInfo;
import com.itextpdf.commons.bouncycastle.asn1.x509.IAlgorithmIdentifier;
import com.itextpdf.commons.bouncycastle.cert.ocsp.ICertificateID;
import com.itextpdf.commons.bouncycastle.cert.ocsp.ISingleResp;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
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.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
/**
* This class does all the processing related to signing
* and verifying a PKCS#7 / CMS signature.
*/
public class PdfPKCS7 {
private static final IBouncyCastleFactory BOUNCY_CASTLE_FACTORY = BouncyCastleFactoryCreator.getFactory();
private ISignaturePolicyIdentifier signaturePolicyIdentifier;
// Encryption provider
/**
* The encryption provider, e.g. "BC" if you use BouncyCastle.
*/
private String provider;
// Signature info
/**
* Holds value of property signName.
*/
private String signName;
/**
* Holds value of property reason.
*/
private String reason;
/**
* Holds value of property location.
*/
private String location;
/**
* Holds value of property signDate.
*/
private Calendar signDate = (Calendar) TimestampConstants.UNDEFINED_TIMESTAMP_DATE;
// Constructors for creating new signatures
/**
* Assembles all the elements needed to create a signature, except for the data.
*
* @param privKey the private key
* @param certChain the certificate chain
* @param interfaceDigest the interface digest
* @param hashAlgorithm the hash algorithm
* @param provider the provider or null
for the default provider
* @param hasEncapContent true
if the sub-filter is adbe.pkcs7.sha1
* @throws InvalidKeyException on error
* @throws NoSuchProviderException on error
* @throws NoSuchAlgorithmException on error
*/
public PdfPKCS7(PrivateKey privKey, Certificate[] certChain,
String hashAlgorithm, String provider, IExternalDigest interfaceDigest, boolean hasEncapContent)
throws InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException {
this.provider = provider;
this.interfaceDigest = interfaceDigest;
// message digest
digestAlgorithmOid = DigestAlgorithms.getAllowedDigest(hashAlgorithm);
if (digestAlgorithmOid == null) {
throw new PdfException(SignExceptionMessageConstant.UNKNOWN_HASH_ALGORITHM)
.setMessageParams(hashAlgorithm);
}
// Copy the certificates
signCert = (X509Certificate) certChain[0];
certs = new ArrayList<>();
for (Certificate element : certChain) {
certs.add(element);
}
// initialize and add the digest algorithms.
digestalgos = new HashSet<>();
digestalgos.add(digestAlgorithmOid);
// find the signing algorithm
if (privKey != null) {
String signatureAlgo = SignUtils.getPrivateKeyAlgorithm(privKey);
String mechanismOid = SignatureMechanisms.getSignatureMechanismOid(signatureAlgo, hashAlgorithm);
if (mechanismOid == null) {
throw new PdfException(SignExceptionMessageConstant.COULD_NOT_DETERMINE_SIGNATURE_MECHANISM_OID)
.setMessageParams(signatureAlgo, hashAlgorithm);
}
this.signatureMechanismOid = mechanismOid;
}
// initialize the encapsulated content
if (hasEncapContent) {
encapMessageContent = new byte[0];
messageDigest = DigestAlgorithms.getMessageDigest(getDigestAlgorithmName(), provider);
}
// initialize the Signature object
if (privKey != null) {
sig = initSignature(privKey);
}
}
// Constructors for validating existing signatures
/**
* Use this constructor if you want to verify a signature using the sub-filter adbe.x509.rsa_sha1.
*
* @param contentsKey the /Contents key
* @param certsKey the /Cert key
* @param provider the provider or null
for the default provider
*/
@SuppressWarnings("unchecked")
public PdfPKCS7(byte[] contentsKey, byte[] certsKey, String provider) {
try {
this.provider = provider;
certs = SignUtils.readAllCerts(certsKey);
signCerts = certs;
signCert = (X509Certificate) SignUtils.getFirstElement(certs);
crls = new ArrayList<>();
try (IASN1InputStream in =
BOUNCY_CASTLE_FACTORY.createASN1InputStream(new ByteArrayInputStream(contentsKey))) {
signatureValue = BOUNCY_CASTLE_FACTORY.createASN1OctetString(in.readObject()).getOctets();
}
sig = SignUtils.getSignatureHelper("SHA1withRSA", provider);
sig.initVerify(signCert.getPublicKey());
// setting the oid to SHA1withRSA
digestAlgorithmOid = "1.2.840.10040.4.3";
signatureMechanismOid = "1.3.36.3.3.1.2";
} catch (Exception e) {
throw new PdfException(e);
}
}
/**
* Use this constructor if you want to verify a signature.
*
* @param contentsKey the /Contents key
* @param filterSubtype the filtersubtype
* @param provider the provider or null
for the default provider
*/
@SuppressWarnings({"unchecked"})
public PdfPKCS7(byte[] contentsKey, PdfName filterSubtype, String provider) {
this.filterSubtype = filterSubtype;
isTsp = PdfName.ETSI_RFC3161.equals(filterSubtype);
isCades = PdfName.ETSI_CAdES_DETACHED.equals(filterSubtype);
try {
this.provider = provider;
//
// Basic checks to make sure it's a PKCS#7 SignedData Object
//
IASN1Primitive pkcs;
try (IASN1InputStream din =
BOUNCY_CASTLE_FACTORY.createASN1InputStream(new ByteArrayInputStream(contentsKey))) {
pkcs = din.readObject();
} catch (IOException e) {
throw new IllegalArgumentException(
SignExceptionMessageConstant.CANNOT_DECODE_PKCS7_SIGNED_DATA_OBJECT);
}
IASN1Sequence signedData = BOUNCY_CASTLE_FACTORY.createASN1Sequence(pkcs);
if (signedData == null) {
throw new IllegalArgumentException(
SignExceptionMessageConstant.NOT_A_VALID_PKCS7_OBJECT_NOT_A_SEQUENCE);
}
IASN1ObjectIdentifier objId = BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(signedData.getObjectAt(0));
if (!objId.getId().equals(SecurityIDs.ID_PKCS7_SIGNED_DATA)) {
throw new IllegalArgumentException(
SignExceptionMessageConstant.NOT_A_VALID_PKCS7_OBJECT_NOT_SIGNED_DATA);
}
IASN1Sequence content = BOUNCY_CASTLE_FACTORY.createASN1Sequence(
BOUNCY_CASTLE_FACTORY.createASN1TaggedObject(signedData.getObjectAt(1)).getObject());
// the positions that we care are:
// 0 - version
// 1 - digestAlgorithms
// 2 - possible ID_PKCS7_DATA
// (the certificates and crls are taken out by other means)
// last - signerInfos
// the version
version = BOUNCY_CASTLE_FACTORY.createASN1Integer(content.getObjectAt(0)).getValue().intValue();
// the digestAlgorithms
digestalgos = new HashSet<>();
Enumeration e = BOUNCY_CASTLE_FACTORY.createASN1Set(content.getObjectAt(1)).getObjects();
while (e.hasMoreElements()) {
IASN1Sequence s = BOUNCY_CASTLE_FACTORY.createASN1Sequence(e.nextElement());
IASN1ObjectIdentifier o = BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(s.getObjectAt(0));
digestalgos.add(o.getId());
}
// the possible ID_PKCS7_DATA
IASN1Sequence encapContentInfo = BOUNCY_CASTLE_FACTORY.createASN1Sequence(content.getObjectAt(2));
if (encapContentInfo.size() > 1) {
IASN1OctetString encapContent = BOUNCY_CASTLE_FACTORY.createASN1OctetString(
BOUNCY_CASTLE_FACTORY.createASN1TaggedObject(encapContentInfo.getObjectAt(1)).getObject());
this.encapMessageContent = encapContent.getOctets();
}
int next = 3;
while (BOUNCY_CASTLE_FACTORY.createASN1TaggedObject(content.getObjectAt(next)) != null) {
++next;
}
// the certificates
certs = SignUtils.readAllCerts(contentsKey);
// the signerInfos
IASN1Set signerInfos = BOUNCY_CASTLE_FACTORY.createASN1Set(content.getObjectAt(next));
if (signerInfos.size() != 1) {
throw new IllegalArgumentException(
SignExceptionMessageConstant.THIS_PKCS7_OBJECT_HAS_MULTIPLE_SIGNERINFOS_ONLY_ONE_IS_SUPPORTED_AT_THIS_TIME);
}
IASN1Sequence signerInfo = BOUNCY_CASTLE_FACTORY.createASN1Sequence(signerInfos.getObjectAt(0));
// the positions that we care are
// 0 - version
// 1 - the signing certificate issuer and serial number
// 2 - the digest algorithm
// 3 or 4 - digestEncryptionAlgorithm
// 4 or 5 - encryptedDigest
signerversion = BOUNCY_CASTLE_FACTORY.createASN1Integer(signerInfo.getObjectAt(0)).getValue().intValue();
// Get the signing certificate
IASN1Sequence issuerAndSerialNumber = BOUNCY_CASTLE_FACTORY.createASN1Sequence(signerInfo.getObjectAt(1));
X500Principal issuer = SignUtils.getIssuerX500Principal(issuerAndSerialNumber);
BigInteger serialNumber = BOUNCY_CASTLE_FACTORY.createASN1Integer(issuerAndSerialNumber.getObjectAt(1))
.getValue();
for (Object element : certs) {
X509Certificate cert = BOUNCY_CASTLE_FACTORY.createX509Certificate(element);
if (cert.getIssuerX500Principal().equals(issuer) && serialNumber.equals(cert.getSerialNumber())) {
signCert = cert;
break;
}
}
if (signCert == null) {
throw new PdfException(SignExceptionMessageConstant.CANNOT_FIND_SIGNING_CERTIFICATE_WITH_THIS_SERIAL).
setMessageParams(issuer.getName() + " / " + serialNumber.toString(16));
}
signCertificateChain();
digestAlgorithmOid = BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(
BOUNCY_CASTLE_FACTORY.createASN1Sequence(signerInfo.getObjectAt(2)).getObjectAt(0)).getId();
next = 3;
boolean foundCades = false;
IASN1TaggedObject tagsig = BOUNCY_CASTLE_FACTORY.createASN1TaggedObject(signerInfo.getObjectAt(next));
if (tagsig != null) {
IASN1Set sseq = BOUNCY_CASTLE_FACTORY.createASN1Set(tagsig, false);
sigAttr = sseq.getEncoded();
// maybe not necessary, but we use the following line as fallback:
sigAttrDer = sseq.getEncoded(BOUNCY_CASTLE_FACTORY.createASN1Encoding().getDer());
for (int k = 0; k < sseq.size(); ++k) {
IASN1Sequence seq2 = BOUNCY_CASTLE_FACTORY.createASN1Sequence(sseq.getObjectAt(k));
String idSeq2 = BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(seq2.getObjectAt(0)).getId();
if (idSeq2.equals(SecurityIDs.ID_MESSAGE_DIGEST)) {
IASN1Set set = BOUNCY_CASTLE_FACTORY.createASN1Set(seq2.getObjectAt(1));
digestAttr = BOUNCY_CASTLE_FACTORY.createASN1OctetString(set.getObjectAt(0)).getOctets();
} else if (idSeq2.equals(SecurityIDs.ID_ADBE_REVOCATION)) {
IASN1Set setout = BOUNCY_CASTLE_FACTORY.createASN1Set(seq2.getObjectAt(1));
IASN1Sequence seqout = BOUNCY_CASTLE_FACTORY.createASN1Sequence(setout.getObjectAt(0));
for (int j = 0; j < seqout.size(); ++j) {
IASN1TaggedObject tg = BOUNCY_CASTLE_FACTORY.createASN1TaggedObject(seqout.getObjectAt(j));
if (tg.getTagNo() == 0) {
IASN1Sequence seqin = BOUNCY_CASTLE_FACTORY.createASN1Sequence(tg.getObject());
findCRL(seqin);
}
if (tg.getTagNo() == 1) {
IASN1Sequence seqin = BOUNCY_CASTLE_FACTORY.createASN1Sequence(tg.getObject());
findOcsp(seqin);
}
}
} else if (isCades && idSeq2.equals(SecurityIDs.ID_AA_SIGNING_CERTIFICATE_V1)) {
IASN1Set setout = BOUNCY_CASTLE_FACTORY.createASN1Set(seq2.getObjectAt(1));
IASN1Sequence seqout = BOUNCY_CASTLE_FACTORY.createASN1Sequence(setout.getObjectAt(0));
ISigningCertificate sv2 = BOUNCY_CASTLE_FACTORY.createSigningCertificate(seqout);
IESSCertID[] cerv2m = sv2.getCerts();
IESSCertID cerv2 = cerv2m[0];
byte[] enc2 = signCert.getEncoded();
MessageDigest m2 = SignUtils.getMessageDigest("SHA-1");
byte[] signCertHash = m2.digest(enc2);
byte[] hs2 = cerv2.getCertHash();
if (!Arrays.equals(signCertHash, hs2)) {
throw new IllegalArgumentException(
"Signing certificate doesn't match the ESS information.");
}
foundCades = true;
} else if (isCades && idSeq2.equals(SecurityIDs.ID_AA_SIGNING_CERTIFICATE_V2)) {
IASN1Set setout = BOUNCY_CASTLE_FACTORY.createASN1Set(seq2.getObjectAt(1));
IASN1Sequence seqout = BOUNCY_CASTLE_FACTORY.createASN1Sequence(setout.getObjectAt(0));
ISigningCertificateV2 sv2 = BOUNCY_CASTLE_FACTORY.createSigningCertificateV2(seqout);
IESSCertIDv2[] cerv2m = sv2.getCerts();
IESSCertIDv2 cerv2 = cerv2m[0];
IAlgorithmIdentifier ai2 = cerv2.getHashAlgorithm();
byte[] enc2 = signCert.getEncoded();
MessageDigest m2
= SignUtils.getMessageDigest(DigestAlgorithms.getDigest(ai2.getAlgorithm().getId()));
byte[] signCertHash = m2.digest(enc2);
byte[] hs2 = cerv2.getCertHash();
if (!Arrays.equals(signCertHash, hs2)) {
throw new IllegalArgumentException(
"Signing certificate doesn't match the ESS information.");
}
foundCades = true;
}
}
if (digestAttr == null) {
throw new IllegalArgumentException(
SignExceptionMessageConstant.AUTHENTICATED_ATTRIBUTE_IS_MISSING_THE_DIGEST);
}
++next;
}
if (isCades && !foundCades) {
throw new IllegalArgumentException("CAdES ESS information missing.");
}
IASN1Sequence signatureMechanismInfo = BOUNCY_CASTLE_FACTORY
.createASN1Sequence(signerInfo.getObjectAt(next));
++next;
signatureMechanismOid = BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(
signatureMechanismInfo.getObjectAt(0)).getId();
if (signatureMechanismInfo.size() > 1) {
signatureMechanismParameters = signatureMechanismInfo.getObjectAt(1);
}
signatureValue = BOUNCY_CASTLE_FACTORY.createASN1OctetString(signerInfo.getObjectAt(next)).getOctets();
++next;
if (next < signerInfo.size()) {
IASN1TaggedObject taggedObject = BOUNCY_CASTLE_FACTORY.createASN1TaggedObject(
signerInfo.getObjectAt(next));
if (taggedObject != null) {
IASN1Set unat = BOUNCY_CASTLE_FACTORY.createASN1Set(taggedObject, false);
IAttributeTable attble = BOUNCY_CASTLE_FACTORY.createAttributeTable(unat);
IPKCSObjectIdentifiers ipkcsObjectIdentifiers = BOUNCY_CASTLE_FACTORY.createPKCSObjectIdentifiers();
IAttribute ts = attble.get(ipkcsObjectIdentifiers.getIdAaSignatureTimeStampToken());
if (ts != null && ts.getAttrValues().size() > 0) {
IASN1Set attributeValues = ts.getAttrValues();
IASN1Sequence tokenSequence =
BOUNCY_CASTLE_FACTORY.createASN1SequenceInstance(attributeValues.getObjectAt(0));
IContentInfo contentInfo = BOUNCY_CASTLE_FACTORY.createContentInfo(tokenSequence);
this.timeStampTokenInfo = BOUNCY_CASTLE_FACTORY.createTSTInfo(contentInfo);
}
}
}
if (isTsp) {
IContentInfo contentInfoTsp = BOUNCY_CASTLE_FACTORY.createContentInfo(signedData);
this.timeStampTokenInfo = BOUNCY_CASTLE_FACTORY.createTSTInfo(contentInfoTsp);
String algOID = timeStampTokenInfo.getMessageImprint().getHashAlgorithm().getAlgorithm().getId();
messageDigest = DigestAlgorithms.getMessageDigestFromOid(algOID, null);
} else {
if (this.encapMessageContent != null || digestAttr != null) {
if (PdfName.Adbe_pkcs7_sha1.equals(getFilterSubtype())) {
messageDigest = DigestAlgorithms.getMessageDigest("SHA1", provider);
} else {
messageDigest = DigestAlgorithms.getMessageDigest(getDigestAlgorithmName(), provider);
}
encContDigest = DigestAlgorithms.getMessageDigest(getDigestAlgorithmName(), provider);
}
sig = initSignature(signCert.getPublicKey());
}
} catch (Exception e) {
throw new PdfException(e);
}
}
public void setSignaturePolicy(SignaturePolicyInfo signaturePolicy) {
this.signaturePolicyIdentifier = signaturePolicy.toSignaturePolicyIdentifier();
}
public void setSignaturePolicy(ISignaturePolicyIdentifier signaturePolicy) {
this.signaturePolicyIdentifier = signaturePolicy;
}
/**
* Getter for property sigName.
*
* @return Value of property sigName.
*/
public String getSignName() {
return this.signName;
}
/**
* Setter for property sigName.
*
* @param signName New value of property sigName.
*/
public void setSignName(String signName) {
this.signName = signName;
}
/**
* Getter for property reason.
*
* @return Value of property reason.
*/
public String getReason() {
return this.reason;
}
/**
* Setter for property reason.
*
* @param reason New value of property reason.
*/
public void setReason(String reason) {
this.reason = reason;
}
/**
* Getter for property location.
*
* @return Value of property location.
*/
public String getLocation() {
return this.location;
}
/**
* Setter for property location.
*
* @param location New value of property location.
*/
public void setLocation(String location) {
this.location = location;
}
/**
* Getter for property signDate.
*
* @return Value of property signDate.
*/
public Calendar getSignDate() {
Calendar dt = getTimeStampDate();
if (dt == TimestampConstants.UNDEFINED_TIMESTAMP_DATE) {
return this.signDate;
} else {
return dt;
}
}
/**
* Setter for property signDate.
*
* @param signDate New value of property signDate.
*/
public void setSignDate(Calendar signDate) {
this.signDate = signDate;
}
// version info
/**
* Version of the PKCS#7 object
*/
private int version = 1;
/**
* Version of the PKCS#7 "SignerInfo" object.
*/
private int signerversion = 1;
/**
* Get the version of the PKCS#7 object.
*
* @return the version of the PKCS#7 object.
*/
public int getVersion() {
return version;
}
/**
* Get the version of the PKCS#7 "SignerInfo" object.
*
* @return the version of the PKCS#7 "SignerInfo" object.
*/
public int getSigningInfoVersion() {
return signerversion;
}
// Message digest algorithm
/**
* The ID of the digest algorithm, e.g. "2.16.840.1.101.3.4.2.1".
*/
private String digestAlgorithmOid;
/**
* The object that will create the digest
*/
private MessageDigest messageDigest;
/**
* The digest algorithms
*/
private Set digestalgos;
/**
* The digest attributes
*/
private byte[] digestAttr;
private PdfName filterSubtype;
/**
* The signature algorithm.
*/
private String signatureMechanismOid;
private IASN1Encodable signatureMechanismParameters = null;
/**
* Getter for the ID of the digest algorithm, e.g. "2.16.840.1.101.3.4.2.1".
* See ISO-32000-1, section 12.8.3.3 PKCS#7 Signatures as used in ISO 32000
*
* @return the ID of the digest algorithm
*/
public String getDigestAlgorithmOid() {
return digestAlgorithmOid;
}
/**
* Returns the name of the digest algorithm, e.g. "SHA256".
*
* @return the digest algorithm name, e.g. "SHA256"
*/
public String getDigestAlgorithmName() {
String hashAlgoName = DigestAlgorithms.getDigest(digestAlgorithmOid);
// Ed25519 and Ed448 do not allow a choice of hashing algorithm,
// and ISO 32002 requires using a fixed hashing algorithm to
// digest the document content
if (SecurityIDs.ID_ED25519.equals(this.signatureMechanismOid)
&& !SecurityIDs.ID_SHA512.equals(digestAlgorithmOid)) {
// We compare based on OID to ensure that there are no name normalisation issues.
throw new PdfException(SignExceptionMessageConstant.ALGO_REQUIRES_SPECIFIC_HASH)
.setMessageParams("Ed25519", "SHA-512", hashAlgoName);
} else if (SecurityIDs.ID_ED448.equals(this.signatureMechanismOid)
&& !SecurityIDs.ID_SHAKE256.equals(digestAlgorithmOid)) {
throw new PdfException(SignExceptionMessageConstant.ALGO_REQUIRES_SPECIFIC_HASH)
.setMessageParams("Ed448", "512-bit SHAKE256", hashAlgoName);
}
return hashAlgoName;
}
/**
* Getter for the signature algorithm OID.
* See ISO-32000-1, section 12.8.3.3 PKCS#7 Signatures as used in ISO 32000
*
* @return the signature algorithm OID
*/
public String getSignatureMechanismOid() {
return signatureMechanismOid;
}
/**
* Get the signature mechanism identifier, including both the digest function
* and the signature algorithm, e.g. "SHA1withRSA".
* See ISO-32000-1, section 12.8.3.3 PKCS#7 Signatures as used in ISO 32000
*
* @return the algorithm used to calculate the signature
*/
public String getSignatureMechanismName() {
switch (this.signatureMechanismOid) {
case SecurityIDs.ID_ED25519:
// Ed25519 and Ed448 do not involve a choice of hashing algorithm
return "Ed25519";
case SecurityIDs.ID_ED448:
return "Ed448";
case SecurityIDs.ID_RSASSA_PSS:
// For RSASSA-PSS, the algorithm parameters dictate everything, so
// there's no need to duplicate that information in the algorithm name.
return "RSASSA-PSS";
default:
return SignatureMechanisms.getMechanism(signatureMechanismOid, getDigestAlgorithmName());
}
}
/**
* Returns the name of the signature algorithm only (disregarding the digest function, if any).
*
* @return the name of an encryption algorithm
*/
public String getSignatureAlgorithmName() {
String signAlgo = SignatureMechanisms.getAlgorithm(signatureMechanismOid);
if (signAlgo == null) {
signAlgo = signatureMechanismOid;
}
return signAlgo;
}
/*
* DIGITAL SIGNATURE CREATION
*/
private IExternalDigest interfaceDigest;
// The signature is created externally
/**
* The signature value or signed digest, if created outside this class
*/
private byte[] externalSignatureValue;
/**
* Externally specified encapsulated message content.
*/
private byte[] externalEncapMessageContent;
/**
* Sets the signature to an externally calculated value.
*
* @param signatureValue the signature value
* @param signedMessageContent the extra data that goes into the data tag in PKCS#7
* @param signatureAlgorithm the signature algorithm. It must be null
if the
* signatureValue
is also null
.
* If the signatureValue
is not null
,
* possible values include "RSA", "DSA", "ECDSA", "Ed25519" and "Ed448".
*/
public void setExternalSignatureValue(byte[] signatureValue, byte[] signedMessageContent, String signatureAlgorithm) {
setExternalSignatureValue(signatureValue, signedMessageContent, signatureAlgorithm, null);
}
/**
* Sets the signature to an externally calculated value.
*
* @param signatureValue the signature value
* @param signedMessageContent the extra data that goes into the data tag in PKCS#7
* @param signatureAlgorithm the signature algorithm. It must be null
if the
* signatureValue
is also null
.
* If the signatureValue
is not null
,
* possible values include "RSA", "RSASSA-PSS", "DSA",
* "ECDSA", "Ed25519" and "Ed448".
* @param signatureMechanismParams parameters for the signature mechanism, if required
*/
public void setExternalSignatureValue(
byte[] signatureValue, byte[] signedMessageContent,
String signatureAlgorithm, ISignatureMechanismParams signatureMechanismParams) {
externalSignatureValue = signatureValue;
externalEncapMessageContent = signedMessageContent;
if (signatureAlgorithm != null) {
String digestAlgo = this.getDigestAlgorithmName();
String oid = SignatureMechanisms.getSignatureMechanismOid(signatureAlgorithm, digestAlgo);
if (oid == null) {
throw new PdfException(SignExceptionMessageConstant.COULD_NOT_DETERMINE_SIGNATURE_MECHANISM_OID)
.setMessageParams(signatureAlgorithm, digestAlgo);
}
this.signatureMechanismOid = oid;
}
if (signatureMechanismParams != null) {
this.signatureMechanismParameters = signatureMechanismParams.toEncodable();
}
}
// The signature is created internally
/**
* Class from the Java SDK that provides the functionality of a digital signature algorithm.
*/
private Signature sig;
/**
* The raw signature value as calculated by this class (or extracted from an existing PDF)
*/
private byte[] signatureValue;
/**
* The content to which the signature applies, if encapsulated in the PKCS #7 payload.
*/
private byte[] encapMessageContent;
// Signing functionality.
private Signature initSignature(PrivateKey key) throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeyException {
Signature signature = SignUtils.getSignatureHelper(getSignatureMechanismName(), provider);
signature.initSign(key);
return signature;
}
private Signature initSignature(PublicKey key) throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeyException {
String signatureMechanism;
if (PdfName.Adbe_x509_rsa_sha1.equals(getFilterSubtype())) {
signatureMechanism = "SHA1withRSA";
} else {
signatureMechanism = getSignatureMechanismName();
}
Signature signature = SignUtils.getSignatureHelper(signatureMechanism, provider);
configureSignatureMechanismParameters(signature);
signature.initVerify(key);
return signature;
}
private void configureSignatureMechanismParameters(Signature signature) {
if (SecurityIDs.ID_RSASSA_PSS.equals(this.signatureMechanismOid)) {
IRSASSAPSSParams params = BOUNCY_CASTLE_FACTORY.createRSASSAPSSParams(this.signatureMechanismParameters);
String mgfOid = params.getMaskGenAlgorithm().getAlgorithm().getId();
if (!SecurityIDs.ID_MGF1.equals(mgfOid)) {
throw new IllegalArgumentException(SignExceptionMessageConstant.ONLY_MGF1_SUPPORTED_IN_RSASSA_PSS);
}
// Even though having separate digests at all "layers" is mathematically fine,
// it's bad practice at best (and a security problem at worst).
// We don't support such hybridisation outside RSASSA-PSS either.
// => on the authority of RFC 8933 we enforce the restriction here.
String mechParamDigestAlgoOid = params.getHashAlgorithm().getAlgorithm().getId();
if (!this.digestAlgorithmOid.equals(mechParamDigestAlgoOid)) {
throw new IllegalArgumentException(MessageFormatUtil.format(
SignExceptionMessageConstant.RSASSA_PSS_DIGESTMISSMATCH,
mechParamDigestAlgoOid, this.digestAlgorithmOid));
}
// This is actually morally an IAlgorithmIdentifier too, but since it's pretty much always going to be a
// one-element sequence, it's probably not worth putting in a conversion method in the factory interface
IASN1Sequence mgfParams = BOUNCY_CASTLE_FACTORY.createASN1Sequence(
params.getMaskGenAlgorithm().getParameters()
);
String mgfParamDigestAlgoOid = BOUNCY_CASTLE_FACTORY
.createASN1ObjectIdentifier(mgfParams.getObjectAt(0))
.getId();
if (!this.digestAlgorithmOid.equals(mgfParamDigestAlgoOid)) {
throw new IllegalArgumentException(
MessageFormatUtil.format(
SignExceptionMessageConstant.DISGEST_ALGORITM_MGF_MISMATCH,
mgfParamDigestAlgoOid , this.digestAlgorithmOid));
}
try {
int saltLength = params.getSaltLength().intValue();
int trailerField = params.getTrailerField().intValue();
SignUtils.setRSASSAPSSParamsWithMGF1(signature, getDigestAlgorithmName(), saltLength, trailerField);
} catch (InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(SignExceptionMessageConstant.INVALID_ARGUMENTS,e);
}
}
}
/**
* Update the digest with the specified bytes.
* This method is used both for signing and verifying
*
* @param buf the data buffer
* @param off the offset in the data buffer
* @param len the data length
*
* @throws SignatureException on error
*/
public void update(byte[] buf, int off, int len) throws SignatureException {
if (encapMessageContent != null || digestAttr != null || isTsp) {
messageDigest.update(buf, off, len);
} else {
sig.update(buf, off, len);
}
}
// adbe.x509.rsa_sha1 (PKCS#1)
/**
* Gets the bytes for the PKCS#1 object.
*
* @return a byte array
*/
public byte[] getEncodedPKCS1() {
try {
if (externalSignatureValue != null) {
signatureValue = externalSignatureValue;
} else {
signatureValue = sig.sign();
}
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
IASN1OutputStream dout = BOUNCY_CASTLE_FACTORY.createASN1OutputStream(bOut);
dout.writeObject(BOUNCY_CASTLE_FACTORY.createDEROctetString(signatureValue));
dout.close();
return bOut.toByteArray();
} catch (Exception e) {
throw new PdfException(e);
}
}
// other subfilters (PKCS#7)
/**
* Gets the bytes for the PKCS7SignedData object.
*
* @return the bytes for the PKCS7SignedData object
*/
public byte[] getEncodedPKCS7() {
return getEncodedPKCS7(null, PdfSigner.CryptoStandard.CMS, null, null, null);
}
/**
* Gets the bytes for the PKCS7SignedData object. Optionally the authenticatedAttributes
* in the signerInfo can also be set. If either of the parameters is null
, none will be used.
*
* @param secondDigest the digest in the authenticatedAttributes
*
* @return the bytes for the PKCS7SignedData object
*/
public byte[] getEncodedPKCS7(byte[] secondDigest) {
return getEncodedPKCS7(secondDigest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
/**
* Gets the bytes for the PKCS7SignedData object. Optionally the authenticatedAttributes
* in the signerInfo can also be set, and/or a time-stamp-authority client
* may be provided.
*
* @param secondDigest the digest in the authenticatedAttributes
* @param sigtype specifies the PKCS7 standard flavor to which created PKCS7SignedData object will adhere:
* either basic CMS or CAdES
* @param tsaClient TSAClient - null or an optional time stamp authority client
* @param ocsp collection of DER-encoded BasicOCSPResponses for the certificate in the signature
* certificates
* chain, or null if OCSP revocation data is not to be added.
* @param crlBytes collection of DER-encoded CRL for certificates from the signature certificates chain,
* or null if CRL revocation data is not to be added.
*
* @return byte[] the bytes for the PKCS7SignedData object
*
* @see RFC 6960 § 4.2.1
*/
public byte[] getEncodedPKCS7(byte[] secondDigest, PdfSigner.CryptoStandard sigtype, ITSAClient tsaClient,
Collection ocsp, Collection crlBytes) {
try {
if (externalSignatureValue != null) {
signatureValue = externalSignatureValue;
if (encapMessageContent != null) {
encapMessageContent = externalEncapMessageContent;
}
} else if (externalEncapMessageContent != null && encapMessageContent != null) {
encapMessageContent = externalEncapMessageContent;
sig.update(encapMessageContent);
signatureValue = sig.sign();
} else {
if (encapMessageContent != null) {
encapMessageContent = messageDigest.digest();
sig.update(encapMessageContent);
}
signatureValue = sig.sign();
}
// Create the set of Hash algorithms
IASN1EncodableVector digestAlgorithms = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
for (Object element : digestalgos) {
IASN1EncodableVector algos = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
algos.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier((String) element));
algos.add(BOUNCY_CASTLE_FACTORY.createDERNull());
digestAlgorithms.add(BOUNCY_CASTLE_FACTORY.createDERSequence(algos));
}
// Create the contentInfo.
IASN1EncodableVector v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_DATA));
if (encapMessageContent != null) {
v.add(BOUNCY_CASTLE_FACTORY.createDERTaggedObject(0,
BOUNCY_CASTLE_FACTORY.createDEROctetString(encapMessageContent)));
}
IDERSequence contentinfo = BOUNCY_CASTLE_FACTORY.createDERSequence(v);
// Get all the certificates
//
v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
for (Object element : certs) {
try (IASN1InputStream tempstream = BOUNCY_CASTLE_FACTORY.createASN1InputStream(
new ByteArrayInputStream(BOUNCY_CASTLE_FACTORY.createX509Certificate(element).getEncoded()))) {
v.add(tempstream.readObject());
}
}
IDERSet dercertificates = BOUNCY_CASTLE_FACTORY.createDERSet(v);
// Create signerinfo structure.
IASN1EncodableVector signerinfo = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
// Add the signerInfo version
signerinfo.add(BOUNCY_CASTLE_FACTORY.createASN1Integer(signerversion));
v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(CertificateInfo.getIssuer(signCert.getTBSCertificate()));
v.add(BOUNCY_CASTLE_FACTORY.createASN1Integer(signCert.getSerialNumber()));
signerinfo.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v));
// Add the digestAlgorithm
v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(digestAlgorithmOid));
v.add(BOUNCY_CASTLE_FACTORY.createDERNull());
signerinfo.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v));
// add the authenticated attribute if present
if (secondDigest != null) {
signerinfo.add(BOUNCY_CASTLE_FACTORY.createDERTaggedObject(false, 0,
getAuthenticatedAttributeSet(secondDigest, ocsp, crlBytes, sigtype)));
}
// Add the digestEncryptionAlgorithm
v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(signatureMechanismOid));
if (this.signatureMechanismParameters == null) {
v.add(BOUNCY_CASTLE_FACTORY.createDERNull());
} else {
v.add(this.signatureMechanismParameters.toASN1Primitive());
}
signerinfo.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v));
// Add the digest
signerinfo.add(BOUNCY_CASTLE_FACTORY.createDEROctetString(signatureValue));
// When requested, go get and add the timestamp. May throw an exception.
// Added by Martin Brunecky, 07/12/2007 folowing Aiken Sam, 2006-11-15
// Sam found Adobe expects time-stamped SHA1-1 of the encrypted digest
if (tsaClient != null) {
byte[] tsImprint = tsaClient.getMessageDigest().digest(signatureValue);
byte[] tsToken = tsaClient.getTimeStampToken(tsImprint);
if (tsToken != null) {
IASN1EncodableVector unauthAttributes = buildUnauthenticatedAttributes(tsToken);
if (unauthAttributes != null) {
signerinfo.add(BOUNCY_CASTLE_FACTORY.createDERTaggedObject(
false, 1, BOUNCY_CASTLE_FACTORY.createDERSet(unauthAttributes)));
}
}
}
// Finally build the body out of all the components above
IASN1EncodableVector body = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
body.add(BOUNCY_CASTLE_FACTORY.createASN1Integer(version));
body.add(BOUNCY_CASTLE_FACTORY.createDERSet(digestAlgorithms));
body.add(contentinfo);
body.add(BOUNCY_CASTLE_FACTORY.createDERTaggedObject(false, 0, dercertificates));
// Only allow one signerInfo
body.add(BOUNCY_CASTLE_FACTORY.createDERSet(BOUNCY_CASTLE_FACTORY.createDERSequence(signerinfo)));
// Now we have the body, wrap it in it's PKCS7Signed shell
// and return it
//
IASN1EncodableVector whole = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
whole.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_SIGNED_DATA));
whole.add(BOUNCY_CASTLE_FACTORY.createDERTaggedObject(0, BOUNCY_CASTLE_FACTORY.createDERSequence(body)));
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
IASN1OutputStream dout = BOUNCY_CASTLE_FACTORY.createASN1OutputStream(bOut);
dout.writeObject(BOUNCY_CASTLE_FACTORY.createDERSequence(whole));
dout.close();
return bOut.toByteArray();
} catch (Exception e) {
throw new PdfException(e);
}
}
/**
* Added by Aiken Sam, 2006-11-15, modifed by Martin Brunecky 07/12/2007
* to start with the timeStampToken (signedData 1.2.840.113549.1.7.2).
* Token is the TSA response without response status, which is usually
* handled by the (vendor supplied) TSA request/response interface).
*
* @param timeStampToken byte[] - time stamp token, DER encoded signedData
*
* @return {@link IASN1EncodableVector}
*
* @throws IOException
*/
private IASN1EncodableVector buildUnauthenticatedAttributes(byte[] timeStampToken) throws IOException {
if (timeStampToken == null) {
return null;
}
// @todo: move this together with the rest of the defintions
String ID_TIME_STAMP_TOKEN = "1.2.840.113549.1.9.16.2.14"; // RFC 3161 id-aa-timeStampToken
IASN1EncodableVector unauthAttributes = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
IASN1EncodableVector v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(ID_TIME_STAMP_TOKEN)); // id-aa-timeStampToken
try (IASN1InputStream tempstream =
BOUNCY_CASTLE_FACTORY.createASN1InputStream(new ByteArrayInputStream(timeStampToken))) {
IASN1Sequence seq = BOUNCY_CASTLE_FACTORY.createASN1Sequence(tempstream.readObject());
v.add(BOUNCY_CASTLE_FACTORY.createDERSet(seq));
}
unauthAttributes.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v));
return unauthAttributes;
}
// Authenticated attributes
/**
* When using authenticatedAttributes the authentication process is different.
* The document digest is generated and put inside the attribute. The signing is done over the DER encoded
* authenticatedAttributes. This method provides that encoding and the parameters must be
* exactly the same as in {@link #getEncodedPKCS7(byte[])}.
*
*
* Note: do not pass in the full DER-encoded OCSPResponse object obtained from the responder,
* only the DER-encoded IBasicOCSPResponse value contained in the response data.
*
*
* A simple example:
*
* Calendar cal = Calendar.getInstance();
* PdfPKCS7 pk7 = new PdfPKCS7(key, chain, null, "SHA1", null, false);
* MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
* byte[] buf = new byte[8192];
* int n;
* InputStream inp = sap.getRangeStream();
* while ((n = inp.read(buf)) > 0) {
* messageDigest.update(buf, 0, n);
* }
* byte[] hash = messageDigest.digest();
* byte[] sh = pk7.getAuthenticatedAttributeBytes(hash, cal);
* pk7.update(sh, 0, sh.length);
* byte[] sg = pk7.getEncodedPKCS7(hash, cal);
*
*
* @param secondDigest the content digest
* @param sigtype specifies the PKCS7 standard flavor to which created PKCS7SignedData object will adhere:
* either basic CMS or CAdES
* @param ocsp collection of DER-encoded BasicOCSPResponses for the certificate in the signature
* certificates
* chain, or null if OCSP revocation data is not to be added.
* @param crlBytes collection of DER-encoded CRL for certificates from the signature certificates chain,
* or null if CRL revocation data is not to be added.
*
* @return the byte array representation of the authenticatedAttributes ready to be signed
*
* @see RFC 6960 § 4.2.1
*/
public byte[] getAuthenticatedAttributeBytes(byte[] secondDigest, PdfSigner.CryptoStandard sigtype,
Collection ocsp, Collection crlBytes) {
try {
return getAuthenticatedAttributeSet(secondDigest, ocsp, crlBytes, sigtype)
.getEncoded(BOUNCY_CASTLE_FACTORY.createASN1Encoding().getDer());
} catch (Exception e) {
throw new PdfException(e);
}
}
/**
* This method provides that encoding and the parameters must be
* exactly the same as in {@link #getEncodedPKCS7(byte[])}.
*
* @param secondDigest the content digest
*
* @return the byte array representation of the authenticatedAttributes ready to be signed
*/
private IDERSet getAuthenticatedAttributeSet(byte[] secondDigest, Collection ocsp,
Collection crlBytes, PdfSigner.CryptoStandard sigtype) {
try {
IASN1EncodableVector attribute = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
IASN1EncodableVector v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(SecurityIDs.ID_CONTENT_TYPE));
v.add(BOUNCY_CASTLE_FACTORY.createDERSet(
BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_DATA)));
attribute.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v));
v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(SecurityIDs.ID_MESSAGE_DIGEST));
v.add(BOUNCY_CASTLE_FACTORY.createDERSet(BOUNCY_CASTLE_FACTORY.createDEROctetString(secondDigest)));
attribute.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v));
boolean haveCrl = false;
if (crlBytes != null) {
for (byte[] bCrl : crlBytes) {
if (bCrl != null) {
haveCrl = true;
break;
}
}
}
if (ocsp != null && !ocsp.isEmpty() || haveCrl) {
v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(SecurityIDs.ID_ADBE_REVOCATION));
IASN1EncodableVector revocationV = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
if (haveCrl) {
IASN1EncodableVector v2 = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
for (byte[] bCrl : crlBytes) {
if (bCrl == null) {
continue;
}
try (IASN1InputStream t =
BOUNCY_CASTLE_FACTORY.createASN1InputStream(new ByteArrayInputStream(bCrl))) {
v2.add(t.readObject());
}
}
revocationV.add(BOUNCY_CASTLE_FACTORY.createDERTaggedObject(
true, 0, BOUNCY_CASTLE_FACTORY.createDERSequence(v2)));
}
if (ocsp != null && !ocsp.isEmpty()) {
IASN1EncodableVector vo1 = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
for (byte[] ocspBytes : ocsp) {
IDEROctetString doctet = BOUNCY_CASTLE_FACTORY.createDEROctetString(ocspBytes);
IASN1EncodableVector v2 = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
IOCSPObjectIdentifiers objectIdentifiers = BOUNCY_CASTLE_FACTORY.createOCSPObjectIdentifiers();
v2.add(objectIdentifiers.getIdPkixOcspBasic());
v2.add(doctet);
IASN1Enumerated den = BOUNCY_CASTLE_FACTORY.createASN1Enumerated(0);
IASN1EncodableVector v3 = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v3.add(den);
v3.add(BOUNCY_CASTLE_FACTORY.createDERTaggedObject(
true, 0, BOUNCY_CASTLE_FACTORY.createDERSequence(v2)));
vo1.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v3));
}
revocationV.add(BOUNCY_CASTLE_FACTORY.createDERTaggedObject(
true, 1, BOUNCY_CASTLE_FACTORY.createDERSequence(vo1)));
}
v.add(BOUNCY_CASTLE_FACTORY.createDERSet(BOUNCY_CASTLE_FACTORY.createDERSequence(revocationV)));
attribute.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v));
}
if (sigtype == PdfSigner.CryptoStandard.CADES) {
v = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
v.add(BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(SecurityIDs.ID_AA_SIGNING_CERTIFICATE_V2));
IASN1EncodableVector aaV2 = BOUNCY_CASTLE_FACTORY.createASN1EncodableVector();
IAlgorithmIdentifier algoId = BOUNCY_CASTLE_FACTORY.createAlgorithmIdentifier(
BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(digestAlgorithmOid));
aaV2.add(algoId);
MessageDigest md = SignUtils.getMessageDigest(getDigestAlgorithmName(), interfaceDigest);
byte[] dig = md.digest(signCert.getEncoded());
aaV2.add(BOUNCY_CASTLE_FACTORY.createDEROctetString(dig));
v.add(BOUNCY_CASTLE_FACTORY.createDERSet(BOUNCY_CASTLE_FACTORY.createDERSequence(
BOUNCY_CASTLE_FACTORY.createDERSequence(BOUNCY_CASTLE_FACTORY.createDERSequence(aaV2)))));
attribute.add(BOUNCY_CASTLE_FACTORY.createDERSequence(v));
}
if (signaturePolicyIdentifier != null) {
IPKCSObjectIdentifiers ipkcsObjectIdentifiers = BOUNCY_CASTLE_FACTORY.createPKCSObjectIdentifiers();
IAttribute attr = BOUNCY_CASTLE_FACTORY.createAttribute(ipkcsObjectIdentifiers.getIdAaEtsSigPolicyId(),
BOUNCY_CASTLE_FACTORY.createDERSet(signaturePolicyIdentifier));
attribute.add(attr);
}
return BOUNCY_CASTLE_FACTORY.createDERSet(attribute);
} catch (Exception e) {
throw new PdfException(e);
}
}
/*
* DIGITAL SIGNATURE VERIFICATION
*/
/**
* Signature attributes
*/
private byte[] sigAttr;
/**
* Signature attributes (maybe not necessary, but we use it as fallback)
*/
private byte[] sigAttrDer;
/**
* encrypted digest
*/
private MessageDigest encContDigest; // Stefan Santesson
/**
* Indicates if a signature has already been verified
*/
private boolean verified;
/**
* The result of the verification
*/
private boolean verifyResult;
// verification
/**
* Verifies that signature integrity is intact (or in other words that signed data wasn't modified)
* by checking that embedded data digest corresponds to the calculated one. Also ensures that signature
* is genuine and is created by the owner of private key that corresponds to the declared public certificate.
*
* Even though signature can be authentic and signed data integrity can be intact,
* one shall also always check that signed data is not only a part of PDF contents but is actually a complete PDF
* file.
* In order to check that given signature covers the current {@link com.itextpdf.kernel.pdf.PdfDocument} please
* use {@link SignatureUtil#signatureCoversWholeDocument(String)} method.
*
* @return true
if the signature checks out, false
otherwise
*
* @throws java.security.GeneralSecurityException if this signature object is not initialized properly,
* the passed-in signature is improperly encoded or of the wrong
* type, if this signature algorithm is unable to
* process the input data provided, if the public key is invalid or
* if security provider or signature algorithm
* are not recognized, etc.
*/
public boolean verifySignatureIntegrityAndAuthenticity() throws GeneralSecurityException {
if (verified) {
return verifyResult;
}
if (isTsp) {
IMessageImprint imprint = timeStampTokenInfo.getMessageImprint();
byte[] md = messageDigest.digest();
byte[] imphashed = imprint.getHashedMessage();
verifyResult = Arrays.equals(md, imphashed);
} else {
if (sigAttr != null || sigAttrDer != null) {
final byte[] msgDigestBytes = messageDigest.digest();
boolean verifySignedMessageContent = true;
// Stefan Santesson fixed a bug, keeping the code backward compatible
boolean encContDigestCompare = false;
if (encapMessageContent != null) {
verifySignedMessageContent = Arrays.equals(msgDigestBytes, encapMessageContent);
encContDigest.update(encapMessageContent);
encContDigestCompare = Arrays.equals(encContDigest.digest(), digestAttr);
}
boolean absentEncContDigestCompare = Arrays.equals(msgDigestBytes, digestAttr);
boolean concludingDigestCompare = absentEncContDigestCompare || encContDigestCompare;
boolean sigVerify = verifySigAttributes(sigAttr) || verifySigAttributes(sigAttrDer);
verifyResult = concludingDigestCompare && sigVerify && verifySignedMessageContent;
} else {
if (encapMessageContent != null) {
SignUtils.updateVerifier(sig, messageDigest.digest());
}
verifyResult = sig.verify(signatureValue);
}
}
verified = true;
return verifyResult;
}
private boolean verifySigAttributes(byte[] attr) throws GeneralSecurityException {
Signature signature = initSignature(signCert.getPublicKey());
SignUtils.updateVerifier(signature, attr);
return signature.verify(signatureValue);
}
/**
* Checks if the timestamp refers to this document.
*
* @return true if it checks false otherwise
*
* @throws GeneralSecurityException on error
*/
public boolean verifyTimestampImprint() throws GeneralSecurityException {
// TODO DEVSIX-6011 ensure this method works correctly
if (timeStampTokenInfo == null) {
return false;
}
IMessageImprint imprint = timeStampTokenInfo.getMessageImprint();
String algOID = imprint.getHashAlgorithm().getAlgorithm().getId();
byte[] md = SignUtils.getMessageDigest(DigestAlgorithms.getDigest(algOID)).digest(signatureValue);
byte[] imphashed = imprint.getHashedMessage();
return Arrays.equals(md, imphashed);
}
// Certificates
/**
* All the X.509 certificates in no particular order.
*/
private Collection certs;
/**
* All the X.509 certificates used for the main signature.
*/
Collection signCerts;
/**
* The X.509 certificate that is used to sign the digest.
*/
private X509Certificate signCert;
/**
* Get all the X.509 certificates associated with this PKCS#7 object in no particular order.
* Other certificates, from OCSP for example, will also be included.
*
* @return the X.509 certificates associated with this PKCS#7 object
*/
public Certificate[] getCertificates() {
return certs.toArray(new X509Certificate[certs.size()]);
}
/**
* Get the X.509 sign certificate chain associated with this PKCS#7 object.
* Only the certificates used for the main signature will be returned, with
* the signing certificate first.
*
* @return the X.509 certificates associated with this PKCS#7 object
*/
public Certificate[] getSignCertificateChain() {
return signCerts.toArray(new X509Certificate[signCerts.size()]);
}
/**
* Get the X.509 certificate actually used to sign the digest.
*
* @return the X.509 certificate actually used to sign the digest
*/
public X509Certificate getSigningCertificate() {
return signCert;
}
/**
* Helper method that creates the collection of certificates
* used for the main signature based on the complete list
* of certificates and the sign certificate.
*/
private void signCertificateChain() {
List cc = new ArrayList<>();
cc.add(signCert);
List oc = new ArrayList<>(certs);
for (int k = 0; k < oc.size(); ++k) {
if (signCert.equals(oc.get(k))) {
oc.remove(k);
--k;
}
}
boolean found = true;
while (found) {
X509Certificate v = (X509Certificate) cc.get(cc.size() - 1);
found = false;
for (int k = 0; k < oc.size(); ++k) {
X509Certificate issuer = (X509Certificate) oc.get(k);
if (SignUtils.verifyCertificateSignature(v, issuer.getPublicKey(), provider)) {
found = true;
cc.add(oc.get(k));
oc.remove(k);
break;
}
}
}
signCerts = cc;
}
// Certificate Revocation Lists
private Collection crls;
/**
* Get the X.509 certificate revocation lists associated with this PKCS#7 object
*
* @return the X.509 certificate revocation lists associated with this PKCS#7 object
*/
public Collection getCRLs() {
return crls;
}
/**
* Helper method that tries to construct the CRLs.
*/
void findCRL(IASN1Sequence seq) {
try {
crls = new ArrayList<>();
for (int k = 0; k < seq.size(); ++k) {
ByteArrayInputStream ar = new ByteArrayInputStream(seq.getObjectAt(k).toASN1Primitive()
.getEncoded(BOUNCY_CASTLE_FACTORY.createASN1Encoding().getDer()));
X509CRL crl = (X509CRL) SignUtils.parseCrlFromStream(ar);
crls.add(crl);
}
} catch (Exception ex) {
// ignore
}
}
// Online Certificate Status Protocol
/**
* BouncyCastle IBasicOCSPResponse
*/
IBasicOCSPResponse basicResp;
/**
* Gets the OCSP basic response if there is one.
*
* @return the OCSP basic response or null
*/
public IBasicOCSPResponse getOcsp() {
return basicResp;
}
/**
* Checks if OCSP revocation refers to the document signing certificate.
*
* @return true if it checks, false otherwise
*/
public boolean isRevocationValid() {
if (basicResp == null) {
return false;
}
if (signCerts.size() < 2) {
return false;
}
try {
X509Certificate[] cs = (X509Certificate[]) getSignCertificateChain();
ISingleResp sr = BOUNCY_CASTLE_FACTORY.createSingleResp(basicResp);
ICertificateID cid = sr.getCertID();
X509Certificate sigcer = getSigningCertificate();
X509Certificate isscer = cs[1];
ICertificateID tis = SignUtils.generateCertificateId(isscer, sigcer.getSerialNumber(), cid.getHashAlgOID());
return tis.equals(cid);
} catch (Exception ignored) {
}
return false;
}
/**
* Helper method that creates the IBasicOCSPResp object.
*
* @param seq
*
* @throws IOException
*/
private void findOcsp(IASN1Sequence seq) throws IOException {
basicResp = null;
boolean ret = false;
while (true) {
IASN1ObjectIdentifier objectIdentifier = BOUNCY_CASTLE_FACTORY.createASN1ObjectIdentifier(
seq.getObjectAt(0));
IOCSPObjectIdentifiers ocspObjectIdentifiers = BOUNCY_CASTLE_FACTORY.createOCSPObjectIdentifiers();
if (objectIdentifier != null
&& objectIdentifier.getId().equals(ocspObjectIdentifiers.getIdPkixOcspBasic().getId())) {
break;
}
ret = true;
for (int k = 0; k < seq.size(); ++k) {
IASN1Sequence nextSeq = BOUNCY_CASTLE_FACTORY.createASN1Sequence(seq.getObjectAt(k));
if (nextSeq != null) {
seq = nextSeq;
ret = false;
break;
}
IASN1TaggedObject tag = BOUNCY_CASTLE_FACTORY.createASN1TaggedObject(seq.getObjectAt(k));
if (tag != null) {
nextSeq = BOUNCY_CASTLE_FACTORY.createASN1Sequence(tag.getObject());
if (nextSeq != null) {
seq = nextSeq;
ret = false;
break;
} else {
return;
}
}
}
if (ret) {
return;
}
}
IASN1OctetString os = BOUNCY_CASTLE_FACTORY.createASN1OctetString(seq.getObjectAt(1));
try (IASN1InputStream inp = BOUNCY_CASTLE_FACTORY.createASN1InputStream(os.getOctets())) {
basicResp = BOUNCY_CASTLE_FACTORY.createBasicOCSPResponse(inp.readObject());
}
}
// Time Stamps
/**
* True if there's a PAdES LTV time stamp.
*/
private boolean isTsp;
/**
* True if it's a CAdES signature type.
*/
private boolean isCades;
/**
* BouncyCastle TSTInfo.
*/
private ITSTInfo timeStampTokenInfo;
/**
* Check if it's a PAdES-LTV time stamp.
*
* @return true if it's a PAdES-LTV time stamp, false otherwise
*/
public boolean isTsp() {
return isTsp;
}
/**
* Gets the timestamp token info if there is one.
*
* @return the timestamp token info or null
*/
public ITSTInfo getTimeStampTokenInfo() {
return timeStampTokenInfo;
}
/**
* Gets the timestamp date.
*
*
* In case the signed document doesn't contain timestamp,
* {@link TimestampConstants#UNDEFINED_TIMESTAMP_DATE} will be returned.
*
* @return the timestamp date
*/
public Calendar getTimeStampDate() {
if (timeStampTokenInfo == null) {
return (Calendar) TimestampConstants.UNDEFINED_TIMESTAMP_DATE;
}
return SignUtils.getTimeStampDate(timeStampTokenInfo);
}
/**
* Getter for the filter subtype.
*
* @return the filter subtype
*/
public PdfName getFilterSubtype() {
return filterSubtype;
}
}