package es.gob.jmulticard.jse.provider.ceres; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.security.Key; import java.security.KeyStore; import java.security.KeyStore.PasswordProtection; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStore.ProtectionParameter; import java.security.KeyStoreSpi; import java.security.PrivateKey; import java.security.ProviderException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.logging.Logger; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.x500.X500Principal; import es.gob.jmulticard.apdu.connection.ApduConnection; import es.gob.jmulticard.card.PrivateKeyReference; import es.gob.jmulticard.card.fnmt.ceres.Ceres; import es.gob.jmulticard.card.fnmt.ceres.CeresPrivateKeyReference; import es.gob.jmulticard.jse.provider.JseCryptoHelper; /** Implementación del SPI KeyStore para tarjeta FNMT-RCM-CERES. * @author Tomás García-Merás */ public final class CeresKeyStoreImpl extends KeyStoreSpi { private static List<String> userCertAliases = null; private Ceres cryptoCard = null; private void loadAliases() { final String[] aliases = this.cryptoCard.getAliases(); userCertAliases = new ArrayList<String>(aliases.length); for (final String alias : aliases) { userCertAliases.add(alias); } } /** {@inheritDoc} */ @Override public Enumeration<String> engineAliases() { return Collections.enumeration(userCertAliases); } /** {@inheritDoc} */ @Override public boolean engineContainsAlias(final String alias) { return userCertAliases.contains(alias); } /** Operación no soportada. */ @Override public void engineDeleteEntry(final String alias) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public Certificate engineGetCertificate(final String alias) { if (!engineContainsAlias(alias)) { return null; } return this.cryptoCard.getCertificate(alias); } /** {@inheritDoc} */ @Override public String engineGetCertificateAlias(final Certificate cert) { if (!(cert instanceof X509Certificate)) { return null; } final BigInteger serial = ((X509Certificate) cert).getSerialNumber(); final X500Principal principal = ((X509Certificate) cert).getIssuerX500Principal(); for (final String alias : userCertAliases) { final X509Certificate c = (X509Certificate) engineGetCertificate(alias); if (c.getSerialNumber() == serial && principal.equals(principal)) { return alias; } } return null; } /** {@inheritDoc} */ @Override public Certificate[] engineGetCertificateChain(final String alias) { if (!engineContainsAlias(alias)) { return null; } return new X509Certificate[] { (X509Certificate) engineGetCertificate(alias) }; } /** Operación no soportada. */ @Override public Date engineGetCreationDate(final String alias) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public Key engineGetKey(final String alias, final char[] password) { if (!engineContainsAlias(alias)) { return null; } // No permitimos PIN nulo, si llega nulo pedimos por dialogo grafico if (password != null) { this.cryptoCard.setPasswordCallback( new CachePasswordCallback(password) ); } final PrivateKeyReference pkRef = this.cryptoCard.getPrivateKey(alias); if (!(pkRef instanceof CeresPrivateKeyReference)) { throw new ProviderException("La clave obtenida de la tarjeta no es del tipo esperado, se ha obtenido: " + pkRef.getClass().getName()); //$NON-NLS-1$ } return new CeresPrivateKey( (CeresPrivateKeyReference) pkRef, this.cryptoCard, ((RSAPublicKey)engineGetCertificate(alias).getPublicKey()).getModulus() ); } /** {@inheritDoc} */ @Override public KeyStore.Entry engineGetEntry(final String alias, final ProtectionParameter protParam) { if (protParam instanceof KeyStore.PasswordProtection) { final PasswordCallback pwc = new CachePasswordCallback(((KeyStore.PasswordProtection)protParam).getPassword()); this.cryptoCard.setPasswordCallback(pwc); } if (!engineContainsAlias(alias)) { return null; } final PrivateKey key = (PrivateKey) engineGetKey( alias, null // Le pasamos null porque ya hemos establecido el PasswordCallback o el CallbackHander antes ); return new PrivateKeyEntry(key, engineGetCertificateChain(alias)); } /** {@inheritDoc} */ @Override public boolean engineIsCertificateEntry(final String alias) { return userCertAliases.contains(alias); } /** {@inheritDoc} */ @Override public boolean engineIsKeyEntry(final String alias) { return userCertAliases.contains(alias); } private static ApduConnection getApduConnection() { return CeresProvider.getDefaultApduConnection(); } /** {@inheritDoc} */ @Override public void engineLoad(final KeyStore.LoadStoreParameter param) throws IOException { if (param != null) { final ProtectionParameter pp = param.getProtectionParameter(); if (pp instanceof KeyStore.CallbackHandlerProtection) { if (((KeyStore.CallbackHandlerProtection) pp).getCallbackHandler() == null) { throw new IllegalArgumentException("El CallbackHandler no puede ser nulo"); //$NON-NLS-1$ } this.cryptoCard = new Ceres( CeresProvider.getDefaultApduConnection(), new JseCryptoHelper() ); this.cryptoCard.setCallbackHandler(((KeyStore.CallbackHandlerProtection) pp).getCallbackHandler()); } else if (pp instanceof KeyStore.PasswordProtection) { final PasswordCallback pwc = new CeresPasswordCallback((PasswordProtection) pp); this.cryptoCard = new Ceres( CeresProvider.getDefaultApduConnection(), new JseCryptoHelper() ); this.cryptoCard.setPasswordCallback(pwc); } else { Logger.getLogger("es.gob.jmulticard").warning( //$NON-NLS-1$ "Se ha proporcionado un LoadStoreParameter de tipo no soportado, se ignorara: " + (pp != null ? pp.getClass().getName() : "NULO") //$NON-NLS-1$ //$NON-NLS-2$ ); } } else { this.cryptoCard = new Ceres( CeresProvider.getDefaultApduConnection(), new JseCryptoHelper() ); } userCertAliases = Arrays.asList(this.cryptoCard.getAliases()); } /** {@inheritDoc} */ @Override public void engineLoad(final InputStream stream, final char[] password) throws IOException { // Aqui se realiza el acceso e inicializacion de la tarjeta this.cryptoCard = new Ceres( getApduConnection(), new JseCryptoHelper() ); // Precargamos los alias loadAliases(); } /** Operación no soportada. */ @Override public void engineSetCertificateEntry(final String alias, final Certificate cert) { throw new UnsupportedOperationException(); } /** Operación no soportada. */ @Override public void engineSetKeyEntry(final String alias, final byte[] key, final Certificate[] chain) { throw new UnsupportedOperationException(); } /** Operación no soportada. */ @Override public void engineSetKeyEntry(final String alias, final Key key, final char[] pass, final Certificate[] chain) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public int engineSize() { return userCertAliases.size(); } /** Operación no soportada. */ @Override public void engineStore(final OutputStream os, final char[] pass) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean engineEntryInstanceOf(final String alias, final Class<? extends KeyStore.Entry> entryClass) { if (!engineContainsAlias(alias)) { return false; } return entryClass.equals(PrivateKeyEntry.class); } /** PasswordCallbak que almacena internamente y devuelve la contraseña con la que se * construyó o la que se le establece posteriormente. */ private static final class CachePasswordCallback extends PasswordCallback { private static final long serialVersionUID = 816457144215238935L; /** Contruye una Callback con una contraseña pre-establecida. * @param password Contraseña por defecto. */ CachePasswordCallback(final char[] password) { super(">", false); //$NON-NLS-1$ setPassword(password); } } }