diff --git a/.gitignore b/.gitignore index a1c2a23..3c8187e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,81 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser \ No newline at end of file diff --git a/README.md b/README.md index fb66b23..ab9ef58 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ # firma-factura-electronica-siat-java Firmado de documentos XML para Factura electrónica SIAT Bolivia + +## Build +[IntelliJ IDEA](https://www.jetbrains.com/es-es/idea/) + +## Documentation +[Factura Electrónica -SIAT](https://siatinfo.impuestos.gob.bo/index.php/facturacion-en-linea/factura-electronica) + +## Authors +[marcelo.romero](https://siatinfo.impuestos.gob.bo/index.php/facturacion-en-linea/firma-digital/firmado-de-xml) +[Diego Guillen](https://www.linkedin.com/in/diegoezequielguillen) \ No newline at end of file diff --git a/TestFirma/.idea/.gitignore b/TestFirma/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/TestFirma/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/TestFirma/.idea/compiler.xml b/TestFirma/.idea/compiler.xml new file mode 100644 index 0000000..0d416f9 --- /dev/null +++ b/TestFirma/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/TestFirma/.idea/encodings.xml b/TestFirma/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/TestFirma/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/TestFirma/.idea/jarRepositories.xml b/TestFirma/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/TestFirma/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/TestFirma/.idea/misc.xml b/TestFirma/.idea/misc.xml new file mode 100644 index 0000000..accd629 --- /dev/null +++ b/TestFirma/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TestFirma/.idea/uiDesigner.xml b/TestFirma/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/TestFirma/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TestFirma/pom.xml b/TestFirma/pom.xml new file mode 100644 index 0000000..62bbe35 --- /dev/null +++ b/TestFirma/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + bo.sin + TestFirma + 1.0-SNAPSHOT + jar + + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + org.bouncycastle + bcprov-jdk15on + 1.56 + + + org.bouncycastle + bcpkix-jdk15on + 1.56 + + + org.apache.santuario + xmlsec + 2.0.5 + jar + + + commons-io + commons-io + 2.5 + test + jar + + + org.slf4j + slf4j-api + 1.7.36 + + + org.slf4j + slf4j-simple + 1.7.36 + + + + + UTF-8 + 1.8 + 1.8 + + + + + src/main/resources + + + + \ No newline at end of file diff --git a/TestFirma/src/main/java/bo/sin/testfirma/Firmador.java b/TestFirma/src/main/java/bo/sin/testfirma/Firmador.java new file mode 100644 index 0000000..6523772 --- /dev/null +++ b/TestFirma/src/main/java/bo/sin/testfirma/Firmador.java @@ -0,0 +1,177 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package bo.sin.testfirma; + +/** + * + * @author marcelo.romero + */ + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.commons.codec.binary.Base64; +import org.apache.xml.security.Init; +import org.apache.xml.security.algorithms.MessageDigestAlgorithm; +import org.apache.xml.security.exceptions.XMLSecurityException; +import org.apache.xml.security.signature.XMLSignature; +import org.apache.xml.security.transforms.Transforms; +import org.apache.xml.security.utils.Constants; +import org.apache.xml.security.utils.ElementProxy; +import org.apache.xml.security.utils.XMLUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +/** + * + * @author + */ + +public class Firmador { + // http://stackoverflow.com/questions/7224626/how-to-sign-string-with-private-key + private static Firmador instancia; + private String ALG = "SHA1withRSA"; + static { + Init.init(); + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * Obtener un firmador por defecto. + * + * @return un Firmador. + */ + + public static Firmador getInstance() { + if (instancia == null) { + instancia = new Firmador(); + } + return instancia; + } + + private Firmador() { + } + + //// Todo: Colocar en un solo directorio la llave privada con la publica + + /** + * Esta funcion añade una firma a un documento XML. + * + * @param datos Documento a firmar XML. + * @param priv Clave privada. + * @param cert Certificado del firmante. + * @return Retorna el documento con una firma. + * @throws ParserConfigurationException + * @throws IOException + * @throws SAXException + * @throws XMLSecurityException + */ + + public static byte[] firmarDsig(byte[] datos, PrivateKey priv, X509Certificate... cert) throws ParserConfigurationException, IOException, SAXException, XMLSecurityException { + ElementProxy.setDefaultPrefix(Constants.SignatureSpecNS, ""); + Document documento = leerXML(datos); + Element root = (Element) documento.getFirstChild(); + documento.setXmlStandalone(false); + XMLSignature signature = new XMLSignature(documento, null, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256); + root.appendChild(signature.getElement()); + Transforms transforms = new Transforms(documento); + transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); + transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS); + signature.addDocument("", transforms, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256); + if (cert != null) { + signature.addKeyInfo(cert[0]); + } + signature.sign(priv); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLUtils.outputDOMc14nWithComments(documento, baos); + return baos.toString().getBytes(); + } + + public static Document leerXML(byte datos[]) throws ParserConfigurationException, IOException, SAXException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + factory.setNamespaceAware(true); + builder = factory.newDocumentBuilder(); + return builder.parse(new ByteArrayInputStream(datos)); + } + + private static String getKey(String filename) throws IOException { + // Read key from file + String strKeyPEM = ""; + BufferedReader br = new BufferedReader(new FileReader(filename)); + String line; + while ((line = br.readLine()) != null) { + strKeyPEM += line + "\n"; + } + br.close(); + return strKeyPEM; + } + + public static RSAPrivateKey getPrivateKey(String filename) throws IOException, GeneralSecurityException { + String privateKeyPEM = getKey(filename); + return getPrivateKeyFromString(privateKeyPEM); + } + + public static RSAPrivateKey getPrivateKeyFromString(String key) throws IOException, GeneralSecurityException { + String privateKeyPEM = key; + privateKeyPEM = privateKeyPEM.replace("-----BEGIN RSA PRIVATE KEY-----\n", ""); + privateKeyPEM = privateKeyPEM.replace("-----END RSA PRIVATE KEY-----", ""); + byte[] encoded = Base64.decodeBase64(privateKeyPEM); + KeyFactory kf = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + RSAPrivateKey privKey = (RSAPrivateKey) kf.generatePrivate(keySpec); + return privKey; + } + + public static RSAPublicKey getPublicKey(String filename) throws IOException, GeneralSecurityException { + + String publicKeyPEM = getKey(filename); + + return getPublicKeyFromString(publicKeyPEM); + + } + + public static RSAPublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException { + String publicKeyPEM = key; + publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", ""); + publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", ""); + byte[] encoded = Base64.decodeBase64(publicKeyPEM); + KeyFactory kf = KeyFactory.getInstance("RSA"); + RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded)); + return pubKey; + } + + public static X509Certificate getX509Certificate(String filename) throws IOException, CertificateException + { + CertificateFactory fact = CertificateFactory.getInstance("X.509"); + FileInputStream is = new FileInputStream (filename); + X509Certificate cer = (X509Certificate) fact.generateCertificate(is); + PublicKey key = cer.getPublicKey(); + return cer; + } + +} \ No newline at end of file diff --git a/TestFirma/src/test/java/bo/sin/testfirma/FirmadorTest.java b/TestFirma/src/test/java/bo/sin/testfirma/FirmadorTest.java new file mode 100644 index 0000000..caff1b2 --- /dev/null +++ b/TestFirma/src/test/java/bo/sin/testfirma/FirmadorTest.java @@ -0,0 +1,70 @@ +package bo.sin.testfirma; + +import org.apache.xml.security.exceptions.XMLSecurityException; +import org.junit.Test; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FirmadorTest { + + @Test + public void getInstance() { + } + + @Test + public void firmarDsig() { + } + + @Test + public void leerXML() { + } + + @Test + public void getPrivateKey() { + } + + @Test + public void getPrivateKeyFromString() { + } + + @Test + public void getPublicKey() { + } + + @Test + public void getPublicKeyFromString() { + } + + @Test + public void getX509Certificate() { + } + + @Test + public void firmarXML() throws URISyntaxException, ParserConfigurationException, XMLSecurityException, org.xml.sax.SAXException { + String xml = "" + + " " + + "AQUI VA LA FACTURA XML " + + ""; + byte[] datos = xml.getBytes(StandardCharsets.UTF_8); + try { + String path = new File(Firmador.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath(); + PrivateKey privateKey = Firmador.getPrivateKey(path + "/private_key.pem"); + X509Certificate cert = Firmador.getX509Certificate(path + "/cert.crt"); + byte[] xmlFirmado = Firmador.firmarDsig(datos, privateKey, cert); + String respuesta = new String(xmlFirmado); + System.out.println("facturaFirmada: "+respuesta); + } catch (IOException | GeneralSecurityException ex) { + Logger.getLogger(FirmadorTest.class.getName()).log(Level.SEVERE, null, ex); + } + } + +} \ No newline at end of file