org.cesecore.keys.token.p11.Pkcs11SlotLabel.java Source code

Java tutorial

Introduction

Here is the source code for org.cesecore.keys.token.p11.Pkcs11SlotLabel.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA Community: The OpenSource Certificate Authority                *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.cesecore.keys.token.p11;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Hex;
import org.cesecore.config.CesecoreConfiguration;
import org.cesecore.keys.token.p11.exception.NoSuchSlotException;
import org.cesecore.util.FileTools;

/**
 * @version $Id: Pkcs11SlotLabel.java 20200 2014-11-06 10:04:43Z mikekushner $
 *
 * Object for handling a PKCS#11 Slot Label.
 *
 */
public class Pkcs11SlotLabel {

    /** The name of Suns pkcs11 implementation */
    public static final String SUN_PKCS11_CLASS = "sun.security.pkcs11.SunPKCS11";
    public static final String IAIK_PKCS11_CLASS = "iaik.pkcs.pkcs11.provider.IAIKPkcs11";
    public static final String IAIK_JCEPROVIDER_CLASS = "iaik.security.provider.IAIK";

    private static final Logger log = Logger.getLogger(Pkcs11SlotLabel.class);

    private final static String DELIMETER = ":";
    private final Pkcs11SlotLabelType type;
    private final String value;

    /**
     * Use explicit values.
     * @param type
     * @param value
     */
    public Pkcs11SlotLabel(final Pkcs11SlotLabelType type, final String value) {
        if (type == null) {
            throw new IllegalArgumentException("Type can not be null");
        }
        this.type = type;
        this.value = value == null ? null : value.trim();
    }

    /**
     * Get a string that later could be used to create a new object with {@link Pkcs11SlotLabel#PKCS11Slot(String)}.
     * Use it when you want to store a reference to the slot.
     * @return the string.
     */
    public String getTaggedString() {
        return this.type.name() + DELIMETER + this.value;
    }

    @Override
    public String toString() {
        return "Slot type: '" + this.type + "'. Slot value: '" + this.value + "'.";
    }

    /**
     * Get provider for the slot.
     * @param fileName path name to the P11 module so file or sun config file (only in the case of {@link #type}=={@link Pkcs11SlotLabelType#SUN_FILE})
     * @param attributesFile Path to file with P11 attributes to be used when generating keys with the provider. If null a good default will be used.
     * @param privateKeyLabel Label that will be set to all private keys generated by the provider. If null no label will be set.
     * @return the provider, or null if none is available.
     * @throws NoSuchSlotException if no slot as defined by this slot label was found
     * 
     */
    public Provider getProvider(final String fileName, final String attributesFile, final String privateKeyLabel)
            throws NoSuchSlotException {
        if (StringUtils.isEmpty(fileName)) {
            throw new IllegalArgumentException("A file name must be supplied.");
        }
        final File libFile = new File(fileName);
        if (!libFile.isFile() || !libFile.canRead()) {
            throw new IllegalArgumentException("The file " + fileName + " can't be read.");
        }
        // We will construct the PKCS11 provider (sun.security..., or iaik...) using reflection, because
        // the sun class does not exist on all platforms in jdk5, and we want to be able to compile everything.

        final long slot;
        log.debug("slot spec: " + this.toString());
        final Pkcs11Wrapper p11 = Pkcs11Wrapper.getInstance(libFile); // must be called before any provider is created for libFile
        switch (this.type) {
        case SLOT_LABEL:
            slot = getSlotID(this.value, p11);
            if (slot < 0) {
                throw new IllegalStateException("Token label '" + this.value + "' not found.");
            }
            break;
        case SLOT_NUMBER:
            slot = Long.parseLong(this.value);
            break;
        case SLOT_INDEX:
            //Be generous and allow numbers to act as indexes as well
            slot = Long.parseLong((this.value.charAt(0) == 'i' ? this.value.substring(1) : this.value));
            break;
        case SUN_FILE:
            FileInputStream fileInputStream;
            try {
                fileInputStream = new FileInputStream(libFile);
            } catch (FileNotFoundException e) {
                throw new IllegalArgumentException("File " + libFile + " was not found.");
            }
            return getSunP11Provider(fileInputStream);
        default:
            throw new IllegalStateException("This should not ever happen if all type of slots are tested.");
        }
        {// We will first try to construct the more competent IAIK provider, if it exists in the classpath
            final Provider prov = getIAIKP11Provider(slot, libFile, this.type);
            if (prov != null) {
                return prov;
            }
        }
        {// if that does not exist, we will revert back to use the SUN provider
            final Provider prov = getSunP11Provider(
                    getSunP11ProviderInputStream(slot, libFile, this.type, attributesFile, privateKeyLabel));
            if (prov != null) {
                return prov;
            }
        }
        log.error("No provider available.");
        return null;
    }

    /** @return a List of "slotId;tokenLabel" in the (indexed) order we get the from the P11 */
    public static List<String> getExtendedTokenLabels(final File libFile) {
        final List<String> tokenLabels = new ArrayList<String>();
        final Pkcs11Wrapper p11 = Pkcs11Wrapper.getInstance(libFile);
        final long slots[] = p11.getSlotList();
        if (log.isDebugEnabled()) {
            log.debug("Found numer of slots:\t" + slots.length);
        }
        for (int i = 0; i < slots.length; i++) {
            final long slotID = slots[i];
            final char label[] = p11.getTokenLabel(slotID);
            if (label == null) {
                continue;
            }
            final String tokenLabel = new String(label);
            if (log.isDebugEnabled()) {
                log.debug(i + ": Found token label:\t" + tokenLabel + "\tid=" + slotID);
            }
            tokenLabels.add(slotID + ";" + tokenLabel.trim());
        }
        return tokenLabels;
    }

    /**
     * Get slot ID for a token label.
     * @param tokenLabel the label.
     * @param object to get slot list and labels for all slots with tokens
     * @return the slot ID.
     * @throws NoSuchSlotException if no slot as defined by tokenLabel was found
        
     */
    private static long getSlotID(final String tokenLabel, final Pkcs11Wrapper p11) throws NoSuchSlotException {
        final long slots[] = p11.getSlotList();
        if (log.isDebugEnabled()) {
            log.debug("Searching for token label:\t" + tokenLabel);
        }

        for (final long slotID : slots) {
            final char label[] = p11.getTokenLabel(slotID);
            if (label == null) {
                continue;
            }
            final String candidateTokenLabel = new String(label);
            if (log.isDebugEnabled()) {
                log.debug("Candidate token label:\t" + candidateTokenLabel);
            }
            if (!tokenLabel.equals(candidateTokenLabel.trim())) {
                continue;
            }
            if (log.isDebugEnabled()) {
                log.debug("Label '" + tokenLabel + "' found. The slot ID is:\t" + slotID);
            }
            return slotID;
        }
        throw new NoSuchSlotException("Token label '" + tokenLabel + "' not found.");
    }

    /**
     * Get the IAIK provider.
     * @param slot Slot list index or slot ID.
     * @param libFile P11 module so file.
     * @param isIndex true if first parameter is a slot list index, false if slot ID.
     * @return the provider
     */
    private static Provider getIAIKP11Provider(final long slot, final File libFile,
            final Pkcs11SlotLabelType type) {
        // Properties for the IAIK PKCS#11 provider
        final Properties prop = new Properties();
        try {
            prop.setProperty("PKCS11_NATIVE_MODULE", libFile.getCanonicalPath());
        } catch (IOException e) {
            throw new RuntimeException("Could for unknown reason not construct canonical filename.", e);
        }
        // If using Slot Index it is denoted by brackets in iaik
        prop.setProperty("SLOT_ID",
                type.equals(Pkcs11SlotLabelType.SLOT_INDEX) ? ("[" + slot + "]") : Long.toString(slot));
        if (log.isDebugEnabled()) {
            log.debug(prop.toString());
        }
        Provider ret = null;
        try {
            @SuppressWarnings("unchecked")
            final Class<? extends Provider> implClass = (Class<? extends Provider>) Class
                    .forName(IAIK_PKCS11_CLASS);
            log.info("Using IAIK PKCS11 provider: " + IAIK_PKCS11_CLASS);
            // iaik PKCS11 has Properties as constructor argument
            ret = implClass.getConstructor(Properties.class).newInstance(new Object[] { prop });
            // It's not enough just to add the p11 provider. Depending on algorithms we may have to install the IAIK JCE provider as well in order
            // to support algorithm delegation
            @SuppressWarnings("unchecked")
            final Class<? extends Provider> jceImplClass = (Class<? extends Provider>) Class
                    .forName(IAIK_JCEPROVIDER_CLASS);
            Provider iaikProvider = jceImplClass.getConstructor().newInstance();
            if (Security.getProvider(iaikProvider.getName()) == null) {
                log.info("Adding IAIK JCE provider for Delegation: " + IAIK_JCEPROVIDER_CLASS);
                Security.addProvider(iaikProvider);
            }
        } catch (InvocationTargetException e) {
            // NOPMD: Ignore, reflection related errors are handled elsewhere
        } catch (InstantiationException e) {
            // NOPMD: Ignore, reflection related errors are handled elsewhere
        } catch (IllegalAccessException e) {
            // NOPMD: Ignore, reflection related errors are handled elsewhere
        } catch (IllegalArgumentException e) {
            // NOPMD: Ignore, reflection related errors are handled elsewhere
        } catch (NoSuchMethodException e) {
            // NOPMD: Ignore, reflection related errors are handled elsewhere
        } catch (SecurityException e) {
            // NOPMD: Ignore, reflection related errors are handled elsewhere
        } catch (ClassNotFoundException e) {
            // NOPMD: Ignore, reflection related errors are handled elsewhere
        }
        return ret;
    }

    /**
     * Get an InputStream to be used to create the Sun provider.
     * @param slot Slot list index or slot ID.
     * @param libFile P11 module so file.
     * @param isIndex true if first parameter is a slot list index, false if slot ID.
     * @param attributesFile Path to file with P11 attributes to be used when generating keys with the provider. If null a good default will be used.
     * @param privateKeyLabel Label that will be set to all private keys generated by the provider. If null no label will be set.
     * @return the stream
     */
    private static InputStream getSunP11ProviderInputStream(final long slot, final File libFile,
            final Pkcs11SlotLabelType type, final String attributesFile, String privateKeyLabel) {

        // Properties for the SUN PKCS#11 provider
        final String sSlot = Long.toString(slot);
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final PrintWriter pw = new PrintWriter(baos);
        pw.println("name = " + libFile.getName() + "-slot" + sSlot);
        try {
            pw.println("library = " + libFile.getCanonicalPath());
        } catch (IOException e) {
            throw new RuntimeException("Could for unknown reason not construct canonical filename.", e);
        }
        if (sSlot != null) {
            pw.println("slot" + (type.isEqual(Pkcs11SlotLabelType.SLOT_INDEX) ? "ListIndex" : "") + " = " + sSlot);
        }
        if (attributesFile != null) {
            byte[] attrs;
            try {
                attrs = FileTools.readFiletoBuffer(attributesFile);
            } catch (FileNotFoundException e) {
                throw new IllegalArgumentException("File " + attributesFile + " was not found.", e);
            }
            pw.println(new String(attrs));
        } else {
            // setting the attributes like this should work for most HSMs.
            pw.println("attributes(*, CKO_PUBLIC_KEY, *) = {");
            pw.println("  CKA_TOKEN = false"); // Do not save public keys permanent in order to save space.
            pw.println("  CKA_ENCRYPT = true");
            pw.println("  CKA_VERIFY = true");
            pw.println("  CKA_WRAP = true");// no harm allowing wrapping of keys. created private keys can not be wrapped anyway since CKA_EXTRACTABLE
            // is false.
            pw.println("}");
            pw.println("attributes(*, CKO_PRIVATE_KEY, *) = {");
            pw.println("  CKA_TOKEN = true"); // all created private keys should be permanent. They should not only exist during the session.
            pw.println("  CKA_PRIVATE = true"); // always require logon with password to use the key
            pw.println("  CKA_SENSITIVE = true"); // not possible to read the key
            pw.println("  CKA_EXTRACTABLE = false"); // not possible to wrap the key with another key
            pw.println("  CKA_DECRYPT = true");
            pw.println("  CKA_SIGN = true");
            if (privateKeyLabel != null && privateKeyLabel.length() > 0) {
                pw.print("  CKA_LABEL = 0h");
                pw.println(new String(Hex.encode(privateKeyLabel.getBytes())));
            }
            pw.println("  CKA_UNWRAP = true");// for unwrapping of session keys,
            pw.println("}");
            if (CesecoreConfiguration.p11disableHashingSignMechanisms()) {
                pw.println("disabledMechanisms = {");
                // by disabling these mechanisms the hashing will be done in the application instead of the HSM.
                pw.println("  CKM_SHA1_RSA_PKCS");
                pw.println("  CKM_SHA256_RSA_PKCS");
                pw.println("  CKM_SHA384_RSA_PKCS");
                pw.println("  CKM_SHA512_RSA_PKCS");
                pw.println("  CKM_MD2_RSA_PKCS");
                pw.println("  CKM_MD5_RSA_PKCS");
                pw.println("  CKM_DSA_SHA1");
                pw.println("  CKM_ECDSA_SHA1");
                pw.println("}");
            }
            pw.println("attributes(*, CKO_SECRET_KEY, *) = {");
            pw.println("  CKA_SENSITIVE = true"); // not possible to read the key
            pw.println("  CKA_EXTRACTABLE = false"); // not possible to wrap the key with another key
            pw.println("  CKA_ENCRYPT = true");
            pw.println("  CKA_DECRYPT = true");
            pw.println("  CKA_SIGN = true");
            pw.println("  CKA_VERIFY = true");
            pw.println("  CKA_WRAP = true");// for unwrapping of session keys,
            pw.println("  CKA_UNWRAP = true");// for unwrapping of session keys,
            pw.println("}");
        }
        pw.flush();
        pw.close();
        if (log.isDebugEnabled()) {
            log.debug(baos.toString());
        }
        return new ByteArrayInputStream(baos.toByteArray());
    }

    /**
     * Get the provider without taking care of exceptions.
     * @param is InputStream for sun configuration file.
     * @return The Sun provider
     * @throws ClassNotFoundException
     * @throws IllegalArgumentException
     * @throws SecurityException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     */
    private static Provider getSunP11ProviderNoExceptionHandeling(final InputStream is)
            throws ClassNotFoundException, IllegalArgumentException, SecurityException, InstantiationException,
            IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        // Sun PKCS11 has InputStream as constructor argument
        @SuppressWarnings("unchecked")
        final Class<? extends Provider> implClass = (Class<? extends Provider>) Class.forName(SUN_PKCS11_CLASS);
        log.info("Using SUN PKCS11 provider: " + SUN_PKCS11_CLASS);
        return implClass.getConstructor(InputStream.class).newInstance(new Object[] { is });
    }

    /**
     * @param is InputStream for sun configuration file.
     * @return The Sun provider
     */
    private static Provider getSunP11Provider(final InputStream is) {
        try {
            return getSunP11ProviderNoExceptionHandeling(is);
        } catch (Exception e) {
            final String msg = "Error constructing pkcs11 provider: " + e.getMessage();
            log.error(msg);
            throw new IllegalStateException(msg, e);
        }
    }

    /**
     * Creating dummy provider just to have C_Initialize executed with multiple
     * thread argument. {@link Pkcs11Wrapper#getInstance(File)} should call it
     * before making any PKCS#11 calls. If not then C_Initialize will be called
     * with null argument causing multi threading to be disabled for the token.
     * 
     * There is a boolean in the sun code ensuring that C_Initialize is only
     * called once.
     * To check this implementation the p11 spy utils could be used. Check that
     * it is only one C_Initialize call and that null is not passed.
     * @param a file with the path of the p11 module on which C_Finalize should
     * be called.
     */
    static void doC_Initialize(final File libFile) {
        try {
            getSunP11ProviderNoExceptionHandeling(
                    getSunP11ProviderInputStream(-1, libFile, Pkcs11SlotLabelType.SLOT_NUMBER, null, null));
        } catch (InvocationTargetException e) {
            // the p11 module don't like the bogus arguments and throws an exception but we don't bother about this since
            // C_Initialize has already been called with multithread arguments.
            log.debug("Get dummy sun provider throws an exception. This is OK.", e);
        } catch (Exception e) {
            final String msg = "Error constructing pkcs11 provider: " + e.getMessage();
            log.error(msg);
            throw new IllegalStateException(msg, e);
        }
    }

    /**
     * Creates a SUN or IAIK PKCS#11 provider using the passed in pkcs11 library. First we try to see if the IAIK provider is available, because it
     * supports more algorithms. If the IAIK provider is not available in the classpath, we try the SUN provider.
     * 
     * @param sSlot
     *            The value of the slot, which may be a number ([0...9]*), an index i[0...9] or a label, but may also be labels matching the former.
     *            To solve this ambiguity, slots will be presumed to be numbers or indexes if the names match, and if no slot is found by that number
     *            or index will then be presumed to be labels (for legacy reasons). 
     * @param fileName
     *            the manufacturers provided pkcs11 library (.dll or .so) or config file name if slot is null
        
     * @param attributesFile
     *            a file specifying PKCS#11 attributes (used mainly for key generation) in the format specified in the
     *            "JavaTM PKCS#11 Reference Guide", http://java.sun.com/javase/6/docs/technotes/guides/security/p11guide.html
     * 
     *            Example contents of attributes file:
     * 
     *            attributes(generate,CKO_PRIVATE_KEY,*) = { CKA_PRIVATE = true CKA_SIGN = true CKA_DECRYPT = true CKA_TOKEN = true }
     * 
     *            See also html documentation for PKCS#11 HSMs in EJBCA.
     * 
     * @return AuthProvider of type "sun.security.pkcs11.SunPKCS11", or null if none is available
     * @throws NoSuchSlotException is no slot as defined by sSlot and slotLabelType was found
     */
    public static Provider getP11Provider(final String sSlot, Pkcs11SlotLabelType slotLabelType,
            final String fileName, final String attributesFile) throws NoSuchSlotException {
        return getP11Provider(sSlot, slotLabelType, fileName, attributesFile, null);
    }

    /**
     * Creates a SUN or IAIK PKCS#11 provider using the passed in pkcs11 library. First we try to see if the IAIK provider is available, because it
     * supports more algorithms. If the IAIK provider is not available in the classpath, we try the SUN provider.
     * 
     * @param sSlot
     *            The value of the slot, which may be a number ([0...9]*), an index i[0...9] or a label, but may also be labels matching the former.
     *            To solve this ambiguity, slots will be presumed to be numbers or indexes if the names match, and if no slot is found by that number
     *            or index will then be presumed to be labels (for legacy reasons). Can be null if slotLabelType is SUN_FILE, then the slot must be specified in the attributesFile.
     * @param fileName
     *            the manufacturers provided pkcs11 library (.dll or .so) or config file name if slot is null
        
     * @param attributesFile
     *            a file specifying PKCS#11 attributes (used mainly for key generation) in the format specified in the
     *            "JavaTM PKCS#11 Reference Guide", http://java.sun.com/javase/6/docs/technotes/guides/security/p11guide.html
     * 
     *            Example contents of attributes file:
     * 
     *            attributes(generate,CKO_PRIVATE_KEY,*) = { CKA_PRIVATE = true CKA_SIGN = true CKA_DECRYPT = true CKA_TOKEN = true }
     * 
     *            See also html documentation for PKCS#11 HSMs in EJBCA.
     * @param privateKeyLabel
     *            The private key label to be set to generated keys. null means no label.
     * 
     * @return AuthProvider of type "sun.security.pkcs11.SunPKCS11", or null if none is available
     * @throws NoSuchSlotException if no slot as defined by this label was found
     */
    public static Provider getP11Provider(final String sSlot, final Pkcs11SlotLabelType slotLabelType,
            final String fileName, final String attributesFile, final String privateKeyLabel)
            throws NoSuchSlotException {
        if ((sSlot == null || sSlot.length() < 1) && !slotLabelType.isEqual(Pkcs11SlotLabelType.SUN_FILE)) {
            return null;
        }
        final Pkcs11SlotLabel slotSpec = new Pkcs11SlotLabel(slotLabelType, sSlot);
        return slotSpec.getProvider(fileName, attributesFile, privateKeyLabel);
    }
}