/* * * This file is part of the iText (R) project. Copyright (c) 1998-2017 iText Group NV * Authors: Bruno Lowagie, Paulo Soares, et al. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS * * 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 http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA, 02110-1301 USA, or download the license from the following URL: * http://itextpdf.com/terms-of-use/ * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * In accordance with Section 7(b) of the GNU Affero General Public License, * a covered work must retain the producer line in every PDF that is created * or manipulated using iText. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the iText software without * disclosing the source code of your own applications. * These activities include: offering paid services to customers as an ASP, * serving PDFs on the fly in a web application, shipping iText with a closed * source product. * * For more information, please contact iText Software Corp. at this * address: sales@itextpdf.com */ package com.itextpdf.text.pdf.security; import com.itextpdf.text.ExceptionConverter; import com.itextpdf.text.error_messages.MessageLocalization; import com.itextpdf.text.pdf.PdfName; import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard; 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.esf.SignaturePolicyIdentifier; import org.bouncycastle.asn1.ess.ESSCertID; import org.bouncycastle.asn1.ess.ESSCertIDv2; import org.bouncycastle.asn1.ess.SigningCertificate; import org.bouncycastle.asn1.ess.SigningCertificateV2; import org.bouncycastle.asn1.ocsp.BasicOCSPResponse; import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.tsp.MessageImprint; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.bouncycastle.cert.ocsp.BasicOCSPResp; import org.bouncycastle.cert.ocsp.CertificateID; import org.bouncycastle.cert.ocsp.SingleResp; import org.bouncycastle.jce.X509Principal; import org.bouncycastle.jce.provider.X509CertParser; import org.bouncycastle.operator.DigestCalculator; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.tsp.TimeStampToken; import org.bouncycastle.tsp.TimeStampTokenInfo; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.security.*; import java.security.cert.*; import java.security.cert.Certificate; import java.util.*; /** * This class does all the processing related to signing * and verifying a PKCS#7 signature. */ public class PdfPKCS7 { // 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 <code>null</code> for the default provider * @param hasRSAdata <CODE>true</CODE> 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, ExternalDigest interfaceDigest, boolean hasRSAdata) throws InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException { this.provider = provider; this.interfaceDigest = interfaceDigest; // message digest digestAlgorithmOid = DigestAlgorithms.getAllowedDigests(hashAlgorithm); if (digestAlgorithmOid == null) throw new NoSuchAlgorithmException(MessageLocalization.getComposedMessage("unknown.hash.algorithm.1", hashAlgorithm)); // Copy the certificates signCert = (X509Certificate)certChain[0]; certs = new ArrayList<Certificate>(); for (Certificate element : certChain) { certs.add(element); } // initialize and add the digest algorithms. digestalgos = new HashSet<String>(); digestalgos.add(digestAlgorithmOid); // find the signing algorithm (RSA or DSA) if (privKey != null) { digestEncryptionAlgorithmOid = privKey.getAlgorithm(); if (digestEncryptionAlgorithmOid.equals("RSA")) { digestEncryptionAlgorithmOid = SecurityIDs.ID_RSA; } else if (digestEncryptionAlgorithmOid.equals("DSA")) { digestEncryptionAlgorithmOid = SecurityIDs.ID_DSA; } else { throw new NoSuchAlgorithmException(MessageLocalization.getComposedMessage("unknown.key.algorithm.1", digestEncryptionAlgorithmOid)); } } // initialize the RSA data if (hasRSAdata) { RSAdata = new byte[0]; messageDigest = DigestAlgorithms.getMessageDigest(getHashAlgorithm(), 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 <code>null</code> for the default provider */ @SuppressWarnings("unchecked") public PdfPKCS7(byte[] contentsKey, byte[] certsKey, String provider) { try { this.provider = provider; X509CertParser cr = new X509CertParser(); cr.engineInit(new ByteArrayInputStream(certsKey)); certs = cr.engineReadAll(); signCerts = certs; signCert = (X509Certificate)certs.iterator().next(); crls = new ArrayList<CRL>(); ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(contentsKey)); digest = ((ASN1OctetString)in.readObject()).getOctets(); if (provider == null) { sig = Signature.getInstance("SHA1withRSA"); } else { sig = Signature.getInstance("SHA1withRSA", provider); } sig.initVerify(signCert.getPublicKey()); // setting the oid to SHA1withRSA digestAlgorithmOid = "1.2.840.10040.4.3"; digestEncryptionAlgorithmOid = "1.3.36.3.3.1.2"; } catch (Exception e) { throw new ExceptionConverter(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 <code>null</code> 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; ASN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(contentsKey)); // // Basic checks to make sure it's a PKCS#7 SignedData Object // ASN1Primitive pkcs; try { pkcs = din.readObject(); } catch (IOException e) { throw new IllegalArgumentException(MessageLocalization.getComposedMessage("can.t.decode.pkcs7signeddata.object")); } if (!(pkcs instanceof ASN1Sequence)) { throw new IllegalArgumentException(MessageLocalization.getComposedMessage("not.a.valid.pkcs.7.object.not.a.sequence")); } ASN1Sequence signedData = (ASN1Sequence)pkcs; ASN1ObjectIdentifier objId = (ASN1ObjectIdentifier)signedData.getObjectAt(0); if (!objId.getId().equals(SecurityIDs.ID_PKCS7_SIGNED_DATA)) throw new IllegalArgumentException(MessageLocalization.getComposedMessage("not.a.valid.pkcs.7.object.not.signed.data")); ASN1Sequence content = (ASN1Sequence)((ASN1TaggedObject)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 = ((ASN1Integer)content.getObjectAt(0)).getValue().intValue(); // the digestAlgorithms digestalgos = new HashSet<String>(); Enumeration<ASN1Sequence> e = ((ASN1Set)content.getObjectAt(1)).getObjects(); while (e.hasMoreElements()) { ASN1Sequence s = e.nextElement(); ASN1ObjectIdentifier o = (ASN1ObjectIdentifier)s.getObjectAt(0); digestalgos.add(o.getId()); } // the possible ID_PKCS7_DATA ASN1Sequence rsaData = (ASN1Sequence)content.getObjectAt(2); if (rsaData.size() > 1) { ASN1OctetString rsaDataContent = (ASN1OctetString)((ASN1TaggedObject)rsaData.getObjectAt(1)).getObject(); RSAdata = rsaDataContent.getOctets(); } int next = 3; while (content.getObjectAt(next) instanceof ASN1TaggedObject) ++next; // the certificates /* This should work, but that's not always the case because of a bug in BouncyCastle: */ X509CertParser cr = new X509CertParser(); cr.engineInit(new ByteArrayInputStream(contentsKey)); certs = cr.engineReadAll(); /* The following workaround was provided by Alfonso Massa, but it doesn't always work either. ASN1Set certSet = null; ASN1Set crlSet = null; while (content.getObjectAt(next) instanceof ASN1TaggedObject) { ASN1TaggedObject tagged = (ASN1TaggedObject)content.getObjectAt(next); switch (tagged.getTagNo()) { case 0: certSet = ASN1Set.getInstance(tagged, false); break; case 1: crlSet = ASN1Set.getInstance(tagged, false); break; default: throw new IllegalArgumentException("unknown tag value " + tagged.getTagNo()); } ++next; } certs = new ArrayList<Certificate>(certSet.size()); CertificateFactory certFact = CertificateFactory.getInstance("X.509", new BouncyCastleProvider()); for (Enumeration en = certSet.getObjects(); en.hasMoreElements();) { ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive(); if (obj instanceof ASN1Sequence) { ByteArrayInputStream stream = new ByteArrayInputStream(obj.getEncoded()); X509Certificate x509Certificate = (X509Certificate)certFact.generateCertificate(stream); stream.close(); certs.add(x509Certificate); } } */ // the signerInfos ASN1Set signerInfos = (ASN1Set)content.getObjectAt(next); if (signerInfos.size() != 1) throw new IllegalArgumentException(MessageLocalization.getComposedMessage("this.pkcs.7.object.has.multiple.signerinfos.only.one.is.supported.at.this.time")); ASN1Sequence signerInfo = (ASN1Sequence)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 = ((ASN1Integer)signerInfo.getObjectAt(0)).getValue().intValue(); // Get the signing certificate ASN1Sequence issuerAndSerialNumber = (ASN1Sequence)signerInfo.getObjectAt(1); X509Principal issuer = new X509Principal(issuerAndSerialNumber.getObjectAt(0).toASN1Primitive().getEncoded()); BigInteger serialNumber = ((ASN1Integer)issuerAndSerialNumber.getObjectAt(1)).getValue(); for (Object element : certs) { X509Certificate cert = (X509Certificate)element; if (cert.getIssuerDN().equals(issuer) && serialNumber.equals(cert.getSerialNumber())) { signCert = cert; break; } } if (signCert == null) { throw new IllegalArgumentException(MessageLocalization.getComposedMessage("can.t.find.signing.certificate.with.serial.1", issuer.getName() + " / " + serialNumber.toString(16))); } signCertificateChain(); digestAlgorithmOid = ((ASN1ObjectIdentifier)((ASN1Sequence)signerInfo.getObjectAt(2)).getObjectAt(0)).getId(); next = 3; boolean foundCades = false; if (signerInfo.getObjectAt(next) instanceof ASN1TaggedObject) { ASN1TaggedObject tagsig = (ASN1TaggedObject)signerInfo.getObjectAt(next); ASN1Set sseq = ASN1Set.getInstance(tagsig, false); sigAttr = sseq.getEncoded(); // maybe not necessary, but we use the following line as fallback: sigAttrDer = sseq.getEncoded(ASN1Encoding.DER); for (int k = 0; k < sseq.size(); ++k) { ASN1Sequence seq2 = (ASN1Sequence)sseq.getObjectAt(k); String idSeq2 = ((ASN1ObjectIdentifier)seq2.getObjectAt(0)).getId(); if (idSeq2.equals(SecurityIDs.ID_MESSAGE_DIGEST)) { ASN1Set set = (ASN1Set)seq2.getObjectAt(1); digestAttr = ((ASN1OctetString)set.getObjectAt(0)).getOctets(); } else if (idSeq2.equals(SecurityIDs.ID_ADBE_REVOCATION)) { ASN1Set setout = (ASN1Set)seq2.getObjectAt(1); ASN1Sequence seqout = (ASN1Sequence)setout.getObjectAt(0); for (int j = 0; j < seqout.size(); ++j) { ASN1TaggedObject tg = (ASN1TaggedObject)seqout.getObjectAt(j); if (tg.getTagNo() == 0) { ASN1Sequence seqin = (ASN1Sequence)tg.getObject(); findCRL(seqin); } if (tg.getTagNo() == 1) { ASN1Sequence seqin = (ASN1Sequence)tg.getObject(); findOcsp(seqin); } } } else if (isCades && idSeq2.equals(SecurityIDs.ID_AA_SIGNING_CERTIFICATE_V1)) { ASN1Set setout = (ASN1Set)seq2.getObjectAt(1); ASN1Sequence seqout = (ASN1Sequence)setout.getObjectAt(0); SigningCertificate sv2 = SigningCertificate.getInstance(seqout); ESSCertID[] cerv2m = sv2.getCerts(); ESSCertID cerv2 = cerv2m[0]; byte[] enc2 = signCert.getEncoded(); MessageDigest m2 = new BouncyCastleDigest().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)) { ASN1Set setout = (ASN1Set)seq2.getObjectAt(1); ASN1Sequence seqout = (ASN1Sequence)setout.getObjectAt(0); SigningCertificateV2 sv2 = SigningCertificateV2.getInstance(seqout); ESSCertIDv2[] cerv2m = sv2.getCerts(); ESSCertIDv2 cerv2 = cerv2m[0]; AlgorithmIdentifier ai2 = cerv2.getHashAlgorithm(); byte[] enc2 = signCert.getEncoded(); MessageDigest m2 = new BouncyCastleDigest().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(MessageLocalization.getComposedMessage("authenticated.attribute.is.missing.the.digest")); ++next; } if (isCades && !foundCades) throw new IllegalArgumentException("CAdES ESS information missing."); digestEncryptionAlgorithmOid = ((ASN1ObjectIdentifier)((ASN1Sequence)signerInfo.getObjectAt(next++)).getObjectAt(0)).getId(); digest = ((ASN1OctetString)signerInfo.getObjectAt(next++)).getOctets(); if (next < signerInfo.size() && signerInfo.getObjectAt(next) instanceof ASN1TaggedObject) { ASN1TaggedObject taggedObject = (ASN1TaggedObject) signerInfo.getObjectAt(next); ASN1Set unat = ASN1Set.getInstance(taggedObject, false); AttributeTable attble = new AttributeTable(unat); Attribute ts = attble.get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken); if (ts != null && ts.getAttrValues().size() > 0) { ASN1Set attributeValues = ts.getAttrValues(); ASN1Sequence tokenSequence = ASN1Sequence.getInstance(attributeValues.getObjectAt(0)); ContentInfo contentInfo = new ContentInfo(tokenSequence); this.timeStampToken = new TimeStampToken(contentInfo); } } if (isTsp) { ContentInfo contentInfoTsp = new ContentInfo(signedData); this.timeStampToken = new TimeStampToken(contentInfoTsp); TimeStampTokenInfo info = timeStampToken.getTimeStampInfo(); String algOID = info.getMessageImprintAlgOID().getId(); messageDigest = DigestAlgorithms.getMessageDigestFromOid(algOID, null); } else { if (RSAdata != null || digestAttr != null) { if (PdfName.ADBE_PKCS7_SHA1.equals(getFilterSubtype())) { messageDigest = DigestAlgorithms.getMessageDigest("SHA1", provider); } else { messageDigest = DigestAlgorithms.getMessageDigest(getHashAlgorithm(), provider); } encContDigest = DigestAlgorithms.getMessageDigest(getHashAlgorithm(), provider); } sig = initSignature(signCert.getPublicKey()); } } catch (Exception e) { throw new ExceptionConverter(e); } } // Encryption provider /** The encryption provider, e.g. "BC" if you use BouncyCastle. */ private String provider; // Signature info private SignaturePolicyIdentifier signaturePolicyIdentifier; /** 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; public void setSignaturePolicy(SignaturePolicyInfo signaturePolicy) { this.signaturePolicyIdentifier = signaturePolicy.toSignaturePolicyIdentifier(); } public void setSignaturePolicy(SignaturePolicyIdentifier 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 == null) 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<String> digestalgos; /** The digest attributes */ private byte[] digestAttr; private PdfName filterSubtype; /** * Getter for the ID of the digest algorithm, e.g. "2.16.840.1.101.3.4.2.1" */ 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 getHashAlgorithm() { return DigestAlgorithms.getDigest(digestAlgorithmOid); } // Encryption algorithm /** The encryption algorithm. */ private String digestEncryptionAlgorithmOid; /** * Getter for the digest encryption algorithm */ public String getDigestEncryptionAlgorithmOid() { return digestEncryptionAlgorithmOid; } /** * Get the algorithm used to calculate the message digest, e.g. "SHA1withRSA". * @return the algorithm used to calculate the message digest */ public String getDigestAlgorithm() { return getHashAlgorithm() + "with" + getEncryptionAlgorithm(); } /* * DIGITAL SIGNATURE CREATION */ private ExternalDigest interfaceDigest; // The signature is created externally /** The signed digest if created outside this class */ private byte externalDigest[]; /** External RSA data */ private byte externalRSAdata[]; /** * Sets the digest/signature to an external calculated value. * @param digest the digest. This is the actual signature * @param RSAdata the extra data that goes into the data tag in PKCS#7 * @param digestEncryptionAlgorithm the encryption algorithm. It may must be <CODE>null</CODE> if the <CODE>digest</CODE> * is also <CODE>null</CODE>. If the <CODE>digest</CODE> is not <CODE>null</CODE> * then it may be "RSA" or "DSA" */ public void setExternalDigest(byte digest[], byte RSAdata[], String digestEncryptionAlgorithm) { externalDigest = digest; externalRSAdata = RSAdata; if (digestEncryptionAlgorithm != null) { if (digestEncryptionAlgorithm.equals("RSA")) { this.digestEncryptionAlgorithmOid = SecurityIDs.ID_RSA; } else if (digestEncryptionAlgorithm.equals("DSA")) { this.digestEncryptionAlgorithmOid = SecurityIDs.ID_DSA; } else if (digestEncryptionAlgorithm.equals("ECDSA")) { this.digestEncryptionAlgorithmOid = SecurityIDs.ID_ECDSA; } else throw new ExceptionConverter(new NoSuchAlgorithmException(MessageLocalization.getComposedMessage("unknown.key.algorithm.1", digestEncryptionAlgorithm))); } } // The signature is created internally /** Class from the Java SDK that provides the functionality of a digital signature algorithm. */ private Signature sig; /** The signed digest as calculated by this class (or extracted from an existing PDF) */ private byte[] digest; /** The RSA data */ private byte[] RSAdata; // Signing functionality. private Signature initSignature(PrivateKey key) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException { Signature signature; if (provider == null) signature = Signature.getInstance(getDigestAlgorithm()); else signature = Signature.getInstance(getDigestAlgorithm(), provider); signature.initSign(key); return signature; } private Signature initSignature(PublicKey key) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException { String digestAlgorithm = getDigestAlgorithm(); if (PdfName.ADBE_X509_RSA_SHA1.equals(getFilterSubtype())) digestAlgorithm = "SHA1withRSA"; Signature signature; if (provider == null) signature = Signature.getInstance(digestAlgorithm); else signature = Signature.getInstance(digestAlgorithm, provider); signature.initVerify(key); return signature; } /** * 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 (RSAdata != 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 (externalDigest != null) digest = externalDigest; else digest = sig.sign(); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); ASN1OutputStream dout = new ASN1OutputStream(bOut); dout.writeObject(new DEROctetString(digest)); dout.close(); return bOut.toByteArray(); } catch (Exception e) { throw new ExceptionConverter(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, null, null, null, CryptoStandard.CMS); } /** * Gets the bytes for the PKCS7SignedData object. Optionally the authenticatedAttributes * in the signerInfo can also be set. If either of the parameters is <CODE>null</CODE>, 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, null, null, null, CryptoStandard.CMS); } /** * Gets the bytes for the PKCS7SignedData object. Optionally the authenticatedAttributes * in the signerInfo can also be set, OR a time-stamp-authority client * may be provided. * @param secondDigest the digest in the authenticatedAttributes * @param tsaClient TSAClient - null or an optional time stamp authority client * @return byte[] the bytes for the PKCS7SignedData object * @since 2.1.6 */ public byte[] getEncodedPKCS7(byte secondDigest[], TSAClient tsaClient, byte[] ocsp, Collection<byte[]> crlBytes, CryptoStandard sigtype) { try { if (externalDigest != null) { digest = externalDigest; if (RSAdata != null) RSAdata = externalRSAdata; } else if (externalRSAdata != null && RSAdata != null) { RSAdata = externalRSAdata; sig.update(RSAdata); digest = sig.sign(); } else { if (RSAdata != null) { RSAdata = messageDigest.digest(); sig.update(RSAdata); } digest = sig.sign(); } // Create the set of Hash algorithms ASN1EncodableVector digestAlgorithms = new ASN1EncodableVector(); for (Object element : digestalgos) { ASN1EncodableVector algos = new ASN1EncodableVector(); algos.add(new ASN1ObjectIdentifier((String)element)); algos.add(DERNull.INSTANCE); digestAlgorithms.add(new DERSequence(algos)); } // Create the contentInfo. ASN1EncodableVector v = new ASN1EncodableVector(); v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_DATA)); if (RSAdata != null) v.add(new DERTaggedObject(0, new DEROctetString(RSAdata))); DERSequence contentinfo = new DERSequence(v); // Get all the certificates // v = new ASN1EncodableVector(); for (Object element : certs) { ASN1InputStream tempstream = new ASN1InputStream(new ByteArrayInputStream(((X509Certificate)element).getEncoded())); v.add(tempstream.readObject()); } DERSet dercertificates = new DERSet(v); // Create signerinfo structure. // ASN1EncodableVector signerinfo = new ASN1EncodableVector(); // Add the signerInfo version // signerinfo.add(new ASN1Integer(signerversion)); v = new ASN1EncodableVector(); v.add(CertificateInfo.getIssuer(signCert.getTBSCertificate())); v.add(new ASN1Integer(signCert.getSerialNumber())); signerinfo.add(new DERSequence(v)); // Add the digestAlgorithm v = new ASN1EncodableVector(); v.add(new ASN1ObjectIdentifier(digestAlgorithmOid)); v.add(new DERNull()); signerinfo.add(new DERSequence(v)); // add the authenticated attribute if present if (secondDigest != null) { signerinfo.add(new DERTaggedObject(false, 0, getAuthenticatedAttributeSet(secondDigest, ocsp, crlBytes, sigtype))); } // Add the digestEncryptionAlgorithm v = new ASN1EncodableVector(); v.add(new ASN1ObjectIdentifier(digestEncryptionAlgorithmOid)); v.add(new DERNull()); signerinfo.add(new DERSequence(v)); // Add the digest signerinfo.add(new DEROctetString(digest)); // 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(digest); byte[] tsToken = tsaClient.getTimeStampToken(tsImprint); if (tsToken != null) { ASN1EncodableVector unauthAttributes = buildUnauthenticatedAttributes(tsToken); if (unauthAttributes != null) { signerinfo.add(new DERTaggedObject(false, 1, new DERSet(unauthAttributes))); } } } // Finally build the body out of all the components above ASN1EncodableVector body = new ASN1EncodableVector(); body.add(new ASN1Integer(version)); body.add(new DERSet(digestAlgorithms)); body.add(contentinfo); body.add(new DERTaggedObject(false, 0, dercertificates)); // Only allow one signerInfo body.add(new DERSet(new DERSequence(signerinfo))); // Now we have the body, wrap it in it's PKCS7Signed shell // and return it // ASN1EncodableVector whole = new ASN1EncodableVector(); whole.add(new ASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_SIGNED_DATA)); whole.add(new DERTaggedObject(0, new DERSequence(body))); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); ASN1OutputStream dout = new ASN1OutputStream(bOut); dout.writeObject(new DERSequence(whole)); dout.close(); return bOut.toByteArray(); } catch (Exception e) { throw new ExceptionConverter(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 ASN1EncodableVector * @throws IOException */ private ASN1EncodableVector 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 ASN1InputStream tempstream = new ASN1InputStream(new ByteArrayInputStream(timeStampToken)); ASN1EncodableVector unauthAttributes = new ASN1EncodableVector(); ASN1EncodableVector v = new ASN1EncodableVector(); v.add(new ASN1ObjectIdentifier(ID_TIME_STAMP_TOKEN)); // id-aa-timeStampToken ASN1Sequence seq = (ASN1Sequence) tempstream.readObject(); v.add(new DERSet(seq)); unauthAttributes.add(new DERSequence(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[])}. * <p> * A simple example: * <p> * <pre> * 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); * </pre> * @param secondDigest the content digest * @return the byte array representation of the authenticatedAttributes ready to be signed */ public byte[] getAuthenticatedAttributeBytes(byte secondDigest[], byte[] ocsp, Collection<byte[]> crlBytes, CryptoStandard sigtype) { try { return getAuthenticatedAttributeSet(secondDigest, ocsp, crlBytes, sigtype).getEncoded(ASN1Encoding.DER); } catch (Exception e) { throw new ExceptionConverter(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 DERSet getAuthenticatedAttributeSet(byte secondDigest[], byte[] ocsp, Collection<byte[]> crlBytes, CryptoStandard sigtype) { try { ASN1EncodableVector attribute = new ASN1EncodableVector(); ASN1EncodableVector v = new ASN1EncodableVector(); v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_CONTENT_TYPE)); v.add(new DERSet(new ASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_DATA))); attribute.add(new DERSequence(v)); v = new ASN1EncodableVector(); v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_MESSAGE_DIGEST)); v.add(new DERSet(new DEROctetString(secondDigest))); attribute.add(new DERSequence(v)); boolean haveCrl = false; if (crlBytes != null) { for (byte[] bCrl : crlBytes) { if (bCrl != null) { haveCrl = true; break; } } } if (ocsp != null || haveCrl) { v = new ASN1EncodableVector(); v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_ADBE_REVOCATION)); ASN1EncodableVector revocationV = new ASN1EncodableVector(); if (haveCrl) { ASN1EncodableVector v2 = new ASN1EncodableVector(); for (byte[] bCrl : crlBytes) { if (bCrl == null) continue; ASN1InputStream t = new ASN1InputStream(new ByteArrayInputStream(bCrl)); v2.add(t.readObject()); } revocationV.add(new DERTaggedObject(true, 0, new DERSequence(v2))); } if (ocsp != null) { DEROctetString doctet = new DEROctetString(ocsp); ASN1EncodableVector vo1 = new ASN1EncodableVector(); ASN1EncodableVector v2 = new ASN1EncodableVector(); v2.add(OCSPObjectIdentifiers.id_pkix_ocsp_basic); v2.add(doctet); ASN1Enumerated den = new ASN1Enumerated(0); ASN1EncodableVector v3 = new ASN1EncodableVector(); v3.add(den); v3.add(new DERTaggedObject(true, 0, new DERSequence(v2))); vo1.add(new DERSequence(v3)); revocationV.add(new DERTaggedObject(true, 1, new DERSequence(vo1))); } v.add(new DERSet(new DERSequence(revocationV))); attribute.add(new DERSequence(v)); } if (sigtype == CryptoStandard.CADES) { v = new ASN1EncodableVector(); v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_AA_SIGNING_CERTIFICATE_V2)); ASN1EncodableVector aaV2 = new ASN1EncodableVector(); String sha256Oid = DigestAlgorithms.getAllowedDigests(DigestAlgorithms.SHA256); // If we look into X.690-0207, clause 11.5, we can see that using DER all the components of a sequence having // default values shall not be included. According to RFC 5035, 5.4.1.1, definition of ESSCertIDv2, default // AlgorithmIdentifier is sha256. if (!sha256Oid.equals(digestAlgorithmOid)) { AlgorithmIdentifier algoId = new AlgorithmIdentifier(new ASN1ObjectIdentifier(digestAlgorithmOid)); aaV2.add(algoId); } MessageDigest md = interfaceDigest.getMessageDigest(getHashAlgorithm()); byte[] dig = md.digest(signCert.getEncoded()); aaV2.add(new DEROctetString(dig)); v.add(new DERSet(new DERSequence(new DERSequence(new DERSequence(aaV2))))); attribute.add(new DERSequence(v)); } if (signaturePolicyIdentifier != null) { attribute.add(new Attribute(PKCSObjectIdentifiers.id_aa_ets_sigPolicyId, new DERSet(signaturePolicyIdentifier))); } return new DERSet(attribute); } catch (Exception e) { throw new ExceptionConverter(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 /** * Verify the digest. * @throws SignatureException on error * @return <CODE>true</CODE> if the signature checks out, <CODE>false</CODE> otherwise * @throws java.security.GeneralSecurityException */ public boolean verify() throws GeneralSecurityException { if (verified) return verifyResult; if (isTsp) { TimeStampTokenInfo info = timeStampToken.getTimeStampInfo(); MessageImprint imprint = info.toASN1Structure().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 verifyRSAdata = true; // Stefan Santesson fixed a bug, keeping the code backward compatible boolean encContDigestCompare = false; if (RSAdata != null) { verifyRSAdata = Arrays.equals(msgDigestBytes, RSAdata); encContDigest.update(RSAdata); 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 && verifyRSAdata; } else { if (RSAdata != null) sig.update(messageDigest.digest()); verifyResult = sig.verify(digest); } } verified = true; return verifyResult; } private boolean verifySigAttributes(byte[] attr) throws GeneralSecurityException { Signature signature = initSignature(signCert.getPublicKey()); signature.update(attr); return signature.verify(digest); } /** * Checks if the timestamp refers to this document. * @return true if it checks false otherwise * @throws GeneralSecurityException on error * @since 2.1.6 */ public boolean verifyTimestampImprint() throws GeneralSecurityException { if (timeStampToken == null) return false; TimeStampTokenInfo info = timeStampToken.getTimeStampInfo(); MessageImprint imprint = info.toASN1Structure().getMessageImprint(); String algOID = info.getMessageImprintAlgOID().getId(); byte[] md = new BouncyCastleDigest().getMessageDigest(DigestAlgorithms.getDigest(algOID)).digest(digest); byte[] imphashed = imprint.getHashedMessage(); boolean res = Arrays.equals(md, imphashed); return res; } // Certificates /** All the X.509 certificates in no particular order. */ private Collection<Certificate> certs; /** All the X.509 certificates used for the main signature. */ private Collection<Certificate> 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 * @since 2.1.6 */ 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() { ArrayList<Certificate> cc = new ArrayList<Certificate>(); cc.add(signCert); ArrayList<Certificate> oc = new ArrayList<Certificate>(certs); for (int k = 0; k < oc.size(); ++k) { if (signCert.equals(oc.get(k))) { oc.remove(k); --k; continue; } } 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); try { if (provider == null) v.verify(issuer.getPublicKey()); else v.verify(issuer.getPublicKey(), provider); found = true; cc.add(oc.get(k)); oc.remove(k); break; } catch (Exception e) { } } } signCerts = cc; } // Certificate Revocation Lists private Collection<CRL> 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<CRL> getCRLs() { return crls; } /** * Helper method that tries to construct the CRLs. */ private void findCRL(ASN1Sequence seq) { try { crls = new ArrayList<CRL>(); for (int k = 0; k < seq.size(); ++k) { ByteArrayInputStream ar = new ByteArrayInputStream(seq.getObjectAt(k).toASN1Primitive().getEncoded(ASN1Encoding.DER)); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509CRL crl = (X509CRL)cf.generateCRL(ar); crls.add(crl); } } catch (Exception ex) { // ignore } } // Online Certificate Status Protocol /** BouncyCastle BasicOCSPResp */ private BasicOCSPResp basicResp; /** * Gets the OCSP basic response if there is one. * @return the OCSP basic response or null * @since 2.1.6 */ public BasicOCSPResp getOcsp() { return basicResp; } /** * Checks if OCSP revocation refers to the document signing certificate. * @return true if it checks, false otherwise * @since 2.1.6 */ public boolean isRevocationValid() { if (basicResp == null) return false; if (signCerts.size() < 2) return false; try { X509Certificate[] cs = (X509Certificate[])getSignCertificateChain(); SingleResp sr = basicResp.getResponses()[0]; CertificateID cid = sr.getCertID(); DigestCalculator digestalg = new JcaDigestCalculatorProviderBuilder().build().get(new AlgorithmIdentifier(cid.getHashAlgOID(), DERNull.INSTANCE)); X509Certificate sigcer = getSigningCertificate(); X509Certificate isscer = cs[1]; CertificateID tis = new CertificateID( digestalg, new JcaX509CertificateHolder(isscer), sigcer.getSerialNumber()); return tis.equals(cid); } catch (Exception ex) { } return false; } /** * Helper method that creates the BasicOCSPResp object. * @param seq * @throws IOException */ private void findOcsp(ASN1Sequence seq) throws IOException { basicResp = null; boolean ret = false; while (true) { if (seq.getObjectAt(0) instanceof ASN1ObjectIdentifier && ((ASN1ObjectIdentifier)seq.getObjectAt(0)).getId().equals(OCSPObjectIdentifiers.id_pkix_ocsp_basic.getId())) { break; } ret = true; for (int k = 0; k < seq.size(); ++k) { if (seq.getObjectAt(k) instanceof ASN1Sequence) { seq = (ASN1Sequence)seq.getObjectAt(0); ret = false; break; } if (seq.getObjectAt(k) instanceof ASN1TaggedObject) { ASN1TaggedObject tag = (ASN1TaggedObject)seq.getObjectAt(k); if (tag.getObject() instanceof ASN1Sequence) { seq = (ASN1Sequence)tag.getObject(); ret = false; break; } else return; } } if (ret) return; } ASN1OctetString os = (ASN1OctetString)seq.getObjectAt(1); ASN1InputStream inp = new ASN1InputStream(os.getOctets()); BasicOCSPResponse resp = BasicOCSPResponse.getInstance(inp.readObject()); basicResp = new BasicOCSPResp(resp); } // 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 TimeStampToken. */ private TimeStampToken timeStampToken; /** * 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 if there is one. * @return the timestamp token or null * @since 2.1.6 */ public TimeStampToken getTimeStampToken() { return timeStampToken; } /** * Gets the timestamp date * @return a date * @since 2.1.6 */ public Calendar getTimeStampDate() { if (timeStampToken == null) return null; Calendar cal = new GregorianCalendar(); Date date = timeStampToken.getTimeStampInfo().getGenTime(); cal.setTime(date); return cal; } /** * Returns the filter subtype. */ public PdfName getFilterSubtype() { return filterSubtype; } /** * Returns the encryption algorithm * @return the name of an encryption algorithm */ public String getEncryptionAlgorithm() { String encryptAlgo = EncryptionAlgorithms.getAlgorithm(digestEncryptionAlgorithmOid); if (encryptAlgo == null) encryptAlgo = digestEncryptionAlgorithmOid; return encryptAlgo; } }