package org.jboss.resteasy.test.crypto;

import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMESigned;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.jboss.logging.Logger;
import org.jboss.resteasy.security.DerUtils;
import org.jboss.resteasy.security.PemUtils;
import org.jboss.resteasy.utils.TestUtil;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetHeaders;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.util.ByteArrayDataSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;

/**
 * @tpSubChapter Crypto
 * @tpChapter Unit tests
 * @tpTestCaseDetails Test for sign by X509Certificate.
 * @tpSince RESTEasy 3.0.16
 */
public class SignedTest {

   protected static final Logger logger = Logger.getLogger(SignedTest.class.getName());
   private static X509Certificate cert;
   private static PrivateKey privateKey;
   private static PrivateKey badKey;
   static final String certPemPath;
   static final String certPrivatePemPath;
   static final String certPrivateKeyDerPath;
   static final String pythonPath;

   static {
      String base = TestUtil.getResourcePath(SignedTest.class, "");
      certPemPath = new StringBuilder().append(base).append("SignedMycert.pem").toString();
      certPrivatePemPath = new StringBuilder().append(base).append("MycertPrivate.pem").toString();
      certPrivateKeyDerPath = new StringBuilder().append(base).append("SignedPrivateDkimKeyDer.der").toString();
      pythonPath = new StringBuilder().append(base).append("SignedPython.txt").toString();
   }

   @BeforeClass
   public static void setup() throws Exception {
      Security.addProvider(new BouncyCastleProvider());
      InputStream certIs = new FileInputStream(certPemPath);
      cert = PemUtils.decodeCertificate(certIs);

      InputStream privateIs = new FileInputStream(certPrivatePemPath);
      privateKey = PemUtils.decodePrivateKey(privateIs);

      InputStream badIs = new FileInputStream(certPrivateKeyDerPath);
      badKey = DerUtils.decodePrivateKey(badIs);
   }

   private MimeBodyPart createMsg() throws MessagingException {
      InternetHeaders ih = new InternetHeaders();
      ih.addHeader("Content-Type", "application/xml");
      return new MimeBodyPart(ih, "<customer name=\"bill\"/>".getBytes());
   }

   private void output(MimeBodyPart mp) throws IOException, MessagingException {
      ByteArrayOutputStream os = new ByteArrayOutputStream();
      mp.writeTo(os);
      String s = new String(os.toByteArray());
      logger.info(s);
   }

   private void output(MimeMultipart mp) throws IOException, MessagingException {
      ByteArrayOutputStream os = new ByteArrayOutputStream();
      mp.writeTo(os);
      String s = new String(os.toByteArray());
      logger.info(s);
   }

   /**
    * @tpTestDetails Multipart test
    * @tpSince RESTEasy 3.0.16
    */
   @Test
   public void testMultipart() throws Exception {
      MimeMultipart mp = new MimeMultipart();
      InternetHeaders ih = new InternetHeaders();
      ih.addHeader("Content-Type", "text/xml");
      MimeBodyPart bp = new MimeBodyPart(ih, "<customer/>".getBytes());
      mp.addBodyPart(bp);

      bp = new MimeBodyPart(ih, "<product/>".getBytes());
      mp.addBodyPart(bp);

      ByteArrayOutputStream os = new ByteArrayOutputStream();
      mp.writeTo(os);
      String s = new String(os.toByteArray());

      logger.info(s);
      logger.info("************");

      String contentType = mp.getContentType();
      contentType = contentType.replace("\r\n", "").replace("\t", " ");
      logger.info("Content-Type: " + contentType);

      mp = new MimeMultipart(new ByteArrayDataSource(s.getBytes(), "multipart/signed"));
      logger.info("count: " + mp.getCount());
   }


   /**
    * @tpTestDetails Test for python sign format
    * @tpSince RESTEasy 3.0.16
    */
   @Test
   public void testPythonSigned() throws Exception {
      final InputStream pythonIs = new FileInputStream(pythonPath);

      ByteArrayDataSource ds = new ByteArrayDataSource(pythonIs, "multipart/signed");
      MimeMultipart mm = new MimeMultipart(ds);

      logger.info(mm.getContentType());

      logger.info("Multipart.count(): " + mm.getCount());

      MimeBodyPart mbp = (MimeBodyPart) mm.getBodyPart(0);

      output(mbp);

      SMIMESigned signed = new SMIMESigned(mm);

      SignerInformationStore signers = signed.getSignerInfos();
      Assert.assertEquals("Wrong count of signers", 1, signers.size());
      SignerInformation signer = signers.getSigners().iterator().next();
      Assert.assertTrue("Unsuccessful verification of signer", signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey())));
   }

   /**
    * @tpTestDetails Check output after signing
    * @tpSince RESTEasy 3.0.16
    */
   @Test
   public void testOutput() throws Exception {
      {
         SMIMESignedGenerator gen = new SMIMESignedGenerator();
         SignerInfoGenerator signer = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1WITHRSA", privateKey, cert);
         gen.addSignerInfoGenerator(signer);

         MimeMultipart mp = gen.generate(createMsg());
         output(mp);
      }

      {
         SMIMESignedGenerator gen = new SMIMESignedGenerator();
         SignerInfoGenerator signer = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1WITHRSA", privateKey, cert);
         gen.addSignerInfoGenerator(signer);

         MimeMultipart mp = gen.generate(createMsg());

         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mp.writeTo(os);

         ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
         String contentType = mp.getContentType();
         contentType = contentType.replace("\r\n", "").replace("\t", " ");

         ByteArrayDataSource ds = new ByteArrayDataSource(is, contentType);
         MimeMultipart mm = new MimeMultipart(ds);
         MimeBodyPart part = (MimeBodyPart) mm.getBodyPart(0);
      }
   }

   /**
    * @tpTestDetails Test python-style verification
    * @tpSince RESTEasy 3.0.16
    */
   @Test
   public void testPythonVerified() throws Exception {
      SMIMESignedGenerator gen = new SMIMESignedGenerator();
      SignerInfoGenerator signer = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1WITHRSA", privateKey, cert);
      gen.addSignerInfoGenerator(signer);

      MimeMultipart mp = gen.generate(createMsg());
      ByteArrayOutputStream os = new ByteArrayOutputStream();
      mp.writeTo(os);
      String contentType = mp.getContentType();
      contentType = contentType.replace("\r\n", "").replace("\t", " ");
      logger.info(contentType);
      String s = new String(os.toByteArray());
      StringBuilder builder = new StringBuilder();
      builder.append("Content-Type: ").append(contentType).append("\r\n\r\n").append(s);
      String output = builder.toString();

      FileOutputStream fp = new FileOutputStream("target/smime_signed.txt");
      fp.write(output.getBytes());
      fp.close();
   }

   /**
    * @tpTestDetails Test python-style wrong verification
    * @tpSince RESTEasy 3.0.16
    */
   @Test
   public void testPythonVerifiedBad() throws Exception {
      SMIMESignedGenerator gen = new SMIMESignedGenerator();
      SignerInfoGenerator signer = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1WITHRSA", badKey, cert);
      gen.addSignerInfoGenerator(signer);

      MimeMultipart mp = gen.generate(createMsg());
      ByteArrayOutputStream os = new ByteArrayOutputStream();
      mp.writeTo(os);
      String contentType = mp.getContentType();
      contentType = contentType.replace("\r\n", "").replace("\t", " ");
      logger.info(contentType);
      String s = new String(os.toByteArray());
      StringBuilder builder = new StringBuilder();
      builder.append("Content-Type: ").append(contentType).append("\r\n\r\n").append(s);
      String output = builder.toString();

      FileOutputStream fp = new FileOutputStream("target/smime_signed_bad.txt");
      fp.write(output.getBytes());
      fp.close();
   }
}
