/**
   * Validates that a signed entity has a valid message and signature. The signer's certificate is
   * validated to ensure authenticity of the message. Message tampering is also checked with the
   * message's digest and the signed digest in the message signature.
   *
   * @param signedEntity The entity containing the original signed part and the message signature.
   * @param signerCertificate The certificate used to sign the message.
   * @param anchors A collection of certificate anchors used to determine if the certificates used
   *     in the signature can be validated as trusted certificates.
   */
  public void checkSignature(
      SignedEntity signedEntity,
      X509Certificate signerCertificate,
      Collection<X509Certificate> anchors)
      throws SignatureValidationException {
    CMSSignedData signatureEnvelope = deserializeSignatureEnvelope(signedEntity);

    try {
      // there may be multiple signatures in the signed part... iterate through all the signing
      // certificates until one
      // is verified with the signerCertificate
      for (SignerInformation sigInfo :
          (Collection<SignerInformation>) signatureEnvelope.getSignerInfos().getSigners())
        if (sigInfo.verify(signerCertificate, CryptoExtensions.getJCEProviderName()))
          return; // verified... return

      // at this point the signerCertificate cannot be verified with one of the signing
      // certificates....
      throw new SignatureValidationException("Signature validation failure.");
    } catch (SignatureValidationException sve) {
      throw sve;
    } catch (Exception e) {
      throw new SignatureValidationException("Signature validation failure.", e);
    }
  }
  private MimeBodyPart createEncryptedEnvelope(
      MimeBodyPart bodyPart, Collection<X509Certificate> encryptingCertificates) {
    if (bodyPart == null || encryptingCertificates == null || encryptingCertificates.size() == 0) {
      throw new IllegalArgumentException();
    }

    if (LOGGER.isDebugEnabled()) {
      writePreEncypt(EntitySerializer.Default.serializeToBytes(bodyPart));
    }

    SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();

    for (X509Certificate cert : encryptingCertificates) gen.addKeyTransRecipient(cert);

    MimeBodyPart retVal = null;

    try {
      retVal =
          gen.generate(
              bodyPart,
              toEncyAlgorithmOid(this.m_encryptionAlgorithm),
              CryptoExtensions.getJCEProviderName());
    } catch (Exception e) {
      throw new MimeException(MimeError.Unexpected, e);
    }

    return retVal;
  }
  private X509Certificate certFromData(byte[] data) {
    X509Certificate retVal = null;
    try {
      ByteArrayInputStream bais = new ByteArrayInputStream(data);

      // lets try this a as a PKCS12 data stream first
      try {
        KeyStore localKeyStore =
            KeyStore.getInstance("PKCS12", CryptoExtensions.getJCEProviderName());

        localKeyStore.load(bais, "".toCharArray());
        Enumeration<String> aliases = localKeyStore.aliases();

        // we are really expecting only one alias
        if (aliases.hasMoreElements()) {
          String alias = aliases.nextElement();
          X509Certificate cert = (X509Certificate) localKeyStore.getCertificate(alias);

          // check if there is private key
          Key key = localKeyStore.getKey(alias, "".toCharArray());
          if (key != null && key instanceof PrivateKey) {
            retVal = X509CertificateEx.fromX509Certificate(cert, (PrivateKey) key);
          } else retVal = cert;
        }
      } catch (Exception e) {
        // must not be a PKCS12 stream, go on to next step
      }

      if (retVal == null) {
        // try X509 certificate factory next
        bais.reset();
        bais = new ByteArrayInputStream(data);

        retVal =
            (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(bais);
      }
      bais.close();
    } catch (Exception e) {
      throw new NHINDException("Data cannot be converted to a valid X.509 Certificate", e);
    }

    return retVal;
  }
  /**
   * Decrypts an entity with the provided certificates' private key.
   *
   * @param encryptedEntity The entity that will be decrypted.
   * @param decryptingCertificate The certificates whose private keys will be used to decrypt the
   *     message.
   * @return A MimeEntity containing the decrypted part.
   */
  public MimeEntity decrypt(
      MimeEntity encryptedEntity, Collection<X509CertificateEx> decryptingCertificates) {
    if (decryptingCertificates == null || decryptingCertificates.size() == 0) {
      throw new IllegalArgumentException();
    }

    MimeEntity retEntity = null;
    try {
      if (LOGGER.isDebugEnabled()) {
        byte[] encryptedContent = encryptedEntity.getContentAsBytes();
        writePreDecrypt(encryptedContent);
      }

      SMIMEEnveloped m = new SMIMEEnveloped(encryptedEntity);

      X509CertificateEx decryptCert = decryptingCertificates.iterator().next();

      RecipientId recId = generateRecipientSelector(decryptCert);

      RecipientInformationStore recipients = m.getRecipientInfos();
      RecipientInformation recipient = recipients.get(recId);

      byte[] decryptedPayload =
          recipient.getContent(decryptCert.getPrivateKey(), CryptoExtensions.getJCEProviderName());

      if (LOGGER.isDebugEnabled()) {
        writePostDecrypt(decryptedPayload);
      }

      ByteArrayInputStream inStream = new ByteArrayInputStream(decryptedPayload);

      retEntity = new MimeEntity(inStream);

    } catch (MessagingException e) {
      throw new MimeException(MimeError.InvalidMimeEntity, e);
    } catch (Exception e) {
      throw new MimeException(MimeError.Unexpected, e);
    }

    return retEntity;
  }
  static {
    initJVMParams();

    CryptoExtensions.registerJCEProviders();
  }
  private X509Certificate certFromData(byte[] data) {
    X509Certificate retVal = null;
    try {
      // first check for wrapped data
      final CertContainer container = CertUtils.toCertContainer(data);
      if (container.getWrappedKeyData() != null) {
        // this is a wrapped key
        // make sure we have a KeyStoreManager configured
        if (this.mgr == null) {
          throw new NHINDException(
              AgentError.Unexpected,
              "Resolved certifiate has wrapped data, but resolver has not been configured to unwrap it.");
        }

        // create a new wrapped certificate object
        retVal =
            WrappedOnDemandX509CertificateEx.fromX509Certificate(
                mgr, container.getCert(), container.getWrappedKeyData());
        return retVal;
      }

      ByteArrayInputStream bais = new ByteArrayInputStream(data);

      // lets try this a as a PKCS12 data stream first
      try {
        KeyStore localKeyStore =
            KeyStore.getInstance("PKCS12", CryptoExtensions.getJCEProviderName());

        localKeyStore.load(bais, "".toCharArray());
        Enumeration<String> aliases = localKeyStore.aliases();

        // we are really expecting only one alias
        if (aliases.hasMoreElements()) {
          String alias = aliases.nextElement();
          X509Certificate cert = (X509Certificate) localKeyStore.getCertificate(alias);

          // check if there is private key
          Key key = localKeyStore.getKey(alias, "".toCharArray());
          if (key != null && key instanceof PrivateKey) {
            retVal = X509CertificateEx.fromX509Certificate(cert, (PrivateKey) key);
          } else retVal = cert;
        }
      } catch (Exception e) {
        // must not be a PKCS12 stream, go on to next step
      }

      if (retVal == null) {
        // try X509 certificate factory next
        bais.reset();
        bais = new ByteArrayInputStream(data);

        retVal =
            (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(bais);
      }
      bais.close();
    } catch (Exception e) {
      throw new NHINDException("Data cannot be converted to a valid X.509 Certificate", e);
    }

    return retVal;
  }
  private MimeMultipart createSignatureEntity(
      byte[] entity, Collection<X509Certificate> signingCertificates) {
    MimeMultipart retVal = null;
    try {
      MimeBodyPart signedContent = new MimeBodyPart(new ByteArrayInputStream(entity));

      ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
      SMIMECapabilityVector caps = new SMIMECapabilityVector();

      caps.addCapability(SMIMECapability.dES_EDE3_CBC);
      caps.addCapability(SMIMECapability.rC2_CBC, 128);
      caps.addCapability(SMIMECapability.dES_CBC);
      caps.addCapability(new DERObjectIdentifier("1.2.840.113549.1.7.1"));
      caps.addCapability(x509CertificateObjectsIdent);
      signedAttrs.add(new SMIMECapabilitiesAttribute(caps));

      List certList = new ArrayList();
      CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
      for (X509Certificate signer : signingCertificates) {
        if (signer instanceof X509CertificateEx) {
          generator.addSigner(
              ((X509CertificateEx) signer).getPrivateKey(),
              signer,
              toDigestAlgorithmOid(this.m_digestAlgorithm),
              createAttributeTable(signedAttrs),
              null);
          certList.add(signer);
        }
      }

      CertStore certsAndcrls =
          CertStore.getInstance(
              "Collection",
              new CollectionCertStoreParameters(certList),
              CryptoExtensions.getJCEProviderName());
      generator.addCertificatesAndCRLs(certsAndcrls);
      CMSProcessableBodyPart content = new CMSProcessableBodyPart(signedContent);

      CMSSignedData signedData =
          generator.generate(content, false, CryptoExtensions.getJCEProviderName());

      String header =
          "signed; protocol=\"application/pkcs7-signature\"; micalg="
              + toDigestAlgorithmMicalg(this.m_digestAlgorithm);

      String encodedSig = Base64.encodeBase64String(signedData.getEncoded());

      retVal = new MimeMultipart(header.toString());

      MimeBodyPart sig = new MimeBodyPart(new InternetHeaders(), encodedSig.getBytes("ASCII"));
      sig.addHeader(
          "Content-Type", "application/pkcs7-signature; name=smime.p7s; smime-type=signed-data");
      sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7s\"");
      sig.addHeader("Content-Description", "S/MIME Cryptographic Signature");
      sig.addHeader("Content-Transfer-Encoding", "base64");

      retVal.addBodyPart(signedContent);
      retVal.addBodyPart(sig);

    } catch (MessagingException e) {
      throw new MimeException(MimeError.InvalidMimeEntity, e);
    } catch (IOException e) {
      throw new SignatureException(SignatureError.InvalidMultipartSigned, e);
    } catch (Exception e) {
      throw new NHINDException(MimeError.Unexpected, e);
    }
    return retVal;
  }