/**
* Copyright [2009] [NIC Labs]
*
* 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 cl.nic.dte.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.ResourceBundle;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.transform.TransformerException;
import org.apache.commons.ssl.Base64;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptionCharEscapeMap;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import cl.nic.dte.VerifyResult;
import cl.sii.siiDte.AUTORIZACIONDocument;
import cl.sii.siiDte.CAFType;
/**
* Clase con varios métodos de apoyo al manejo del XML.
*
* @author Tomás Barros <tbarros@nic.cl>
*
*/
public class XMLUtil {
/**
* Verifica la estructura XML de un nodo, es decir, si cumple lo exigido por
* su XML Schema.
*
* @param xml
* El nodo a verificar.
* @return El resultado de la verificación.
* @see cl.nic.dte.VerifyResult
*/
public static VerifyResult verifyXML(XmlObject xml) {
XmlOptions validateOptions = new XmlOptions();
ArrayList<XmlError> errorList = new ArrayList<XmlError>();
validateOptions.setErrorListener(errorList);
if (!xml.validate(validateOptions)) {
String message = "";
for (XmlError error : errorList) {
message = Utilities.verificationLabels.getString(
"XML_BAD_STRUCTURE").replaceAll("%1",
error.getMessage());
}
return (new VerifyResult(VerifyResult.XML_STRUCTURE_WRONG, false,
message));
}
return (new VerifyResult(VerifyResult.XML_STRUCTURE_OK, true, null));
}
/**
* Firma digitalmente usando la forma "enveloped signature" según el
* estándar de la W3C (<a
* href="http://www.w3.org/TR/xmldsig-core/">http://www.w3.org/TR/xmldsig-core/</a>).
* <p>
*
* Este método además incorpora la información del
* certificado a la sección <KeyInfo> opcional del
* estándar, según lo exige SII.
* <p>
*
* @param doc
* El documento a firmar
* @param uri
* La referencia dentro del documento que debe ser firmada
* @param pKey
* La llave privada para firmar
* @param cert
* El certificado digital correspondiente a la llave privada
* @throws NoSuchAlgorithmException
* Si el algoritmo de firma de la llave no está soportado
* (Actualmente soportado RSA+SHA1, DSA+SHA1 y HMAC+SHA1).
* @throws InvalidAlgorithmParameterException
* Si los algoritmos de canonización (parte del
* estándar XML Signature) no son soportados (actaulmente
* se usa el por defecto)
* @throws KeyException
* Si hay problemas al incluir la llave pública en el
* <KeyValue>.
* @throws MarshalException
* @throws XMLSignatureException
*
* @see javax.xml.crypto.dsig.XMLSignature#sign(javax.xml.crypto.dsig.XMLSignContext)
*/
public static void signEmbeded(Node doc, String uri, PrivateKey pKey,
X509Certificate cert) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, KeyException, MarshalException,
XMLSignatureException {
// Create a DOM XMLSignatureFactory that will be used to generate the
// enveloped signature
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create a Reference to the enveloped document (in this case we are
// signing the whole document, so a URI of "" signifies that) and
// also specify the SHA1 digest algorithm and the ENVELOPED Transform.
Reference ref = fac.newReference(uri, fac.newDigestMethod(
DigestMethod.SHA1, null), Collections.singletonList(fac
.newTransform(Transform.ENVELOPED,
(TransformParameterSpec) null)), null, null);
// Create the SignedInfo
String method = SignatureMethod.RSA_SHA1; // default by SII
if ("DSA".equals(cert.getPublicKey().getAlgorithm()))
method = SignatureMethod.DSA_SHA1;
else if ("HMAC".equals(cert.getPublicKey().getAlgorithm()))
method = SignatureMethod.HMAC_SHA1;
SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(
CanonicalizationMethod.INCLUSIVE, // Default canonical and
// default by SII
(C14NMethodParameterSpec) null), fac.newSignatureMethod(method,
null), Collections.singletonList(ref));
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(cert.getPublicKey());
// Create a KeyInfo and add the KeyValue to it
List<XMLStructure> kidata = new ArrayList<XMLStructure>();
kidata.add(kv);
kidata.add(kif.newX509Data(Collections.singletonList(cert)));
KeyInfo ki = kif.newKeyInfo(kidata);
// Create a DOMSignContext and specify the PrivateKey and
// location of the resulting XMLSignature's parent element
DOMSignContext dsc = new DOMSignContext(pKey, doc);
// Create the XMLSignature (but don't sign it yet)
XMLSignature signature = fac.newXMLSignature(si, ki);
// Marshal, generate (and sign) the enveloped signature
signature.sign(dsc);
}
/**
* @see #getCertificate(XMLSignature)
*/
public static X509Certificate getCertificate(
cl.sii.siiDte.dsig.SignatureType xml) {
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Unmarshal the signature
XMLSignature signature;
try {
signature = fac.unmarshalXMLSignature(new DOMStructure(xml
.getDomNode()));
} catch (MarshalException e) {
return null;
}
return (getCertificate(signature));
}
/**
* @see #getCertificate(XMLSignature)
*/
public static X509Certificate getCertificate(
cl.sii.siiDte.libroguia.SignatureType xml) {
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Unmarshal the signature
XMLSignature signature;
try {
signature = fac.unmarshalXMLSignature(new DOMStructure(xml
.getDomNode()));
} catch (MarshalException e) {
return null;
}
return (getCertificate(signature));
}
/**
* @see #getCertificate(XMLSignature)
*/
public static X509Certificate getCertificate(
cl.sii.siiDte.libroboletas.SignatureType xml) {
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Unmarshal the signature
XMLSignature signature;
try {
signature = fac.unmarshalXMLSignature(new DOMStructure(xml
.getDomNode()));
} catch (MarshalException e) {
return null;
}
return (getCertificate(signature));
}
/**
* Obtiene el certificado digital contenido en un nodo XML Sinature (<a
* href="http://www.w3.org/TR/xmldsig-core/">http://www.w3.org/TR/xmldsig-core/</a>)
*
* @param signature
* el nodo con el tag <Signature>.
* @return El certificado digital contenido en el <KeyInfo> o
* <code>null</code> en caso que el <Signature> no contenga
* tal información.
*/
@SuppressWarnings("unchecked")
public static X509Certificate getCertificate(XMLSignature signature) {
String alg = signature.getSignedInfo().getSignatureMethod()
.getAlgorithm();
KeyInfo kinf = signature.getKeyInfo();
// Check for keyinfo
if (kinf == null) {
return null;
}
PublicKey pKey = null;
List<X509Certificate> x509 = new ArrayList<X509Certificate>();
// I look for the public key and the certificates
for (XMLStructure xst : (List<XMLStructure>) kinf.getContent()) {
if (xst instanceof KeyValue) {
PublicKey pk;
try {
pk = ((KeyValue) xst).getPublicKey();
if (algEquals(alg, pk.getAlgorithm()))
pKey = pk;
} catch (KeyException e) {
// nothing
}
}
if (xst instanceof X509Data) {
for (Object cont : ((X509Data) xst).getContent())
if (cont instanceof X509Certificate)
x509.add((X509Certificate) cont);
}
}
// return of the certificates that matchs the public key.
for (X509Certificate cert : x509) {
if (cert.getPublicKey().equals(pKey)) {
return cert;
}
}
return null;
}
/**
* Verifica si una firma XML embedida es válida según define
* el estándar XML Signature (<a
* href="http://www.w3.org/TR/xmldsig-core/#sec-CoreValidation">Core
* Validation</a>), y si el certificado era válido en la fecha dada.
* <p>
*
* Esta rutina <b>NO</b> verifica si el certificado embedido en
* <KeyInfo> es válido (eso debe verificarlo con la autoridad
* certificadora que emitió el certificado), pero si verifica que la
* llave utilizada para verificar corresponde a la contenida en el
* certificado.
*
* @param xml
* el nodo <Signature>
* @param date
* una fecha en la que se verifica la validez del certificado
* @return el resultado de la verificación
*
* @see javax.xml.crypto.dsig.XMLSignature#sign(javax.xml.crypto.dsig.XMLSignContext)
* @see cl.nic.dte.VerifyResult
* @see cl.nic.dte.extension.DTEDefTypeExtensionHandler
* @see #getCertificate(XMLSignature)
*/
public static VerifyResult verifySignature(Node xml, Date date) {
try {
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
KeyValueKeySelector ksel = new KeyValueKeySelector();
DOMValidateContext valContext = new DOMValidateContext(ksel, xml);
// Unmarshal the signature
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
X509Certificate x509 = getCertificate(signature);
// Verifica que un certificado bien embedido
if (x509 == null) {
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG,
false, Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_NO509")));
}
try {
// Valida que en la fecha dada el certificado era va'lido
x509.checkValidity(date);
} catch (CertificateExpiredException e) {
String message = Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_NOTVALID");
message = message.replaceAll("%1", DateFormat.getDateInstance()
.format(date));
message = message.replaceAll("%2", DateFormat.getDateInstance()
.format(x509.getNotBefore()));
message = message.replaceAll("%3", DateFormat.getDateInstance()
.format(x509.getNotAfter()));
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG,
false, message));
} catch (CertificateNotYetValidException e) {
String message = Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_NOTVALID");
message = message.replaceAll("%1", DateFormat.getDateInstance()
.format(date));
message = message.replaceAll("%2", DateFormat.getDateInstance()
.format(x509.getNotBefore()));
message = message.replaceAll("%3", DateFormat.getDateInstance()
.format(x509.getNotAfter()));
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG,
false, message));
}
return (verifySignature(signature, valContext));
} catch (MarshalException e1) {
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG, false,
Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_UNMARSHAL")
+ ": " + e1.getMessage()));
}
}
/**
* Verifica si una firma XML embedida es válida según define
* el estándar XML Signature (<a
* href="http://www.w3.org/TR/xmldsig-core/#sec-CoreValidation">Core
* Validation</a>), y si el certificado era válido en la fecha dada.
* <p>
*
* Esta rutina <b>NO</b> verifica si el certificado embedido en
* <KeyInfo> es válido (eso debe verificarlo con la autoridad
* certificadora que emitió el certificado), pero si verifica que la
* llave utilizada para verificar corresponde a la contenida en el
* certificado.
*
* @param xml
* el nodo <Signature>
* @param date
* una fecha en la que se verifica la validez del certificado
* @return el resultado de la verificación
*
* @see javax.xml.crypto.dsig.XMLSignature#sign(javax.xml.crypto.dsig.XMLSignContext)
* @see cl.nic.dte.VerifyResult
* @see cl.nic.dte.extension.DTEDefTypeExtensionHandler
* @see #getCertificate(XMLSignature)
*/
public static VerifyResult verifySignature(Node xml) {
try {
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
KeyValueKeySelector ksel = new KeyValueKeySelector();
DOMValidateContext valContext = new DOMValidateContext(ksel, xml);
// Unmarshal the signature
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
X509Certificate x509 = getCertificate(signature);
// Verifica que un certificado bien embedido
if (x509 == null) {
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG,
false, Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_NO509")));
}
return (verifySignature(signature, valContext));
} catch (MarshalException e1) {
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG, false,
Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_UNMARSHAL")
+ ": " + e1.getMessage()));
}
}
/**
* Verifica si una firma XML embedida es válida según define
* el estándar XML Signature (<a
* href="http://www.w3.org/TR/xmldsig-core/#sec-CoreValidation">Core
* Validation</a>), y si el certificado era válido en la fecha dada.
* <p>
*
* Esta rutina <b>NO</b> verifica si el certificado embedido en
* <KeyInfo> es válido (eso debe verificarlo con la autoridad
* certificadora que emitió el certificado), pero si verifica que la
* llave utilizada para verificar corresponde a la contenida en el
* certificado.
*
* @param xml
* el nodo <Signature>
* @param date
* una fecha en la que se verifica la validez del certificado
* @return el resultado de la verificación
*
* @see javax.xml.crypto.dsig.XMLSignature#sign(javax.xml.crypto.dsig.XMLSignContext)
* @see cl.nic.dte.VerifyResult
* @see cl.nic.dte.extension.DTEDefTypeExtensionHandler
* @see #getCertificate(XMLSignature)
*/
@SuppressWarnings("unchecked")
public static VerifyResult verifySignature(XMLSignature signature,
DOMValidateContext valContext) {
try {
KeyValueKeySelector ksel = (KeyValueKeySelector)valContext.getKeySelector();
X509Certificate x509 = getCertificate(signature);
// Verifica que un certificado bien embedido
if (x509 == null) {
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG,
false, Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_NO509")));
}
// Validate the XMLSignature
boolean coreValidity = signature.validate(valContext);
// Check core validation status
if (coreValidity == false) {
boolean sv = signature.getSignatureValue().validate(valContext);
if (!sv)
return new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG,
false, Utilities.verificationLabels
.getString("XML_SIGNATURE_BAD_VALUE"));
// check the validation status of each Reference
String message = "";
for (Reference ref : (List<Reference>) signature
.getSignedInfo().getReferences()) {
if (!ref.validate(valContext)) {
message += Utilities.verificationLabels
.getString("XML_SIGNATURE_BAD_REFERENCE");
message = message.replaceAll("%1", new String(Base64
.encodeBase64(ref.getCalculatedDigestValue())));
message = message.replaceAll("%2", new String(Base64
.encodeBase64(ref.getDigestValue())));
message += "\n";
}
}
return new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG,
false, message);
}
// Verifica que la llave del certificado corresponde a la usada para
// la firma
if (!ksel.getPk().equals(x509.getPublicKey())) {
String message = Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_BADKEY");
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG,
false, message));
}
return new VerifyResult(VerifyResult.XML_SIGNATURE_OK, true, null);
} catch (XMLSignatureException e) {
return (new VerifyResult(VerifyResult.XML_SIGNATURE_WRONG, false,
Utilities.verificationLabels
.getString("XML_SIGNATURE_ERROR_UNKNOWN")
+ ": " + e.getMessage()));
}
}
private static boolean algEquals(String algURI, String algName) {
if (algName.equalsIgnoreCase("DSA")
&& algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return true;
} else if (algName.equalsIgnoreCase("RSA")
&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return true;
} else {
return false;
}
}
/**
* Obtiene una representación "limpia" de un elemento XML, esto
* quiere decir, sin espacios ni nuevas líneas entre tags. Este
* método se utiliza en la generación PDF del <TED>.
*
* @param xml
* El nodo XML
* @return El arreglo de bytes codificado con ISO-8859-1 (norma exigida por
* SII) del contenido del nodo (incluyendo los tags y caracteres
* especiales de XML como &).
*/
public static byte[] getCleaned(XmlObject xml) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
XmlOptionCharEscapeMap escapes = new XmlOptionCharEscapeMap();
escapes.addMapping('\'', XmlOptionCharEscapeMap.PREDEF_ENTITY);
escapes.addMapping('&', XmlOptionCharEscapeMap.PREDEF_ENTITY);
XmlOptions opts = new XmlOptions();
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("", "http://www.sii.cl/SiiDte");
opts.setCharacterEncoding("ISO-8859-1");
opts.setSaveImplicitNamespaces(namespaces);
opts.setSaveOuter();
opts.setSavePrettyPrint();
opts.setSavePrettyPrintIndent(0);
opts.setSaveNoXmlDecl();
opts.setSaveSubstituteCharacters(escapes);
xml.save(out, opts);
return out.toString("ISO-8859-1").replaceAll("\n", "").replaceAll("\r", "").getBytes("ISO-8859-1");
// return out.toString("ISO-8859-1").getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException e) {
// Nunca debe invocarse
e.printStackTrace();
return null;
} catch (IOException e) {
// Nunca debe invocarse
e.printStackTrace();
return null;
} catch (XmlException e) {
e.printStackTrace();
return null;
}
}
public static byte[] getBytesXML(XmlObject xml) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
XmlOptions opts = new XmlOptions();
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("", "http://www.sii.cl/SiiDte");
opts.setCharacterEncoding("ISO-8859-1");
opts.setSaveImplicitNamespaces(namespaces);
opts.setSaveOuter();
opts.setSavePrettyPrint();
opts.setSavePrettyPrintIndent(0);
opts.setSaveNoXmlDecl();
xml.save(out, opts);
return out.toString("ISO-8859-1").getBytes(
"ISO-8859-1");
} catch (UnsupportedEncodingException e) {
// Nunca debe invocarse
e.printStackTrace();
return null;
} catch (IOException e) {
// Nunca debe invocarse
e.printStackTrace();
return null;
}
}
/**
* KeySelector which retrieves the public key out of the KeyValue element
* and returns it. NOTE: If the key algorithm doesn't match signature
* algorithm, then the public key will be ignored.
*/
private static class KeyValueKeySelector extends KeySelector {
PublicKey pk;
@SuppressWarnings("unchecked")
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose, AlgorithmMethod method,
XMLCryptoContext context) throws KeySelectorException {
if (keyInfo == null) {
throw new KeySelectorException("Null KeyInfo object!");
}
SignatureMethod sm = (SignatureMethod) method;
List<XMLStructure> list = keyInfo.getContent();
for (int i = 0; i < list.size(); i++) {
XMLStructure xmlStructure = list.get(i);
if (xmlStructure instanceof KeyValue) {
try {
setPk(((KeyValue) xmlStructure).getPublicKey());
} catch (KeyException ke) {
throw new KeySelectorException(ke);
}
// make sure algorithm is compatible with method
if (algEquals(sm.getAlgorithm(), getPk().getAlgorithm())) {
return new SimpleKeySelectorResult(getPk());
}
}
}
throw new KeySelectorException("No KeyValue element found!");
}
// @@@FIXME: this should also work for key types other than DSA/RSA
static boolean algEquals(String algURI, String algName) {
if (algName.equalsIgnoreCase("DSA")
&& algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return true;
} else if (algName.equalsIgnoreCase("RSA")
&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return true;
} else {
return false;
}
}
public PublicKey getPk() {
return pk;
}
public void setPk(PublicKey pk) {
this.pk = pk;
}
}
private static class SimpleKeySelectorResult implements KeySelectorResult {
private PublicKey pk;
SimpleKeySelectorResult(PublicKey pk) {
this.pk = pk;
}
public Key getKey() {
return pk;
}
}
public static AUTORIZACIONDocument generateAuthorization(
AUTORIZACIONDocument template, PrivateKey pKey)
throws NoSuchAlgorithmException, SignatureException,
TransformerException, InvalidKeyException, IOException {
// Generation of keys
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
CAFType caf = template.getAUTORIZACION().getCAF();
CAFType.DA.RSAPK rsapk = caf.getDA().addNewRSAPK();
rsapk.setM(((RSAPublicKey) kp.getPublic()).getModulus().toByteArray());
rsapk.setE(((RSAPublicKey) kp.getPublic()).getPublicExponent()
.toByteArray());
ResourceBundle labels = ResourceBundle
.getBundle("cl.nic.dte.resources.VerifyResults");
Signature sig = null;
if (pKey.getAlgorithm().equals("RSA")) {
sig = Signature.getInstance("SHA1withRSA");
caf.addNewFRMA().setAlgoritmo("SHA1withRSA");
} else if (pKey.getAlgorithm().equals("DSA")) {
sig = Signature.getInstance("SHA1withDSA");
caf.addNewFRMA().setAlgoritmo("SHA1withDSA");
} else {
throw new NoSuchAlgorithmException(labels.getString(
"ALGORITHM_NOT_SUPPORTED").replaceAll("%1",
pKey.getAlgorithm()));
}
template.getAUTORIZACION().setRSASK(
"-----BEGIN RSA PRIVATE KEY-----\n"
+ new String(Base64.encodeBase64(kp.getPrivate()
.getEncoded(), true))
+ "-----END RSA PRIVATE KEY-----\n");
template.getAUTORIZACION().setRSAPUBK(
"-----BEGIN RSA PUBLIC KEY-----\n"
+ new String(Base64.encodeBase64(kp.getPublic()
.getEncoded(), true))
+ "-----END RSA PUBLIC KEY-----\n");
sig.initSign(pKey);
sig.update(XMLUtil.getCleaned(caf.getDA()));
caf.getFRMA().setByteArrayValue(Base64.encodeBase64(sig.sign()));
return template;
}
/**
* Obtiene una representación "limpia" de un elemento XML, esto
* quiere decir, sin espacios ni nuevas líneas entre tags. Este
* método se utiliza en la generación PDF del <TED>.
*
* @param xml
* El nodo XML
* @return El arreglo de bytes codificado con ISO-8859-1 (norma exigida por
* SII) del contenido del nodo (incluyendo los tags y caracteres
* especiales de XML como &).
*/
public static byte[] getCleanedII(XmlObject xml) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
XmlOptions opts = new XmlOptions();
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("", "http://www.sii.cl/SiiDte");
opts.setCharacterEncoding("ISO-8859-1");
opts.setSaveImplicitNamespaces(namespaces);
opts.setSaveOuter();
//opts.setSavePrettyPrint();
//opts.setSavePrettyPrintIndent(0);
opts.setSaveNoXmlDecl();
xml.save(out, opts);
return out.toByteArray();
} catch (UnsupportedEncodingException e) {
// Nunca debe invocarse
e.printStackTrace();
return null;
} catch (IOException e) {
// Nunca debe invocarse
e.printStackTrace();
return null;
}
}
/**
* Firma digitalmente usando la forma "enveloped signature" según el
* estándar de la W3C (<a
* href="http://www.w3.org/TR/xmldsig-core/">http://www.w3.org/TR/xmldsig-core/</a>).
* <p>
*
* Este método además incorpora la información del
* certificado a la sección <KeyInfo> opcional del
* estándar, según lo exige SII.
* <p>
*
* @param doc
* El documento a firmar
* @param uri
* La referencia dentro del documento que debe ser firmada
* @param pKey
* La llave privada para firmar
* @param cert
* El certificado digital correspondiente a la llave privada
* @throws NoSuchAlgorithmException
* Si el algoritmo de firma de la llave no está soportado
* (Actualmente soportado RSA+SHA1, DSA+SHA1 y HMAC+SHA1).
* @throws InvalidAlgorithmParameterException
* Si los algoritmos de canonización (parte del
* estándar XML Signature) no son soportados (actaulmente
* se usa el por defecto)
* @throws KeyException
* Si hay problemas al incluir la llave pública en el
* <KeyValue>.
* @throws MarshalException
* @throws XMLSignatureException
*
* @see javax.xml.crypto.dsig.XMLSignature#sign(javax.xml.crypto.dsig.XMLSignContext)
*/
public static void signEmbededApache(Document doc, String uri, PrivateKey pKey,
X509Certificate cert) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, KeyException, MarshalException,
XMLSignatureException {
try {
org.apache.xml.security.signature.XMLSignature sig = new org.apache.xml.security.signature.XMLSignature(doc, uri,
org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA);
doc.getDocumentElement().appendChild(sig.getElement());
//ObjectContainer obj = new ObjectContainer(doc);
//obj.setId(uri);
//sig.appendObject(obj);
Transforms transforms = new Transforms(doc);
transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
sig.addDocument(uri,transforms);
sig.addKeyInfo(cert.getPublicKey());
sig.addKeyInfo(cert);
// sig.setXPathNamespaceContext("xmlns", "http://www.w3.org/2000/09/xmldsig#");
sig.sign(pKey);
} catch (XMLSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
static {
org.apache.xml.security.Init.init();
}
}