/**
   * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
   * the signer's associated certificate using the sha1DigestCalculator.
   *
   * @param sha1DigestCalculator calculator for SHA-1 of certificate.
   * @param signerInfoGen the generator for the signer we are using.
   * @param tsaPolicy tasPolicy to send.
   * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated
   *     certificate for the signer,
   * @throws TSPException if the signer certificate cannot be processed.
   */
  public TimeStampTokenGenerator(
      DigestCalculator sha1DigestCalculator,
      final SignerInfoGenerator signerInfoGen,
      ASN1ObjectIdentifier tsaPolicy)
      throws IllegalArgumentException, TSPException {
    this.signerInfoGen = signerInfoGen;
    this.tsaPolicyOID = tsaPolicy;

    if (!sha1DigestCalculator
        .getAlgorithmIdentifier()
        .getAlgorithm()
        .equals(OIWObjectIdentifiers.idSHA1)) {
      throw new IllegalArgumentException("Digest calculator must be for SHA-1");
    }

    if (!signerInfoGen.hasAssociatedCertificate()) {
      throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate");
    }

    TSPUtil.validateCertificate(signerInfoGen.getAssociatedCertificate());

    try {
      OutputStream dOut = sha1DigestCalculator.getOutputStream();

      dOut.write(signerInfoGen.getAssociatedCertificate().getEncoded());

      dOut.close();

      final ESSCertID essCertid = new ESSCertID(sha1DigestCalculator.getDigest());

      this.signerInfoGen =
          new SignerInfoGenerator(
              signerInfoGen,
              new CMSAttributeTableGenerator() {
                public AttributeTable getAttributes(Map parameters)
                    throws CMSAttributeTableGenerationException {
                  AttributeTable table =
                      signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);

                  return table.add(
                      PKCSObjectIdentifiers.id_aa_signingCertificate,
                      new SigningCertificate(essCertid));
                }
              },
              signerInfoGen.getUnsignedAttributeTableGenerator());

    } catch (IOException e) {
      throw new TSPException("Exception processing certificate.", e);
    }
  }
  public boolean verify(DigestCalculatorProvider calculatorProvider) throws CMSException {
    try {
      ContentInfo content = digestedData.getEncapContentInfo();
      DigestCalculator calc = calculatorProvider.get(digestedData.getDigestAlgorithm());

      OutputStream dOut = calc.getOutputStream();

      dOut.write(((ASN1OctetString) content.getContent()).getOctets());

      return Arrays.areEqual(digestedData.getDigest(), calc.getDigest());
    } catch (OperatorCreationException e) {
      throw new CMSException("unable to create digest calculator: " + e.getMessage(), e);
    } catch (IOException e) {
      throw new CMSException("unable process content: " + e.getMessage(), e);
    }
  }
  public boolean isVerified(
      X509CertificateHolder certHolder, DigestCalculatorProvider digesterProvider)
      throws CMPException {
    AlgorithmIdentifier digAlg =
        digestAlgFinder.find(certHolder.toASN1Structure().getSignatureAlgorithm());
    if (digAlg == null) {
      throw new CMPException("cannot find algorithm for digest from signature");
    }

    DigestCalculator digester;

    try {
      digester = digesterProvider.get(digAlg);
    } catch (OperatorCreationException e) {
      throw new CMPException("unable to create digester: " + e.getMessage(), e);
    }

    CMPUtil.derEncodeToStream(certHolder.toASN1Structure(), digester.getOutputStream());

    return Arrays.areEqual(certStatus.getCertHash().getOctets(), digester.getDigest());
  }
  /**
   * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
   * the signer's associated certificate using the sha1DigestCalculator. If alternate values are
   * required for id-aa-signingCertificate they should be added to the signerInfoGen object before
   * it is passed in, otherwise a standard digest based value will be added.
   *
   * @param signerInfoGen the generator for the signer we are using.
   * @param digestCalculator calculator for to use for digest of certificate.
   * @param tsaPolicy tasPolicy to send.
   * @param isIssuerSerialIncluded should issuerSerial be included in the ESSCertIDs, true if yes,
   *     by default false.
   * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated
   *     certificate for the signer,
   * @throws TSPException if the signer certificate cannot be processed.
   */
  public TimeStampTokenGenerator(
      final SignerInfoGenerator signerInfoGen,
      DigestCalculator digestCalculator,
      ASN1ObjectIdentifier tsaPolicy,
      boolean isIssuerSerialIncluded)
      throws IllegalArgumentException, TSPException {
    this.signerInfoGen = signerInfoGen;
    this.tsaPolicyOID = tsaPolicy;

    if (!signerInfoGen.hasAssociatedCertificate()) {
      throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate");
    }

    X509CertificateHolder assocCert = signerInfoGen.getAssociatedCertificate();
    TSPUtil.validateCertificate(assocCert);

    try {
      OutputStream dOut = digestCalculator.getOutputStream();

      dOut.write(assocCert.getEncoded());

      dOut.close();

      if (digestCalculator
          .getAlgorithmIdentifier()
          .getAlgorithm()
          .equals(OIWObjectIdentifiers.idSHA1)) {
        final ESSCertID essCertid =
            new ESSCertID(
                digestCalculator.getDigest(),
                isIssuerSerialIncluded
                    ? new IssuerSerial(
                        new GeneralNames(new GeneralName(assocCert.getIssuer())),
                        assocCert.getSerialNumber())
                    : null);

        this.signerInfoGen =
            new SignerInfoGenerator(
                signerInfoGen,
                new CMSAttributeTableGenerator() {
                  public AttributeTable getAttributes(Map parameters)
                      throws CMSAttributeTableGenerationException {
                    AttributeTable table =
                        signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);

                    if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificate) == null) {
                      return table.add(
                          PKCSObjectIdentifiers.id_aa_signingCertificate,
                          new SigningCertificate(essCertid));
                    }

                    return table;
                  }
                },
                signerInfoGen.getUnsignedAttributeTableGenerator());
      } else {
        AlgorithmIdentifier digAlgID =
            new AlgorithmIdentifier(digestCalculator.getAlgorithmIdentifier().getAlgorithm());
        final ESSCertIDv2 essCertid =
            new ESSCertIDv2(
                digAlgID,
                digestCalculator.getDigest(),
                isIssuerSerialIncluded
                    ? new IssuerSerial(
                        new GeneralNames(new GeneralName(assocCert.getIssuer())),
                        new ASN1Integer(assocCert.getSerialNumber()))
                    : null);

        this.signerInfoGen =
            new SignerInfoGenerator(
                signerInfoGen,
                new CMSAttributeTableGenerator() {
                  public AttributeTable getAttributes(Map parameters)
                      throws CMSAttributeTableGenerationException {
                    AttributeTable table =
                        signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);

                    if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2) == null) {
                      return table.add(
                          PKCSObjectIdentifiers.id_aa_signingCertificateV2,
                          new SigningCertificateV2(essCertid));
                    }

                    return table;
                  }
                },
                signerInfoGen.getUnsignedAttributeTableGenerator());
      }
    } catch (IOException e) {
      throw new TSPException("Exception processing certificate.", e);
    }
  }
  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);
    }
  }
 /**
  * Calculates message digest using the provided digest calculator.
  *
  * @param dc the digest calculator
  * @param data the data
  * @return message digest
  * @throws IOException if the digest cannot be calculated
  */
 public static byte[] calculateDigest(DigestCalculator dc, InputStream data) throws IOException {
   IOUtils.copy(data, dc.getOutputStream());
   dc.getOutputStream().close();
   return dc.getDigest();
 }
 /**
  * Calculates message digest using the provided digest calculator.
  *
  * @param dc the digest calculator
  * @param data the data
  * @return message digest
  * @throws IOException if the digest cannot be calculated
  */
 public static byte[] calculateDigest(DigestCalculator dc, byte[] data) throws IOException {
   dc.getOutputStream().write(data);
   dc.getOutputStream().close();
   return dc.getDigest();
 }