/*
 * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Base64;
import sun.security.util.DerValue;
import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.EdECPublicKey;
import java.security.spec.EdECPrivateKeySpec;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/*
 * @test
 * @bug 8209632
 * @summary OpenSSL generated compatibility test with EDDSA Java.
 * @modules java.base/sun.security.util
 * @run main EdDSAKeyCompatibility
 */
public class EdDSAKeyCompatibility {

    private static final String EDDSA = "EdDSA";
    private static final String ED25519 = "Ed25519";
    private static final String ED448 = "Ed448";
    private static final String PROVIDER = System.getProperty("test.provider.name", "SunEC");

    public static void main(String[] args) throws Exception {

        boolean result = true;
        result &= validateCert(EDDSA, PROVIDER, ED25519CERT);
        result &= validateCert(EDDSA, PROVIDER, ED448CERT);
        result &= validateCert(ED25519, PROVIDER, ED25519CERT);
        result &= validateCert(ED448, PROVIDER, ED448CERT);

        result &= validatePrivate(ED25519, PROVIDER, ED25519KEY);
        result &= validatePrivate(ED448, PROVIDER, ED448KEY);

        if (!result) {
            throw new RuntimeException("Some test cases failed");
        }
    }

    private static boolean validatePrivate(String algorithm, String provider,
            String key) {

        try {
            KeyFactory kf = KeyFactory.getInstance(algorithm, provider);
            PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(
                    Base64.getMimeDecoder().decode(key));
            EdECPrivateKey privKey
                    = (EdECPrivateKey) kf.generatePrivate(privSpec);
            checkPrivKeyFormat(privKey.getEncoded());

            NamedParameterSpec namedSpec = new NamedParameterSpec(algorithm);
            EdECPrivateKeySpec edprivSpec = new EdECPrivateKeySpec(
                    namedSpec, privKey.getBytes().get());
            EdECPrivateKey privKey1
                    = (EdECPrivateKey) kf.generatePrivate(edprivSpec);
            checkPrivKeyFormat(privKey1.getEncoded());
            EdECPrivateKey privKey2 = (EdECPrivateKey) kf.translateKey(privKey);
            checkPrivKeyFormat(privKey2.getEncoded());
            equals(privKey, privKey1);
            equals(privKey, privKey2);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException
                | NoSuchProviderException | InvalidKeyException e) {
            e.printStackTrace(System.out);
            return false;
        }
        System.out.println("PASSED - validatePrivate");
        return true;
    }

    private static boolean validateCert(String algorithm, String provider,
            String certificate) {

        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            Certificate cert = cf.generateCertificate(
                    new ByteArrayInputStream(certificate.getBytes()));
            System.out.println(cert);
            KeyFactory kf = KeyFactory.getInstance(algorithm, provider);
            X509EncodedKeySpec pubSpec = kf.getKeySpec(
                    cert.getPublicKey(), X509EncodedKeySpec.class);
            EdECPublicKey pubKey = (EdECPublicKey) kf.generatePublic(pubSpec);
            EdECPublicKeySpec edpubSpec = kf.getKeySpec(
                    cert.getPublicKey(), EdECPublicKeySpec.class);
            EdECPublicKey pubKey1 = (EdECPublicKey) kf.generatePublic(edpubSpec);
            EdECPublicKey pubKey2 = (EdECPublicKey) kf.translateKey(pubKey);
            equals(pubKey, pubKey1);
            equals(pubKey, pubKey2);
            equals(pubKey, cert.getPublicKey());
        } catch (CertificateException | NoSuchAlgorithmException
                | InvalidKeySpecException | NoSuchProviderException
                | InvalidKeyException e) {
            e.printStackTrace(System.out);
            return false;
        }
        System.out.println("PASSED - validateCert");
        return true;
    }

    private static void checkPrivKeyFormat(byte[] key) throws IOException {

        // key value should be nested octet strings
        DerValue val = new DerValue(new ByteArrayInputStream(key));
        BigInteger version = val.data.getBigInteger();
        DerValue algId = val.data.getDerValue();
        byte[] keyValue = val.data.getOctetString();
        val = new DerValue(new ByteArrayInputStream(keyValue));
        if (val.tag != DerValue.tag_OctetString) {
            throw new RuntimeException("incorrect format");
        }
    }

    private static void equals(Object actual, Object expected) {
        if (!actual.equals(expected)) {
            throw new RuntimeException(String.format("Actual: %s, Expected: %s",
                    actual, expected));
        }
    }

    // Following EdDSA Certificates/Keys are generated through openssl_1.1.1d

    /*
     * Certificate:
     *  Data:
     *     Version: 3 (0x2)
     *     Serial Number:
     *         1d:d3:87:b9:e4:c6:9e:4a:5c:78:a8:f0:c7:9b:37:be:1e:31:dd:20
     *     Signature Algorithm: ED25519
     *     Issuer: C = US, ST = CA, L = SCA, O = test, OU = test, CN = localhost
     *     Validity
     *         Not Before: Mar  6 05:55:31 2020 GMT
     *         Not After : Mar  1 05:55:31 2040 GMT
     *     Subject: C = US, ST = CA, L = SCA, O = test, OU = test, CN = localhost
     *     Subject Public Key Info:
     *         Public Key Algorithm: ED25519
     */
    private static final String ED25519KEY
            = "MC4CAQAwBQYDK2VwBCIEIP8xGaQTMh+I+59I66AaN+B4qnY1oWGjPwbSY4r+D08f";

    private static final String ED25519CERT
            = "-----BEGIN CERTIFICATE-----\n"
            + "MIIByTCCAXugAwIBAgIUHdOHueTGnkpceKjwx5s3vh4x3SAwBQYDK2VwMFoxCzAJ\n"
            + "BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0NBMQ0wCwYDVQQKDAR0\n"
            + "ZXN0MQ0wCwYDVQQLDAR0ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjAwMzA2\n"
            + "MDU1NTMxWhcNNDAwMzAxMDU1NTMxWjBaMQswCQYDVQQGEwJVUzELMAkGA1UECAwC\n"
            + "Q0ExDDAKBgNVBAcMA1NDQTENMAsGA1UECgwEdGVzdDENMAsGA1UECwwEdGVzdDES\n"
            + "MBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEAdqQ4Nduhbl+ShGeKdOryVMKy\n"
            + "1t1LjyjPyCBC+gSk0eCjUzBRMB0GA1UdDgQWBBS01/VQEzwkFNRW/esQxaB6+uId\n"
            + "8jAfBgNVHSMEGDAWgBS01/VQEzwkFNRW/esQxaB6+uId8jAPBgNVHRMBAf8EBTAD\n"
            + "AQH/MAUGAytlcANBAEJkLuNfyVws7HKqHL7oDqQkp5DSwh+bGjrr2p4zSvs5PZ8o\n"
            + "jRXWV0SMt/MB+90ubMD5tL7H7J6DR5PUFBIwGwc=\n"
            + "-----END CERTIFICATE-----";

    /*
     * Certificate:
     *  Data:
     *     Version: 3 (0x2)
     *     Serial Number:
     *         42:eb:8c:a2:a0:6f:8e:5a:a5:f8:7c:72:c1:f1:8b:7e:44:1b:37:80
     *     Signature Algorithm: ED448
     *     Issuer: C = US, ST = CA, L = SCA, O = test, OU = test, CN = localhost
     *     Validity
     *         Not Before: Mar  6 05:57:42 2020 GMT
     *         Not After : Mar  1 05:57:42 2040 GMT
     *     Subject: C = US, ST = CA, L = SCA, O = test, OU = test, CN = localhost
     *     Subject Public Key Info:
     *         Public Key Algorithm: ED448
     */
    private static final String ED448KEY
            = "MEcCAQAwBQYDK2VxBDsEOdG4lrYO0xBaf3aJWYMZ8XAxitA1zV4/ghG8wPBag8HQ"
            + "XN+3OmS8wR1KfeGQysHQr3JHco3Mwiaz8w==";

    private static final String ED448CERT
            = "-----BEGIN CERTIFICATE-----\n"
            + "MIICFDCCAZSgAwIBAgIUQuuMoqBvjlql+HxywfGLfkQbN4AwBQYDK2VxMFoxCzAJ\n"
            + "BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0NBMQ0wCwYDVQQKDAR0\n"
            + "ZXN0MQ0wCwYDVQQLDAR0ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjAwMzA2\n"
            + "MDU1NzQyWhcNNDAwMzAxMDU1NzQyWjBaMQswCQYDVQQGEwJVUzELMAkGA1UECAwC\n"
            + "Q0ExDDAKBgNVBAcMA1NDQTENMAsGA1UECgwEdGVzdDENMAsGA1UECwwEdGVzdDES\n"
            + "MBAGA1UEAwwJbG9jYWxob3N0MEMwBQYDK2VxAzoAfKlXpT0ymcvz2Gp+8HLzBpaz\n"
            + "5mQqMaDbGmcq8gSIdeEUtVmv4OplE+4GSnrbJnEn99LQdbanL/MAo1MwUTAdBgNV\n"
            + "HQ4EFgQUXkm9LVUkB0f/1MiPFjQPHGJ8THIwHwYDVR0jBBgwFoAUXkm9LVUkB0f/\n"
            + "1MiPFjQPHGJ8THIwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXEDcwDvE3LKCg1bTjHi\n"
            + "MI1EiMqZN3PJYVBsztecBXm3ELDlT+F0Z1H2vjaROkJc8PdpUOxyed1xDjwq3IB/\n"
            + "nYYJNVyt6Dy3d12kl77ev+YMD83OuqM6F5O6MdDUxYQu9u3NasZAU5FQ6zklWWpI\n"
            + "8jCPtOvcAQA=\n"
            + "-----END CERTIFICATE-----";
}