package org.bouncycastle.tsp.test;

import org.bouncycastle.asn1.cmp.PKIFailureInfo;
import org.bouncycastle.asn1.cmp.PKIStatus;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.tsp.TSPAlgorithms;
import org.bouncycastle.tsp.TimeStampRequest;
import org.bouncycastle.tsp.TimeStampResponse;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.test.SimpleTestResult;
import org.bouncycastle.util.test.Test;
import org.bouncycastle.util.test.TestResult;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

/**
 * Test Cases
 */
public class ParseTest
    implements Test
{
    private byte[] sha1Request = Base64.decode(
          "MDACAQEwITAJBgUrDgMCGgUABBT5UbEBmJssO3RxcQtOePxNvfoMpgIIC+Gv"
        + "YW2mtZQ=");


    private byte[] sha1noNonse = Base64.decode(
        "MCYCAQEwITAJBgUrDgMCGgUABBT5UbEBmJssO3RxcQtOePxNvfoMpg==");

    private byte[] md5Request = Base64.decode(
          "MDoCAQEwIDAMBggqhkiG9w0CBQUABBDIl9FBCvjyx0+6EbHbUR6eBgkrBgEE"
        + "AakHBQECCDQluayIxIzn");

    private byte[] ripemd160Request = Base64.decode(
        "MD8CAQEwITAJBgUrJAMCAQUABBSq03a/mk50Yd9lMF+BSqOp/RHGQQYJKwYB"
      + "BAGpBwUBAgkA4SZs9NfqISMBAf8=");

    private byte[] sha1Response = Base64.decode(
          "MIICbDADAgEAMIICYwYJKoZIhvcNAQcCoIICVDCCAlACAQMxCzAJBgUrDgMC"
        + "GgUAMIHaBgsqhkiG9w0BCRABBKCBygSBxzCBxAIBAQYEKgMEATAhMAkGBSsO"
        + "AwIaBQAEFPlRsQGYmyw7dHFxC054/E29+gymAgEEGA8yMDA0MTIwOTA3NTIw"
        + "NVowCgIBAYACAfSBAWQBAf8CCAvhr2FtprWUoGmkZzBlMRgwFgYDVQQDEw9F"
        + "cmljIEguIEVjaGlkbmExJDAiBgkqhkiG9w0BCQEWFWVyaWNAYm91bmN5Y2Fz"
        + "dGxlLm9yZzEWMBQGA1UEChMNQm91bmN5IENhc3RsZTELMAkGA1UEBhMCQVUx"
        + "ggFfMIIBWwIBATAqMCUxFjAUBgNVBAoTDUJvdW5jeSBDYXN0bGUxCzAJBgNV"
        + "BAYTAkFVAgECMAkGBSsOAwIaBQCggYwwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3"
        + "DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0wNDEyMDkwNzUyMDVaMCMGCSqGSIb3"
        + "DQEJBDEWBBTGR1cbm94tWbcpDWrH+bD8UYePsTArBgsqhkiG9w0BCRACDDEc"
        + "MBowGDAWBBS37aLzFcheqeJ5cla0gjNWHGKbRzANBgkqhkiG9w0BAQEFAASB"
        + "gBrc9CJ3xlcTQuWQXJUqPEn6f6vfJAINKsn22z8LIfS/2p/CTFU6+W/bz8j8"
        + "j+8uWEJe8okTsI0FflljIsspqOPTB/RrnXteajbkuk/rLmz1B2g/qWBGAzPI"
        + "D214raBc1a7Bpd76PkvSSdjqrEaaskd+7JJiPr9l9yeSoh1AIt0N");

    private byte[] sha1noNonseResponse = Base64.decode(
          "MIICYjADAgEAMIICWQYJKoZIhvcNAQcCoIICSjCCAkYCAQMxCzAJBgUrDgMC"
        + "GgUAMIHQBgsqhkiG9w0BCRABBKCBwASBvTCBugIBAQYEKgMEATAhMAkGBSsO"
        + "AwIaBQAEFPlRsQGYmyw7dHFxC054/E29+gymAgECGA8yMDA0MTIwOTA3MzQx"
        + "MlowCgIBAYACAfSBAWQBAf+gaaRnMGUxGDAWBgNVBAMTD0VyaWMgSC4gRWNo"
        + "aWRuYTEkMCIGCSqGSIb3DQEJARYVZXJpY0Bib3VuY3ljYXN0bGUub3JnMRYw"
        + "FAYDVQQKEw1Cb3VuY3kgQ2FzdGxlMQswCQYDVQQGEwJBVTGCAV8wggFbAgEB"
        + "MCowJTEWMBQGA1UEChMNQm91bmN5IENhc3RsZTELMAkGA1UEBhMCQVUCAQIw"
        + "CQYFKw4DAhoFAKCBjDAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJ"
        + "KoZIhvcNAQkFMQ8XDTA0MTIwOTA3MzQxMlowIwYJKoZIhvcNAQkEMRYEFMNA"
        + "xlscHYiByHL9DIEh3FewIhgSMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFLft"
        + "ovMVyF6p4nlyVrSCM1YcYptHMA0GCSqGSIb3DQEBAQUABIGAaj46Tarrg7V7"
        + "z13bbetrGv+xy159eE8kmIW9nPegru3DuK/GmbMx9W3l0ydx0zdXRwYi6NZc"
        + "nNqbEZQZ2L1biJVTflgWq4Nxu4gPGjH/BGHKdH/LyW4eDcXZR39AkNBMnDAK"
        + "EmhhJo1/Tc+S/WkV9lnHJCPIn+TAijBUO6EiTik=");
    
    private byte[] md5Response = Base64.decode(
          "MIICcDADAgEAMIICZwYJKoZIhvcNAQcCoIICWDCCAlQCAQMxCzAJBgUrDgMC"
        + "GgUAMIHeBgsqhkiG9w0BCRABBKCBzgSByzCByAIBAQYJKwYBBAGpBwUBMCAw"
        + "DAYIKoZIhvcNAgUFAAQQyJfRQQr48sdPuhGx21EengIBAxgPMjAwNDEyMDkw"
        + "NzQ2MTZaMAoCAQGAAgH0gQFkAQH/Agg0JbmsiMSM56BppGcwZTEYMBYGA1UE"
        + "AxMPRXJpYyBILiBFY2hpZG5hMSQwIgYJKoZIhvcNAQkBFhVlcmljQGJvdW5j"
        + "eWNhc3RsZS5vcmcxFjAUBgNVBAoTDUJvdW5jeSBDYXN0bGUxCzAJBgNVBAYT"
        + "AkFVMYIBXzCCAVsCAQEwKjAlMRYwFAYDVQQKEw1Cb3VuY3kgQ2FzdGxlMQsw"
        + "CQYDVQQGEwJBVQIBAjAJBgUrDgMCGgUAoIGMMBoGCSqGSIb3DQEJAzENBgsq"
        + "hkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMDQxMjA5MDc0NjE2WjAjBgkq"
        + "hkiG9w0BCQQxFgQUFpRpaiRUUjiY7EbefbWLKDIY0XMwKwYLKoZIhvcNAQkQ"
        + "AgwxHDAaMBgwFgQUt+2i8xXIXqnieXJWtIIzVhxim0cwDQYJKoZIhvcNAQEB"
        + "BQAEgYBTwKsLLrQm+bvKV7Jwto/cMQh0KsVB5RoEeGn5CI9XyF2Bm+JRcvQL"
        + "Nm7SgSOBVt4A90TqujxirNeyQnXRiSnFvXd09Wet9WIQNpwpiGlE7lCrAhuq"
        + "/TAUe79VIpoQZDtyhbh0Vzxl24yRoechabC0zuPpOWOzrA4YC3Hv1J2tAA==");

    private byte[] signingCert = Base64.decode(
        "MIICWjCCAcOgAwIBAgIBAjANBgkqhkiG9w0BAQQFADAlMRYwFAYDVQQKEw1Cb3Vu"
      + "Y3kgQ2FzdGxlMQswCQYDVQQGEwJBVTAeFw0wNDEyMDkwNzEzMTRaFw0wNTAzMTkw"
      + "NzEzMTRaMGUxGDAWBgNVBAMTD0VyaWMgSC4gRWNoaWRuYTEkMCIGCSqGSIb3DQEJ"
      + "ARYVZXJpY0Bib3VuY3ljYXN0bGUub3JnMRYwFAYDVQQKEw1Cb3VuY3kgQ2FzdGxl"
      + "MQswCQYDVQQGEwJBVTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqGAFO3dK"
      + "jB7Ca7u5Z3CabsbGr2Exg+3sztSPiRCIba03es4295EhtDF5bXQvrW2R1Bg72vED"
      + "5tWaQjVDetvDfCzVC3ErHLTVk3OgpLIP1gf2T0LcOH2pTh2LP9c5Ceta+uggK8zK"
      + "9sYUUnzGPSAZxrqHIIAlPIgqk0BMV+KApyECAwEAAaNaMFgwHQYDVR0OBBYEFO4F"
      + "YoqogtB9MjD0NB5x5HN3TrGUMB8GA1UdIwQYMBaAFPXAecuwLqNkCxYVLE/ngFQR"
      + "7RLIMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBBAUAA4GBADGi"
      + "D5/qmGvcBgswEM/z2dF4lOxbTNKUW31ZHiU8CXlN0IkFtNbBLBTbJOQIAUnNEabL"
      + "T7aYgj813OZKUbJTx4MuGChhot/TEP7hKo/xz9OnXLsqYDKbqbo8iLOode+SI7II"
      + "+yYghOtqvx32cL2Qmffi1LaMbhJP+8NbsIxowdRC");

    private byte[] unacceptablePolicy = Base64.decode(
          "MDAwLgIBAjAkDCJSZXF1ZXN0ZWQgcG9saWN5IGlzIG5vdCBzdXBwb3J0ZWQu"
        + "AwMAAAE=");

    /* (non-Javadoc)
     * @see org.bouncycastle.util.test.Test#getName()
     */
    public String getName()
    {
        return "ParseTest";
    }

    private TestResult requestParse(
        byte[]  request,
        String  algorithm) 
        throws IOException
    {
        TimeStampRequest    req = new TimeStampRequest(request);
        
        if (!req.getMessageImprintAlgOID().equals(algorithm))
        {
            return new SimpleTestResult(false, getName() + ": failed to get expected algorithm - got " 
                    + req.getMessageImprintAlgOID() + " not " + algorithm);
        }
        
        if (request != sha1Request && request != sha1noNonse)
        {
            if (!req.getReqPolicy().equals(TSPTestUtil.EuroPKI_TSA_Test_Policy.getId()))
            {
                return new SimpleTestResult(false, getName() + ": " + algorithm + " failed policy check.");
            }
            
            if (request == ripemd160Request)
            {
                if (!req.getCertReq())
                {
                    return new SimpleTestResult(false, getName() + ": " + algorithm + " failed certReq check.");
                }
            }
        }
        
        if (request != sha1noNonse)
        {
            if (req.getNonce() == null)
            {
                return new SimpleTestResult(false, getName() + ": " + algorithm + " nonse not found when one expected.");
            }
        }
        else
        {
            if (req.getNonce() != null)
            {
                return new SimpleTestResult(false, getName() + ": " + algorithm + " nonse not found when one not expected.");
            } 
        }
        
        try
        {
            req.validate(TSPAlgorithms.ALLOWED, null, null, "BC");
        }
        catch (Exception e)
        {
            return new SimpleTestResult(false, getName() + ": validation exception.");
        }
        
        if (!Arrays.areEqual(req.getEncoded(), request))
        {
            return new SimpleTestResult(false, getName() + ": " + algorithm + " failed encode check."); 
        }
        
        return new SimpleTestResult(true, getName() + ": Okay");
    }
    
    private TestResult responseParse(
        byte[]  request,
        byte[]  response,
        String  algorithm) 
        throws Exception
    {
        TimeStampRequest  req = new TimeStampRequest(request);
        TimeStampResponse resp = new TimeStampResponse(response);

        CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
                
        X509Certificate cert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(signingCert));

        resp.validate(req);

        resp.getTimeStampToken().validate(cert, "BC");
        
        return new SimpleTestResult(true, getName() + ": Okay");
    }
    
    private TestResult unacceptableResponseParse(
        byte[]  response) 
        throws Exception
    {
        TimeStampResponse resp = new TimeStampResponse(response);

        if (resp.getStatus() != PKIStatus.REJECTION)
        {
            return new SimpleTestResult(false, getName() + ": request not rejected.");
        }
        
        if (resp.getFailInfo().intValue() != PKIFailureInfo.UNACCEPTED_POLICY)
        {
            return new SimpleTestResult(false, getName() + ": request not rejected.");
        }
        
        return new SimpleTestResult(true, getName() + ": Okay");
    }
    
    /* (non-Javadoc)
     * @see org.bouncycastle.util.test.Test#perform()
     */
    public TestResult perform()
    {
        try
        {   
            TestResult  res = requestParse(sha1Request, TSPAlgorithms.SHA1);
            if (!res.isSuccessful())
            {
                return res;
            }
            
            res = requestParse(sha1noNonse, TSPAlgorithms.SHA1);
            if (!res.isSuccessful())
            {
                return res;
            }
            
            res = requestParse(md5Request, TSPAlgorithms.MD5);
            if (!res.isSuccessful())
            {
                return res;
            }
            
            res = requestParse(ripemd160Request, TSPAlgorithms.RIPEMD160);
            if (!res.isSuccessful())
            {
                return res;
            }
            
            res = responseParse(sha1Request, sha1Response, TSPAlgorithms.SHA1);
            if (!res.isSuccessful())
            {
                return res;
            }
            
            res = responseParse(sha1noNonse, sha1noNonseResponse, TSPAlgorithms.SHA1);
            if (!res.isSuccessful())
            {
                return res;
            }
            
            res = responseParse(md5Request, md5Response, TSPAlgorithms.MD5);
            if (!res.isSuccessful())
            {
                return res;
            }

            res = unacceptableResponseParse(unacceptablePolicy);
            if (!res.isSuccessful())
            {
                return res;
            }
            return new SimpleTestResult(true, getName() + ": Okay");
        }
        catch (Exception e)
        {
            return new SimpleTestResult(false, getName() + ": Exception - " + e.toString(), e);
        }
    }
    
    public TestResult parse(
        byte[]  encoded,
        boolean tokenPresent)
        throws Exception
    {
        TimeStampResponse   response = new TimeStampResponse(encoded);

        if (tokenPresent && response.getTimeStampToken() == null)
        {
            return new SimpleTestResult(false, getName() + ": token not found when expected.");
        }
        
        return new SimpleTestResult(true, getName() + ": Okay");
    }
    
    public static void main(String[] args)
    {
        Security.addProvider(new BouncyCastleProvider());

        Test            test = new ParseTest();
        TestResult      result = test.perform();

        System.out.println(result.toString());
    }
}
