Java tutorial
/* * Copyright 2010 BigData.mx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mx.bigdata.sat.cfdi; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.StringWriter; import java.math.BigInteger; import java.security.PrivateKey; import java.security.Security; import java.security.Signature; import java.security.cert.X509Certificate; import java.util.Date; import java.util.List; import java.util.UUID; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.util.JAXBSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import mx.bigdata.sat.cfdi.schema.v32.Comprobante; import mx.bigdata.sat.cfdi.schema.v32.ObjectFactory; import mx.bigdata.sat.cfdi.schema.v32.TimbreFiscalDigital; import mx.bigdata.sat.common.NamespacePrefixMapperImpl; import org.apache.commons.codec.binary.Base64; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.ErrorHandler; public final class TFDv1_v32 { private static final String XML_HEADER = "\ufeff"; private static final String XSLT = "/xslt/v32/cadenaoriginal_TFD_1_0.xslt"; private static final String XSD = "/xsd/v32/TimbreFiscalDigital.xsd"; private final static Joiner JOINER = Joiner.on(':'); private static final JAXBContext CONTEXT = createContext(); private static final JAXBContext createContext() { try { List<String> ctx = Lists.asList("mx.bigdata.sat.cfdi.schema.v32", new String[] { "mx.bigdata.sat.complementos.schema.nomina", "mx.bigdata.sat.complementos.schema.implocal" }); //return JAXBContext.newInstance("mx.bigdata.sat.cfdi.schema.v32"); return JAXBContext.newInstance(JOINER.join(ctx)); } catch (Exception e) { throw new Error(e); } } private final Comprobante document; private final TimbreFiscalDigital tfd; private final X509Certificate cert; private TransformerFactory tf; public TFDv1_v32(CFDv32 cfd, TimbreFiscalDigital timbreFiscalDigital, X509Certificate cert) throws Exception { this.cert = cert; this.document = cfd.getComprobante(); this.tfd = timbreFiscalDigital; } public TFDv1_v32(CFDv32 cfd, X509Certificate cert) throws Exception { this(cfd, cert, UUID.randomUUID(), new Date()); } TFDv1_v32(CFDv32 cfd, X509Certificate cert, UUID uuid, Date date) throws Exception { this.cert = cert; this.document = cfd.getComprobante(); this.tfd = getTimbreFiscalDigital(document, uuid, date); } public void setTransformerFactory(TransformerFactory tf) { this.tf = tf; } public int timbrar(PrivateKey key) throws Exception { if (tfd.getSelloSAT() != null) { return 304; } String signature = getSignature(key); tfd.setSelloSAT(signature); stamp(); return 300; } public void validar() throws Exception { validar(null); } public void validar(ErrorHandler handler) throws Exception { SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = sf.newSchema(getClass().getResource(XSD)); Validator validator = schema.newValidator(); if (handler != null) { validator.setErrorHandler(handler); } validator.validate(new JAXBSource(CONTEXT, tfd)); } public int verificar() throws Exception { if (tfd == null) { return 601; //No contiene timbrado } Base64 b64 = new Base64(); String sigStr = tfd.getSelloSAT(); byte[] signature = b64.decode(sigStr); byte[] bytes = getOriginalBytes(); Signature sig = Signature.getInstance("SHA1withRSA"); sig.initVerify(cert); sig.update(bytes); boolean verified = sig.verify(signature); return verified ? 600 : 602; //Sello del timbrado no valido } public String getCadenaOriginal() throws Exception { byte[] bytes = getOriginalBytes(); return new String(bytes, "UTF-8"); } public void guardar(OutputStream out) throws Exception { Marshaller m = CONTEXT.createMarshaller(); m.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapperImpl(CFDv32.PREFIXES)); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); m.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.sat.gob.mx/cfd/3 " + "http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd " + CFDv32.getComplementosNameSpaceAndSchema() + "http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/TimbreFiscalDigital/TimbreFiscalDigital.xsd "); byte[] xmlHeaderBytes = XML_HEADER.getBytes("UTF8"); out.write(xmlHeaderBytes); m.marshal(document, out); } public TimbreFiscalDigital getTimbre() { return tfd; } byte[] getOriginalBytes() throws Exception { JAXBSource in = new JAXBSource(CONTEXT, tfd); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Result out = new StreamResult(baos); TransformerFactory factory = tf; if (factory == null) { factory = TransformerFactory.newInstance(); } Transformer transformer = factory.newTransformer(new StreamSource(getClass().getResourceAsStream(XSLT))); transformer.transform(in, out); return baos.toByteArray(); } String getSignature(PrivateKey key) throws Exception { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); byte[] bytes = getOriginalBytes(); Signature sig = Signature.getInstance("SHA1withRSA"); sig.initSign(key); sig.update(bytes); byte[] signed = sig.sign(); Base64 b64 = new Base64(-1); return b64.encodeToString(signed); } private void stamp() throws Exception { Element element = marshalTFD(); ObjectFactory of = new ObjectFactory(); Comprobante.Complemento comp = of.createComprobanteComplemento(); List<Object> list = comp.getAny(); list.add(element); //timbreFiscalDigitalString = nodeToString(element); //System.out.println("*******Timbre Fiscal: \n" + timbreFiscalDigitalString); document.setComplemento(comp); } private Element marshalTFD() throws Exception { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.newDocument(); Marshaller m = CONTEXT.createMarshaller(); m.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapperImpl(CFDv32.PREFIXES)); m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); m.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/TimbreFiscalDigital/TimbreFiscalDigital.xsd"); m.marshal(tfd, doc); return doc.getDocumentElement(); } private TimbreFiscalDigital createStamp(UUID uuid, Date date) { ObjectFactory of = new ObjectFactory(); TimbreFiscalDigital tfd = of.createTimbreFiscalDigital(); tfd.setVersion("1.0"); tfd.setFechaTimbrado(date); tfd.setSelloCFD(document.getSello()); tfd.setUUID(uuid.toString().toUpperCase()); ///-----------------CAMBIADO A MAYUSCULAS BigInteger bi = cert.getSerialNumber(); tfd.setNoCertificadoSAT(new String(bi.toByteArray())); return tfd; } private TimbreFiscalDigital getTimbreFiscalDigital(Comprobante document, UUID uuid, Date date) throws Exception { Comprobante.Complemento comp = document.getComplemento(); if (comp != null) { List<Object> list = comp.getAny(); for (Object o : list) { if (o instanceof TimbreFiscalDigital) { return (TimbreFiscalDigital) o; } } } return createStamp(uuid, date); } private String nodeToString(Element node) { StringWriter sw = new StringWriter(); try { Transformer t = TransformerFactory.newInstance().newTransformer(); t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); t.transform(new DOMSource(node), new StreamResult(sw)); } catch (TransformerException te) { System.out.println("nodeToString Transformer Exception"); } return sw.toString(); } }