/**
* LICENCIA LGPL:
*
* Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
* bajo los términos de la GNU Lesser General Public License (LGPL) tal y como
* ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de
* la Licencia, o (a su elección) cualquier versión posterior.
*
* Esta librería se distribuye con la esperanza de que sea útil, pero SIN
* NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o
* ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public
* License (LGPL) para más detalles
*
* Usted debe recibir una copia de la GNU Lesser General Public License (LGPL)
* junto con esta librería; si no es así, escriba a la Free Software Foundation
* Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
* .
*
* Copyright 2011 Agencia de Tecnología y Certificación Electrónica
*/
package es.accv.arangi.base.device;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import org.apache.log4j.Logger;
import sun.security.pkcs11.SunPKCS11;
import com.sun.security.auth.callback.DialogCallbackHandler;
import es.accv.arangi.base.device.util.CustomDialogCallbackHandler;
import es.accv.arangi.base.device.util.MozillaPkcs11Manager;
import es.accv.arangi.base.exception.device.InitializeProviderException;
import es.accv.arangi.base.exception.device.LoadingObjectException;
import es.accv.arangi.base.exception.device.MozillaKeyStoreException;
import es.accv.arangi.base.exception.device.MozillaNotFoundException;
import es.accv.arangi.base.exception.device.OpeningDeviceException;
import es.accv.arangi.base.util.Util;
/**
* Clase para tratar almacenes de claves de Mozilla Firefox.
*
* Este almacén es el propio del navegador Firefox. Si se desea, la clase puede tratar
* los certificados que se encuentran en dispositivos PKCS#11 y que han sido configurados
* como dispositivos de seguridad en Firefox. Esta solución es una buena idea en sistemas
* Linux donde no se dispone del almacén de Windows para gestionar los certificados en
* tarjeta.
*
* NOTA: Esta clase no es capaz de trabajar con Firefox en Mac OS X por problemas con las
* dependencias del módulo PKCS#11.
*
* Ejemplo de uso:
*
*
* IDocument document = new FileDocument(new File ("/documento.txt"));
* MozillaKeyStoreManager manager = new MozillaKeyStoreManager ("Escriba contraseña maestra Firefox : ", "Contraseña maestra Firefox", false);
* String aliases = manager.getAliasNamesList();
* for (int i=0;i
* System.out.println ("Certificate: " + manager.getCertificate(aliases[i]));
* System.out.println ("Firma: " + manager.signDocument(document, aliases[i])); // Firma con la clave privada del alias
* }
*
*
* @author José M Gutiérrez
*/
public class MozillaKeyStoreManager extends AbstractBrowserKeyStoreManager {
/*
* Logger de la clase
*/
Logger logger = Logger.getLogger(MozillaKeyStoreManager.class);
/*
* Nombre del provider
*/
String PROVIDER_NAME = "NSS";
/*
* Array de almacenes PKCS#11 que utiliza Mozilla
*/
List pkcs11Managers;
/*
* Texto del dialogo en el que se pedirá el pin al usuario
*/
String textPinDialog = null;
/*
* Texto del título del dialogo en el que se pedirá el pin al usuario
*/
String titleDialog = null;
/**
* Constructor: inicializa el proveedor de SUN si es necesario para acceder al
* repositorio de Mozilla.
*
* Se cargarán los módulos PKCS#11 asociados al almacén de claves de Mozilla.
*
* @throws MozillaNotFoundException No se ha podido encontrar la carpeta de
* instalación de Mozilla en los archivos de programa o no se ha encontrado
* la carpeta del perfil de Mozilla del usuario.
* @throws MozillaKeyStoreException No se puede abrir el almacén de claves del
* usuario de Mozilla.
*/
public MozillaKeyStoreManager () throws MozillaNotFoundException, MozillaKeyStoreException {
initKeystores(new DialogCallbackHandler(), false);
}
/**
* Constructor: inicializa el proveedor de SUN si es necesario para acceder al
* repositorio de Mozilla.
*
* Se cargarán los módulos PKCS#11 asociados al almacén de claves de Mozilla.
*
* @param textPinDialog texto del dialogo en el que se pedirá el pin al usuario.
* @param titleDialog texto del título del dialogo en el que se pedirá el pin al usuario.
*
* @throws MozillaNotFoundException No se ha podido encontrar la carpeta de
* instalación de Mozilla en los archivos de programa o no se ha encontrado
* la carpeta del perfil de Mozilla del usuario.
* @throws MozillaKeyStoreException No se puede abrir el almacén de claves del
* usuario de Mozilla.
*/
public MozillaKeyStoreManager (String textPinDialog, String titleDialog) throws MozillaNotFoundException, MozillaKeyStoreException {
this.textPinDialog = textPinDialog;
this.titleDialog = titleDialog;
initKeystores(new CustomDialogCallbackHandler(textPinDialog, titleDialog), false);
}
/**
* Constructor: inicializa el proveedor de SUN si es necesario para
* acceder al repositorio de Mozilla.
*
* @param withoutPkcs11Modules Si es cierto no se cargarán los módulos PKCS#11
* asociados al almacén de claves de Mozilla.
* @throws MozillaNotFoundException No se ha podido encontrar la carpeta de
* instalación de Mozilla en los archivos de programa o no se ha encontrado
* la carpeta del perfil de Mozilla del usuario.
* @throws MozillaKeyStoreException No se puede abrir el almacén de claves del
* usuario de Mozilla.
*/
public MozillaKeyStoreManager (boolean withoutPkcs11Modules) throws MozillaNotFoundException, MozillaKeyStoreException {
initKeystores(new DialogCallbackHandler(), withoutPkcs11Modules);
}
/**
* Constructor: inicializa el proveedor de SUN si es necesario para
* acceder al repositorio de Mozilla.
*
* @param textPinDialog texto del dialogo en el que se pedirá el pin al usuario.
* @param titleDialog texto del título del dialogo en el que se pedirá el pin al usuario.
* @param withoutPkcs11Modules Si es cierto no se cargarán los módulos PKCS#11
* asociados al almacén de claves de Mozilla.
*
* @throws MozillaNotFoundException No se ha podido encontrar la carpeta de
* instalación de Mozilla en los archivos de programa o no se ha encontrado
* la carpeta del perfil de Mozilla del usuario.
* @throws MozillaKeyStoreException No se puede abrir el almacén de claves del
* usuario de Mozilla.
*/
public MozillaKeyStoreManager (String textPinDialog, String titleDialog, boolean withoutPkcs11Modules) throws MozillaNotFoundException, MozillaKeyStoreException {
this.textPinDialog = textPinDialog;
this.titleDialog = titleDialog;
initKeystores(new CustomDialogCallbackHandler(textPinDialog, titleDialog), withoutPkcs11Modules);
}
//-- Métodos públicos
/**
* Cerrar el keystore y eliminar el provider de SUN de la lista de proveedores
*/
public void close() {
logger.debug ("Cerrando Keystore...");
ks = null;
//-- Eliminar provider de SUN
logger.debug("[CLOSE]::Eliminando el provider de SUN");
Security.removeProvider("SunPKCS11-NSS");
logger.debug("[CLOSE]::Eliminado el provider de SUN");
logger.debug("Cerrado Keystore...OK");
//-- Cerrar PKCS11s
for (Iterator iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
MozillaPkcs11Manager pkcs11Manager = iterator.next();
logger.debug("[MozillaKeyStoreManager.getAliasNamesList]::Cerrando '" + pkcs11Manager.getProviderName() + "'");
pkcs11Manager.close();
}
}
@Override
public void initialize() {
if (textPinDialog != null || titleDialog != null) {
try {
initKeystores(new DialogCallbackHandler(), false);
} catch (MozillaKeyStoreException e) {
logger.info("[MozillaKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
} catch (MozillaNotFoundException e) {
logger.info("[MozillaKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
}
} else {
try {
initKeystores(new CustomDialogCallbackHandler(textPinDialog, titleDialog), false);
} catch (MozillaKeyStoreException e) {
logger.info("[MozillaKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
} catch (MozillaNotFoundException e) {
logger.info("[MozillaKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
}
}
}
/*
* (non-Javadoc)
* @see es.accv.arangi.base.device.DeviceManager#getAliasNamesList()
*/
public String[] getAliasNamesList() {
logger.debug("Obteniendo todos los alias...");
//-- Obtener los alias del keystore
Enumeration enumAlias = null;
try {
enumAlias = ks.aliases();
} catch (KeyStoreException e) {
//-- Este error sólo se produce si no se ha inicializado la clase, lo que
//-- ya se comprueba y no puede ocurrir
logger.debug("[MozillaKeyStoreManager.getAliasNamesList]::Keystore no inicializado", e);
}
List lAlias = new ArrayList ();
while (enumAlias != null && enumAlias.hasMoreElements()) {
String alias = (String) enumAlias.nextElement();
lAlias.add(alias);
}
//-- Obtener los alias de los PKCS#11
for (Iterator iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
MozillaPkcs11Manager pkcs11Manager = iterator.next();
//-- Obtener los alias
logger.debug("[MozillaKeyStoreManager.getAliasNamesList]::Obteniendo alias para '" + pkcs11Manager.getProviderName() + "'");
lAlias.addAll(pkcs11Manager.getAliasNamesList());
}
return (String[]) lAlias.toArray(new String[0]);
}
/*
* (non-Javadoc)
* @see es.accv.arangi.base.device.DeviceManager#getCertificate(java.lang.String)
*/
public X509Certificate getCertificate(String alias) {
logger.debug("getCertificate...");
logger.debug("alias: " + alias);
//-- Buscar en el keystore
try {
X509Certificate certificate = (X509Certificate) ks.getCertificate(alias);
logger.debug ("[MozillaKeyStoreManager.getCertificate]::Obtenido certificado::" + certificate);
if (certificate != null) {
return certificate;
}
} catch (KeyStoreException e) {
//-- Este error sólo se produce si no se ha inicializado la clase, lo que
//-- ya se comprueba y no puede ocurrir
logger.debug("[MozillaKeyStoreManager.getCertificate]::Keystore no inicializado", e);
}
//-- Buscar en los alias de los PKCS#11
X509Certificate certificate;
for (Iterator iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
MozillaPkcs11Manager pkcs11Manager = iterator.next();
//-- Buscar
logger.debug("[MozillaKeyStoreManager.getCertificate]::Buscando en los alias para '" + pkcs11Manager.getProviderName() + "'");
if ((certificate = pkcs11Manager.getCertificate(alias)) != null) {
logger.debug("[MozillaKeyStoreManager.getCertificate]::Encontrado alias '" + alias + "' en '" + pkcs11Manager.getProviderName() + "'");
return certificate;
}
}
logger.info("[MozillaKeyStoreManager.getCertificate]::No se ha encontrado el alias '" + alias + "'");
return null;
}
/*
* (non-Javadoc)
* @see es.accv.arangi.base.device.DeviceManager#getPrivateKey(java.lang.String, java.lang.String)
*/
public PrivateKey getPrivateKey(String alias, String keyPassword) throws LoadingObjectException{
logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Entrada::" + Arrays.asList(new Object [] { alias }));
//-- Buscar en el keystore
try {
//-- Buscar por alias y viendo que sea una clave
if (!isAliasFree(alias) && ks.isKeyEntry(alias)) {
logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Encontrada clave privada con alias '" + alias + "'");
return (PrivateKey) ks.getKey(alias, keyPassword.toCharArray());
}
} catch (KeyStoreException e) {
//-- Este error sólo se produce si no se ha inicializado la clase, lo que
//-- ya se comprueba y no puede ocurrir
logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Keystore no inicializado", e);
} catch (NoSuchAlgorithmException e) {
//-- Este error sólo se produce si no se ha cargado Bouncy, con lo que
//-- el error se habría producido mucho antes
logger.debug("[MozillaKeyStoreManager.getPrivateKey]::No se encuentra el algoritmo para alguno de los certificados de la cadena", e);
return null;
} catch (UnrecoverableKeyException e) {
logger.info("[MozillaKeyStoreManager.getPrivateKey]::La clave privada del alias '" + alias + "' no ha podido ser recuperada. " +
"Compruebe que su password coincide con el PIN del dispositivo.", e);
throw new LoadingObjectException("La clave privada del alias '" + alias + "' no ha podido ser recuperada. Compruebe " +
"que su password coincide con el PIN del dispositivo.", e);
}
//-- Buscar en los alias de los PKCS#11
PrivateKey pk;
for (Iterator iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
MozillaPkcs11Manager pkcs11Manager = iterator.next();
//-- Buscar
logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Buscando en los alias para '" + pkcs11Manager.getProviderName() + "'");
if ((pk = pkcs11Manager.getPrivateKey(alias)) != null) {
logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Encontrado alias '" + alias + "' en '" + pkcs11Manager.getProviderName() + "'");
return pk;
}
}
logger.info("[MozillaKeyStoreManager.getPrivateKey]::No se ha encontrado una pk en el alias '" + alias + "'");
return null;
}
/* (non-Javadoc)
* @see es.accv.arangi.base.device.DeviceManager#isAliasFree(java.lang.String)
*/
public boolean isAliasFree(String alias){
logger.debug ("[MozillaKeyStoreManager.isAliasFree]::Entrada::" + alias);
try {
if (ks.containsAlias(alias)) {
return false;
}
} catch (KeyStoreException e) {
logger.info("[MozillaKeyStoreManager.isAliasFree]::Error comprobando si existe el alias '" + alias + "'", e);
}
//-- Buscar en los alias de los PKCS#11
for (Iterator iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
MozillaPkcs11Manager pkcs11Manager = iterator.next();
//-- Buscar
logger.debug("[MozillaKeyStoreManager.isAliasFree]::Buscando en los alias para '" + pkcs11Manager.getProviderName() + "'");
if (pkcs11Manager.getAliasNamesList().contains(alias)) {
logger.debug("[MozillaKeyStoreManager.isAliasFree]::Encontrado alias '" + alias + "' en '" + pkcs11Manager.getProviderName() + "'");
return false;
}
}
return true;
}
//-- Métodos privados
/*
* Inicializa el keystore de Mozilla y los keystores pkcs11 asociados
*/
private void initKeystores(CallbackHandler handler, boolean withoutPkcs11Modules) throws MozillaKeyStoreException, MozillaNotFoundException{
List lKeystores = new ArrayList();
this.pin = "";
this.ksType = STORE_TYPE_MOZILLA;
SunPKCS11 pk11provider;
if (!Util.existsCryptographicProvider(SUN_PROVIDER_PREFIX + PROVIDER_NAME)) {
//-- Buscar la carpeta de instalación de Mozilla
File mozillaFolder = getMozillaFolder ();
//-- Buscar la carpeta para el secmod
File secModFolder = getSecModFolder ();
//-- Cargar las librerías de Mozilla
try {
if (System.getProperty("os.name").startsWith("Mac OS X")) {
configureMacNSS(mozillaFolder);
} else {
loadMozillaLibraries (mozillaFolder);
}
} catch (Throwable e) {
logger.info("[MozillaKeyStoreManager.initKeystores]::No se han podido cargar las librerías para Mozilla Firefox", e);
throw new MozillaKeyStoreException("No se han podido cargar las librerías para Mozilla Firefox", e);
}
//-- Obtener el path al fichero de la librería
int javaVersion = 6;
if (System.getProperty("java.version").indexOf(".") > -1) {
try {
javaVersion = Integer.parseInt(System.getProperty("java.version").split("\\.")[1]);
} catch (Exception e) {
logger.info("No se puede determinar la versión de Java: " + System.getProperty("java.version"));
}
}
String pathLibrary;
try {
pathLibrary = new File (mozillaFolder, getMozillaNSSLibraryName()).getCanonicalPath();
} catch (IOException e1) {
pathLibrary = new File (mozillaFolder, getMozillaNSSLibraryName()).getAbsolutePath();
}
if (javaVersion > 7) {
pathLibrary = "\"" + pathLibrary.replace('\\', '/') + "\"";
}
try {
//-- Configurar el provider
String providerConfig = "name = " + PROVIDER_NAME + "\r" +
"library = " + pathLibrary + "\r" +
"attributes= compatibility" + "\r" +
"slot=2\r" +
"nssArgs=\"" +
"configdir='" + secModFolder.getCanonicalPath() + "' " +
"certPrefix='' " +
"keyPrefix='' " +
"secmod=' secmod.db' " +
"flags=readWrite\"\r";
providerConfig = providerConfig.replaceAll("\\\\", "/");
//-- Inicializar el provider
ByteArrayInputStream localByteArrayInputStream = new ByteArrayInputStream(providerConfig.getBytes());
pk11provider = new SunPKCS11(localByteArrayInputStream);
Security.addProvider(pk11provider);
} catch (Throwable e) {
logger.info("[MozillaKeyStoreManager.initKeystores]::No se ha podido inicializar el proveedor criptográfico PKCS#11 para Mozilla Firefox", e);
throw new MozillaKeyStoreException("No se ha podido inicializar el proveedor criptográfico PKCS#11 para Mozilla Firefox", e);
}
} else {
pk11provider = (SunPKCS11) Security.getProvider(SUN_PROVIDER_PREFIX + PROVIDER_NAME);
}
try {
pk11provider.login(new Subject(), handler);
ks = KeyStore.getInstance("PKCS11",pk11provider);
ks.load(null, null);
lKeystores.add(ks);
} catch (Exception e) {
logger.info("[MozillaKeyStoreManager.initKeystores]::No se ha podido abrir el almacén de claves del usuario de Mozilla Firefox", e);
throw new MozillaKeyStoreException("No se ha podido abrir el almacén de claves del usuario de Mozilla Firefox", e);
}
//-- Tarjetas tratadas con Mozilla
pkcs11Managers = new ArrayList();
if (withoutPkcs11Modules) {
//-- No se usan los PKCS#11 asociados a Mozilla
logger.debug("[MozillaKeyStoreManager.initKeystores]::No se usan los PKCS#11 asociados a Mozilla");
} else {
logger.debug("[MozillaKeyStoreManager.initKeystores]::Se usan los PKCS#11 asociados a Mozilla");
Hashtable hmPkcs11Modules = getMozillaPKCS11Modules();
Enumeration enumeration = hmPkcs11Modules.keys();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
try {
MozillaPkcs11Manager pkcs11Manager = new MozillaPkcs11Manager(name, hmPkcs11Modules.get(name));
pkcs11Managers.add(pkcs11Manager);
} catch(InitializeProviderException e) {
logger.info("[MozillaKeyStoreManager.initKeystores]::No se ha podido cargar el proveedor PKCS#11 para el módulo '" + name + "'", e);
continue;
} catch(OpeningDeviceException e) {
logger.info("[MozillaKeyStoreManager.initKeystores]::No se ha podido abrir el almacén de claves del módulo '" + name + "'", e);
continue;
} catch (Throwable e) {
logger.info("[MozillaKeyStoreManager.initKeystores]::Error desconocido al cargar el proveedor PKCS#11 para el módulo '" + name + "'", e);
continue;
}
logger.debug("[MozillaKeyStoreManager.initKeystores]::Se ha inicializado el módulo '" + name + "'");
}
}
}
//-- Métodos privados
/**
* Obtiene la ruta a la carpeta donde se encuentra instalado Mozilla
*
* @return Ruta a la carpeta donde se encuentra instalado Mozilla
* @throws MozillaNotFoundException No se ha encontrado una instalación de Mozilla
*/
private File getMozillaFolder() throws MozillaNotFoundException {
//-- Si el SO es Windows ...
if (System.getProperty("os.name").contains("indows")) {
logger.debug("[MozillaKeyStoreManager]::Buscando la carpeta de instalación de Mozilla en Windows");
File mozillaFolder = new File (new File (System.getenv("PROGRAMFILES")), "Mozilla Firefox");
if (mozillaFolder.exists()) {
logger.debug("[MozillaKeyStoreManager]::Se ha encontrado la carpeta de instalación de Mozilla Firefox en Windows: " + mozillaFolder.getAbsolutePath());
//-- Si el path al directorio contiene caracteres extraños no funcionará la carga del
//-- provider PKCS#11 de SUN
if (mozillaFolder.getAbsolutePath().contains(")") ||
mozillaFolder.getAbsolutePath().contains("(") ||
mozillaFolder.getAbsolutePath().contains("\u007E")) {
//-- Copiar las DLLs a una ubicación temporal que será la que utilizará a partir de ahora
try {
logger.debug("[MozillaKeyStoreManager]::La carpeta de instalación de Mozilla Firefox en Windows contiene caracteres incompatibles con provider PKCS11: " + mozillaFolder.getAbsolutePath());
File mozillaTempFolder = File.createTempFile("mozillaTemp", null);
mozillaTempFolder.delete();
mozillaTempFolder.mkdirs();
try {
Util.copyFile(new File (mozillaFolder, "softokn3.dll"), new File (mozillaTempFolder, "softokn3.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "plc4.dll"), new File (mozillaTempFolder, "plc4.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "plds4.dll"), new File (mozillaTempFolder, "plds4.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "nspr4.dll"), new File (mozillaTempFolder, "nspr4.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "nss3.dll"), new File (mozillaTempFolder, "nss3.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "mozutils.dll"), new File (mozillaTempFolder, "mozutils.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "mozglue.dll"), new File (mozillaTempFolder, "mozglue.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "mozsqlite3.dll"), new File (mozillaTempFolder, "mozsqlite3.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "sqlite3.dll"), new File (mozillaTempFolder, "sqlite3.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "mozcrt19.dll"), new File (mozillaTempFolder, "mozcrt19.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "nssutil3.dll"), new File (mozillaTempFolder, "nssutil3.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "freebl3.dll"), new File (mozillaTempFolder, "freebl3.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "nssdbm3.dll"), new File (mozillaTempFolder, "nssdbm3.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "msvcr120.dll"), new File (mozillaTempFolder, "msvcr120.dll"));
} catch (FileNotFoundException e) {}
try {
Util.copyFile(new File (mozillaFolder, "msvcp120.dll"), new File (mozillaTempFolder, "msvcp120.dll"));
} catch (FileNotFoundException e) {}
return mozillaTempFolder;
} catch (Exception e) {
logger.info("[MozillaKeyStoreManager]::No se han podido cargar las DLLs de Mozilla Firefoxen una carpeta temporal", e);
throw new MozillaNotFoundException("No se han podido cargar las DLLs de Mozilla Firefoxen una carpeta temporal", e);
}
}
return mozillaFolder;
}
}
//-- Si el SO es Linux / Unix ...
if(System.getProperty("os.name").contains("inux") || System.getProperty("os.name").contains("SunOS") ||
System.getProperty("os.name").contains("olaris")){
logger.debug("[MozillaKeyStoreManager]::Buscando la carpeta con las librerías de Mozilla en Linux/Unix");
String[] posiblesCarpetas = new String[] {
"/usr/lib/firefox-" + searchLastFirefoxVersion("/usr/lib/"),
"/usr/lib/firefox",
"/opt/firefox",
"/opt/firefox-" + searchLastFirefoxVersion("/opt/"),
"/lib",
"/usr/lib",
"/usr/lib/nss",
"/opt/fedora-ds/clients/lib",
"/usr/lib/i386-linux-gnu/nss"
};
for(String carpeta : posiblesCarpetas) {
if (new File(new File (carpeta), "libsoftokn3.so").exists()) {
logger.debug("[MozillaKeyStoreManager]::Se ha encontrado la carpeta con las librerías de Mozilla Firefox en Linux/Unix: " + carpeta);
return new File(carpeta);
}
}
}
//-- Si el SO es Mac OS X
if(System.getProperty("os.name").startsWith("Mac OS X")) {
logger.debug("[MozillaKeyStoreManager]::Buscando la carpeta con las librerías de Mozilla en Mac OS X");
String[] posiblesCarpetas = new String[] {
"/Applications/Firefox.app/Contents/MacOS",
"/lib",
"/usr/lib",
"/usr/lib/nss",
"/Applications/Minefield.app/Contents/MacOS"
};
for(String carpeta : posiblesCarpetas) {
if (new File(new File (carpeta), "libsoftokn3.dylib").exists()) {
logger.debug("[MozillaKeyStoreManager]::Se ha encontrado la carpeta con las librerías de Mozilla Firefox en Mac OS X: " + carpeta);
return new File(carpeta);
}
}
}
logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta de instalación de Mozilla Firefox");
throw new MozillaNotFoundException("No se ha podido obtener la carpeta de instalación de Mozilla Firefox");
}
/**
* Obtiene la ruta donde se encuentran los ficheros con el almacén de claves de Mozilla
*
* @return Ruta donde se encuentran los ficheros con el almacén de claves de Mozilla
* @throws MozillaNotFoundException No se ha encontrado una instalación de Mozilla
*/
private File getSecModFolder() throws MozillaNotFoundException {
if (System.getProperty("os.name").contains("indows")) {
logger.debug("[MozillaKeyStoreManager]::Buscando el almacén de claves de Mozilla en Windows");
//-- Buscar carpeta de mozilla en Datos de Programa del usuario
File secModFolder = new File (new File (System.getenv("APPDATA")), "Mozilla/Firefox");
if (!secModFolder.exists() || secModFolder.list() == null || secModFolder.list().length == 0) {
logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario de Mozilla Firefox: " + secModFolder.getAbsolutePath());
throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario de Mozilla Firefox: " + secModFolder.getAbsolutePath());
}
logger.debug("[MozillaKeyStoreManager]::La carpeta de usuario de Mozilla Firefox es: " + secModFolder.getAbsolutePath());
//-- Obtener el fichero de inicio que contiene la referencia de los perfiles
File iniFile = new File (secModFolder, "profiles.ini");
if (!iniFile.exists() || !iniFile.isFile()) {
logger.info("[MozillaKeyStoreManager]::No se ha podido obtener el fichero de inicio con los perfiles de Mozilla: " + iniFile.getAbsolutePath());
throw new MozillaNotFoundException("No se ha podido obtener el fichero de inicio con los perfiles de Mozilla: " + iniFile.getAbsolutePath());
}
//-- Obtener la carpeta del perfil
try {
return getFirefoxProfileDirectory (iniFile);
} catch (IOException e) {
logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario en Windows", e);
throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario en Windows", e);
}
}
//-- Si estamos en un Linux...
logger.debug("[MozillaKeyStoreManager]::Buscando el almacén de claves de Mozilla en Linux");
File regFile = new File(new File (System.getProperty("user.home")), "/.mozilla/firefox/profiles.ini");
if (regFile.exists()) {
try {
return getFirefoxProfileDirectory(regFile);
} catch (Exception e) {
logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario en Linux", e);
throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario en Linux", e);
}
}
//-- Si es un Mac OS X...
regFile = new File(new File (System.getProperty("user.home")), "/Library/Application Support/Firefox/profiles.ini");
if (regFile.exists()) {
try {
return getFirefoxProfileDirectory(regFile);
} catch (Exception e) {
logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario en Mac OS X", e);
throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario en Mac OS X", e);
}
}
//-- Intento con el registro clasico de Mozilla
regFile = new File(new File (System.getProperty("user.home")), "/.mozilla/appreg");
if (regFile.exists()) {
try {
return getFirefoxProfileDirectory(regFile);
} catch (Exception e) {
logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario con el registro clásico de Mozilla", e);
throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario con el registro clásico de Mozilla", e);
}
}
logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario de Mozilla Firefox");
throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario de Mozilla Firefox");
}
/**
* Devuelve el directorio del perfil activo de Firefox. Si no hubiese perfil activo,
* devolvería el directorio del perfil por defecto y si tampoco lo hubiese
* el del primer perfil encontrado. Si no hubiese perfiles configurados,
* devolvería {@code null}.
*
* @param iniFile Fichero con la información de los perfiles de Firefox.
* @return Directorio con la información del perfil.
* @throws IOException Cuando ocurre un error abriendo o leyendo el fichero.
*/
private File getFirefoxProfileDirectory(File iniFile) throws IOException {
String currentProfilePath = null;
// Leemos el fichero con la informacion de los perfiles y buscamos el activo(el que esta bloqueado)
FirefoxProfile[] profiles = readProfiles(iniFile);
for (FirefoxProfile profile : profiles) {
if (isProfileLocked(profile)) {
currentProfilePath = profile.absolutePath;
logger.debug("[MozillaKeyStoreManager]::Perfil actual activo: " + currentProfilePath);
return new File (currentProfilePath);
}
}
// Si no hay ninguno actualmente activo, tomamos el por defecto
if (currentProfilePath == null) {
for (FirefoxProfile profile : profiles) {
if (profile.isDefault) {
currentProfilePath = profile.absolutePath;
logger.debug("[MozillaKeyStoreManager]::Perfil actual por defecto: " + currentProfilePath);
return new File (currentProfilePath);
}
}
}
// Si no hay ninguno por defecto, se toma el primero
if (profiles.length > 0) {
currentProfilePath = profiles[0].absolutePath;
logger.debug("[MozillaKeyStoreManager]::Primer perfil: " + currentProfilePath);
return new File (currentProfilePath);
}
return null;
}
/**
* Parsea la informacion de los perfiles declarada en el fichero "profiles.ini".
* Para identificar correctamente los perfiles es necesario que haya al menos una
* línea de separación entre los bloques de información de
* cada perfil.
* @param iniFile Fichero con lainformación de los perfiles.
* @return Listado de perfiles completos encontrados.
* @throws IOException Cuando se produce un error durante la lectura de la
* configuración.
*/
private static FirefoxProfile[] readProfiles(File iniFile) throws IOException {
final String NAME_ATR = "name=";
final String IS_RELATIVE_ATR = "isrelative=";
final String PATH_PROFILES_ATR = "path=";
final String IS_DEFAULT_ATR = "default=";
String line = null;
Vector profiles = new Vector();
BufferedReader in = new BufferedReader(new FileReader(iniFile));
try {
while ((line = in.readLine()) != null) {
// Buscamos un nuevo bloque de perfil
if (!line.trim().toLowerCase().startsWith("[profile"))
continue;
FirefoxProfile profile = new FirefoxProfile();
while((line = in.readLine()) != null && line.trim().length() > 0 && !line.trim().toLowerCase().startsWith("[profile")) {
if(line.trim().toLowerCase().startsWith(NAME_ATR))
profile.name = line.trim().substring(NAME_ATR.length());
else if(line.trim().toLowerCase().startsWith(IS_RELATIVE_ATR))
profile.isRelative = line.trim().substring(IS_RELATIVE_ATR.length()).equals("1");
else if(line.trim().toLowerCase().startsWith(PATH_PROFILES_ATR))
profile.path = line.trim().substring(PATH_PROFILES_ATR.length());
else if(line.trim().toLowerCase().startsWith(IS_DEFAULT_ATR))
profile.isDefault = line.trim().substring(IS_DEFAULT_ATR.length()).equals("1");
else
break;
}
// Debemos encontrar al menos el nombre y la ruta del perfil
if (profile.name != null || profile.path != null) {
profile.absolutePath =
profile.isRelative ?
new File(iniFile.getParent(), profile.path).toString() :
profile.path;
profiles.add(profile);
}
}
} catch (Exception e) {
throw new IOException("Ocurrio un error al leer la configuracion de los perfiles de Firefox: " + e);
} finally {
try { in.close(); } catch (Exception e) {}
}
return profiles.toArray(new FirefoxProfile[profiles.size()]);
}
/**
* Comprueba que un perfil de Firefox esté bloqueado. Un perfil esta
* bloqueado cuando en su directorio se encuentra el fichero "parent.lock".
* @param profile Información del perfil de Firefox.
* @return Devuelve {@code true} si el perfil esta bloqueado, {@code false}
* en caso contrario.
*/
private static boolean isProfileLocked(FirefoxProfile profile) {
return new File(profile.absolutePath, "parent.lock").exists() || // En Windows
new File(profile.absolutePath, "lock").exists(); // En Linux
}
/**
* Obtiene el nombre de la librería del provider NSS dependiendo del SO
*
* @return Nombre de la librería del provider NSS
*/
private String getMozillaNSSLibraryName () {
String nssLib = "libsoftokn3.so";
if(System.getProperty("os.name").contains("indows")) {
nssLib = "softokn3.dll";
}
else if (System.getProperty("os.name").startsWith("Mac OS X")) {
nssLib = "libsoftokn3.dylib";
}
return nssLib;
}
/**
* Carga las librerías de mozilla
*
* @param mozillaFolder Carpeta donde se encuentran las librerías
*/
private void loadMozillaLibraries(File mozillaFolder) throws Throwable {
logger.debug("[MozillaKeyStoreManager.loadMozillaLibraries]::Entrada::" + mozillaFolder);
String[] librariesNames = null;
if (!System.getProperty("os.name").contains("indows") && !System.getProperty("os.name").contains("inux")
&& !System.getProperty("os.name").contains("SunOS") && !System.getProperty("os.name").contains("olaris")) {
throw new Exception ("Sistema Operativo desconocido");
}
// Fedora: librerías entre /usr/lib y /lib
if (System.getProperty("os.name").contains("inux") && new File("/usr/lib/libsoftokn3.so").exists()
&& new File("/lib/libnspr4.so").exists()) {
librariesNames = new String[] {
System.mapLibraryName("/usr/lib/libsoftokn3"),
System.mapLibraryName("/lib/libplds4"),
System.mapLibraryName("/usr/lib/libplds4"),
System.mapLibraryName("/lib/libplc4"),
System.mapLibraryName("/usr/lib/libplc4"),
System.mapLibraryName("/lib/libnssutil3"),
System.mapLibraryName("/usr/lib/libnssutil3"),
System.mapLibraryName("/lib/libsqlite3"),
System.mapLibraryName("/usr/lib/libsqlite3"),
System.mapLibraryName("/lib/libmozsqlite3"),
System.mapLibraryName("/usr/lib/libmozsqlite3")
};
}
//-- Windows
if (System.getProperty("os.name").contains("indows")) {
librariesNames = new String[] {
System.mapLibraryName("msvcr120"),
System.mapLibraryName("msvcp120"),
System.mapLibraryName("mozcrt19"),
System.mapLibraryName("mozutils"),
System.mapLibraryName("mozglue"),
System.mapLibraryName("sqlite3"),
System.mapLibraryName("mozsqlite3"),
System.mapLibraryName("nspr4"),
System.mapLibraryName("plds4"),
System.mapLibraryName("plc4"),
System.mapLibraryName("nss3"),
System.mapLibraryName("nssutil3"),
System.mapLibraryName("nssdbm3"),
System.mapLibraryName("freebl3"),
System.mapLibraryName("nss3")
};
}
//-- Linux/Unix
if (System.getProperty("os.name").contains("inux") || System.getProperty("os.name").contains("SunOS") ||
System.getProperty("os.name").contains("olaris")) {
librariesNames = new String[] {
"libnspr4.so",
"libplds4.so",
"libplc4.so",
"libnssutil3.so",
"libsqlite3.so",
"libmozsqlite3.so"
};
}
for(String libraryName : librariesNames) {
try {
System.load(new File (mozillaFolder, libraryName).getAbsolutePath());
} catch (UnsatisfiedLinkError e) {
logger.debug("No se ha podido cargar la librería: " + new File (mozillaFolder, libraryName).getAbsolutePath(), e);
}
}
}
/**
* Obtiene las rutas completas hacia las bibliotecas (.dll o .so) de los módulos de seguridad externos (PKCS#11)
* instalados en Mozilla / Firefox, indexados por su descripción dentro de una
* Hashtable
.
* @return Nombres de las bibliotecas de los módulos de seguridad de Mozilla / Firefox
*/
private Hashtable getMozillaPKCS11Modules() {
try {
final Hashtable modsByDesc = new Hashtable();
for(MozillaPkcs11Module module : getCardModules()) {
modsByDesc.put(module.getName(), module.getLibrary());
}
return modsByDesc;
}
catch(Throwable t) {
return new Hashtable(0);
}
}
/**
* Obtiene los módulos de seguridad PKCS#11 instalados en la base de datos secmod.db.
* @return Vector con los módulos encontrados, el vector estará vacío
* si se encuentra algún problema durante la b´squeda
* @throws Exception
* @throws AOException Cuando ocurre cualquier problema durante el proceso
*/
public ArrayList getCardModules() throws Exception {
File secmod = new File(getSecModFolder(), "secmod.db");
if (!secmod.exists()) {
throw new Exception("El directorio del perfil de Mozilla proporcionado no contiene una base de datos de modulos (secmod.db)");
}
ArrayList lModules = new ArrayList();
HashSet moduleNames = new HashSet();
String contenidoSecmod = new String (Util.loadFile(secmod));
while(contenidoSecmod.indexOf(".dll") > -1) {
int index = contenidoSecmod.indexOf(".dll") + 3;
MozillaPkcs11Module module = getModule(contenidoSecmod, ".dll");
if (!moduleNames.contains(module.getName())) {
lModules.add (module);
moduleNames.add(module.getName());
}
contenidoSecmod = contenidoSecmod.substring(0, index) + contenidoSecmod.substring (contenidoSecmod.indexOf(".dll") + 4);
}
while(contenidoSecmod.indexOf(".DLL") > -1) {
int index = contenidoSecmod.indexOf(".DLL") + 3;
MozillaPkcs11Module module = getModule(contenidoSecmod, ".DLL");
if (!moduleNames.contains(module.getName())) {
lModules.add (module);
moduleNames.add(module.getName());
}
contenidoSecmod = contenidoSecmod.substring(0, index) + contenidoSecmod.substring (contenidoSecmod.indexOf(".DLL") + 4);
}
while(contenidoSecmod.indexOf(".so") > -1) {
int index = contenidoSecmod.indexOf(".so") + 2;
MozillaPkcs11Module module = getModule(contenidoSecmod, ".so");
if (!moduleNames.contains(module.getName())) {
lModules.add (module);
moduleNames.add(module.getName());
}
contenidoSecmod = contenidoSecmod.substring(0, index) + contenidoSecmod.substring (contenidoSecmod.indexOf(".so") + 3);
}
while(contenidoSecmod.indexOf(".dylib") > -1) {
int index = contenidoSecmod.indexOf(".dylib") + 5;
MozillaPkcs11Module module = getModule(contenidoSecmod, ".dylib");
if (!moduleNames.contains(module.getName())) {
lModules.add (module);
moduleNames.add(module.getName());
}
contenidoSecmod = contenidoSecmod.substring(0, index) + contenidoSecmod.substring (contenidoSecmod.indexOf(".dylib") + 5);
}
return lModules;
}
public MozillaPkcs11Module getModule(String contenidoSecmod, String extension) throws Exception {
while(contenidoSecmod.indexOf(extension) > -1) {
int index = contenidoSecmod.indexOf(extension) + (extension.length() - 1);
String name = "", module = "";
while (contenidoSecmod.charAt(index) != 0) {
module = contenidoSecmod.charAt(index) + module;
index--;
}
if (module.length() > 0) {
module = module.substring(1);
}
index--;
while (contenidoSecmod.charAt(index) != 0) {
name = contenidoSecmod.charAt(index) + name;
index--;
}
if (name.length() > 0) {
name = name.substring(1);
}
return new MozillaPkcs11Module(getValidName (name), new File (module));
}
return null;
}
/*
* Obtiene un nombre válido para un provider: sólo letras y números
*/
private String getValidName(String name) {
return name.replaceAll("[^a-zA-Z1-9]", "");
}
/**
* Copyright (C) 2011 [Gobierno de Espana]
* Este método forma parte del "Cliente @Firma".
*
* Busca la última versión de firefox instalada en un sistema
* Linux o Solaris
* @return Última versión instalada en el sistema */
private static String searchLastFirefoxVersion(final String startDir) {
final File directoryLib = new File(startDir);
if (directoryLib.isDirectory()) {
final String filenames[] = directoryLib.list();
final List firefoxDirectories = new ArrayList();
for (final String filename : filenames) {
if (filename.startsWith("firefox-")) { //$NON-NLS-1$
firefoxDirectories.add(filename.replace("firefox-", "")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (firefoxDirectories.isEmpty()) {
return ""; //$NON-NLS-1$
}
for (int i = 0; i < firefoxDirectories.size(); i++) {
try {
Integer.getInteger(firefoxDirectories.get(i));
}
catch (final Exception e) {
firefoxDirectories.remove(i);
}
}
if (firefoxDirectories.size() == 1) {
return firefoxDirectories.get(0);
}
Collections.sort(firefoxDirectories, new Comparator() {
/** {@inheritDoc} */
public int compare(final String o1, final String o2) {
return o1.compareTo(o2);
}
});
return firefoxDirectories.get(0);
}
return ""; //$NON-NLS-1$
}
/**
* Copyright (C) 2011 [Gobierno de Espana]
* Este método forma parte del "Cliente @Firma".
*/
private void configureMacNSS(File mozillaFolder) throws Throwable {
// Intentamos la carga, para ver si es necesaria la reconfiguracion
try {
System.load(new File (mozillaFolder, "libsoftokn3.dylib").getAbsolutePath()); //$NON-NLS-1$
return; // Si funciona salimos sin hacer nada
}
catch (final Exception e) {
// Se ignora el error
}
catch(final UnsatisfiedLinkError e) {
// Se ignora el error
}
final String[] libs = new String[] {
"libmozutils.dylib", // Firefox 9 y superiores //$NON-NLS-1$
"libnspr4.dylib", //$NON-NLS-1$
"libplds4.dylib", //$NON-NLS-1$
"libplc4.dylib", //$NON-NLS-1$
"libmozsqlite3.dylib", //$NON-NLS-1$
"libnssutil3.dylib" //$NON-NLS-1$
};
// Creamos enlaces simbolicos via AppleScript
final StringBuilder sb = new StringBuilder();
for (final String lib : libs) {
File fileLib = new File(mozillaFolder, lib);
if (fileLib.exists()) {
sb.append("ln -s "); //$NON-NLS-1$
sb.append(fileLib.getAbsolutePath());
sb.append(" /usr/lib/"); //$NON-NLS-1$
sb.append(lib);
sb.append("; "); //$NON-NLS-1$
}
}
try {
final Class> scriptEngineManagerClass = Class.forName("javax.script.ScriptEngineManager"); //$NON-NLS-1$
final Object scriptEngineManager = scriptEngineManagerClass.newInstance();
final Method getEngineByNameMethod = scriptEngineManagerClass.getMethod("getEngineByName", String.class); //$NON-NLS-1$
final Object scriptEngine = getEngineByNameMethod.invoke(scriptEngineManager, "AppleScript"); //$NON-NLS-1$
final Class> scriptEngineClass = Class.forName("javax.script.ScriptEngine"); //$NON-NLS-1$
final Method evalMethod = scriptEngineClass.getMethod("eval", String.class); //$NON-NLS-1$
evalMethod.invoke(scriptEngine, "do shell script \"" + sb.toString() + "\" with administrator privileges"); //$NON-NLS-1$ //$NON-NLS-2$
//new ScriptEngineManager().getEngineByName("AppleScript").eval("do shell script \"" + sb.toString() + "\" with administrator privileges");
}
catch(final Exception e) {
logger.info("No se ha podido crear los enlaces simbolicos para NSS: " + e, e); //$NON-NLS-1$
}
// Y reintentamos la carga, para ver si ha surtido efecto
try {
System.load(new File (mozillaFolder, "libsoftokn3.dylib").getAbsolutePath()); //$NON-NLS-1$
}
catch (final Exception e) {
//-- A veces se carga dos veces en la misma JVM y la segunda pasa por estas excepciones
//-- porque ya ha cargado la librería, así que lo mejor es seguir
// throw new Exception("La configuracion de NSS para Mac OS X ha fallado por motivos de seguridad: " + e); //$NON-NLS-1$
}
catch(final UnsatisfiedLinkError e) {
// throw new Exception("La configuracion de NSS para Mac OS X ha fallado: " + e); //$NON-NLS-1$
}
}
//-- Clases
/**
* Clase que almacena la configuración para la identificacion de un perfil
* de Mozilla Firefox.
*/
private static class FirefoxProfile {
String name = null;
boolean isRelative = true;
String path = null;
String absolutePath = null;
boolean isDefault = false;
}
/**
* Clase que representa la información de un módulo pkcs#11 definido en Mozilla
*/
public class MozillaPkcs11Module {
private String name;
private File library;
public MozillaPkcs11Module(String name, File library) {
super();
this.name = name;
this.library = library;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public File getLibrary() {
return library;
}
public void setLibrary(File library) {
this.library = library;
}
}
}