Hola a todos,
En esta ocasión quiero compartir con vosotros algo en lo que todos nos vemos envueltos algunas vez, firmas digitales... toda una aventura :).
En este articulo os mostraré como, a partir de un archivo .pfx, poder realizar peticiones XML firmadas desde java, para ello dividiremos el articulo en dos partes:
Transformar archivo pfx en jks
Existen formas de hacer esta transformacion en varios pasos, con la ayuda de openssl, en este articulo usaremos una característica del comando del jdk keytool que se introdujo en la version 6 de java (no estoy muy seguro de si en la versión 5 de la jdk ya venía).
Usaremos el siguiente comando:
"C:\Archivos de programa\Java\jdk1.6.0_17\bin\keytool" -importkeystore -srckeystore tu_archivo_pfx
-destkeystore FirmaKeystore -srcstoretype pkcs12 -deststoretype jks -srcstorepass clave_de_tu_pfx
-deststorepass clave_para_tu_nuevo_almacen -srcalias c4a3f5f494165a13ccbb1473e3b69c6f_1f97c812-2351-470b-aae7-10698e811751 -destalias nuevo_alias -v
Aunque el ejemplo está indicado para windows, en unix funciona igual. Como se ve, hay algunos valores en azul, los comentamos:
- srckeystore: Es la ruta a tu archivo pfx de origen.
- srcstorepass: Clave del archivo pfx, que nos debe dar el proveedor del certificado.
- deststorepass: Clave que asignaremos a nuestro nuevo almacen (en ejemplo llamado FirmaKeystore).
- destalias: Alias que asignaremos en nuestro almacen al certificado que se genera.
Aún queda un punto pendiente, en 'srcalias' tenemoms un 'chorizo' que no es aleatorio, es el alias dentro del pfx del certificado, para obtener este dato ejecutamos el siguiente comando:
keytool -list -storetype pkcs12 -keystore your_pfx_file -v | grep Alias
Se observa el comando '| grep Alias' al final, esto solo es valido para unix, en windows lo
copiaremos de la salida de comando en pantalla. Usamos ese valor en la opcion 'srcalias'.
Con esto ya tenemos nuestro almacen generado.
Firmar XMLs
Una vez que tenemos nuestro almacen generado, con el siguiente código firmamos nuestro XML. Se usa el API de Apache (abajo teneis los imports)
/**
* Punto de entrada al ejemplo.
*/
public Document firmarXml(Document doc) throws Exception {
System.out.println("/ INICIO.");
// Obtenemos las propiedades para firmar el documento.
String sTipoAlmacen = "jks";
String sAlmacen = "ruta_a_nuestro_almacen_generado_en_el_punto1";
String sClaveAlmacen = "clave_que_dimos_en_deststorepass";
String sClavePrivada = "valor_de_srcstorepass";
String sAlias = "valor_de_destalias";
org.apache.xml.security.Init.init();
Constants.setSignatureSpecNSprefix("ds"); // Sino, pone por defecto como prefijo: "ns"
// Cargamos el almacen de claves
KeyStore ks = KeyStore.getInstance(sTipoAlmacen);
ks.load(new FileInputStream(sAlmacen), sClaveAlmacen.toCharArray());
// Obtenemos la clave privada, pues la necesitaremos para encriptar.
PrivateKey privateKey = (PrivateKey) ks.getKey(sAlias, sClavePrivada.toCharArray());
File signatureFile = new File("signature.xml");
String baseURI = signatureFile.toURI().toString(); // BaseURI para las URL Relativas.
// Instanciamos un objeto XMLSignature desde el Document. El algoritmo de firma será RSA
XMLSignature xmlSignature = new XMLSignature(doc, baseURI, XMLSignature.ALGO_ID_SIGNATURE_RSA);
// Añadimos el nodo de la firma a la raiz antes de firmar.
// Observe que ambos elementos pueden ser mezclados en una forma con referencias separadas
doc.getDocumentElement().appendChild(xmlSignature.getElement());
// Creamos el objeto que mapea: Document/Reference
Transforms transforms = new Transforms(doc);
transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
transforms.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS);
// Añadimos lo anterior Documento / Referencia
// ALGO_ID_DIGEST_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1";
xmlSignature.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);
// Añadimos el KeyInfo del certificado cuya clave privada usamos
X509Certificate cert = (X509Certificate) ks.getCertificate(sAlias);
xmlSignature.addKeyInfo(cert.getPublicKey());
xmlSignature.addKeyInfo(cert);
// Realizamos la firma
xmlSignature.sign(privateKey);
System.out.println("\\ FIN.");
return doc;
}
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
La entrada del método es el XML a firmar en formato org.w3c.dom.Document, el cual es devuelto al final del proceso.
Recordar que debemos importar a nuestro almacen cualquier certificado intermedio o de root que
complete la ruta de certificación del certificado que hemos transformado, ya que si no
no se podrá completar la ruta de certificación.
Espero que os sirva, y cualquier cosa me comentais
Hasta pronto!!
UPDATE: El comando correcto es -importkeystore, no -import keystore como estaba incialmente indicado. Ya está corregido!! :)