// recupera il signing time di un firmatario
  private static Date getSigningTime(SignerInformation signer) throws FirmapiuException {
    AttributeTable signedAttr = signer.getSignedAttributes();
    Attribute signingTimeAttr = signedAttr.get(CMSAttributes.signingTime);
    if (signingTimeAttr != null) {
      Enumeration<?> en = signingTimeAttr.getAttrValues().getObjects();

      Date signingTime = null;

      Object obj = en.nextElement();
      try {
        if (obj instanceof ASN1UTCTime) {
          ASN1UTCTime asn1Time = (ASN1UTCTime) obj;
          signingTime = asn1Time.getDate();
        } else if (obj instanceof DERUTCTime) {
          DERUTCTime derTime = (DERUTCTime) obj;
          signingTime = derTime.getDate();
        }
        return signingTime;
      } catch (ParseException e) {
        // TODO eccezioni ammodo
        throw new FirmapiuException();
      }

    } else {
      // non ha trovato il signing time come attributo
      // TODO eccezioni ammodo
      throw new FirmapiuException();
    }
  } // fine metodo
  private DERObject getSingleValuedSignedAttribute(
      DERObjectIdentifier attrOID, String printableName) throws CMSException {
    AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
    if (unsignedAttrTable != null && unsignedAttrTable.getAll(attrOID).size() > 0) {
      throw new CMSException(
          "The " + printableName + " attribute MUST NOT be an unsigned attribute");
    }

    AttributeTable signedAttrTable = this.getSignedAttributes();
    if (signedAttrTable == null) {
      return null;
    }

    ASN1EncodableVector v = signedAttrTable.getAll(attrOID);
    switch (v.size()) {
      case 0:
        return null;
      case 1:
        {
          Attribute t = (Attribute) v.get(0);
          ASN1Set attrValues = t.getAttrValues();
          if (attrValues.size() != 1) {
            throw new CMSException(
                "A " + printableName + " attribute MUST have a single attribute value");
          }

          return attrValues.getObjectAt(0).getDERObject();
        }
      default:
        throw new CMSException(
            "The SignedAttributes in a signerInfo MUST NOT include multiple instances of the "
                + printableName
                + " attribute");
    }
  }
 // controlla che nel firmatario sia presente l'attributo ESSCertIDv2 e che esso sia valido
 // in questo caso la busta crittografica è espressa correttamente nel formato CADES-BES secondo
 // la  DELIBERAZIONE ministeriale del N . 45 DEL 21 MAGGIO 2009
 private boolean isLegallySigned(SignerInformation signer, X509CertificateHolder cert)
     throws FirmapiuException, NoSuchAlgorithmException, IOException {
   AttributeTable signAttr = signer.getSignedAttributes();
   if (signAttr == null) throw new FirmapiuException(VERIFY_SIGNER_SIGNINGATTRIBUTE_NOTFOUND);
   Attribute attr = signAttr.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2);
   if (attr == null) throw new FirmapiuException(VERIFY_SIGNER_SIGNINGATTRIBUTE_NOTFOUND);
   ASN1Sequence sequence = ASN1Sequence.getInstance(attr.getAttrValues().getObjectAt(0));
   SigningCertificateV2 scv2 = SigningCertificateV2.getInstance(sequence);
   ESSCertIDv2[] essCert = scv2.getCerts();
   if (essCert == null || essCert.length < 1)
     throw new FirmapiuException(VERIFY_SIGNER_SIGNINGATTRIBUTE_NOTFOUND);
   // controlla l'hash del certificato se si restituisce true se no restituisce no
   // aggiungere hash del certificato di sottoscrizione
   String digestAlgorithm = "SHA-256";
   MessageDigest sha = null;
   sha = MessageDigest.getInstance(digestAlgorithm);
   byte[] digestedCert = sha.digest(cert.getEncoded());
   byte[] essCertHash = essCert[0].getCertHash();
   // affinché la firma sia valida digestCert e essCertHash devono essere uguali
   if (digestedCert.length != essCertHash.length) return false;
   else {
     for (int i = 0; i < digestedCert.length; i++)
       if (digestedCert[i] != essCertHash[i]) {
         return false;
       }
     return true;
   } // fine if
 } // fine metodo
  /**
   * Return a signer information object with passed in SignerInformationStore representing counter
   * signatures attached as an unsigned attribute.
   *
   * @param signerInformation the signerInfo to be used as the basis.
   * @param counterSigners signer info objects carrying counter signature.
   * @return a copy of the original SignerInformationObject with the changed attributes.
   */
  public static SignerInformation addCounterSigners(
      SignerInformation signerInformation, SignerInformationStore counterSigners) {
    // TODO Perform checks from RFC 3852 11.4

    SignerInfo sInfo = signerInformation.info;
    AttributeTable unsignedAttr = signerInformation.getUnsignedAttributes();
    ASN1EncodableVector v;

    if (unsignedAttr != null) {
      v = unsignedAttr.toASN1EncodableVector();
    } else {
      v = new ASN1EncodableVector();
    }

    ASN1EncodableVector sigs = new ASN1EncodableVector();

    for (Iterator it = counterSigners.getSigners().iterator(); it.hasNext(); ) {
      sigs.add(((SignerInformation) it.next()).toSignerInfo());
    }

    v.add(new Attribute(CMSAttributes.counterSignature, new DERSet(sigs)));

    return new SignerInformation(
        new SignerInfo(
            sInfo.getSID(),
            sInfo.getDigestAlgorithm(),
            sInfo.getAuthenticatedAttributes(),
            sInfo.getDigestEncryptionAlgorithm(),
            sInfo.getEncryptedDigest(),
            new DERSet(v)),
        signerInformation.contentType,
        signerInformation.content,
        null,
        new DefaultSignatureAlgorithmIdentifierFinder());
  }
  public void multipartMixedTest(MimeBodyPart part1, MimeBodyPart part2) throws Exception {
    MimeMultipart mp = new MimeMultipart();

    mp.addBodyPart(part1);
    mp.addBodyPart(part2);

    MimeBodyPart m = new MimeBodyPart();

    m.setContent(mp);

    MimeMultipart smm =
        generateMultiPartRsa("SHA1withRSA", m, SMIMESignedGenerator.RFC3851_MICALGS);
    SMIMESigned s = new SMIMESigned(smm);

    verifySigners(s.getCertificates(), s.getSignerInfos());

    AttributeTable attr =
        ((SignerInformation) s.getSignerInfos().getSigners().iterator().next())
            .getSignedAttributes();

    Attribute a = attr.get(CMSAttributes.messageDigest);
    byte[] contentDigest =
        ASN1OctetString.getInstance(a.getAttrValues().getObjectAt(0)).getOctets();

    mp = (MimeMultipart) m.getContent();
    ContentType contentType = new ContentType(mp.getContentType());
    String boundary = "--" + contentType.getParameter("boundary");

    ByteArrayOutputStream bOut = new ByteArrayOutputStream();
    LineOutputStream lOut = new LineOutputStream(bOut);

    Enumeration headers = m.getAllHeaderLines();
    while (headers.hasMoreElements()) {
      lOut.writeln((String) headers.nextElement());
    }

    lOut.writeln(); // CRLF separator

    lOut.writeln(boundary);
    writePart(mp.getBodyPart(0), bOut);
    lOut.writeln(); // CRLF terminator

    lOut.writeln(boundary);
    writePart(mp.getBodyPart(1), bOut);
    lOut.writeln();

    lOut.writeln(boundary + "--");

    MessageDigest dig = MessageDigest.getInstance("SHA1", BC);

    assertTrue(Arrays.equals(contentDigest, dig.digest(bOut.toByteArray())));
  }
  private CMSEncryptedData doGenerate(CMSTypedData content, OutputEncryptor contentEncryptor)
      throws CMSException {
    AlgorithmIdentifier encAlgId;
    ASN1OctetString encContent;

    ByteArrayOutputStream bOut = new ByteArrayOutputStream();

    try {
      OutputStream cOut = contentEncryptor.getOutputStream(bOut);

      content.write(cOut);

      cOut.close();
    } catch (IOException e) {
      throw new CMSException("");
    }

    byte[] encryptedContent = bOut.toByteArray();

    encAlgId = contentEncryptor.getAlgorithmIdentifier();

    encContent = new BEROctetString(encryptedContent);

    EncryptedContentInfo eci =
        new EncryptedContentInfo(content.getContentType(), encAlgId, encContent);

    ASN1Set unprotectedAttrSet = null;
    if (unprotectedAttributeGenerator != null) {
      AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap());

      unprotectedAttrSet = new BERSet(attrTable.toASN1EncodableVector());
    }

    ContentInfo contentInfo =
        new ContentInfo(
            CMSObjectIdentifiers.encryptedData, new EncryptedData(eci, unprotectedAttrSet));

    return new CMSEncryptedData(contentInfo);
  }
  public void testMimeMultipart() throws Exception {
    MimeBodyPart m = createMultipartMessage();

    List certList = new ArrayList();

    certList.add(_signCert);
    certList.add(_origCert);

    Store certs = new JcaCertStore(certList);

    ASN1EncodableVector signedAttrs = generateSignedAttributes();

    SMIMESignedGenerator gen = new SMIMESignedGenerator("binary");

    gen.addSignerInfoGenerator(
        new JcaSimpleSignerInfoGeneratorBuilder()
            .setProvider(BC)
            .setSignedAttributeGenerator(new AttributeTable(signedAttrs))
            .build("SHA1withRSA", _signKP.getPrivate(), _signCert));
    gen.addCertificates(certs);

    MimeMultipart mm = gen.generate(m);

    SMIMESigned s = new SMIMESigned(mm);

    verifySigners(s.getCertificates(), s.getSignerInfos());

    byte[] contentDigest = (byte[]) gen.getGeneratedDigests().get(SMIMESignedGenerator.DIGEST_SHA1);

    AttributeTable table =
        ((SignerInformation) s.getSignerInfos().getSigners().iterator().next())
            .getSignedAttributes();
    Attribute hash = table.get(CMSAttributes.messageDigest);

    assertTrue(
        MessageDigest.isEqual(
            contentDigest, ((ASN1OctetString) hash.getAttrValues().getObjectAt(0)).getOctets()));
  }
  private Date findTimestamp(CMSSignedData cmsSignedData) {
    Iterator iterator = cmsSignedData.getSignerInfos().getSigners().iterator();

    while (iterator.hasNext()) {

      SignerInformation signerInformation = (SignerInformation) iterator.next();
      AttributeTable signedAttrTable = signerInformation.getSignedAttributes();
      if (signedAttrTable == null) {
        continue;
      }

      ASN1EncodableVector v = signedAttrTable.getAll(CMSAttributes.signingTime);
      switch (v.size()) {
        case 0:
          continue;
        case 1:
          Attribute t = (Attribute) v.get(0);
          ASN1Set attrValues = t.getAttrValues();
          if (attrValues.size() != 1) {
            continue;
          }

          // found it
          try {
            return ((ASN1UTCTime) attrValues.getObjectAt(0).getDERObject()).getDate();
          } catch (ParseException e) {
            e.printStackTrace();
          }
          continue;
        default:
          continue;
      }
    }

    // no timestamp found
    return null;
  }
  protected static AttributeTable appendTimestampAttribute(
      byte[] timeStampTokenBytes, AttributeTable attributeTable) {
    Hashtable unsignedAttributesHT;
    if (attributeTable == null) {
      unsignedAttributesHT = new Hashtable();
    } else {
      unsignedAttributesHT = attributeTable.toHashtable();
    }

    try {
      UnsignedAttributesHelper.addTimestampAttribute(unsignedAttributesHT, timeStampTokenBytes);
    } catch (Exception e) {
      ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e);
    }
    attributeTable = new AttributeTable(unsignedAttributesHT);
    return attributeTable;
  }
  /**
   * create with a signer with extra signed/unsigned attributes.
   *
   * @deprecated use SignerInfoGenerator constructor
   */
  public TimeStampTokenGenerator(
      PrivateKey key,
      X509Certificate cert,
      String digestOID,
      String tsaPolicyOID,
      AttributeTable signedAttr,
      AttributeTable unsignedAttr)
      throws IllegalArgumentException, TSPException {
    this.key = key;
    this.cert = cert;
    this.digestOID = digestOID;
    this.tsaPolicyOID = tsaPolicyOID;
    this.unsignedAttr = unsignedAttr;

    //
    // add the essCertid
    //
    Hashtable signedAttrs = null;

    if (signedAttr != null) {
      signedAttrs = signedAttr.toHashtable();
    } else {
      signedAttrs = new Hashtable();
    }

    TSPUtil.validateCertificate(cert);

    try {
      ESSCertID essCertid =
          new ESSCertID(MessageDigest.getInstance("SHA-1").digest(cert.getEncoded()));
      signedAttrs.put(
          PKCSObjectIdentifiers.id_aa_signingCertificate,
          new Attribute(
              PKCSObjectIdentifiers.id_aa_signingCertificate,
              new DERSet(new SigningCertificate(essCertid))));
    } catch (NoSuchAlgorithmException e) {
      throw new TSPException("Can't find a SHA-1 implementation.", e);
    } catch (CertificateEncodingException e) {
      throw new TSPException("Exception processing certificate.", e);
    }

    this.signedAttr = new AttributeTable(signedAttrs);
  }
  /**
   * Return a signer information object with the passed in unsigned attributes replacing the ones
   * that are current associated with the object passed in.
   *
   * @param signerInformation the signerInfo to be used as the basis.
   * @param unsignedAttributes the unsigned attributes to add.
   * @return a copy of the original SignerInformationObject with the changed attributes.
   */
  public static SignerInformation replaceUnsignedAttributes(
      SignerInformation signerInformation, AttributeTable unsignedAttributes) {
    SignerInfo sInfo = signerInformation.info;
    ASN1Set unsignedAttr = null;

    if (unsignedAttributes != null) {
      unsignedAttr = new DERSet(unsignedAttributes.toASN1EncodableVector());
    }

    return new SignerInformation(
        new SignerInfo(
            sInfo.getSID(),
            sInfo.getDigestAlgorithm(),
            sInfo.getAuthenticatedAttributes(),
            sInfo.getDigestEncryptionAlgorithm(),
            sInfo.getEncryptedDigest(),
            unsignedAttr),
        signerInformation.contentType,
        signerInformation.content,
        null,
        new DefaultSignatureAlgorithmIdentifierFinder());
  }
  private boolean doVerify(SignerInformationVerifier verifier) throws CMSException {
    String digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(this.getDigestAlgOID());
    String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
    String signatureName = digestName + "with" + encName;

    try {
      if (digestCalculator != null) {
        resultDigest = digestCalculator.getDigest();
      } else {
        DigestCalculator calc = verifier.getDigestCalculator(this.getDigestAlgorithmID());
        if (content != null) {
          OutputStream digOut = calc.getOutputStream();

          content.write(digOut);

          digOut.close();
        } else if (signedAttributeSet == null) {
          // TODO Get rid of this exception and just treat content==null as empty not missing?
          throw new CMSException("data not encapsulated in signature - use detached constructor.");
        }

        resultDigest = calc.getDigest();
      }
    } catch (IOException e) {
      throw new CMSException("can't process mime object to create signature.", e);
    } catch (NoSuchAlgorithmException e) {
      throw new CMSException("can't find algorithm: " + e.getMessage(), e);
    } catch (OperatorCreationException e) {
      throw new CMSException("can't create digest calculator: " + e.getMessage(), e);
    }

    // RFC 3852 11.1 Check the content-type attribute is correct
    {
      DERObject validContentType =
          getSingleValuedSignedAttribute(CMSAttributes.contentType, "content-type");
      if (validContentType == null) {
        if (!isCounterSignature && signedAttributeSet != null) {
          throw new CMSException(
              "The content-type attribute type MUST be present whenever signed attributes are present in signed-data");
        }
      } else {
        if (isCounterSignature) {
          throw new CMSException(
              "[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
        }

        if (!(validContentType instanceof DERObjectIdentifier)) {
          throw new CMSException(
              "content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
        }

        DERObjectIdentifier signedContentType = (DERObjectIdentifier) validContentType;

        if (!signedContentType.equals(contentType)) {
          throw new CMSException("content-type attribute value does not match eContentType");
        }
      }
    }

    // RFC 3852 11.2 Check the message-digest attribute is correct
    {
      DERObject validMessageDigest =
          getSingleValuedSignedAttribute(CMSAttributes.messageDigest, "message-digest");
      if (validMessageDigest == null) {
        if (signedAttributeSet != null) {
          throw new CMSException(
              "the message-digest signed attribute type MUST be present when there are any signed attributes present");
        }
      } else {
        if (!(validMessageDigest instanceof ASN1OctetString)) {
          throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
        }

        ASN1OctetString signedMessageDigest = (ASN1OctetString) validMessageDigest;

        if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets())) {
          throw new CMSSignerDigestMismatchException(
              "message-digest attribute value does not match calculated value");
        }
      }
    }

    // RFC 3852 11.4 Validate countersignature attribute(s)
    {
      AttributeTable signedAttrTable = this.getSignedAttributes();
      if (signedAttrTable != null
          && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0) {
        throw new CMSException("A countersignature attribute MUST NOT be a signed attribute");
      }

      AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
      if (unsignedAttrTable != null) {
        ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature);
        for (int i = 0; i < csAttrs.size(); ++i) {
          Attribute csAttr = (Attribute) csAttrs.get(i);
          if (csAttr.getAttrValues().size() < 1) {
            throw new CMSException(
                "A countersignature attribute MUST contain at least one AttributeValue");
          }

          // Note: We don't recursively validate the countersignature value
        }
      }
    }

    try {
      ContentVerifier contentVerifier =
          verifier.getContentVerifier(sigAlgFinder.find(signatureName));
      OutputStream sigOut = contentVerifier.getOutputStream();

      if (signedAttributeSet == null) {
        if (digestCalculator != null) {
          if (contentVerifier instanceof RawContentVerifier) {
            RawContentVerifier rawVerifier = (RawContentVerifier) contentVerifier;

            if (encName.equals("RSA")) {
              DigestInfo digInfo = new DigestInfo(digestAlgorithm, resultDigest);

              return rawVerifier.verify(digInfo.getDEREncoded(), this.getSignature());
            }

            return rawVerifier.verify(resultDigest, this.getSignature());
          }

          throw new CMSException("verifier unable to process raw signature");
        } else if (content != null) {
          // TODO Use raw signature of the hash value instead
          content.write(sigOut);
        }
      } else {
        sigOut.write(this.getEncodedSignedAttributes());
      }

      sigOut.close();

      return contentVerifier.verify(this.getSignature());
    } catch (IOException e) {
      throw new CMSException("can't process mime object to create signature.", e);
    } catch (OperatorCreationException e) {
      throw new CMSException("can't create content verifier: " + e.getMessage(), e);
    }
  }
  /** @deprecated */
  private boolean doVerify(PublicKey key, Provider sigProvider)
      throws CMSException, NoSuchAlgorithmException {
    String digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(this.getDigestAlgOID());
    String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
    String signatureName = digestName + "with" + encName;
    Signature sig = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, sigProvider);
    MessageDigest digest = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider);

    // TODO [BJA-109] Note: PSSParameterSpec requires JDK1.4+
    /*
            try
            {
                DERObjectIdentifier sigAlgOID = encryptionAlgorithm.getObjectId();
                DEREncodable sigParams = this.encryptionAlgorithm.getParameters();
                if (sigAlgOID.equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
                {
                    // RFC 4056
                    // When the id-RSASSA-PSS algorithm identifier is used for a signature,
                    // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params.
                    if (sigParams == null)
                    {
                        throw new CMSException(
                            "RSASSA-PSS signature must specify algorithm parameters");
                    }

                    AlgorithmParameters params = AlgorithmParameters.getInstance(
                        sigAlgOID.getId(), sig.getProvider().getName());
                    params.init(sigParams.getDERObject().getEncoded(), "ASN.1");

                    PSSParameterSpec spec = (PSSParameterSpec)params.getParameterSpec(PSSParameterSpec.class);
                    sig.setParameter(spec);
                }
                else
                {
                    // TODO Are there other signature algorithms that provide parameters?
                    if (sigParams != null)
                    {
                        throw new CMSException("unrecognised signature parameters provided");
                    }
                }
            }
            catch (IOException e)
            {
                throw new CMSException("error encoding signature parameters.", e);
            }
            catch (InvalidAlgorithmParameterException e)
            {
                throw new CMSException("error setting signature parameters.", e);
            }
            catch (InvalidParameterSpecException e)
            {
                throw new CMSException("error processing signature parameters.", e);
            }
    */

    try {
      if (digestCalculator != null) {
        resultDigest = digestCalculator.getDigest();
      } else {
        if (content != null) {
          content.write(new DigOutputStream(digest));
        } else if (signedAttributeSet == null) {
          // TODO Get rid of this exception and just treat content==null as empty not missing?
          throw new CMSException("data not encapsulated in signature - use detached constructor.");
        }

        resultDigest = digest.digest();
      }
    } catch (IOException e) {
      throw new CMSException("can't process mime object to create signature.", e);
    }

    // RFC 3852 11.1 Check the content-type attribute is correct
    {
      DERObject validContentType =
          getSingleValuedSignedAttribute(CMSAttributes.contentType, "content-type");
      if (validContentType == null) {
        if (!isCounterSignature && signedAttributeSet != null) {
          throw new CMSException(
              "The content-type attribute type MUST be present whenever signed attributes are present in signed-data");
        }
      } else {
        if (isCounterSignature) {
          throw new CMSException(
              "[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
        }

        if (!(validContentType instanceof DERObjectIdentifier)) {
          throw new CMSException(
              "content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
        }

        DERObjectIdentifier signedContentType = (DERObjectIdentifier) validContentType;

        if (!signedContentType.equals(contentType)) {
          throw new CMSException("content-type attribute value does not match eContentType");
        }
      }
    }

    // RFC 3852 11.2 Check the message-digest attribute is correct
    {
      DERObject validMessageDigest =
          getSingleValuedSignedAttribute(CMSAttributes.messageDigest, "message-digest");
      if (validMessageDigest == null) {
        if (signedAttributeSet != null) {
          throw new CMSException(
              "the message-digest signed attribute type MUST be present when there are any signed attributes present");
        }
      } else {
        if (!(validMessageDigest instanceof ASN1OctetString)) {
          throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
        }

        ASN1OctetString signedMessageDigest = (ASN1OctetString) validMessageDigest;

        if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets())) {
          throw new CMSSignerDigestMismatchException(
              "message-digest attribute value does not match calculated value");
        }
      }
    }

    // RFC 3852 11.4 Validate countersignature attribute(s)
    {
      AttributeTable signedAttrTable = this.getSignedAttributes();
      if (signedAttrTable != null
          && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0) {
        throw new CMSException("A countersignature attribute MUST NOT be a signed attribute");
      }

      AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
      if (unsignedAttrTable != null) {
        ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature);
        for (int i = 0; i < csAttrs.size(); ++i) {
          Attribute csAttr = (Attribute) csAttrs.get(i);
          if (csAttr.getAttrValues().size() < 1) {
            throw new CMSException(
                "A countersignature attribute MUST contain at least one AttributeValue");
          }

          // Note: We don't recursively validate the countersignature value
        }
      }
    }

    try {
      sig.initVerify(key);

      if (signedAttributeSet == null) {
        if (digestCalculator != null) {
          // need to decrypt signature and check message bytes
          return verifyDigest(resultDigest, key, this.getSignature(), sigProvider);
        } else if (content != null) {
          // TODO Use raw signature of the hash value instead
          content.write(new SigOutputStream(sig));
        }
      } else {
        sig.update(this.getEncodedSignedAttributes());
      }

      return sig.verify(this.getSignature());
    } catch (InvalidKeyException e) {
      throw new CMSException("key not appropriate to signature in message.", e);
    } catch (IOException e) {
      throw new CMSException("can't process mime object to create signature.", e);
    } catch (SignatureException e) {
      throw new CMSException("invalid signature format in message: " + e.getMessage(), e);
    }
  }
  /**
   * Return a SignerInformationStore containing the counter signatures attached to this signer. If
   * no counter signatures are present an empty store is returned.
   */
  public SignerInformationStore getCounterSignatures() {
    // TODO There are several checks implied by the RFC3852 comments that are missing

    /*
    The countersignature attribute MUST be an unsigned attribute; it MUST
    NOT be a signed attribute, an authenticated attribute, an
    unauthenticated attribute, or an unprotected attribute.
    */
    AttributeTable unsignedAttributeTable = getUnsignedAttributes();
    if (unsignedAttributeTable == null) {
      return new SignerInformationStore(new ArrayList(0));
    }

    List counterSignatures = new ArrayList();

    /*
    The UnsignedAttributes syntax is defined as a SET OF Attributes.  The
    UnsignedAttributes in a signerInfo may include multiple instances of
    the countersignature attribute.
    */
    ASN1EncodableVector allCSAttrs = unsignedAttributeTable.getAll(CMSAttributes.counterSignature);

    for (int i = 0; i < allCSAttrs.size(); ++i) {
      Attribute counterSignatureAttribute = (Attribute) allCSAttrs.get(i);

      /*
      A countersignature attribute can have multiple attribute values.  The
      syntax is defined as a SET OF AttributeValue, and there MUST be one
      or more instances of AttributeValue present.
      */
      ASN1Set values = counterSignatureAttribute.getAttrValues();
      if (values.size() < 1) {
        // TODO Throw an appropriate exception?
      }

      for (Enumeration en = values.getObjects(); en.hasMoreElements(); ) {
        /*
        Countersignature values have the same meaning as SignerInfo values
        for ordinary signatures, except that:

           1. The signedAttributes field MUST NOT contain a content-type
              attribute; there is no content type for countersignatures.

           2. The signedAttributes field MUST contain a message-digest
              attribute if it contains any other attributes.

           3. The input to the message-digesting process is the contents
              octets of the DER encoding of the signatureValue field of the
              SignerInfo value with which the attribute is associated.
        */
        SignerInfo si = SignerInfo.getInstance(en.nextElement());

        String digestName =
            CMSSignedHelper.INSTANCE.getDigestAlgName(
                si.getDigestAlgorithm().getObjectId().getId());

        counterSignatures.add(
            new SignerInformation(
                si,
                null,
                null,
                new CounterSignatureDigestCalculator(digestName, null, getSignature()),
                new DefaultSignatureAlgorithmIdentifierFinder()));
      }
    }

    return new SignerInformationStore(counterSignatures);
  }