/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2010 Luis Goncalves.
*
* XAdES4j 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 3 of the License, or any later version.
*
* XAdES4j is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/
package xades4j.providers.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.KeyStore;
import java.security.KeyStore.Builder;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStoreException;
import java.security.Provider;
import java.security.ProviderException;
import java.security.Security;
import java.security.cert.X509Certificate;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
/**
* A specification of {@code KeyStoreKeyingDataProvider} for PKCS#11 keystores.
* This class uses the SUN's PKCS#11 provider, which brigdes with the native PKCS#11
* library. Note that this provider is not included in some versions of the JRE,
* namely the 64 bits Windows version. On those scenarios this class will fail at
* runtime.
* <p>
* The {@code KeyStorePasswordProvider} and {@code KeyEntryPasswordProvider} may
* be {@code null}. In that case the keystore protection has to be handled by the
* native library. If the {@code KeyEntryPasswordProvider} is supplied, the protection
* used to access an entry is a {@code CallbackHandlerProtection} that invokes the
* {@code KeyEntryPasswordProvider} exactly when when the password is requested.
* @see xades4j.providers.impl.KeyStoreKeyingDataProvider
* @author Luís
*/
public class PKCS11KeyStoreKeyingDataProvider extends KeyStoreKeyingDataProvider
{
/**
* The provider name is used has a key to search for installed providers. If a
* provider exists with the same name, it will be used even if it relies on a
* different native library.
* @param nativeLibraryPath the path for the native library of the specific PKCS#11 provider
* @param providerName this string is concatenated with the prefix SunPKCS11- to produce this provider instance's name
* @param certificateSelector the selector of signing certificate
* @param keyStorePasswordProvider the provider of the keystore loading password (may be {@code null})
* @param entryPasswordProvider the provider of entry passwords (may be {@code null})
* @param returnFullChain indicates of the full certificate chain should be returned, if available
* @throws KeyStoreException
*/
public PKCS11KeyStoreKeyingDataProvider(
final String nativeLibraryPath,
final String providerName,
SigningCertSelector certificateSelector,
KeyStorePasswordProvider keyStorePasswordProvider,
KeyEntryPasswordProvider entryPasswordProvider,
boolean returnFullChain) throws KeyStoreException
{
this(nativeLibraryPath, providerName, null,
certificateSelector, keyStorePasswordProvider, entryPasswordProvider,
returnFullChain);
}
/**
* The provider name is used as a key to search for installed providers. If a
* provider exists with the same name, it will be used even if it relies on a
* different native library.
* @param nativeLibraryPath the path for the native library of the specific PKCS#11 provider
* @param providerName this string is concatenated with the prefix SunPKCS11- to produce this provider instance's name
* @param slotId the id of the slot that this provider instance is to be associated with (can be {@code null})
* @param certificateSelector the selector of signing certificate
* @param keyStorePasswordProvider the provider of the keystore loading password (can be {@code null})
* @param entryPasswordProvider the provider of entry passwords (may be {@code null})
* @param returnFullChain indicates of the full certificate chain should be returned, if available
* @throws KeyStoreException
*/
public PKCS11KeyStoreKeyingDataProvider(
final String nativeLibraryPath,
final String providerName,
final Integer slotId,
SigningCertSelector certificateSelector,
KeyStorePasswordProvider keyStorePasswordProvider,
KeyEntryPasswordProvider entryPasswordProvider,
boolean returnFullChain) throws KeyStoreException
{
super(new KeyStoreBuilderCreator()
{
@Override
public Builder getBuilder(ProtectionParameter loadProtection)
{
Provider p = getInstalledProvider(providerName);
if (p == null)
{
StringBuilder config = new StringBuilder("name = ").append(providerName);
config.append(System.getProperty("line.separator"));
config.append("library = ").append(nativeLibraryPath);
if(slotId != null)
{
config.append(System.getProperty("line.separator"));
config.append("slot = ").append(slotId);
}
ByteArrayInputStream configStream = new ByteArrayInputStream(config.toString().getBytes());
p = createPkcs11Provider(configStream);
Security.addProvider(p);
}
return KeyStore.Builder.newInstance("PKCS11", p, loadProtection);
}
}, certificateSelector, keyStorePasswordProvider, entryPasswordProvider, returnFullChain);
}
/**
* Shortcut constructor using {@code null} for the password providers and slot
* and {@code false} for the {@code returnFullChain} parameter.
* @param nativeLibraryPath
* @param providerName
* @param slotId
* @param certificateSelector
* @throws KeyStoreException
*/
public PKCS11KeyStoreKeyingDataProvider(
String nativeLibraryPath,
String providerName,
Integer slotId,
SigningCertSelector certificateSelector) throws KeyStoreException
{
this(nativeLibraryPath, providerName, slotId, certificateSelector, null, null, false);
}
/**
* Shortcut constructor using {@code null} for the password providers and slot,
* and {@code false} for the {@code returnFullChain} parameter.
* @param nativeLibraryPath
* @param providerName
* @param certificateSelector
* @throws KeyStoreException
*/
public PKCS11KeyStoreKeyingDataProvider(
final String nativeLibraryPath,
final String providerName,
SigningCertSelector certificateSelector) throws KeyStoreException
{
this(nativeLibraryPath, providerName, null, certificateSelector);
}
@Override
protected final KeyStore.ProtectionParameter getKeyProtection(
final String entryAlias,
final X509Certificate entryCert,
final KeyEntryPasswordProvider entryPasswordProvider)
{
if (null == entryPasswordProvider)
{
return null;
}
return new KeyStore.CallbackHandlerProtection(new CallbackHandler()
{
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
{
PasswordCallback c = (PasswordCallback) callbacks[0];
c.setPassword(entryPasswordProvider.getPassword(entryAlias, entryCert));
}
});
}
private static Provider getInstalledProvider(String providerName)
{
Class<Provider> pkcs11Class = getPkcs11ProviderClass();
Provider p = Security.getProvider("SunPKCS11-" + providerName);
// Throws expcetion if the provider is not of the expected type
return pkcs11Class.cast(p);
}
private static Provider createPkcs11Provider(InputStream configStream)
{
try
{
Class<Provider> providerClass = getPkcs11ProviderClass();
Constructor<Provider> ctor = providerClass.getConstructor(InputStream.class);
return ctor.newInstance(configStream);
}
// Since the provider class was loaded, these exceptions are unexpected
catch (IllegalAccessException ex)
{
throw new ProviderException(ex);
}
catch (IllegalArgumentException ex)
{
throw new ProviderException(ex);
}
catch (InvocationTargetException ex)
{
throw new ProviderException(ex);
}
catch (NoSuchMethodException ex)
{
throw new ProviderException(ex);
}
catch(InstantiationException ex)
{
throw new ProviderException(ex);
}
}
private static Class getPkcs11ProviderClass()
{
try
{
return Class.forName("sun.security.pkcs11.SunPKCS11");
}
catch (ClassNotFoundException ex)
{
throw new ProviderException("Cannot find SunPKCS11 provider", ex);
}
}
public static boolean isProviderAvailable()
{
try
{
getPkcs11ProviderClass();
return true;
}
catch(ProviderException ex)
{
return false;
}
}
}