Java tutorial
/** * j4sign - an open, multi-platform digital signature solution * Copyright (c) 2014 Roberto Resoli - Servizio Sistema Informativo, Comune di Trento. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package it.trento.comune.j4sign.cms.utils; import it.trento.comune.j4sign.cms.ExternalSignatureCMSSignedDataGenerator; import it.trento.comune.j4sign.cms.ExternalSignatureSignerInfoGenerator; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.security.DigestInputStream; import java.security.InvalidAlgorithmParameterException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.security.cert.CertStore; import java.security.cert.CertStoreException; import java.security.cert.CertificateException; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.SimpleTimeZone; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DEROutputStream; import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.CMSAttributes; import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.util.ASN1Dump; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.DigestInfo; import org.bouncycastle.asn1.x509.Time; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.x509.NoSuchStoreException; import org.bouncycastle.x509.X509Store; /** * An helper class for generating CMS files, following CAdES specification, * where encryption is done elsewhere. * <p> * CMSBuilder takes care of: * <ul> * <li>Generating data to be signed, and streaming content to be signed, in a * synchronized way.</li> * <li>Receiving raw signature and building the corresponding CMS envelope.</li> * </ul> * </p> * */ public class CMSBuilder implements Serializable { private static int WRAP_AFTER = 16; private ExternalSignatureSignerInfoGenerator infoGen = null; private String digestAlgorithm = null; private String encryptionAlgorithm = null; private byte[] dataHash = null; private String dataPath = null; private byte[] streamHash = null; private byte[] certBytes = null; private String encodedDigest = null; private Date signingTime = null; private String dataContentType = null; private String dataFileName = null; public CMSBuilder(String digestAlgorithm, String encryptionAlgorithm) { super(); initializeInfoGen(digestAlgorithm, encryptionAlgorithm); } public CMSBuilder(InputStream dataStream, String digestAlgorithm, String encryptionAlgorithm) throws IOException, NoSuchAlgorithmException { super(); initializeInfoGen(digestAlgorithm, encryptionAlgorithm); // Calcolo dataHash initializeDataHash(dataStream); } public CMSBuilder(byte[] docHash, String digestAlgorithm, String encryptionAlgorithm) throws IOException, NoSuchAlgorithmException { super(); initializeInfoGen(digestAlgorithm, encryptionAlgorithm); this.dataHash = docHash; } /** * Calculates the content data hash. * * <p> * All subsequent content related calculation, such as in * {@link #updateEncodedDigest()} method, will keep this value as integrity * reference. * </p> * * @param dataStream * @throws NoSuchAlgorithmException * @throws IOException */ public void initializeDataHash(InputStream dataStream) throws NoSuchAlgorithmException, IOException { this.dataHash = hashContent(dataStream); this.streamHash = null; dataStream.close(); } private void initializeInfoGen(String digestAlgorithm, String encryptionAlgorithm) { this.digestAlgorithm = digestAlgorithm; this.encryptionAlgorithm = encryptionAlgorithm; if (java.security.Security.getProvider("BC") == null) { System.out.println("Adding BC provider as second...(to avoid JDK 1.4 bug)."); Security.insertProviderAt(new BouncyCastleProvider(), 2); System.out.println("BC provider added."); } System.out.println("Building SignerInfoGenerator."); this.infoGen = new ExternalSignatureSignerInfoGenerator(this.digestAlgorithm, this.encryptionAlgorithm); } /** * Triggers encoded digest recalculation. * <p> * Invokes the private <code>getAuthenticatedAttributesBytes()</code> method * obtaining the raw digest, encapsulates it in a <code>digestInfo</code> * structure, finally encoding the result in <code>base64</code>. * </p> * * @return the <code>base64</code> encoding of the data to be signed. * @throws NoSuchAlgorithmException * @throws IOException */ public String updateEncodedDigest() throws NoSuchAlgorithmException, IOException { String ed = null; byte[] bytesToSign = getAuthenticatedAttributesBytes(); byte[] rawDigest = null; byte[] dInfoBytes = null; if (bytesToSign != null) { rawDigest = applyDigest(digestAlgorithm, bytesToSign); System.out.println("Raw digest bytes:\n" + formatAsString(rawDigest, " ", WRAP_AFTER)); System.out.println("Encapsulating in a DigestInfo..."); dInfoBytes = encapsulateInDigestInfo(digestAlgorithm, rawDigest); System.out.println("DigestInfo bytes:\n" + formatAsString(dInfoBytes, " ", WRAP_AFTER)); ed = new String(Base64.encode(dInfoBytes)); this.encodedDigest = ed; } return ed; } private byte[] applyDigest(String digestAlg, byte[] bytes) throws NoSuchAlgorithmException { System.out.println("Applying digest algorithm..."); MessageDigest md = MessageDigest.getInstance(this.digestAlgorithm); md.update(bytes); return md.digest(); } private byte[] encapsulateInDigestInfo(String digestAlg, byte[] digestBytes) throws IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); DEROutputStream dOut = new DEROutputStream(bOut); DERObjectIdentifier digestObjId = new DERObjectIdentifier(digestAlg); AlgorithmIdentifier algId = new AlgorithmIdentifier(digestObjId, null); DigestInfo dInfo = new DigestInfo(algId, digestBytes); dOut.writeObject(dInfo); return bOut.toByteArray(); } private byte[] applyPkcs1Padding(int resultLength, byte[] srcBytes) { int paddingLength = resultLength - srcBytes.length; byte[] dstBytes = new byte[resultLength]; dstBytes[0] = 0x00; dstBytes[1] = 0x01; for (int i = 2; i < (paddingLength - 1); i++) { dstBytes[i] = (byte) 0xFF; } dstBytes[paddingLength - 1] = 0x00; for (int i = 0; i < srcBytes.length; i++) { dstBytes[paddingLength + i] = srcBytes[i]; } return dstBytes; } /** * * Calculates data to be signed. * <p> * Builds the CMS authenticated attributes; ContentType and MessageDigest * are mandatory, optional SigningTime (taken from current system time) is * added by default. This method waits for the completion of the * synchronized {@link streamAndHashContent} method, so that bytes to sign * is returned only when the streamed content is identical to the original * one. * </p> * * @return the byte[] containing the calculated authenticated attributes; */ private synchronized byte[] getAuthenticatedAttributesBytes() { System.out.println("Building AuthenticatedAttributes from content."); byte[] bytesToSign = null; long timeout = 10000; try { long millisBefore = System.currentTimeMillis(); long millisWaited = 0; if (this.streamHash == null) { System.out.println("getAuthenticatedAttributesBytes: Thread '" + Thread.currentThread().getName() + "' starts waiting; timeout " + timeout + " ms."); /* * // Notify streamAndHashContent System.out * .println("getAuthenticatedAttributesBytes: Thread '" + * Thread.currentThread().getName() + "' issues notify."); * notify(); */ wait(timeout); millisWaited = System.currentTimeMillis() - millisBefore; if (millisWaited < timeout) System.out.println("getAuthenticatedAttributesBytes: Thread '" + Thread.currentThread().getName() + "' waited: " + millisWaited + "ms"); else System.out.println("getAuthenticatedAttributesBytes: Thread '" + Thread.currentThread().getName() + " " + timeout + "ms timeout expired!"); } if (this.streamHash != null) { if (Arrays.equals(this.streamHash, this.dataHash)) { this.signingTime = new Date(); bytesToSign = this.infoGen.getBytesToSign(PKCSObjectIdentifiers.data, this.dataHash, this.signingTime, "BC"); this.streamHash = null; } else System.out.println( "getAuthenticatedAttributesBytes: Error - stream Hash is different from data Hash"); } else System.out.println("getAuthenticatedAttributesBytes: Error - stream Hash is null!!!"); } catch (Exception e) { System.out.println("getAuthenticatedAttributesBytes: Error - " + e); } if (bytesToSign != null) { StringWriter printout = new StringWriter(); PrintWriter pw = new PrintWriter(printout); // Now signingTime is explicitly set in getBytesToSign(), see above // this.signingTime = parseSigningTime(bytesToSign, pw); // System.out.println(printout); } return bytesToSign; } public String getSigningTimeAsString() { SimpleDateFormat df = new SimpleDateFormat("dd MMMMM yyyy HH:mm:ss z"); if (this.signingTime != null) return df.format(this.signingTime); return ""; } public String getEncodedGMTSigningTime() { if (this.signingTime != null) { SimpleDateFormat df = new SimpleDateFormat("dd MMMMM yyyy HH:mm:ss z"); Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "GMT")); df.setCalendar(cal); try { return new String(Base64.encode(df.format(this.signingTime).getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { System.out.println(e); } } return ""; } private Date parseSigningTime(byte[] bytes, PrintWriter pw) { Date parsedSigningTime = null; try { ASN1InputStream aIn = new ASN1InputStream(bytes); ASN1Set signedAttributes = (ASN1Set) aIn.readObject(); AttributeTable attr = new AttributeTable(signedAttributes); Iterator iter = attr.toHashtable().values().iterator(); pw.println("Listing authenticated attributes:"); int count = 1; while (iter.hasNext()) { Attribute a = (Attribute) iter.next(); pw.println("Attribute " + count + ":"); if (a.getAttrType().getId().equals(CMSAttributes.signingTime.getId())) { Time time = Time.getInstance(a.getAttrValues().getObjectAt(0)); pw.println("Authenticated time (SERVER local time): " + time.getDate()); parsedSigningTime = time.getDate(); } if (a.getAttrType().getId().equals(CMSAttributes.contentType.getId())) { if (CMSObjectIdentifiers.data.getId() .equals(DERObjectIdentifier.getInstance(a.getAttrValues().getObjectAt(0)).getId())) pw.println("Content Type: PKCS7_DATA"); } if (a.getAttrType().getId().equals(CMSAttributes.messageDigest.getId())) { byte[] md = DEROctetString.getInstance(a.getAttrValues().getObjectAt(0)).getOctets(); pw.println("Message Digest (hash of data content): " + formatAsString(md, " ", 16)); } pw.println("\nAttribute dump follows:"); pw.println(ASN1Dump.dumpAsString(a) + "\n"); count++; } } catch (Exception e) { pw.println(e); return null; } pw.flush(); return parsedSigningTime; } public CMSSignedData buildCMSSignedData(InputStream contentStream, String encodedEcryptedDigest) throws NoSuchAlgorithmException, IOException { byte[] ecryptedDigestBytes = Base64.decode(encodedEcryptedDigest); ByteArrayOutputStream baos = new ByteArrayOutputStream(); streamAndHashContent(contentStream, baos); contentStream.close(); baos.close(); CMSSignedData sd = buildCMSSignedData(this.infoGen, baos.toByteArray(), ecryptedDigestBytes, this.certBytes); return sd; } public CMSSignedData buildCMSSignedData(InputStream contentStream, String encodedEcryptedDigest, String encodedCert) throws IOException, NoSuchAlgorithmException { byte[] ecryptedDigestBytes = Base64.decode(encodedEcryptedDigest); byte[] certBytes = Base64.decode(encodedCert); ByteArrayOutputStream baos = new ByteArrayOutputStream(); streamAndHashContent(contentStream, baos); contentStream.close(); baos.close(); CMSSignedData sd = buildCMSSignedData(this.infoGen, baos.toByteArray(), ecryptedDigestBytes, certBytes); return sd; } /* * public CMSSignedData buildCMSSignedData(String encodedEcryptedDigest, * String encodedCert) { * * byte[] ecryptedDigestBytes = Base64.decode(encodedEcryptedDigest); byte[] * certBytes = Base64.decode(encodedCert); * * CMSSignedData sd = buildCMSSignedData(this.infoGen, this.data, * ecryptedDigestBytes, certBytes); * * return sd; } */ public CMSSignedData buildCMSSignedData(ExternalSignatureSignerInfoGenerator infoGen, byte[] data, byte[] sigBytes, byte[] certBytes) { CMSSignedData cms = null; System.out.println("building CMSSignedData."); try { CMSProcessable msg = new CMSProcessableByteArray(data); // Conterr la lista dei certificati; come minimo dovr // contenere i certificati dei firmatari; opzionale, ma // consigliabile, // l'aggiunta dei certificati root per completare le catene di // certificazione. ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(); // get Certificate java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509"); java.io.ByteArrayInputStream bais1 = new java.io.ByteArrayInputStream(certBytes); java.security.cert.X509Certificate javaCert = (java.security.cert.X509Certificate) cf .generateCertificate(bais1); infoGen.setCertificate(javaCert); infoGen.setSignedBytes(sigBytes); certList.add(javaCert); // questa versione del generatore priva della classe interna per // la generazione delle SignerInfo, che stata promossa a classe a // s. ExternalSignatureCMSSignedDataGenerator gen = new ExternalSignatureCMSSignedDataGenerator(); gen.addSignerInf(infoGen); if (certList.size() != 0) { // Per passare i certificati al generatore li si incapsula in un // CertStore. CertStore store; store = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), "BC"); System.out.println("Adding certificates ... "); gen.addCertificatesAndCRLs(store); // Finalmente, si pu creare il l'oggetto CMS. System.out.println("Generating CMSSignedData "); cms = gen.generate(msg, true); } } catch (CertificateException e) { System.out.println("Eccezione certificato: " + e); } catch (InvalidAlgorithmParameterException e) { System.out.println("Algoritmo non valido: " + e); } catch (NoSuchAlgorithmException e) { System.out.println("Algoritmo non trovato: " + e); } catch (NoSuchProviderException e) { System.out.println("Provider non trovato: " + e); } catch (CertStoreException e) { System.out.println("Eccezione CertStore: " + e); } catch (CMSException e) { System.out.println("Eccezione CMS: " + e); } return cms; } public static String formatAsString(byte[] bytes, String byteSeparator, int wrapAfter) { int n, x; String w = new String(); String s = new String(); String separator = null; for (n = 0; n < bytes.length; n++) { x = (int) (0x000000FF & bytes[n]); w = Integer.toHexString(x).toUpperCase(); if (w.length() == 1) w = "0" + w; if ((n % wrapAfter) == (wrapAfter - 1)) separator = "\n"; else separator = byteSeparator; s = s + w + ((n + 1 == bytes.length) ? "" : separator); } // for return s; } public Date getSigningTime() { return signingTime; } public void setSigningTime(Date signingTime) { this.signingTime = signingTime; } public void setCertBytes(byte[] certBytes) { this.certBytes = certBytes; if (this.infoGen != null) { java.security.cert.CertificateFactory cf; try { cf = java.security.cert.CertificateFactory.getInstance("X.509"); java.io.ByteArrayInputStream bais1 = new java.io.ByteArrayInputStream(certBytes); java.security.cert.X509Certificate javaCert = (java.security.cert.X509Certificate) cf .generateCertificate(bais1); this.infoGen.setCertificate(javaCert); } catch (CertificateException e) { System.out.println("Eccezione certificato: " + e); } } } public void setCertBytes(String encodedCert) { setCertBytes(Base64.decode(encodedCert)); } public String getEncodedDigest() { return encodedDigest; } public String getEncodedDataHash() { return new String(Base64.encode(this.dataHash)); } /** * Connects an input stream to an output stream hashing on the fly. * <p> * The calculated hash is saved in a private property. This method is * synchronized with the private * <code>getAuthenticatedAttributesBytes</code> (invoked by public * {@link #updateEncodedDigest()}) which waits for its completion. * </p> * * @param in * The input stream the hash is calculated upon. * @param out * The output stream. * @throws IOException * @throws NoSuchAlgorithmException */ public synchronized void streamAndHashContent(InputStream in, OutputStream out) throws IOException, NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance(this.digestAlgorithm); DigestInputStream dis = new DigestInputStream(in, md); /* * // Certificate already set, wait for getAuthenticatedAttributesBytes * // notify if (wait && this.certBytes != null) { long timeout = 10000; * * long millisBefore = System.currentTimeMillis(); long millisWaited = * 0; * * try { System.out.println("streamAndHashContent: Thread '" + * Thread.currentThread().getName() + "' starts waiting (timeout " + * timeout + "ms)."); * * wait(timeout); * * millisWaited = System.currentTimeMillis() - millisBefore; * * if (millisWaited < timeout) * System.out.println("streamAndHashContent: Thread '" + * Thread.currentThread().getName() + "' waited: " + millisWaited + * "ms"); else System.out.println("streamAndHashContent: Thread '" + * Thread.currentThread().getName() + " " + timeout + * "ms timeout expired!"); * * } catch (InterruptedException e) { * System.out.println("streamAndHashContent: Error, " + e.getMessage()); * } * * } */ int bytesRead = 0; byte[] buffer = new byte[1024]; while ((bytesRead = dis.read(buffer, 0, buffer.length)) >= 0) out.write(buffer, 0, bytesRead); this.streamHash = md.digest(); System.out .println("streamAndHashContent: stream hash follows:\n" + formatAsString(this.streamHash, " ", 16)); System.out .println("streamAndHashContent: Thread '" + Thread.currentThread().getName() + "' issues notify."); notify(); } private byte[] hashContent(InputStream in) throws IOException, NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance(this.digestAlgorithm); DigestInputStream dis = new DigestInputStream(in, md); int bytesRead = 0; byte[] buffer = new byte[1024]; while ((bytesRead = dis.read(buffer, 0, buffer.length)) >= 0) ; return md.digest(); } public String getDataPath() { return dataPath; } public void setDataPath(String dataPath) { this.dataPath = dataPath; } public String getDataContentType() { return dataContentType; } public void setDataContentType(String dataContentType) { this.dataContentType = dataContentType; } public String getDataFileName() { return dataFileName; } public void setDataFileName(String dataFileName) { this.dataFileName = dataFileName; } /** * Merges two SignedData Objects * * @param cms * existing cms signed data * @param s * new cms signed data * @param checkSameDigest * check if messageDigest value is the same for all signers? * @return the merged cms */ public CMSSignedData mergeCms(CMSSignedData cms, CMSSignedData s) { try { SignerInformationStore existingSignersStore = cms.getSignerInfos(); Collection<SignerInformation> existingSignersCollection = existingSignersStore.getSigners(); SignerInformationStore newSignersStore = s.getSignerInfos(); Collection<SignerInformation> newSignersCollection = newSignersStore.getSigners(); // do some sanity checks if (existingSignersCollection.isEmpty()) { System.out.println("Error: existing signed data has no signers."); return null; } if (newSignersCollection.isEmpty()) { System.out.println("Error: new signed data has no signers."); return null; } byte[] cmsBytes = (byte[]) cms.getSignedContent().getContent(); byte[] sBytes = (byte[]) s.getSignedContent().getContent(); if (!Arrays.equals(cmsBytes, sBytes)) { System.out.println("Error: content data differs."); return null; } /* Digest could differ, if hashing algorithms are different if (checkSameDigest) if (!isSameDigest(existingSignersCollection, newSignersCollection)) { System.out .println("Error: messageDigest for some signers differ."); return null; } */ CertStore existingCertsStore = cms.getCertificatesAndCRLs("Collection", "BC"); CertStore newCertsStore = s.getCertificatesAndCRLs("Collection", "BC"); X509Store x509Store = cms.getAttributeCertificates("Collection", "BC"); X509Store newX509Store = s.getAttributeCertificates("Collection", "BC"); Collection newCertsCollection = newCertsStore.getCertificates(null); Iterator<SignerInformation> existingSignersIterator = existingSignersCollection.iterator(); // ciclo tra tutti i vecchi firmatari while (existingSignersIterator.hasNext()) { SignerInformation exSigner = existingSignersIterator.next(); // Controllo la presenza di certificati firmatario corrente // tra i nuovi certificati Collection exSignerCerts = newCertsStore.getCertificates(exSigner.getSID()); // ... e nel caso li rimuovo Iterator exSignerCertsIt = exSignerCerts.iterator(); while (exSignerCertsIt.hasNext()) newCertsCollection.remove(exSignerCertsIt.next()); } // Rigenero la lista dei nuovi certificati, // ora disgiunta da quella dei vecchi newCertsStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(newCertsCollection), "BC"); // Si crea un CMSSignedDataGenerator locale, // inizializzandolo conn i dati gi presenti. CMSSignedDataGenerator signGen = new CMSSignedDataGenerator(); // add old certs signGen.addCertificatesAndCRLs(existingCertsStore); // add old certs attributes signGen.addAttributeCertificates(x509Store); // add old signers signGen.addSigners(existingSignersStore); // add new certs signGen.addCertificatesAndCRLs(newCertsStore); // add new certs attributes signGen.addAttributeCertificates(newX509Store); // add new signers signGen.addSigners(newSignersStore); CMSProcessable cp = new CMSProcessableByteArray((byte[]) cms.getSignedContent().getContent()); s = signGen.generate(cp, true, "BC"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchProviderException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (CMSException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchStoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (CertStoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { // TODO Auto-generated catch block e.printStackTrace(); } return s; } private boolean isSameDigest(Collection<SignerInformation> sc1, Collection<SignerInformation> sc2) { boolean sameDigest = false; for (Iterator<SignerInformation> sc1i = sc1.iterator(); sc1i.hasNext();) { SignerInformation s1 = sc1i.next(); AttributeTable s1Attrs = s1.getSignedAttributes(); Attribute s1MdAttr = s1Attrs.get(CMSAttributes.messageDigest); byte[] s1Md = DEROctetString.getInstance(s1MdAttr.getAttrValues().getObjectAt(0)).getOctets(); for (Iterator<SignerInformation> sc2i = sc2.iterator(); sc2i.hasNext();) { SignerInformation s2 = sc2i.next(); AttributeTable s2Attrs = s2.getSignedAttributes(); Attribute s2MdAttr = s2Attrs.get(CMSAttributes.messageDigest); byte[] s2Md = DEROctetString.getInstance(s2MdAttr.getAttrValues().getObjectAt(0)).getOctets(); sameDigest = Arrays.equals(s1Md, s2Md); if (!sameDigest) return false; } } return sameDigest; } }