package org.bouncycastle.openpgp.test;

import java.io.IOException;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;

import javax.crypto.KeyAgreement;

import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.bcpg.AEADAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
import org.bouncycastle.bcpg.PublicKeyPacket;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.X25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.X25519PublicKeyParameters;
import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.bouncycastle.jcajce.spec.HybridValueParameterSpec;
import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPKdfParameters;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPContentVerifier;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaAEADSecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.test.SimpleTest;

public class OperatorJcajceTest
    extends SimpleTest
{
    public static void main(String[] args)
    {
        Security.addProvider(new BouncyCastleProvider());

        runTest(new OperatorJcajceTest());
    }

    @Override
    public String getName()
    {
        return "OperatorJcajceTest";
    }

    @Override
    public void performTest()
        throws Exception
    {
        testPGPKeyEncryptionMethodGenerator();
        testJcaAEADSecretKeyEncryptorBuilder();
        testCreateDigest();
        testX25519HKDF();
        testJcePBESecretKeyEncryptorBuilder();
        testJcaPGPContentVerifierBuilderProvider();
        testJcaPGPDigestCalculatorProviderBuilder();
        testJcePGPDataEncryptorBuilder();
        testJcaKeyFingerprintCalculator();
        testStandardDigests();
    }

    private void testPGPKeyEncryptionMethodGenerator()
        throws Exception
    {
        v4PBEKeyEncryptionMethodGenerator();
        v5PBEKeyEncryptionMethodGenerator();
        v6PBEKeyEncryptionMethodGenerator();

        v6PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.ECDH, "X448");
        v6PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.ECDH, "X25519");
        v6PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.ECDH, "ECDH");
        v6PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.X448, "X448");
        v6PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.X25519, "X25519");
        v6PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.RSA_GENERAL, "RSA");

        v3PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.ECDH, "X448");
        v3PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.ECDH, "X25519");
        v3PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.ECDH, "ECDH");
        v3PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.X448, "X448");
        v3PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.X25519, "X25519");
        v3PublicKeyKeyEncryptionMethodGenerator(PublicKeyAlgorithmTags.RSA_GENERAL, "RSA");

    }

    private void v3PublicKeyKeyEncryptionMethodGenerator(int publicKeyID, String algorithmName)
        throws Exception
    {
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance(algorithmName, "BC");
        if (publicKeyID == PublicKeyAlgorithmTags.ECDH && algorithmName.equals("ECDH"))
        {
            kpGen.initialize(new ECNamedCurveGenParameterSpec("P-256"));
        }
        PGPKdfParameters parameters = null;
        if (algorithmName.equals("X448"))
        {
            parameters = new PGPKdfParameters(HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_128);
        }
        else if (algorithmName.equals("X25519"))
        {
            parameters = new PGPKdfParameters(HashAlgorithmTags.SHA512, SymmetricKeyAlgorithmTags.AES_256);
        }
        PGPKeyPair pgpKeyPair = new JcaPGPKeyPair(PublicKeyPacket.VERSION_4, publicKeyID,
            parameters, kpGen.generateKeyPair(), new Date());

        JcePublicKeyKeyEncryptionMethodGenerator methodGenerator = new JcePublicKeyKeyEncryptionMethodGenerator(pgpKeyPair.getPublicKey());
        int symAlgId = SymmetricKeyAlgorithmTags.CAST5;
        JcePGPDataEncryptorBuilder v4 = new JcePGPDataEncryptorBuilder(symAlgId);
        byte[] sessionKey = PGPUtil.makeRandomKey(symAlgId, new SecureRandom());
        PublicKeyEncSessionPacket packet = (PublicKeyEncSessionPacket)methodGenerator.generate(v4, sessionKey);
        PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder().build(pgpKeyPair.getPrivateKey());
        byte[] data = decryptorFactory.recoverSessionData(publicKeyID, packet.getEncSessionKey(), PublicKeyEncSessionPacket.VERSION_3);
        if (publicKeyID == PublicKeyAlgorithmTags.X448 || publicKeyID == PublicKeyAlgorithmTags.X25519)
        {
            isTrue(Arrays.areEqual(sessionKey, data));
        }
        else
        {
            isTrue(Arrays.areEqual(sessionKey, Arrays.copyOfRange(data, 1, data.length - 2)));
        }
    }

    private void v6PublicKeyKeyEncryptionMethodGenerator(int publicKeyID, String algorithmName)
        throws Exception
    {
        int symAlgId = SymmetricKeyAlgorithmTags.CAMELLIA_128;
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance(algorithmName, "BC");
        if (publicKeyID == PublicKeyAlgorithmTags.ECDH && algorithmName.equals("ECDH"))
        {
            kpGen.initialize(new ECNamedCurveGenParameterSpec("P-256"));
        }
        PGPKdfParameters parameters = null;
        if (algorithmName.equals("X448"))
        {
            parameters = new PGPKdfParameters(HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_128);
        }
        else if (algorithmName.equals("X25519"))
        {
            parameters = new PGPKdfParameters(HashAlgorithmTags.SHA512, SymmetricKeyAlgorithmTags.AES_256);
        }
        PGPKeyPair pgpKeyPair = new JcaPGPKeyPair(PublicKeyPacket.VERSION_4, publicKeyID,
            parameters, kpGen.generateKeyPair(), new Date());

        JcePublicKeyKeyEncryptionMethodGenerator methodGenerator = new JcePublicKeyKeyEncryptionMethodGenerator(pgpKeyPair.getPublicKey());

        JcePGPDataEncryptorBuilder v6 = new JcePGPDataEncryptorBuilder(symAlgId).setUseV6AEAD().setWithAEAD(AEADAlgorithmTags.OCB, 8);
        byte[] sessionKey = PGPUtil.makeRandomKey(symAlgId, new SecureRandom());
        PublicKeyEncSessionPacket packet = (PublicKeyEncSessionPacket)methodGenerator.generate(v6, sessionKey);
        PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder().build(pgpKeyPair.getPrivateKey());
        byte[] data = decryptorFactory.recoverSessionData(publicKeyID, packet.getEncSessionKey(), PublicKeyEncSessionPacket.VERSION_6);
        if (publicKeyID == PublicKeyAlgorithmTags.X448 || publicKeyID == PublicKeyAlgorithmTags.X25519)
        {
            isTrue(Arrays.areEqual(sessionKey, data));
        }
        else
        {
            isTrue(Arrays.areEqual(sessionKey, Arrays.copyOfRange(data, 0, data.length - 2)));
        }
    }

    private void v4PBEKeyEncryptionMethodGenerator()
        throws Exception
    {
        int symAlgId = SymmetricKeyAlgorithmTags.CAMELLIA_128;

        JcePBEKeyEncryptionMethodGenerator methodGenerator = new JcePBEKeyEncryptionMethodGenerator("password".toCharArray());
        byte[] sessionKey = PGPUtil.makeRandomKey(symAlgId, new SecureRandom());
        JcePGPDataEncryptorBuilder v4 = new JcePGPDataEncryptorBuilder(symAlgId);
        SymmetricKeyEncSessionPacket packet = (SymmetricKeyEncSessionPacket)methodGenerator.generate(v4, sessionKey);
        PBEDataDecryptorFactory pbeDataDecryptorFactory = new JcePBEDataDecryptorFactoryBuilder().build("password".toCharArray());
        byte[] key = pbeDataDecryptorFactory.makeKeyFromPassPhrase(packet.getEncAlgorithm(), packet.getS2K());
        byte[] data = pbeDataDecryptorFactory.recoverSessionData(packet.getEncAlgorithm(), key, packet.getSecKeyData());
        isTrue(Arrays.areEqual(sessionKey, Arrays.copyOfRange(data, 1, data.length)));
    }

    private void v5PBEKeyEncryptionMethodGenerator()
        throws Exception
    {
        int symAlgId = SymmetricKeyAlgorithmTags.CAMELLIA_128;
        JcePBEKeyEncryptionMethodGenerator methodGenerator = new JcePBEKeyEncryptionMethodGenerator("password".toCharArray());
        byte[] sessionKey = PGPUtil.makeRandomKey(symAlgId, new SecureRandom());
        JcePGPDataEncryptorBuilder v5 = new JcePGPDataEncryptorBuilder(symAlgId).setUseV5AEAD().setWithAEAD(AEADAlgorithmTags.OCB, 10);
        SymmetricKeyEncSessionPacket packet = (SymmetricKeyEncSessionPacket)methodGenerator.generate(v5, sessionKey);
        PBEDataDecryptorFactory pbeDataDecryptorFactory = new JcePBEDataDecryptorFactoryBuilder().build("password".toCharArray());
        byte[] key = pbeDataDecryptorFactory.makeKeyFromPassPhrase(packet.getEncAlgorithm(), packet.getS2K());
        byte[] data = pbeDataDecryptorFactory.recoverAEADEncryptedSessionData(packet, key);
        isTrue(Arrays.areEqual(sessionKey, data));
    }

    private void v6PBEKeyEncryptionMethodGenerator()
        throws Exception
    {
        int symAlgId = SymmetricKeyAlgorithmTags.CAMELLIA_128;

        JcePBEKeyEncryptionMethodGenerator methodGenerator = new JcePBEKeyEncryptionMethodGenerator("password".toCharArray());
        byte[] sessionKey = PGPUtil.makeRandomKey(symAlgId, new SecureRandom());
        JcePGPDataEncryptorBuilder v6 = new JcePGPDataEncryptorBuilder(symAlgId).setUseV6AEAD().setWithAEAD(AEADAlgorithmTags.OCB, 10);
        SymmetricKeyEncSessionPacket packet = (SymmetricKeyEncSessionPacket)methodGenerator.generate(v6, sessionKey);
        PBEDataDecryptorFactory pbeDataDecryptorFactory = new JcePBEDataDecryptorFactoryBuilder().build("password".toCharArray());
        byte[] key = pbeDataDecryptorFactory.makeKeyFromPassPhrase(packet.getEncAlgorithm(), packet.getS2K());
        byte[] data = pbeDataDecryptorFactory.recoverAEADEncryptedSessionData(packet, key);
        isTrue(Arrays.areEqual(sessionKey, data));
    }
    private void testStandardDigests()
        throws Exception
    {
        PGPDigestCalculatorProvider digCalcBldr =
            new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build();

        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.MD5), Hex.decode("900150983cd24fb0d6963f7d28e17f72"));
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.SHA1), Hex.decode("a9993e364706816aba3e25717850c26c9cd0d89d"));
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.RIPEMD160), Hex.decode("8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"));
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.SHA256), Hex.decode("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"));
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.SHA384), Hex.decode("cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"));
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.SHA512), Hex.decode("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"));
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.SHA224), Hex.decode("23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7"));
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.SHA3_256), Hex.decode("3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"));
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.SHA3_512), Hex.decode("b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"));
    }

    private void testDigestCalc(PGPDigestCalculator digCalc, byte[] expected)
        throws IOException
    {
        OutputStream dOut = digCalc.getOutputStream();

        dOut.write(Strings.toByteArray("abc"));

        dOut.close();

        byte[] res = digCalc.getDigest();

        isTrue(Arrays.areEqual(res, expected));
    }

    public void testJcaKeyFingerprintCalculator()
        throws Exception
    {
        final JcaKeyFingerprintCalculator calculator = new JcaKeyFingerprintCalculator().setProvider(new NullProvider());
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
        kpGen.initialize(1024);
        KeyPair kp = kpGen.generateKeyPair();

        JcaPGPKeyConverter converter = new JcaPGPKeyConverter().setProvider(new BouncyCastleProvider());
        final PGPPublicKey pubKey = converter.getPGPPublicKey(PublicKeyAlgorithmTags.RSA_GENERAL, kp.getPublic(), new Date());

        testException("can't find MD5", "PGPException", new TestExceptionOperation()
        {
            @Override
            public void operation()
                throws Exception
            {
                calculator.calculateFingerprint(new PublicKeyPacket(3, PublicKeyAlgorithmTags.RSA_GENERAL, new Date(), pubKey.getPublicKeyPacket().getKey()));
            }
        });
        testException("can't find SHA1", "PGPException", new TestExceptionOperation()
        {
            @Override
            public void operation()
                throws Exception
            {
                calculator.calculateFingerprint(new PublicKeyPacket(4, PublicKeyAlgorithmTags.RSA_GENERAL, new Date(), pubKey.getPublicKeyPacket().getKey()));
            }
        });
        testException("can't find SHA-256", "PGPException", new TestExceptionOperation()
        {
            @Override
            public void operation()
                throws Exception
            {
                calculator.calculateFingerprint(new PublicKeyPacket(6, PublicKeyAlgorithmTags.RSA_GENERAL, new Date(), pubKey.getPublicKeyPacket().getKey()));
            }
        });
        //JcaKeyFingerprintCalculator calculator2 = new JcaKeyFingerprintCalculator().setProvider("BC");
        JcaKeyFingerprintCalculator calculator2 = calculator.setProvider("BC");
        PublicKeyPacket pubKeyPacket = new PublicKeyPacket(6, PublicKeyAlgorithmTags.RSA_GENERAL, new Date(), pubKey.getPublicKeyPacket().getKey());
        byte[] output = calculator2.calculateFingerprint(new PublicKeyPacket(6, PublicKeyAlgorithmTags.RSA_GENERAL, new Date(), pubKey.getPublicKeyPacket().getKey()));
        byte[] kBytes = pubKeyPacket.getEncodedContents();
        SHA256Digest digest = new SHA256Digest();

        digest.update((byte)0x9b);

        digest.update((byte)(kBytes.length >> 24));
        digest.update((byte)(kBytes.length >> 16));
        digest.update((byte)(kBytes.length >> 8));
        digest.update((byte)kBytes.length);

        digest.update(kBytes, 0, kBytes.length);
        byte[] digBuf = new byte[digest.getDigestSize()];

        digest.doFinal(digBuf, 0);
        isTrue(areEqual(output, digBuf));

        testException("Unsupported PGP key version: ", "UnsupportedPacketVersionException", new TestExceptionOperation()
        {
            @Override
            public void operation()
                throws Exception
            {
                calculator.calculateFingerprint(new PublicKeyPacket(7, PublicKeyAlgorithmTags.RSA_GENERAL, new Date(), pubKey.getPublicKeyPacket().getKey()));
            }
        });
    }

    public void testJcePGPDataEncryptorBuilder()
        throws Exception
    {
        testException("null cipher specified", "IllegalArgumentException", new TestExceptionOperation()
        {
            @Override
            public void operation()
                throws Exception
            {
                new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.NULL);
            }
        });

        //testException("AEAD algorithms can only be used with AES", "IllegalStateException", () -> new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.IDEA).setWithAEAD(AEADAlgorithmTags.OCB, 6));

        testException("minimum chunkSize is 6", "IllegalArgumentException", new TestExceptionOperation()
        {
            @Override
            public void operation()
                throws Exception
            {
                new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256).setWithAEAD(AEADAlgorithmTags.OCB, 5);
            }
        });

        isEquals(16, new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256).setProvider(new BouncyCastleProvider()).setWithAEAD(AEADAlgorithmTags.OCB, 6).build(new byte[32]).getBlockSize());
    }

    public void testJcaPGPDigestCalculatorProviderBuilder()
        throws Exception
    {

        PGPDigestCalculatorProvider digCalcBldr = new JcaPGPDigestCalculatorProviderBuilder().setProvider(new NonDashProvider()).build();
        testDigestCalc(digCalcBldr.get(HashAlgorithmTags.SHA256), Hex.decode("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"));

        PGPDigestCalculatorProvider digCalcBldr2 = new JcaPGPDigestCalculatorProviderBuilder().setProvider(new DashProvider()).build();
        testDigestCalc(digCalcBldr2.get(HashAlgorithmTags.SHA256), Hex.decode("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"));

        PGPDigestCalculatorProvider digCalcBldr3 = new JcaPGPDigestCalculatorProviderBuilder().setProvider(new NonDashProvider()).build();
        testDigestCalc(digCalcBldr3.get(HashAlgorithmTags.SHA1), Hex.decode("a9993e364706816aba3e25717850c26c9cd0d89d"));

        PGPDigestCalculatorProvider digCalcBldr4 = new JcaPGPDigestCalculatorProviderBuilder().setProvider(new DashProvider()).build();
        testDigestCalc(digCalcBldr4.get(HashAlgorithmTags.SHA1), Hex.decode("a9993e364706816aba3e25717850c26c9cd0d89d"));


        final PGPDigestCalculatorProvider provider = new JcaPGPDigestCalculatorProviderBuilder().setProvider(new NullProvider()).build();
        testException("exception on setup: ", "PGPException", new TestExceptionOperation()
        {
            @Override
            public void operation()
                throws Exception
            {
                provider.get(SymmetricKeyAlgorithmTags.AES_256);
            }
        });
    }

    public void testJcaPGPContentVerifierBuilderProvider()
        throws Exception
    {
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
        kpGen.initialize(1024);
        KeyPair kp = kpGen.generateKeyPair();

        JcaPGPKeyConverter converter = new JcaPGPKeyConverter().setProvider(new BouncyCastleProvider());
        final PGPPublicKey pubKey = converter.getPGPPublicKey(PublicKeyAlgorithmTags.RSA_GENERAL, kp.getPublic(), new Date());
        PGPContentVerifier verifier = new JcaPGPContentVerifierBuilderProvider().setProvider(new BouncyCastleProvider()).get(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA256).build(pubKey);
        isTrue(verifier.getHashAlgorithm() == HashAlgorithmTags.SHA256);
        isTrue(verifier.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL);
        isTrue(verifier.getKeyID() == pubKey.getKeyID());
    }

    public void testJcePBESecretKeyEncryptorBuilder()
        throws Exception
    {
        final PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
        testException("s2KCount value outside of range 0 to 255.", "IllegalArgumentException", new TestExceptionOperation()
        {
            @Override
            public void operation()
                throws Exception
            {
                new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha1Calc, -1);
            }
        });
    }

    public void testCreateDigest()
        throws Exception
    {
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1).getAlgorithm(), HashAlgorithmTags.SHA1);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.MD2).getAlgorithm(), HashAlgorithmTags.MD2);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.MD5).getAlgorithm(), HashAlgorithmTags.MD5);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.RIPEMD160).getAlgorithm(), HashAlgorithmTags.RIPEMD160);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA256).getAlgorithm(), HashAlgorithmTags.SHA256);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA224).getAlgorithm(), HashAlgorithmTags.SHA224);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA384).getAlgorithm(), HashAlgorithmTags.SHA384);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA512).getAlgorithm(), HashAlgorithmTags.SHA512);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA3_256).getAlgorithm(), HashAlgorithmTags.SHA3_256);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA3_224).getAlgorithm(), HashAlgorithmTags.SHA3_224);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA3_384).getAlgorithm(), HashAlgorithmTags.SHA3_384);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA3_512).getAlgorithm(), HashAlgorithmTags.SHA3_512);
        isEquals(new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.TIGER_192).getAlgorithm(), HashAlgorithmTags.TIGER_192);
    }

    public void testX25519HKDF()
        throws Exception
    {
        byte[] ephmeralKey = Hex.decode("87cf18d5f1b53f817cce5a004cf393cc8958bddc065f25f84af509b17dd36764");
        byte[] ephmeralSecretKey = Hex.decode("af1e43c0d123efe893a7d4d390f3a761e3fac33dfc7f3edaa830c9011352c779");
        byte[] publicKey = Hex.decode("8693248367f9e5015db922f8f48095dda784987f2d5985b12fbad16caf5e4435");
        byte[] expectedHKDF = Hex.decode("f66dadcff64592239b254539b64ff607");
        byte[] keyEnc = Hex.decode("dea355437956617901e06957fbca8a6a47a5b5153e8d3ab7");
        byte[] expectedDecryptedSessionKey = Hex.decode("dd708f6fa1ed65114d68d2343e7c2f1d");
        X25519PrivateKeyParameters ephmeralprivateKeyParameters = new X25519PrivateKeyParameters(ephmeralSecretKey);
        X25519PublicKeyParameters publicKeyParameters = new X25519PublicKeyParameters(publicKey);
        KeyFactory keyfact = KeyFactory.getInstance("X25519", "BC");
        PrivateKey privKey = keyfact.generatePrivate(new PKCS8EncodedKeySpec(PrivateKeyInfoFactory.createPrivateKeyInfo(ephmeralprivateKeyParameters).getEncoded()));
        PublicKey pubKey = keyfact.generatePublic(new X509EncodedKeySpec(SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKeyParameters).getEncoded()));
        KeyAgreement agreement = KeyAgreement.getInstance("X25519withSHA256HKDF", "BC");
        agreement.init(privKey, new HybridValueParameterSpec(Arrays.concatenate(ephmeralKey, publicKey), true, new UserKeyingMaterialSpec(Strings.toByteArray("OpenPGP X25519"))));
        agreement.doPhase(pubKey, true);
        Key secretKey = agreement.generateSecret(NISTObjectIdentifiers.id_aes128_wrap.getId());

//        agreement.init(ephmeralprivateKeyParameters);
//        byte[] secret = new byte[agreement.getAgreementSize()];
//        agreement.calculateAgreement(publicKeyParameters, secret, 0);
        byte[] output2 = secretKey.getEncoded();

//        HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest());
//        hkdf.init(new HKDFParameters(Arrays.concatenate(ephmeralKey, publicKey, secret), null, "OpenPGP X25519".getBytes()));
//        hkdf.generateBytes(output2, 0, 16);
//
        isTrue("hkdf failed", Arrays.areEqual(output2, expectedHKDF));
//        Wrapper c = new RFC3394WrapEngine(AESEngine.newInstance());
//        c.init(false, new KeyParameter(output2));
//        byte[] output = c.unwrap(keyEnc, 0, keyEnc.length);
        //isTrue(Arrays.areEqual(output, expectedDecryptedSessionKey));
    }

    public void testJcaAEADSecretKeyEncryptorBuilder()
        throws Exception
    {
        BouncyCastleProvider prov = new BouncyCastleProvider();
        KeyPairGenerator eddsaGen = KeyPairGenerator.getInstance("EdDSA", prov);
        Date creationTime = new Date();
        eddsaGen.initialize(new ECNamedCurveGenParameterSpec("ed25519"));
        KeyPair kp = eddsaGen.generateKeyPair();
        SecureRandom random = new SecureRandom();
        for (int version : new int[]{PublicKeyPacket.VERSION_4, PublicKeyPacket.VERSION_6})
        {
            PGPKeyPair keyPair = new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.Ed25519, kp, creationTime);
            JcaAEADSecretKeyEncryptorBuilder jcaEncBuilder = new JcaAEADSecretKeyEncryptorBuilder(
                AEADAlgorithmTags.OCB, SymmetricKeyAlgorithmTags.AES_256,
                S2K.Argon2Params.memoryConstrainedParameters())
                .setProvider(new BouncyCastleProvider());
            PBESecretKeyEncryptor encryptor = jcaEncBuilder.build(
                "Yin".toCharArray(),
                keyPair.getPublicKey().getPublicKeyPacket());
            byte[] key = new byte[16];
            random.nextBytes(key);
            byte[] input1 = new byte[64];
            random.nextBytes(input1);

            byte[] input2 = Arrays.copyOfRange(input1, 32, 64);
            byte[] output1 = encryptor.encryptKeyData(key, input1, 32, 32);
            byte[] output2 = encryptor.encryptKeyData(key, input2, 0, 32);
            isTrue(Arrays.areEqual(output1, output2));
        }
    }

    private class NullProvider
        extends Provider
    {
        NullProvider()
        {
            super("NULL", 0.0, "Null Provider");
        }
    }

    private class NonDashProvider
        extends Provider
    {
        NonDashProvider()
        {
            super("NonDash", 0.0, "NonDash Provider");
            putService(new Provider.Service(this, "MessageDigest", "SHA256", "org.bouncycastle.openpgp.test.SHA256", null, null));
            putService(new Provider.Service(this, "MessageDigest", "SHA1", "org.bouncycastle.openpgp.test.SHA1", null, null));
        }
    }

    private class DashProvider
        extends Provider
    {
        DashProvider()
        {
            super("Dash", 0.0, "Dash Provider");
            putService(new Service(this, "MessageDigest", "SHA-256", "org.bouncycastle.openpgp.test.SHA256", null, null));
            putService(new Service(this, "MessageDigest", "SHA-1", "org.bouncycastle.openpgp.test.SHA1", null, null));
        }
    }

}
