Java tutorial
/************************************************************************* * * * CESeCore: CE Security Core * * * * 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; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.KeyStore.PasswordProtection; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; import java.security.cert.CertificateException; import java.security.spec.AlgorithmParameterSpec; import java.util.Properties; import javax.security.auth.DestroyFailedException; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.cesecore.internal.InternalResources; import org.cesecore.keys.token.p11.P11Slot; import org.cesecore.keys.token.p11.P11SlotUser; import org.cesecore.keys.token.p11.Pkcs11SlotLabel; import org.cesecore.keys.token.p11.Pkcs11SlotLabelType; import org.cesecore.keys.token.p11.exception.NoSuchSlotException; import org.cesecore.keys.util.KeyStoreTools; /** * Class implementing a keystore on PKCS11 tokens. * * @version $Id: PKCS11CryptoToken.java 20895 2015-03-12 15:31:48Z jeklund $ */ public class PKCS11CryptoToken extends BaseCryptoToken implements P11SlotUser { private static final long serialVersionUID = 7719014139640717867L; /** Log4j instance */ private static final Logger log = Logger.getLogger(PKCS11CryptoToken.class); /** Internal localization of logs and errors */ private static final InternalResources intres = InternalResources.getInstance(); /** Keys, specific to PKCS#11, that can be defined in CA token properties */ public static final String SLOT_LABEL_VALUE = "slotLabelValue"; public static final String SLOT_LABEL_TYPE = "slotLabelType"; public static final String SHLIB_LABEL_KEY = "sharedLibrary"; public static final String ATTRIB_LABEL_KEY = "attributesFile"; public static final String PASSWORD_LABEL_KEY = "pin"; @Deprecated //Remove once upgrading from 5.0->6.0 is no longer supported public static final String SLOT_LIST_INDEX_KEY = "slotListIndex"; @Deprecated //Remove once upgrading from 5.0->6.0 is no longer supported public static final String SLOT_LABEL_KEY = "slot"; /** A user defined name of the slot provider. Used in order to be able to have two different providers * (with different PKCS#11 attributes) for the same slot. If this is not set (null), the default * java provider name is used (SunPKCS11-pkcs11LibName-slotNr for example SunPKCS11-libcryptoki.so-slot1). */ public final static String TOKEN_FRIENDLY_NAME = "tokenFriendlyName"; private transient P11Slot p11slot; private String sSlotLabel = null; /** * @param providerClass * @throws InstantiationException */ public PKCS11CryptoToken() throws InstantiationException { super(); try { Thread.currentThread().getContextClassLoader().loadClass(Pkcs11SlotLabel.SUN_PKCS11_CLASS); } catch (ClassNotFoundException t) { throw new InstantiationException( "PKCS11 provider class " + Pkcs11SlotLabel.SUN_PKCS11_CLASS + " not found."); } } @Override public void init(final Properties properties, final byte[] data, final int id) throws CryptoTokenOfflineException, NoSuchSlotException { // Don't autoactivate this right away, we must dynamically create the auth-provider with a slot setProperties(properties); init(properties, false, id); sSlotLabel = getSlotLabel(SLOT_LABEL_VALUE, properties); Pkcs11SlotLabelType type = Pkcs11SlotLabelType.getFromKey(getSlotLabel(SLOT_LABEL_TYPE, properties)); String sharedLibrary = properties.getProperty(PKCS11CryptoToken.SHLIB_LABEL_KEY); String attributesFile = properties.getProperty(PKCS11CryptoToken.ATTRIB_LABEL_KEY); String friendlyName = properties.getProperty(TOKEN_FRIENDLY_NAME); if (friendlyName != null) { p11slot = P11Slot.getInstance(friendlyName, sSlotLabel, sharedLibrary, type, attributesFile, this, id); } else { // getInstance will run autoActivate() p11slot = P11Slot.getInstance(sSlotLabel, sharedLibrary, type, attributesFile, this, id); } final Provider provider = p11slot.getProvider(); setJCAProvider(provider); } @Override public boolean isActive() { return getTokenStatus() == CryptoToken.STATUS_ACTIVE; } @Override public void activate(final char[] authCode) throws CryptoTokenOfflineException, CryptoTokenAuthenticationFailedException { if (this.p11slot == null) { throw new CryptoTokenOfflineException("Slot not initialized."); } try { final KeyStore keyStore = createKeyStore(authCode); setKeyStore(keyStore); } catch (Throwable t) { // NOPMD: when dealing with HSMs we need to catch everything log.warn("Failed to initialize PKCS11 provider slot '" + this.sSlotLabel + "'.", t); CryptoTokenAuthenticationFailedException authfe = new CryptoTokenAuthenticationFailedException( "Failed to initialize PKCS11 provider slot '" + this.sSlotLabel + "'."); authfe.initCause(t); throw authfe; } String msg = intres.getLocalizedMessage("token.activated", getId()); log.info(msg); } private KeyStore createKeyStore(final char[] authCode) throws NoSuchAlgorithmException, CertificateException, UnsupportedEncodingException, IOException, KeyStoreException { final PasswordProtection pwp = new PasswordProtection(authCode); Provider provider = this.p11slot.getProvider(); final KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS11", provider, pwp); final KeyStore keyStore = builder.getKeyStore(); log.debug("Loading key from slot '" + this.sSlotLabel + "' using pin."); // See ECA-1395 for an explanation of this special handling for the IAIK provider. // If the application uses several instances of the IAIKPkcs11 provider, it has two options to get an initialized key store. First, it can get // the initialized key store directly from the provider instance. This looks like this // KeyStore tokenKeyStore = pkcs11Provider_.getTokenManager().getKeyStore(); // where pkcs11Provider_ is the instance of the IAIKPkcs11 provider. Second, the application can instantiate the key store as usual and then // initialize it. For initialization, the application must provide the name of the instance that this key store shall operate with. Just // instantiating the key store is not enough, and if the application calls tokenKeyStore.load(null, null), it always(!) binds the key store to // the first instance of the IAIKPkcs11 provider. This is the case, because there is no means for the KeyStoreSPI class to get the instance of // the provider that was used to instantiate it. This means, it does not help to provide the provider name and calling // KeyStore.getInstance("PKCS11KeyStore", providerName), the call to the load(InputStream, char[]) method with appropriate arguments is // required nevertheless. The correct usage will look like this // KeyStore cardKeyStore = KeyStore.getInstance("PKCS11KeyStore"); // String providerName = pkcs11Provider_.getName(); // ByteArrayInputStream providerNameInpustStream = // new ByteArrayInputStream(providerName.getBytes("UTF-8")); // cardKeyStore.load(providerNameInpustStream, null); // The password parameter of the load method (this is the second parameter, which is null here) will be used if provided (i.e. if it is not // null). If it is null, the default login manager will use the configured method for prompting the PIN on demand. If the application just // provides the instance number as a string instead of the complete provider name, the key store will also accept it. if (provider.getClass().getName().equals(Pkcs11SlotLabel.IAIK_PKCS11_CLASS)) { keyStore.load(new ByteArrayInputStream(getSignProviderName().getBytes("UTF-8")), authCode); } else { // For the Sun provider this works fine to initialize the provider using previously provided protection parameters. keyStore.load(null, null); } try { pwp.destroy(); } catch (DestroyFailedException e) { // Log but otherwise ignore log.info("Detroy failed: ", e); } return keyStore; } @Override public void deactivate() { try { setKeyStore(null); } catch (KeyStoreException e) { // Exception should only be thrown if loading a non-null KeyStore fails throw new IllegalStateException("This should never happen."); } this.p11slot.logoutFromSlotIfNoTokensActive(); String msg = intres.getLocalizedMessage("token.deactivate", getId()); log.info(msg); } @Override public void reset() { if (this.p11slot != null) { this.p11slot.reset(); } } @Override public void deleteEntry(final String alias) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, CryptoTokenOfflineException { if (StringUtils.isNotEmpty(alias)) { KeyStoreTools cont = new KeyStoreTools(getKeyStore(), getSignProviderName()); cont.deleteEntry(alias); String msg = intres.getLocalizedMessage("token.deleteentry", alias, getId()); log.info(msg); } else { log.debug("Trying to delete keystore entry with empty alias."); } } @Override public void generateKeyPair(final String keySpec, final String alias) throws InvalidAlgorithmParameterException, CryptoTokenOfflineException { if (StringUtils.isNotEmpty(alias)) { KeyStoreTools cont = new KeyStoreTools(getKeyStore(), getSignProviderName()); cont.generateKeyPair(keySpec, alias); } else { log.debug("Trying to generate keys with empty alias."); } } @Override public void generateKeyPair(final AlgorithmParameterSpec spec, final String alias) throws InvalidAlgorithmParameterException, CertificateException, IOException, CryptoTokenOfflineException { if (StringUtils.isNotEmpty(alias)) { KeyStoreTools cont = new KeyStoreTools(getKeyStore(), getSignProviderName()); cont.generateKeyPair(spec, alias); } else { log.debug("Trying to generate keys with empty alias."); } } @Override public void generateKey(final String algorithm, final int keysize, final String alias) throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException, CryptoTokenOfflineException { if (log.isDebugEnabled()) { log.debug("Generate key, " + algorithm + ", " + keysize + ", " + alias); } if (StringUtils.isNotEmpty(alias)) { KeyStoreTools cont = new KeyStoreTools(getKeyStore(), getSignProviderName()); cont.generateKey(algorithm, keysize, alias); } else { log.debug("Trying to generate keys with empty alias."); } } @Override public byte[] getTokenData() { return null; } /** Used for testing */ protected P11Slot getP11slot() { return p11slot; } /** * Extracts the slotLabel that is used for many tokens in construction of the provider * * @param sSlotLabelKey which key in the properties that gives us the label * @param properties CA token properties * @return String with the slot label, trimmed from whitespace */ private static String getSlotLabel(String sSlotLabelKey, Properties properties) { String ret = null; if (sSlotLabelKey != null && properties != null) { ret = properties.getProperty(sSlotLabelKey); if (ret != null) { ret = ret.trim(); } } return ret; } /** * Will replace deprecated properties values with the new ones. * * @param properties a properties file of the old format. * @return a defensive copy of the submitted properties */ @Deprecated //Remove when we no longer support upgrading from 5.0.x -> 6.0.x public static Properties upgradePropertiesFileFrom5_0_x(final Properties properties) { Properties returnValue = new Properties(); for (Object key : properties.keySet()) { String keyString = (String) key; if (keyString.equals(SLOT_LABEL_KEY)) { String keyValue = properties.getProperty(keyString); // In 5.0.11, the "slot" value may contain just an integer, but may also encode an integer, an index // a token label or a config file. final String oldLabelPrefix = "TOKEN_LABEL:"; final String oldIndexPrefix = "SLOT_LIST_IX:"; final String oldSlotNumberPrefix = "SLOT_ID:"; final String oldSunFilePrefix = "SUN_FILE:"; final String delimiter = ":"; if (Pkcs11SlotLabelType.SLOT_NUMBER.validate(keyValue)) { //If it was a straight integer, then save as is returnValue.setProperty(SLOT_LABEL_VALUE, keyValue); returnValue.setProperty(SLOT_LABEL_TYPE, Pkcs11SlotLabelType.SLOT_NUMBER.getKey()); } else if (keyValue.startsWith(oldSlotNumberPrefix)) { //If not, check with the rest of the values returnValue.setProperty(SLOT_LABEL_VALUE, keyValue.split(delimiter, 2)[1]); returnValue.setProperty(SLOT_LABEL_TYPE, Pkcs11SlotLabelType.SLOT_NUMBER.getKey()); } else if (keyValue.startsWith(oldIndexPrefix)) { returnValue.setProperty(SLOT_LABEL_VALUE, keyValue.split(delimiter, 2)[1]); returnValue.setProperty(SLOT_LABEL_TYPE, Pkcs11SlotLabelType.SLOT_INDEX.getKey()); } else if (keyValue.startsWith(oldLabelPrefix)) { returnValue.setProperty(SLOT_LABEL_VALUE, keyValue.split(delimiter, 2)[1]); returnValue.setProperty(SLOT_LABEL_TYPE, Pkcs11SlotLabelType.SLOT_LABEL.getKey()); } else if (keyValue.startsWith(oldSunFilePrefix)) { returnValue.setProperty(SLOT_LABEL_TYPE, Pkcs11SlotLabelType.SUN_FILE.getKey()); } } else if (((String) key).equals(SLOT_LIST_INDEX_KEY)) { String indexValue = properties.getProperty(keyString); if (indexValue.charAt(0) != 'i') { indexValue = "i" + indexValue; } returnValue.setProperty(SLOT_LABEL_VALUE, indexValue); returnValue.setProperty(SLOT_LABEL_TYPE, Pkcs11SlotLabelType.SLOT_INDEX.getKey()); } else { returnValue.setProperty(keyString, properties.getProperty(keyString)); } } return returnValue; } @Override public boolean permitExtractablePrivateKeyForTest() { return doPermitExtractablePrivateKey(); } }