/**
 * 
 */
package test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;

import sun.security.ec.ECPublicKeyImpl;
import sun.security.pkcs11.SunPKCS11;

/**
 * @author lars
 *
 */
public class Main {

    private static Provider getP11Provider(final String fileName, final String slot,
                                           final boolean isIndex) throws IOException {
        if (fileName==null || fileName.trim().isEmpty()) {
            throw new IOException("A file name must be supplied.");
        }
        final File libFile = new File(fileName);
        if ( !libFile.isFile() || !libFile.canRead() ) {
            throw new IOException("The file "+fileName+" can't be read.");
        }
        if ( slot==null )
            return new SunPKCS11(new FileInputStream(fileName));

//      Properties for the SUN PKCS#11 provider
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintWriter pw = new PrintWriter(baos);
        pw.println("name = "+libFile.getName()+"-slot"+slot);
        pw.println("library = "+libFile.getCanonicalPath());

        final int slotNr;
        try {
            if (slot.length()>0)
                slotNr = Integer.parseInt(slot);
            else
                slotNr = -1;
        } catch( NumberFormatException e ) {
            throw new IOException("Slot nr "+slot+" not an integer.");
        }
        if ( slotNr>=0 ) {
            pw.println("slot"+(isIndex ? "ListIndex":"")+" = "+slot);           
        }
        pw.flush();
        pw.close();
        return new SunPKCS11(new ByteArrayInputStream(baos.toByteArray()));
    }
    private static byte[] sign(Provider provider, PrivateKey privateKey, byte signInput[], String signAlgName) throws Exception {
        Signature signature = Signature.getInstance(signAlgName, provider);
        signature.initSign( privateKey );
        signature.update( signInput );
        return signature.sign();
    }
    private static boolean verify(byte signInput[], byte signature[], PublicKey publicKey, String signAlgName, Provider swProvider) throws Exception {

        Signature verifySignature = Signature.getInstance(signAlgName, swProvider);
        verifySignature.initVerify(publicKey);
        verifySignature.update(signInput);
        return verifySignature.verify(signature);
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        final String slotID;
        final boolean isIndex;
        if ( args.length<2 ) {
            System.out.println("<file name of shared lib p11 module or if no more args the sun config file> <password for slot> [<slot number. start with 'i' to indicate index in list>] [<provider name for verification. if not specified no verification is done>] [<curve name>]");
            System.out.println("Example: java -cp $TEST_HOME/bin:$JAVA_LIBS/bcprov-jdk16-140.jar test.Main $P11_LIBS/libcs2_pkcs11-32-1.4.6.so user1 i1 org.bouncycastle.jce.provider.BouncyCastleProvider");
            System.out.println("You may download bc from http://www.bouncycastle.org/latest_releases.html");
            System.out.println("Use the p11 module from your HSM vendor.");
            return;
        }
        final String fileName = args[0];
        final String storePassword = args[1];
        final String storeID = args.length>2 ? args[2] : null;
        final String providerClassName = args.length>3 ? args[3] : null;
        final String curveName = args.length>4 ? args[4] : null;
        {
            final char firstChar = storeID!=null && storeID.length()>0 ? storeID.charAt(0) : '\0';
            if ( storeID!=null && (firstChar=='i'||firstChar=='I') ) {
                slotID = storeID.substring(1);
                isIndex = true;
            } else {
                slotID = storeID;
                isIndex = false;
            }
        }
        try {
            final Provider provider = getP11Provider(fileName, slotID, isIndex);
            final KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS11", provider,
                                                                          new KeyStore.PasswordProtection(storePassword.toCharArray()));
            // get keystore just to force autentication
            final KeyStore keyStore = builder.getKeyStore();
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", provider);
            if ( curveName!=null )
                kpg.initialize(new ECGenParameterSpec(curveName));
            final KeyPair keyPair = kpg.generateKeyPair();
            final ECPublicKey ecPublicKey = (ECPublicKey)keyPair.getPublic();
            // ecPublicKey.getW() will fail if bug not corrected
            final PublicKey publicKey = new ECPublicKeyImpl(ecPublicKey.getW(), ecPublicKey.getParams());
            final PrivateKey privateKey = keyPair.getPrivate();
            final String signAlgName = "SHA1withECDSA";
            final byte signInput[] = "Lillan gick på vägen ut.".getBytes();
            final byte signature[] = sign(provider, privateKey, signInput, signAlgName);

            if ( providerClassName==null ) {
                System.out.println("Not possible to test verification if no JCA provider for ECC available.");
                return;
            }
            final Provider swProvider = (Provider)Class.forName(providerClassName).newInstance();
            final boolean result = verify(signInput, signature, publicKey, signAlgName, swProvider);
            System.out.println("Signature is "+(result?"":"NOT ")+"verifying.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
