/* * 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 xades4j.providers.*; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStore.Builder; import java.security.KeyStore.ProtectionParameter; import java.security.KeyStoreException; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.List; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import xades4j.verification.UnexpectedJCAException; /** * A KeyStore-based implementation of {@code KeyingDataProvider}. The keystore is * loaded on first access (thread-safe). * <p> * The following procedure is done to get the signing certificate: * <ol> * <li>Get all the X509Certificates in private key entries</li> * <li>Invoke the supplied {@code SigningCertSelector} to choose the certificate and thus the entry</li> * <li>Get the entry alias matching the selected certificate</li> * <li>Get the certificate chain for that entry</li> * </ol> * <p> * The following procedure is done to get the signing key: * <ol> * <li>Get the entry alias matching the provided certificate</li> * <li>Get the protection to access that entry</li> * <li>Return the entry's private key</li> * </ol> * * @see FileSystemKeyStoreKeyingDataProvider * @see PKCS11KeyStoreKeyingDataProvider * @author Luís */ public abstract class KeyStoreKeyingDataProvider implements KeyingDataProvider { /** * Provides a password to load the keystore. */ public interface KeyStorePasswordProvider { char[] getPassword(); } /** * Provides a password to access a keystore entry. Must be thread-safe. */ public interface KeyEntryPasswordProvider { char[] getPassword(String entryAlias, X509Certificate entryCert); } /** * Used to select a certificate from the available certificates. All the * X509Certificates in private key entries are passed. */ public interface SigningCertSelector { X509Certificate selectCertificate( List<X509Certificate> availableCertificates); } /**/ /** * Gets a builder that will create the keystore instance. This is usued because * different types of keystores may be configured differently. */ protected interface KeyStoreBuilderCreator { /** * @param loadProtection the protection that should be used to load the keystore (may be null) * @return the builder */ Builder getBuilder(ProtectionParameter loadProtection); } /**/ /**/ private final KeyStoreBuilderCreator builderCreator; private final SigningCertSelector certificateSelector; private final KeyStorePasswordProvider storePasswordProvider; private final KeyEntryPasswordProvider entryPasswordProvider; private final boolean returnFullChain; private KeyStore keyStore; private final Object lockObj; private boolean initialized; /** * * @param builderCreator * @param certificateSelector * @param storePasswordProvider * @param entryPasswordProvider * @param returnFullChain return the full certificate chain, if available */ protected KeyStoreKeyingDataProvider( KeyStoreBuilderCreator builderCreator, SigningCertSelector certificateSelector, KeyStorePasswordProvider storePasswordProvider, KeyEntryPasswordProvider entryPasswordProvider, boolean returnFullChain) { this.builderCreator = builderCreator; this.certificateSelector = certificateSelector; this.storePasswordProvider = storePasswordProvider; this.entryPasswordProvider = entryPasswordProvider; this.returnFullChain = returnFullChain; this.lockObj = new Object(); this.initialized = false; } private void ensureInitialized() throws UnexpectedJCAException { synchronized(this.lockObj) { if (!this.initialized) { try { KeyStore.CallbackHandlerProtection storeLoadProtec = null; if (storePasswordProvider != null) // Create the load protection with callback. storeLoadProtec = new KeyStore.CallbackHandlerProtection(new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { PasswordCallback c = (PasswordCallback)callbacks[0]; c.setPassword(storePasswordProvider.getPassword()); } }); else // If no load password provider is supplied is because it shouldn't // be needed. Create a dummy protection because the keystore // builder needs it to be non-null. storeLoadProtec = new KeyStore.CallbackHandlerProtection(new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { throw new UnsupportedOperationException("No KeyStorePasswordProvider"); } }); this.keyStore = builderCreator.getBuilder(storeLoadProtec).getKeyStore(); } catch (KeyStoreException ex) { throw new UnexpectedJCAException("The keystore couldn't be initialized", ex); } this.initialized = true; } } } @Override public List<X509Certificate> getSigningCertificateChain() throws SigningCertChainException, UnexpectedJCAException { ensureInitialized(); try { List<X509Certificate> availableSignCerts = new ArrayList<X509Certificate>(keyStore.size()); for (Enumeration<String> aliases = keyStore.aliases(); aliases.hasMoreElements();) { String alias = aliases.nextElement(); if (keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) { Certificate cer = keyStore.getCertificate(alias); if (cer instanceof X509Certificate) availableSignCerts.add((X509Certificate)cer); } } if (availableSignCerts.isEmpty()) throw new SigningCertChainException("No certificates available in the key store"); // Select the signing certificate from the available certificates. X509Certificate signingCert = this.certificateSelector.selectCertificate(availableSignCerts); String signingCertAlias = this.keyStore.getCertificateAlias(signingCert); if (null == signingCertAlias) throw new SigningCertChainException("Selected certificate not present in the key store"); Certificate[] signingCertChain = this.keyStore.getCertificateChain(signingCertAlias); if (null == signingCertChain) throw new SigningCertChainException("Selected certificate doesn't match a key and corresponding certificate chain"); if (this.returnFullChain) { List lChain = Arrays.asList(signingCertChain); return Collections.checkedList(lChain, X509Certificate.class); } else return Collections.singletonList((X509Certificate)signingCertChain[0]); } catch (KeyStoreException ex) { // keyStore.getCertificateAlias, keyStore.getCertificateChain -> if the // keystore is not loaded. throw new UnexpectedJCAException(ex.getMessage(), ex); } } @Override public PrivateKey getSigningKey(X509Certificate signingCert) throws SigningKeyException, UnexpectedJCAException { ensureInitialized(); try { // The certificate supplied by the library is always the first certificate // in the chain supplied by getSigningCertificateChain, which means // that an entry will always be present. Also, this entry is always // a PrivateKeyEntry. String entryAlias = this.keyStore.getCertificateAlias(signingCert); KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)this.keyStore.getEntry( entryAlias, getKeyProtection(entryAlias, signingCert, this.entryPasswordProvider)); return entry.getPrivateKey(); } catch (UnrecoverableKeyException ex) { throw new SigningKeyException("Invalid key entry password", ex); } catch (GeneralSecurityException ex) { // NoSuchAlgorithmException // UnrecoverableEntryException // KeyStoreException throw new UnexpectedJCAException(ex.getMessage(), ex); } } /** * Gets a protection parameter to access the specified entry. * @param entryAlias the alias of the entry that is being accessed * @param entryCert the cerificate in the entry * @param entryPasswordProvider the password provider that should be used to * get the actual password (may be {@code null}) * @return the protection */ protected abstract KeyStore.ProtectionParameter getKeyProtection( String entryAlias, X509Certificate entryCert, KeyEntryPasswordProvider entryPasswordProvider); }