/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
* @author Vladimir N. Molotkov
* @version $Revision$
*/

package tests.security.cert;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import junit.framework.TestCase;
import org.apache.harmony.security.tests.support.cert.MyCertificate;
import org.apache.harmony.security.tests.support.cert.MyFailingCertificate;
import org.apache.harmony.security.tests.support.cert.TestUtils;
import org.apache.harmony.testframework.serialization.SerializationTest;

/**
 * Tests for <code>Certificate</code> fields and methods
 *
 */
public class CertificateTest extends TestCase {
    /**
     * Meaningless cert encoding just for testing purposes
     */
    private static final byte[] testEncoding = new byte[] { (byte) 1, (byte) 2,
            (byte) 3, (byte) 4, (byte) 5 };

    /**
     * Test for <code>Certificate(String type)</code> method<br>
     */
    public final void testCertificate() throws Exception {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        assertTrue(Arrays.equals(testEncoding, c1.getEncoded()));
        assertEquals("TEST", c1.getPublicKey().getAlgorithm());
        assertTrue(Arrays.equals(new byte[] { (byte) 1, (byte) 2, (byte) 3 },
                                 c1.getPublicKey().getEncoded()));
        assertEquals("TEST_FORMAT", c1.getPublicKey().getFormat());
        assertEquals("TEST_TYPE", c1.getType());
    }

    /**
     * Test for <code>hashCode()</code> method<br>
     * Assertion: returns hash of the <code>Certificate</code> instance
     * @throws CertificateEncodingException
     */
    public final void testHashCode() throws CertificateEncodingException {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        Certificate c2 = new MyCertificate("TEST_TYPE", testEncoding);

        assertTrue(c1.hashCode() == c2.hashCode());

        assertFalse(c1.hashCode() == new MyCertificate("TEST_TYPE", cert
                .getEncoded()).hashCode());
        assertFalse(c1.hashCode() == cert.hashCode());
    }

    /**
     * Test for <code>hashCode()</code> method<br>
     * Assertion: hash code of equal objects should be the same
     */
    public final void testHashCodeEqualsObject() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        Certificate c2 = new MyCertificate("TEST_TYPE", testEncoding);

        assertTrue((c1.hashCode() == c2.hashCode()) && c1.equals(c2));
        assertFalse(cert.equals(c1));
    }

    /**
     * Test for <code>hashCode()</code> method<br>
     * Assertion: returns the value computed with the algorithm in jdk8u60.
     */
    public final void testHashCodeValue() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        // Result used to be 40 prior to jdk8u60.
        assertEquals(29615266, c1.hashCode());
    }

    /**
     * Test for <code>getType()</code> method<br>
     * Assertion: returns this certificate type
     */
    public final void testGetType() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        assertEquals("TEST_TYPE", c1.getType());
    }

    /**
     * Test #1 for <code>equals(Object)</code> method<br>
     * Assertion: object equals to itself
     */
    public final void testEqualsObject01() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        assertTrue(c1.equals(c1));
    }

    /**
     * Test for <code>equals(Object)</code> method<br>
     * Assertion: object equals to other <code>Certificate</code>
     * instance with the same state
     */
    public final void testEqualsObject02() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        Certificate c2 = new MyCertificate("TEST_TYPE", testEncoding);
        assertTrue(c1.equals(c2) && c2.equals(c1));
    }

    /**
     * Test for <code>equals(Object)</code> method<br>
     * Assertion: object not equals to <code>null</code>
     */
    public final void testEqualsObject03() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        assertFalse(c1.equals(null));
    }

    /**
     * Test for <code>equals(Object)</code> method<br>
     * Assertion: object not equals to other which is not
     * instance of <code>Certificate</code>
     */
    public final void testEqualsObject04() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        assertFalse(c1.equals("TEST_TYPE"));
    }

    // the following tests just call methods
    // that are abstract in <code>Certificate</code>
    // (So they just like signature tests)

    /**
     * This test just calls <code>getEncoded()</code> method<br>
     * @throws CertificateException
     */
    public final void testGetEncoded() throws CertificateException {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        assertNotNull(c1.getEncoded());
        assertTrue(Arrays.equals(testEncoding,c1.getEncoded()));

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        byte[] expectedEncoding = cert.getEncoded();
        Certificate actual = cf.generateCertificate(new ByteArrayInputStream(expectedEncoding));
        byte[] actualEncoding = actual.getEncoded();
        assertTrue(Arrays.equals(expectedEncoding, actualEncoding));

        assertFalse(expectedEncoding[4] == (byte) 200);
        expectedEncoding[4] = (byte) 200;
        try {
            cf.generateCertificate(new ByteArrayInputStream(expectedEncoding));
            fail();
        } catch (CertificateException expected) {
        }
    }

    /**
     * <code>verify(PublicKey)</code> with null args
     *
     * @throws InvalidKeyException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws SignatureException
     */
    public final void testVerifyPublicKey()
        throws InvalidKeyException,
               CertificateException,
               NoSuchAlgorithmException,
               NoSuchProviderException,
               SignatureException {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        c1.verify(null);
    }

    /**
     * <code>verify(PublicKey,String)</code> with null args
     *
     * @throws InvalidKeyException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws SignatureException
     */
    public final void testVerifyPublicKeyString()
        throws InvalidKeyException,
               CertificateException,
               NoSuchAlgorithmException,
               NoSuchProviderException,
               SignatureException {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        c1.verify((PublicKey) null, (String) null);
    }

    /**
     * <code>verify(PublicKey,Provider)</code> with null args
     *
     * @throws InvalidKeyException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws SignatureException
     */
    public final void testVerifyPublicKeyProvider()
        throws Exception  {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        try {
            // Android-changed: throw UOE instead of infinite recursion.
            c1.verify((PublicKey) null, (Provider) null);
            fail();
        } catch(UnsupportedOperationException expected) {}
    }

    /**
     * This test just calls <code>toString()</code> method<br>
     */
    public final void testToString() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        c1.toString();
    }

    /**
     * This test just calls <code>testGetPublicKey()</code> method<br>
     */
    public final void testGetPublicKey() {
        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        c1.getPublicKey();
    }

    /**
     * This test just calls <code>writeReplace()</code> method<br>
     */
    public final void testWriteReplace() throws Exception {
        MyCertificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        Object obj = c1.writeReplace();
        assertTrue(obj.toString().contains("java.security.cert.Certificate$CertificateRep"));
    }

public class MyModifiablePublicKey implements PublicKey {

        private PublicKey key;
        private boolean modifiedAlgo;
        private String algo;
        private String format;
        private boolean modifiedFormat;
        private boolean modifiedEncoding;
        private byte[] encoding;

        public MyModifiablePublicKey(PublicKey k) {
            super();
            this.key = k;
        }

        public String getAlgorithm() {
            if (modifiedAlgo) {
                return algo;
            } else {
                return key.getAlgorithm();
            }
        }

        public String getFormat() {
            if (modifiedFormat) {
                return this.format;
            } else {
                return key.getFormat();
            }

        }

        public byte[] getEncoded() {
            if (modifiedEncoding) {
                return this.encoding;
            } else {
                return key.getEncoded();
            }

        }

        public long getSerVerUID() {
            return key.serialVersionUID;
        }

        public void setAlgorithm(String myAlgo) {
            modifiedAlgo = true;
            this.algo = myAlgo;
        }

        public void setFormat(String myFormat) {
            modifiedFormat = true;
            format = myFormat;
        }

        public void setEncoding(byte[] myEncoded) {
            modifiedEncoding = true;
            encoding = myEncoded;
        }
    }

    private Certificate cert;

    public void setUp() throws Exception {
        super.setUp();
        TestUtils.initCertPathSSCertChain();
        cert = TestUtils.rootCertificateSS;
    }

    /**
     * This test just calls <code>verify(PublicKey,String)</code> method<br>
     *
     * @throws InvalidKeyException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws SignatureException
     */
    public final void testVerifyPublicKeyString2() throws InvalidKeyException,
            CertificateException, NoSuchAlgorithmException,
            NoSuchProviderException, SignatureException {

        final Signature sig = Signature.getInstance("SHA1WithRSA");
        sig.initVerify(cert.getPublicKey());
        final Provider provider = sig.getProvider();
        // real test
        cert.verify(cert.getPublicKey(), provider.getName());

        // Exception tests

        try {
            cert.verify(cert.getPublicKey(), "UnknownProvider");
            fail();
        } catch (NoSuchProviderException expected) {
        }

        // This test has side effects affecting all other tests running later
        // on in the same vm instance. Maybe a better way would be to first add
        // a new provider, test if it works, then remove it and test if the
        // exception is thrown.
        //
        // CertificateFactory cf = CertificateFactory.getInstance("X.509");
        // Provider wrongProvider = cf.getProvider();
        //
        // Security.removeProvider(wrongProvider.getName());
        //
        // try {
        //     cert.verify(cert.getPublicKey(), wrongProvider.getName());
        //     fail();
        // } catch (NoSuchAlgorithmException expected) {
        // }
        //
        // Security.insertProviderAt(wrongProvider, oldPosition);

        /*
        PublicKey k = cert.getPublicKey();
        MyModifiablePublicKey tamperedKey = new MyModifiablePublicKey(k);
        tamperedKey.setAlgorithm("wrongAlgo");

        try {
            cert.verify(tamperedKey, provs[0].getName());
            fail();
        } catch (SignatureException expected) {
        }

        try {
            cert.verify(c1.getPublicKey(), provs[0].getName());
            fail();
        } catch (InvalidKeyException expected) {
        }
        */
    }

    public final void testVerifyPublicKeyProvider2() throws Exception {
        final Signature sig = Signature.getInstance("SHA1WithRSA");
        sig.initVerify(cert.getPublicKey());
        final Provider provider = sig.getProvider();
        cert.verify(cert.getPublicKey(), provider);
        // equivalent to calling cert.verify(cert.getPublicKey())
        cert.verify(cert.getPublicKey(), (Provider)null);
    }

    /**
     * This test just calls <code>verify(PublicKey)</code> method<br>
     *
     * @throws InvalidKeyException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws SignatureException
     * @throws IOException
     * @throws InvalidAlgorithmParameterException
     */
    public final void testVerifyPublicKey2()
            throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, InvalidAlgorithmParameterException, IOException {

        Certificate c1 = new MyCertificate("TEST_TYPE", testEncoding);
        c1.verify(null);

        cert.verify(cert.getPublicKey());

        PublicKey k = cert.getPublicKey();

        MyModifiablePublicKey changedEncoding = new MyModifiablePublicKey(k);
        changedEncoding.setEncoding(new byte[cert.getEncoded().length - 1]);

        try {
            cert.verify(c1.getPublicKey());
            fail();
        } catch (InvalidKeyException expected) {
        }

        try {
            cert.verify(changedEncoding);
            fail();
        } catch (Exception expected) {
        }
    }

    /**
     * This test just calls <code>writeReplace()</code> method<br>
     */
    public final void testWriteReplace2() {
        MyCertificate c1 = new MyFailingCertificate("TEST_TYPE", testEncoding);

        try {
            c1.writeReplace();
            fail();
        } catch (ObjectStreamException expected) {
        }
    }

    /**
     * serialization/deserialization compatibility.
     */
    public void testSerializationSelf() throws Exception {
        TestUtils.initCertPathSSCertChain();

        SerializationTest.verifySelf(TestUtils.rootCertificateSS);
    }

    /**
     * serialization/deserialization compatibility with RI.
     */
    public void testSerializationCompatibility() throws Exception {
        // create test file (once)
        // SerializationTest.createGoldenFile("/sdcard", this, TestUtils.rootCertificateSS);
        TestUtils.initCertPathSSCertChain();

        SerializationTest.verifyGolden(this, TestUtils.rootCertificateSS);
    }
}
