package net.i2p.crypto.provider;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.Provider;
import java.security.Security;

/**
 *  @since 0.9.15
 */
public final class I2PProvider extends Provider {
    public static final String PROVIDER_NAME = "I2P";
    private static final String INFO = "I2P Security Provider v0.1, implementing" +
            "several algorithms used by I2P.";
    private static boolean _installed;

    /**
     * Construct a new provider.  This should only be required when
     * using runtime registration of the provider using the
     * <code>Security.addProvider()</code> mechanism.
     */
    public I2PProvider() {
        super(PROVIDER_NAME, 0.1, INFO);

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            @Override
            public Void run() {
                setup();
                return null;
            }
        });
    }

    private void setup() {
        // TODO: Implement SPIs for existing code
        // However -
        // quote
        // http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html
        //
        // If your provider is supplying encryption algorithms through the
        // Cipher, KeyAgreement, KeyGenerator, Mac, or SecretKeyFactory classes,
        // you will need to sign your JAR file so that the JCA can authenticate the code at runtime.
        // If you are NOT providing an implementation of this type you can skip this step.
        //
        //put("Cipher.AES", "net.i2p.crypto.provider.CipherSpi$aesCBC");
        //put("Cipher.ElGamal", "net.i2p.crypto.provider.CipherSpi$elGamal");
        //put("Mac.HmacMD5-I2P", "net.i2p.crypto.provider.MacSpi");

        put("MessageDigest.SHA-1", "net.i2p.crypto.SHA1");
        //put("Signature.SHA1withDSA", "net.i2p.crypto.provider.SignatureSpi");

        // EdDSA
        // Key OID: 1.3.101.100; Sig OID: 1.3.101.101
        put("KeyFactory.EdDSA", "net.i2p.crypto.eddsa.KeyFactory");
        put("KeyPairGenerator.EdDSA", "net.i2p.crypto.eddsa.KeyPairGenerator");
        put("Signature.SHA512withEdDSA", "net.i2p.crypto.eddsa.EdDSAEngine");
        // Didn't find much documentation on these at all,
        // see http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html
        // section "Mapping from OID to name"
        // without these, Certificate.verify() fails
        put("Alg.Alias.KeyFactory.1.3.101.100", "EdDSA");
        put("Alg.Alias.KeyFactory.OID.1.3.101.100", "EdDSA");
        // Without these, keytool fails with:
        // keytool error: java.security.NoSuchAlgorithmException: unrecognized algorithm name: SHA512withEdDSA
        put("Alg.Alias.KeyPairGenerator.1.3.101.100", "EdDSA");
        put("Alg.Alias.KeyPairGenerator.OID.1.3.101.100", "EdDSA");
        // with this setting, keytool keygen doesn't work
        // java.security.cert.CertificateException: Signature algorithm mismatch
        // it must match the key setting (1.3.101.100) to work
        // but this works fine with programmatic cert generation
        put("Alg.Alias.Signature.1.3.101.101", "SHA512withEdDSA");
        put("Alg.Alias.Signature.OID.1.3.101.101", "SHA512withEdDSA");
        // TODO Ed25519ph
        // OID: 1.3.101.101

        // ElGamal
        // OID: 1.3.14.7.2.1.1
        put("KeyFactory.DH", "net.i2p.crypto.elgamal.KeyFactory");
        put("KeyFactory.DiffieHellman", "net.i2p.crypto.elgamal.KeyFactory");
        put("KeyFactory.ElGamal", "net.i2p.crypto.elgamal.KeyFactory");
        put("KeyPairGenerator.DH", "net.i2p.crypto.elgamal.KeyPairGenerator");
        put("KeyPairGenerator.DiffieHellman", "net.i2p.crypto.elgamal.KeyPairGenerator");
        put("KeyPairGenerator.ElGamal", "net.i2p.crypto.elgamal.KeyPairGenerator");
        put("Signature.SHA256withElGamal", "net.i2p.crypto.elgamal.ElGamalSigEngine");
        put("Alg.Alias.KeyFactory.1.3.14.7.2.1.1", "ElGamal");
        put("Alg.Alias.KeyFactory.OID.1.3.14.7.2.1.1", "ElGamal");
        put("Alg.Alias.KeyPairGenerator.1.3.14.7.2.1.1", "ElGamal");
        put("Alg.Alias.KeyPairGenerator.OID.1.3.14.7.2.1.1", "ElGamal");
        put("Alg.Alias.Signature.1.3.14.7.2.1.1", "SHA256withElGamal");
        put("Alg.Alias.Signature.OID.1.3.14.7.2.1.1", "SHA256withElGamal");
    }

    /**
     *  Install the I2PProvider.
     *  Harmless to call multiple times.
     *  @since 0.9.25
     */
    public static void addProvider() {
        synchronized(I2PProvider.class) {
            if (!_installed) {
                try {
                    Provider us = new I2PProvider();
                    // put ours ahead of BC, if installed, because our ElGamal
                    // implementation may not be fully compatible with BC
                    Provider[] provs = Security.getProviders();
                    for (int i = 0; i < provs.length; i++) {
                        if (provs[i].getName().equals("BC")) {
                            Security.insertProviderAt(us, i);
                            _installed = true;
                            return;
                        }
                    }
                    Security.addProvider(us);
                    _installed = true;
                } catch (SecurityException se) {
                    System.out.println("WARN: Could not install I2P provider: " + se);
                }
            }
        }
    }
}
