es.gob.afirma.crypto.jarverifier.JarSignatureCertExtractor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of afirma-crypto-jarverifier Show documentation
Show all versions of afirma-crypto-jarverifier Show documentation
Modulo para la comprobacion de la firma de un JAR
/* Copyright (C) 2011 [Gobierno de Espana]
* This file is part of "Cliente @Firma".
* "Cliente @Firma" is free software; you can redistribute it and/or modify it under the terms of:
* - the GNU General Public License as published by the Free Software Foundation;
* either version 2 of the License, or (at your option) any later version.
* - or The European Software License; either version 1.1 or (at your option) any later version.
* You may contact the copyright holder at: [email protected]
*/
package es.gob.afirma.crypto.jarverifier;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.CodeSource;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.spongycastle.cert.X509CertificateHolder;
import org.spongycastle.cms.CMSException;
import org.spongycastle.cms.CMSSignedData;
import org.spongycastle.util.Store;
import es.gob.afirma.core.misc.AOUtil;
import es.gob.afirma.core.ui.AOUIFactory;
/** Clase de utilidad para obtener los certificados de la firma del JAR
* que contiene a esta propia clase.
* Basado en la implementación de la Universidad de Murcia
* @author Tomás García-Merás */
public final class JarSignatureCertExtractor {
private static final Logger LOGGER = Logger.getLogger("es.gob.afirma"); //$NON-NLS-1$
private static final int BUFFER_SIZE = 1024;
private static final String SIGNATURE_DIR_PATH = "META-INF/"; //$NON-NLS-1$
private static final String SIGNATURE_EXT_RSA = ".RSA"; //$NON-NLS-1$
private static final String SIGNATURE_EXT_DSA = ".DSA"; //$NON-NLS-1$
private static final String USER_HOME = "$USER_HOME"; //$NON-NLS-1$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private static final String[] CACERTS_DEFAULT_PASSWORDS = {
EMPTY_STRING,
"changeit", //$NON-NLS-1$
"changeme", //$NON-NLS-1$
};
private static String keystorePassword = null;
private JarSignatureCertExtractor() {
// No permitimos la instanciacion
}
static X509Certificate[] getJarSignatureCertChain(final byte[] signature) throws IOException, CertificateException {
if (signature == null) {
return null;
}
final CMSSignedData signedData;
try {
signedData = new CMSSignedData(signature);
}
catch (final CMSException e) {
LOGGER.severe(
"La firma proporcionada no es un SignedData compatible CMS, se devolvera una lista de certificados vacia: " + e //$NON-NLS-1$
);
return new X509Certificate[0];
}
final Store store = signedData.getCertificates();
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
final List ret = new ArrayList<>();
final Iterator certIt = store.getMatches(null).iterator();
while (certIt.hasNext()) {
final X509Certificate cert = (X509Certificate) certFactory.generateCertificate(
new ByteArrayInputStream(
certIt.next().getEncoded()
)
);
ret.add(cert);
}
return ret.toArray(new X509Certificate[0]);
}
static byte[] getJarSignature(final InputStream jarIs) throws IOException {
int n = 0;
ZipEntry e;
ByteArrayOutputStream baos = null;
final byte[] buffer = new byte[BUFFER_SIZE];
final ZipInputStream zip = new ZipInputStream(jarIs);
while((e = zip.getNextEntry()) != null) {
final String name = e.getName();
if (name.startsWith(SIGNATURE_DIR_PATH) && (name.endsWith(SIGNATURE_EXT_RSA) || name.endsWith(SIGNATURE_EXT_DSA))) {
baos = new ByteArrayOutputStream();
while ((n = zip.read(buffer)) > 0) {
baos.write(buffer, 0, n);
}
break;
}
}
return baos == null ? null : baos.toByteArray();
}
private static InputStream getJarInputStream() throws IOException {
final CodeSource src = JarSignatureCertExtractor.class.getProtectionDomain().getCodeSource();
if (src == null) {
throw new IOException("No se ha podido acceder a los recursos del JAR"); //$NON-NLS-1$
}
return src.getLocation().openStream();
}
/**
* Recupera el fichero con el almacén de CAs de confianza del usuario.
* Si no lo localiza ni lo puede crear, devuelve {@code null}.
* @return Fichero con el almacén de CAs de confianza del usuario.
*/
private static File getUsersJavaCaKeyStoreFile() {
String keystoreFilename = System.getProperty(
"deployment.user.security.trusted.cacerts" //$NON-NLS-1$
);
// Comprobacion por el error http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7140869
if (keystoreFilename != null && keystoreFilename.contains(USER_HOME)) {
keystoreFilename = keystoreFilename.replace(
USER_HOME,
System.getProperty("user.home") //$NON-NLS-1$
);
}
final File ret = keystoreFilename != null ? new File(keystoreFilename) : null;
// Devolvemos el fichero con el truststore si existe su directorio padre, ya que entonces
// o existe o lo podemos crear en ese directorio
return ret != null && ret.getParentFile().exists() ? ret : null;
}
/**
* Recupera el fichero con el almacén de CAs de confianza del sistema.
* Si no lo localiza, devuelve {@code null}.
* @return Fichero con el almacén de CAs de confianza del sistema.
*/
private static File getSystemsJavaCaKeyStoreFile() {
final String keystoreFilename = System.getProperty(
"deployment.system.security.cacerts" //$NON-NLS-1$
);
if (keystoreFilename == null) {
return null;
}
final File ret = new File(keystoreFilename);
return ret.exists() ? ret : null;
}
private static KeyStore getJavaCaKeyStore(final File storeFile) throws KeyStoreException,
NoSuchAlgorithmException,
CertificateException,
IOException {
final KeyStore ks;
try (
final FileInputStream fis = new FileInputStream(storeFile);
) {
ks = KeyStore.getInstance("JKS"); //$NON-NLS-1$
for (final String password : CACERTS_DEFAULT_PASSWORDS) {
try {
ks.load(fis, password.toCharArray());
keystorePassword = password;
break;
}
catch (final IOException e) {
// Si el error no se debe a una clave erronea, la subimos
if (!(e.getCause() instanceof UnrecoverableKeyException)) {
fis.close();
throw e;
}
}
}
}
return ks;
}
private static void checkCertChain(final X509Certificate[] chain,
final KeyStore trustStore) throws CertPathValidatorException,
KeyStoreException,
InvalidAlgorithmParameterException,
CertificateException,
NoSuchAlgorithmException {
// Si no hay certificados en el almacen, no estara entre los certificados de confianza
if (trustStore.size() == 0) {
throw new CertPathValidatorException("No hay certificados en el almacen de confianza"); //$NON-NLS-1$
}
// Miramos si el certificado mas elevado de la cadena esta en el trustrore,
// en cuyo caso no hacemos nada
final X509Certificate chainEdge = chain[chain.length - 1];
final Enumeration aliases = trustStore.aliases();
while (aliases.hasMoreElements()) {
if (chainEdge.getSerialNumber().equals(
((X509Certificate) trustStore.getCertificate(aliases.nextElement())).getSerialNumber())) {
LOGGER.info("El extremo de la cadena de certificados esta en el truststore de Java"); //$NON-NLS-1$
return;
}
}
// Comprobamos ahora la cadena normalmente
final PKIXParameters params = new PKIXParameters(trustStore);
params.setRevocationEnabled(false);
CertPathValidator.getInstance(CertPathValidator.getDefaultType()).validate(
CertificateFactory.getInstance("X.509").generateCertPath(Arrays.asList(chain)), //$NON-NLS-1$
params
);
}
/** Inserta los certificados con los que se ha firmado el JAR que contiene esta clase en
* el almacén de certificados raíz del JRE, con el permiso del usuario.
* @param dialogParent Padre para el diálogo de solicitud de permiso
* @throws KeyStoreException Si no se puede tratar el almacén de certificados raíz del JRE
* @throws NoSuchAlgorithmException Si no se soporta algún algoritmo necesario
* @throws CertificateException Cuando ocurren errores relacionados con los certificados X.509
* @throws IOException Cuando ocurren errores de entrada / salida
* @throws InvalidAlgorithmParameterException Si no se soporta algún parámetro necesario
* para algún algoritmo */
public static void insertJarSignerOnCACerts(final Object dialogParent) throws KeyStoreException,
NoSuchAlgorithmException,
CertificateException,
IOException,
InvalidAlgorithmParameterException {
// Primero, obtenemos los certificados con los que se ha firmado la aplicacion
final byte[] signature;
try (
final InputStream jarIs = getJarInputStream();
) {
signature = getJarSignature(jarIs);
}
final X509Certificate[] certs = getJarSignatureCertChain(signature);
if (certs == null || certs.length < 1) {
LOGGER.warning("La aplicacion no esta firmada"); //$NON-NLS-1$
return;
}
// A continuacion, comprobamos si esos certificados son de confianza para el sistema
final File systemsCaCertFile = getSystemsJavaCaKeyStoreFile();
if (systemsCaCertFile != null) {
try {
checkCertChain(certs, getJavaCaKeyStore(systemsCaCertFile));
// Si no salta excepcion, salimos, porque ha validado y no hay que hacer nada
LOGGER.warning("Los certificados de firma del JAR son de confianza en Java"); //$NON-NLS-1$
return;
}
catch (final Exception e) {
LOGGER.warning(
"Error en la validacion de los certificados contra el almacen de Java: " + e //$NON-NLS-1$
);
// Si falla continuamos con el almacen de confianza del usuario
}
}
// Si no son de confianza para el sistema, comprobamos si lo son para el usuario
// Cargamos el fichero del almacen
final File usersCaCertFile = getUsersJavaCaKeyStoreFile();
if (usersCaCertFile == null) {
LOGGER.warning("No se puede localizar el almacen de confianza del usuario, se suspende la validacion"); //$NON-NLS-1$
return;
}
// Si existe el fichero, lo cargamos, si no existe pero se puede crear, lo creamos
final KeyStore usersTruststore;
if (!usersCaCertFile.exists()) {
keystorePassword = EMPTY_STRING;
usersTruststore = KeyStore.getInstance(KeyStore.getDefaultType());
usersTruststore.load(null, keystorePassword.toCharArray());
LOGGER.info("Creamos el truststore ya que no existia previamente"); //$NON-NLS-1$
}
else {
try {
usersTruststore = getJavaCaKeyStore(usersCaCertFile);
}
catch (final Exception e) {
LOGGER.warning("No se ha podido cargar el almacen de certificados de CA de confianza del usuario, no se agregara el certificado: " + e); //$NON-NLS-1$
return;
}
}
// Comprobamos si el extremo de la cadena es de confianza o no
try {
checkCertChain(certs, usersTruststore);
// Si no salta excepcion, salimos, porque ha validado y no hay que hacer
// nada
LOGGER.info("Los certificados de firma del JAR ya son de confianza para el usuario"); //$NON-NLS-1$
return;
}
catch (final CertPathValidatorException e) {
LOGGER.warning("Debemos agregar el certificado al truststore del usuario para que sea de confianza: " + e); //$NON-NLS-1$
// Se ignora, porque si falla la validacion es que debemos continuar
// normalmente con el proceso, ya que significa que no se valida la
// cadena y hay que insertar la raiz
}
// Creamos la lista de certificados que se van a insertar para preguntarle al usuario
final StringBuilder sb = new StringBuilder("
"); //$NON-NLS-1$
for (final X509Certificate cert : certs) {
sb.append(" - "); //$NON-NLS-1$
sb.append(AOUtil.getCN(cert));
// El dialogo tendra formato HTML
sb.append("
"); //$NON-NLS-1$
}
if (AOUIFactory.showConfirmDialog(
dialogParent,
"" + //$NON-NLS-1$
JarSignatureCertExtractorMessages.getString("JarSignatureCertExtractor.0") + //$NON-NLS-1$
"
" + //$NON-NLS-1$
JarSignatureCertExtractorMessages.getString("JarSignatureCertExtractor.1") + //$NON-NLS-1$
"
" + //$NON-NLS-1$
JarSignatureCertExtractorMessages.getString("JarSignatureCertExtractor.2") + //$NON-NLS-1$
sb.toString() +
"
", //$NON-NLS-1$
JarSignatureCertExtractorMessages.getString("JarSignatureCertExtractor.3"), //$NON-NLS-1$
AOUIFactory.YES_NO_OPTION,
AOUIFactory.WARNING_MESSAGE
) == AOUIFactory.NO_OPTION) {
return;
}
for (final X509Certificate cert : certs) {
usersTruststore.setCertificateEntry(
AOUtil.getCN(cert) + cert.getSerialNumber(),
cert
);
}
try (
final OutputStream fos = new FileOutputStream(usersCaCertFile);
) {
usersTruststore.store(fos, keystorePassword.toCharArray());
}
LOGGER.info("Se han insertado correctamente certificados en el cacerts del usuario"); //$NON-NLS-1$
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy