Comments (4)
It would have been nice to have info about version of xml-crypto.
Decided to go with following
{
"name": "foo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"node-forge": "^1.3.1",
"xml-crypto": "^6.0.0"
}
}
Furthermore removed / simplified your example code quite a bit in order be able to run it (you could have done that also :) i.e. to provide immediately runnable version):
//import forge from 'node-forge'
const forge=require('node-forge')
//import { readFileSync } from 'fs'
// ^ not needed to debugging
//import { SignedXml } from 'xml-crypto'
const SignedXml=require('xml-crypto').SignedXml
//import { createClient, WSSecurityCert } from 'soap'
// ^ not needed to debugging
//import { EXTRACT_X509_CERTS } from 'xml-crypto/lib/utils.js'
const EXTRACT_X509_CERTS=require('xml-crypto/lib/utils.js').EXTRACT_X509_CERTS
// https://raw.githubusercontent.com/node-saml/xml-crypto/21201723d2ca9bc11288f62cf72552b7d659b000/test/static/client_public.pem
var certificate = `
-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
-----END CERTIFICATE-----
`
// https://raw.githubusercontent.com/node-saml/xml-crypto/21201723d2ca9bc11288f62cf72552b7d659b000/test/static/client.pem
var privateKey = `
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj
7RAGxefGRATiq4mXtzc9Q91W7uT0DTaFEbjzVch9aGsNjmLs4QHsoZbuoUmi0st4
x5z9SQpTAKC/dW8muzacT3E7dJJYh03MAO6RiH4LG34VRTq1SQN6qDt2rCK85eG4
5NHI4jceptZNu6Zot1zyO5/PYuFpAgMBAAECgYAhspeyF3M/xB7WIixy1oBiXMLY
isESFAumgfhwU2LotkVRD6rgNl1QtMe3kCNWa9pCWQcYkxeI0IzA+JmFu2shVvoR
oL7eV4VCe1Af33z24E46+cY5grxNhHt/LyCnZKcitvCcrzXExUc5n6KngX0mMKgk
W7skZDwsnKzhyUV8wQJBAN2bQMeASQVOqdfqBdFgC/NPnKY2cuDi6h659QN1l+kg
X3ywdZ7KKftJo1G9l45SN9YpkyEd9zEO6PMFaufJvZUCQQDbtAWxk0i8BT3UTNWC
T/9bUQROPcGZagwwnRFByX7gpmfkf1ImIvbWVXSpX68/IjbjSkTw1nj/Yj1NwFZ0
nxeFAkEAzPhRpXVBlPgaXkvlz7AfvY+wW4hXHyyi0YK8XdPBi25XA5SPZiylQfjt
Z6iN6qSfYqYXoPT/c0/QJR+orvVJNQJBANhRPNXljVTK2GDCseoXd/ZiI5ohxg+W
UaA/1fDvQsRQM7TQA4NXI7BO/YmSk4rW1jIeOxjiIspY4MFAIh+7UL0CQFL6zTg6
wfeMlEZzvgqwCGoLuvTnqtvyg45z7pfcrg2cHdgCXIy9kErcjwGiu6BOevEA1qTW
Rk+bv0tknWvcz/s=
-----END PRIVATE KEY-----
`
// reduced version (no need to have that much data to debug this)
const inputXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd"
xmlns:DGICFE="http://cfe.dgi.gub.uy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE version="1.0"
xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eFact>
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
</ns0:CFE>
</DGICFE:EnvioCFE>
`
SignedXml.getKeyInfoContent = ({ publicCert, prefix }) => {
if (publicCert == null) return null
prefix = prefix ? `${prefix}:` : ''
let x509Certs = ''
if (Buffer.isBuffer(publicCert)) {
publicCert = publicCert.toString('latin1')
}
let publicCertMatches = []
if (typeof publicCert === 'string') {
publicCertMatches = publicCert.match(EXTRACT_X509_CERTS) || []
}
if (publicCertMatches.length > 0) {
x509Certs = publicCertMatches
.map(c => {
const certificate = forge.pki.certificateFromPem(c)
const issuerName = certificate.issuer.attributes.map(attr => `${attr.shortName}=${attr.value}`).join(', ')
const serialNumber = certificate.serialNumber
return (
`<${prefix}X509IssuerSerial>` +
`<${prefix}X509IssuerName>${issuerName}</${prefix}X509IssuerName>` +
`<${prefix}X509SerialNumber>${BigInt(`0x${serialNumber}`).toString()}</${prefix}X509SerialNumber>` +
`</${prefix}X509IssuerSerial>`
)
})
.join('')
}
return `<${prefix}X509Data>${x509Certs}</${prefix}X509Data>`
}
const firmarXml = (xml) => {
// const { privateKey, certificate } = retornaCertificado(certPath, password)
const sig = new SignedXml({ privateKey: privateKey, publicCert: certificate })
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
sig.canonicalizationAlgorithm = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
sig.addReference({
xpath: "//*[local-name(.)='eFact']",
transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"],
digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256',
isEmptyUri: true,
})
sig.computeSignature(xml, {
location: {
reference: "//*[local-name(.)='CFE']",
action: 'append',
},
})
return { Datain: { xmlData: sig.getSignedXml() } }
}
console.log(firmarXml(inputXml).Datain.xmlData)
and when following result is pasted to chilkat it infact says that digests do not match:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd" xmlns:DGICFE="http://cfe.dgi.gub.uy" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE version="1.0" xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eFact>
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>gNFx5CofL1bIBBsVCoo+dX11Nw7o2FzLZ8wZXV8EAac=</DigestValue></Reference></SignedInfo><SignatureValue>lpSZ7wi0NVJJWR19QtvFyOXx7b4Xq7dJgvka+DMqi6C9wrhhdagbpOOENPzD/nB3x4gjtUgZOjrZqEfoQ6aqRAqP9esnYN4Zdy0f0f2jQkwa2s2L4sOatbkpZYAEPXgPQJIjBA6+Qp1SWYRIN1j9UZy3DwFKCwzMC8z56QBQUl8=</SignatureValue><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName>CN=Root Agency</X509IssuerName><X509SerialNumber>262214055838675473468217939292187724434</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo></Signature></ns0:CFE>
</DGICFE:EnvioCFE>
pretty formatted version being:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE xmlns:DGICFE="http://cfe.dgi.gub.uy" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE xmlns:ns0="http://cfe.dgi.gub.uy" version="1.0">
<ns0:eFact>
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>gNFx5CofL1bIBBsVCoo+dX11Nw7o2FzLZ8wZXV8EAac=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>lpSZ7wi0NVJJWR19QtvFyOXx7b4Xq7dJgvka+DMqi6C9wrhhdagbpOOENPzD/nB3x4gjtUgZOjrZqEfoQ6aqRAqP9esnYN4Zdy0f0f2jQkwa2s2L4sOatbkpZYAEPXgPQJIjBA6+Qp1SWYRIN1j9UZy3DwFKCwzMC8z56QBQUl8=</SignatureValue>
<KeyInfo>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>CN=Root Agency</X509IssuerName>
<X509SerialNumber>262214055838675473468217939292187724434</X509SerialNumber>
</X509IssuerSerial>
</X509Data>
</KeyInfo>
</Signature>
</ns0:CFE>
</DGICFE:EnvioCFE>
What caught my eye is that you select document fragment to be signed but you set Reference UR=""
. I have thought that this would mean that when such reference is validated it must take whole document (starting from the root).
I must emphasize that I am NOT xml signature expert.
But lets test this assumption and modify
xpath: "//*[local-name(.)='eFact']",
// to -->
xpath: "//*[local-name(.)='EnvioCFE']",
i.e. test code is
//import forge from 'node-forge'
const forge=require('node-forge')
//import { readFileSync } from 'fs'
// ^ not needed to debugging
//import { SignedXml } from 'xml-crypto'
const SignedXml=require('xml-crypto').SignedXml
//import { createClient, WSSecurityCert } from 'soap'
// ^ not needed to debugging
//import { EXTRACT_X509_CERTS } from 'xml-crypto/lib/utils.js'
const EXTRACT_X509_CERTS=require('xml-crypto/lib/utils.js').EXTRACT_X509_CERTS
// https://raw.githubusercontent.com/node-saml/xml-crypto/21201723d2ca9bc11288f62cf72552b7d659b000/test/static/client_public.pem
var certificate = `
-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
-----END CERTIFICATE-----
`
// https://raw.githubusercontent.com/node-saml/xml-crypto/21201723d2ca9bc11288f62cf72552b7d659b000/test/static/client.pem
var privateKey = `
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj
7RAGxefGRATiq4mXtzc9Q91W7uT0DTaFEbjzVch9aGsNjmLs4QHsoZbuoUmi0st4
x5z9SQpTAKC/dW8muzacT3E7dJJYh03MAO6RiH4LG34VRTq1SQN6qDt2rCK85eG4
5NHI4jceptZNu6Zot1zyO5/PYuFpAgMBAAECgYAhspeyF3M/xB7WIixy1oBiXMLY
isESFAumgfhwU2LotkVRD6rgNl1QtMe3kCNWa9pCWQcYkxeI0IzA+JmFu2shVvoR
oL7eV4VCe1Af33z24E46+cY5grxNhHt/LyCnZKcitvCcrzXExUc5n6KngX0mMKgk
W7skZDwsnKzhyUV8wQJBAN2bQMeASQVOqdfqBdFgC/NPnKY2cuDi6h659QN1l+kg
X3ywdZ7KKftJo1G9l45SN9YpkyEd9zEO6PMFaufJvZUCQQDbtAWxk0i8BT3UTNWC
T/9bUQROPcGZagwwnRFByX7gpmfkf1ImIvbWVXSpX68/IjbjSkTw1nj/Yj1NwFZ0
nxeFAkEAzPhRpXVBlPgaXkvlz7AfvY+wW4hXHyyi0YK8XdPBi25XA5SPZiylQfjt
Z6iN6qSfYqYXoPT/c0/QJR+orvVJNQJBANhRPNXljVTK2GDCseoXd/ZiI5ohxg+W
UaA/1fDvQsRQM7TQA4NXI7BO/YmSk4rW1jIeOxjiIspY4MFAIh+7UL0CQFL6zTg6
wfeMlEZzvgqwCGoLuvTnqtvyg45z7pfcrg2cHdgCXIy9kErcjwGiu6BOevEA1qTW
Rk+bv0tknWvcz/s=
-----END PRIVATE KEY-----
`
// reduced version (no need to have that much data to debug this)
const inputXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd"
xmlns:DGICFE="http://cfe.dgi.gub.uy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE version="1.0"
xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eFact>
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
</ns0:CFE>
</DGICFE:EnvioCFE>
`
SignedXml.getKeyInfoContent = ({ publicCert, prefix }) => {
if (publicCert == null) return null
prefix = prefix ? `${prefix}:` : ''
let x509Certs = ''
if (Buffer.isBuffer(publicCert)) {
publicCert = publicCert.toString('latin1')
}
let publicCertMatches = []
if (typeof publicCert === 'string') {
publicCertMatches = publicCert.match(EXTRACT_X509_CERTS) || []
}
if (publicCertMatches.length > 0) {
x509Certs = publicCertMatches
.map(c => {
const certificate = forge.pki.certificateFromPem(c)
const issuerName = certificate.issuer.attributes.map(attr => `${attr.shortName}=${attr.value}`).join(', ')
const serialNumber = certificate.serialNumber
return (
`<${prefix}X509IssuerSerial>` +
`<${prefix}X509IssuerName>${issuerName}</${prefix}X509IssuerName>` +
`<${prefix}X509SerialNumber>${BigInt(`0x${serialNumber}`).toString()}</${prefix}X509SerialNumber>` +
`</${prefix}X509IssuerSerial>`
)
})
.join('')
}
return `<${prefix}X509Data>${x509Certs}</${prefix}X509Data>`
}
const firmarXml = (xml) => {
// const { privateKey, certificate } = retornaCertificado(certPath, password)
const sig = new SignedXml({ privateKey: privateKey, publicCert: certificate })
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
sig.canonicalizationAlgorithm = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
sig.addReference({
xpath: "//*[local-name(.)='EnvioCFE']",
transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"],
digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256',
isEmptyUri: true,
})
sig.computeSignature(xml, {
location: {
reference: "//*[local-name(.)='CFE']",
action: 'append',
},
})
return { Datain: { xmlData: sig.getSignedXml() } }
}
console.log(firmarXml(inputXml).Datain.xmlData)
output is (and when pasted to chilkat it says that reference 1 digest is valid):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd" xmlns:DGICFE="http://cfe.dgi.gub.uy" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE version="1.0" xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eFact>
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>sZ3A0cgXIJEMyI5hdGk4aRR8BzVrJgjBPyxBBuhwTXU=</DigestValue></Reference></SignedInfo><SignatureValue>ZFEOBc0jQpBdARFvSp5USZvxhgtL8T+XJpFprrsOIHHDf6SUKlhefqtpbbRMZ2ZRHvLlzomPGhO5BJIiomtB3UuRoEYbduU1wtxoQys8JK6qapan6P6bIUfv4sj1hWHAHWoE5XP9/0LEny8PFVLlbVh4LLcam/wwPWIQQblsmt4=</SignatureValue><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName>CN=Root Agency</X509IssuerName><X509SerialNumber>262214055838675473468217939292187724434</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo></Signature></ns0:CFE>
</DGICFE:EnvioCFE>
pretty printed version:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE xmlns:DGICFE="http://cfe.dgi.gub.uy" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE xmlns:ns0="http://cfe.dgi.gub.uy" version="1.0">
<ns0:eFact>
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>sZ3A0cgXIJEMyI5hdGk4aRR8BzVrJgjBPyxBBuhwTXU=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>ZFEOBc0jQpBdARFvSp5USZvxhgtL8T+XJpFprrsOIHHDf6SUKlhefqtpbbRMZ2ZRHvLlzomPGhO5BJIiomtB3UuRoEYbduU1wtxoQys8JK6qapan6P6bIUfv4sj1hWHAHWoE5XP9/0LEny8PFVLlbVh4LLcam/wwPWIQQblsmt4=</SignatureValue>
<KeyInfo>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>CN=Root Agency</X509IssuerName>
<X509SerialNumber>262214055838675473468217939292187724434</X509SerialNumber>
</X509IssuerSerial>
</X509Data>
</KeyInfo>
</Signature>
</ns0:CFE>
</DGICFE:EnvioCFE>
Based on my assumtions (and I must emphasize again that I am NOT xml signature expert) if you sign some fragment / subtree of XML document you must add URI reference to that subtree.
I.e. lets change this (from original reduced example code):
isEmptyUri: true,
// to -->
isEmptyUri: false,
i.e. your reduced example code would look something like this:
//import forge from 'node-forge'
const forge=require('node-forge')
//import { readFileSync } from 'fs'
// ^ not needed to debugging
//import { SignedXml } from 'xml-crypto'
const SignedXml=require('xml-crypto').SignedXml
//import { createClient, WSSecurityCert } from 'soap'
// ^ not needed to debugging
//import { EXTRACT_X509_CERTS } from 'xml-crypto/lib/utils.js'
const EXTRACT_X509_CERTS=require('xml-crypto/lib/utils.js').EXTRACT_X509_CERTS
// https://raw.githubusercontent.com/node-saml/xml-crypto/21201723d2ca9bc11288f62cf72552b7d659b000/test/static/client_public.pem
var certificate = `
-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
-----END CERTIFICATE-----
`
// https://raw.githubusercontent.com/node-saml/xml-crypto/21201723d2ca9bc11288f62cf72552b7d659b000/test/static/client.pem
var privateKey = `
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj
7RAGxefGRATiq4mXtzc9Q91W7uT0DTaFEbjzVch9aGsNjmLs4QHsoZbuoUmi0st4
x5z9SQpTAKC/dW8muzacT3E7dJJYh03MAO6RiH4LG34VRTq1SQN6qDt2rCK85eG4
5NHI4jceptZNu6Zot1zyO5/PYuFpAgMBAAECgYAhspeyF3M/xB7WIixy1oBiXMLY
isESFAumgfhwU2LotkVRD6rgNl1QtMe3kCNWa9pCWQcYkxeI0IzA+JmFu2shVvoR
oL7eV4VCe1Af33z24E46+cY5grxNhHt/LyCnZKcitvCcrzXExUc5n6KngX0mMKgk
W7skZDwsnKzhyUV8wQJBAN2bQMeASQVOqdfqBdFgC/NPnKY2cuDi6h659QN1l+kg
X3ywdZ7KKftJo1G9l45SN9YpkyEd9zEO6PMFaufJvZUCQQDbtAWxk0i8BT3UTNWC
T/9bUQROPcGZagwwnRFByX7gpmfkf1ImIvbWVXSpX68/IjbjSkTw1nj/Yj1NwFZ0
nxeFAkEAzPhRpXVBlPgaXkvlz7AfvY+wW4hXHyyi0YK8XdPBi25XA5SPZiylQfjt
Z6iN6qSfYqYXoPT/c0/QJR+orvVJNQJBANhRPNXljVTK2GDCseoXd/ZiI5ohxg+W
UaA/1fDvQsRQM7TQA4NXI7BO/YmSk4rW1jIeOxjiIspY4MFAIh+7UL0CQFL6zTg6
wfeMlEZzvgqwCGoLuvTnqtvyg45z7pfcrg2cHdgCXIy9kErcjwGiu6BOevEA1qTW
Rk+bv0tknWvcz/s=
-----END PRIVATE KEY-----
`
// reduced version (no need to have that much data to debug this)
const inputXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd"
xmlns:DGICFE="http://cfe.dgi.gub.uy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE version="1.0"
xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eFact>
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
</ns0:CFE>
</DGICFE:EnvioCFE>
`
SignedXml.getKeyInfoContent = ({ publicCert, prefix }) => {
if (publicCert == null) return null
prefix = prefix ? `${prefix}:` : ''
let x509Certs = ''
if (Buffer.isBuffer(publicCert)) {
publicCert = publicCert.toString('latin1')
}
let publicCertMatches = []
if (typeof publicCert === 'string') {
publicCertMatches = publicCert.match(EXTRACT_X509_CERTS) || []
}
if (publicCertMatches.length > 0) {
x509Certs = publicCertMatches
.map(c => {
const certificate = forge.pki.certificateFromPem(c)
const issuerName = certificate.issuer.attributes.map(attr => `${attr.shortName}=${attr.value}`).join(', ')
const serialNumber = certificate.serialNumber
return (
`<${prefix}X509IssuerSerial>` +
`<${prefix}X509IssuerName>${issuerName}</${prefix}X509IssuerName>` +
`<${prefix}X509SerialNumber>${BigInt(`0x${serialNumber}`).toString()}</${prefix}X509SerialNumber>` +
`</${prefix}X509IssuerSerial>`
)
})
.join('')
}
return `<${prefix}X509Data>${x509Certs}</${prefix}X509Data>`
}
const firmarXml = (xml) => {
// const { privateKey, certificate } = retornaCertificado(certPath, password)
const sig = new SignedXml({ privateKey: privateKey, publicCert: certificate })
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
sig.canonicalizationAlgorithm = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
sig.addReference({
xpath: "//*[local-name(.)='eFact']",
transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"],
digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256',
isEmptyUri: false,
})
sig.computeSignature(xml, {
location: {
reference: "//*[local-name(.)='CFE']",
action: 'append',
},
})
return { Datain: { xmlData: sig.getSignedXml() } }
}
console.log(firmarXml(inputXml).Datain.xmlData)
and resulting xml is (and when pasted to chilkat it reports that digests match)
Notice how xml-crypto added implicitily Id="_0"
to eFact
:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd" xmlns:DGICFE="http://cfe.dgi.gub.uy" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE version="1.0" xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eFact Id="_0">
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI="#_0"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>F/TXqkksBOGoTcRU6pKJ5cCRPZtYGblpEeDz4aI+S6Y=</DigestValue></Reference></SignedInfo><SignatureValue>cMKIFLrUlgWxBKTe/TOPHpszA4B66l0noE8ED/sSCr7FimrmDs3U0Cx8bTI9C4Zc4bF+215N4pwVcdwqsq3/4LUUl9npmRNSfuQsbhUjdUMXclKALXXY/ORErWQVXDYRWmWAFhWmxiWYCVhP4Wx775ZnbCMqg86sm7VFjWBuP9Y=</SignatureValue><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName>CN=Root Agency</X509IssuerName><X509SerialNumber>262214055838675473468217939292187724434</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo></Signature></ns0:CFE>
</DGICFE:EnvioCFE>
and pretty printed:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DGICFE:EnvioCFE xmlns:DGICFE="http://cfe.dgi.gub.uy" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd">
<DGICFE:Caratula version="1.0">
<DGICFE:RutReceptor>219999830019</DGICFE:RutReceptor>
</DGICFE:Caratula>
<ns0:CFE xmlns:ns0="http://cfe.dgi.gub.uy" version="1.0">
<ns0:eFact Id="_0">
<ns0:TmstFirma>2024-05-22T11:51:55-03:00</ns0:TmstFirma>
</ns0:eFact>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="#_0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>F/TXqkksBOGoTcRU6pKJ5cCRPZtYGblpEeDz4aI+S6Y=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>cMKIFLrUlgWxBKTe/TOPHpszA4B66l0noE8ED/sSCr7FimrmDs3U0Cx8bTI9C4Zc4bF+215N4pwVcdwqsq3/4LUUl9npmRNSfuQsbhUjdUMXclKALXXY/ORErWQVXDYRWmWAFhWmxiWYCVhP4Wx775ZnbCMqg86sm7VFjWBuP9Y=</SignatureValue>
<KeyInfo>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>CN=Root Agency</X509IssuerName>
<X509SerialNumber>262214055838675473468217939292187724434</X509SerialNumber>
</X509IssuerSerial>
</X509Data>
</KeyInfo>
</Signature>
</ns0:CFE>
</DGICFE:EnvioCFE>
Here is link to https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/ for you in order to be able to make your own interpretation of spec.
Happy debugging / spec interpretation and/or maybe someone with more knowledge about xml signature can jump in and continue from this point on.
from xml-crypto.
Thank you so much for de response. Indeed im using "node-forge": "^1.3.1" and “xml-crypto": "^6.0.0”. Yesterday i was trying to replicate your recommendations and i didn't get the digest value error anymore, but the signature still is invalid for some reason that i cant find . I used your generated certificate and private key too, but the result was the same when i use chillket to test.
Does anyone know why this could be happening?
from xml-crypto.
Adding more context to the problem: In each EnvioCFE we should put a lot of CFEs, each one with his individual signature for his subtree. Like in this example:
<?xml version="1.0" encoding="iso-8859-1"?>
<DGICFE:EnvioCFE version="1.0" xsi:schemaLocation="http://cfe.dgi.gub.uy EnvioCFE_v1.24.xsd"
xmlns:DGICFE="http://cfe.dgi.gub.uy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DGICFE:Caratula version="1.0"> Bla bla bla </DGICFE:Caratula>
<ns0:CFE version="1.0" xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eTck>
<ns0:TmstFirma>2016-07-19T15:56:20-03:00</ns0:TmstFirma>
</ns0:eTck>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>xpXiLtg0nWejadP44Gm7ArFMk7Y=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>ke8XBfPeT4OzEK5enxCJ6nK367fS9RFx007Yy+A8NAz1+cPIfSgqJhg68nesUCy/XlxbYkFAEU62GvhDg/XdiBFzkwMXac896pVFZr4S45v4FZX3YkosGIi64honz/2tvyUiN977Ek0HtLMoz6DVK5L/5FspvanZqJLEDqEGqJk=</SignatureValue>
<KeyInfo>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>CN=Correo Uruguayo - CA, OU=SERVICIOS ELECTRONICOS, O=ADMINISTRACION NACIONAL DE CORREOS, C=UY</X509IssuerName>
<X509SerialNumber>155761856642617054135126896023459966393</X509SerialNumber>
</X509IssuerSerial>
</X509Data>
</KeyInfo>
</Signature>
</ns0:CFE>
<ns0:CFE version="1.0" xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eTck>
<ns0:TmstFirma>2016-07-19T15:56:20-03:00</ns0:TmstFirma>
</ns0:eTck>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>wwNHpZT9gDKlCUalcLcwo5DIkNk=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>Oa6tS9NreWBwRR8RhT2rTOr4Gm3OSWklWzKR3Z+u+LGzQo1C+EskajdxmzdkKvVMgQ4O9CmNL+6RILCFTtFi7j1jFVuAyH5eejLNnNJxFziS0FMYj0uLdj6lqkEO+txlYMXhUG8/k/ZKXC3Ur9Em2/MCAYZB+qFw3DBe7OZcxEk=</SignatureValue>
<KeyInfo>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>CN=Correo Uruguayo - CA, OU=SERVICIOS ELECTRONICOS, O=ADMINISTRACION NACIONAL DE CORREOS, C=UY</X509IssuerName>
<X509SerialNumber>155761856642617054135126896023459966393</X509SerialNumber>
</X509IssuerSerial>
</X509Data>
</KeyInfo>
</Signature>
</ns0:CFE>
<ns0:CFE version="1.0" xmlns:ns0="http://cfe.dgi.gub.uy">
<ns0:eTck>
<ns0:TmstFirma>2016-07-19T15:56:20-03:00</ns0:TmstFirma>
</ns0:eTck>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>p1VrpYT0zI7huek2QhjKTGbCBsY=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>Ad2/KMLEZgYPxlDVBUenLJ1L/AGYd9cskYzk09vJr2e6qoO/y6Swt5J5eN2ruPpNDj5HK80Ric+IiEDUdLEjBaveg/6Ao7v8v+biDUWCii7uWhGZ8wfyyEXbcrsZ+oATMmn0g36JWI3nFMkPlTkKvIIGJHPJR8xIQxcOI3vVgr0=</SignatureValue>
<KeyInfo>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>CN=Correo Uruguayo - CA, OU=SERVICIOS ELECTRONICOS, O=ADMINISTRACION NACIONAL DE CORREOS, C=UY</X509IssuerName>
<X509SerialNumber>155761856642617054135126896023459966393</X509SerialNumber>
</X509IssuerSerial>
</X509Data>
</KeyInfo>
</Signature>
</ns0:CFE>
Having spoken with the institution that receives and validates the xml (They practically have no idea how it works.
), I think if i sign an CFE, the digest value must be generated by the canonization of the subsection of that CFE.
So if then i use the chillkat to comprobate if digest value is correct i only should pass a signed subsecciton? I'm not entirely sure how to do it with the library.
I appreciate your understanding for my lack of knowledge on the subject. Any other information you need that may help understand the problem, please ask me.
from xml-crypto.
Yesterday i was trying to replicate your recommendations and i didn't get the digest value error anymore, but the signature still is invalid for some reason that i cant find. I used your generated certificate and private key too, but the result was the same when i use chillket to test. >
Does anyone know why this could be happening?
Chilkat doesn't have your private key's public certificate which it could use to verify signature.
Use some other online service or use some other tool like:
xmlsec1 --verify ...
(I don't have time to lookup and provide rest of options for that)
Having spoken with the institution that receives and validates the xml (They practically have no idea how it works.) ...
...
I get a feeling that no-one knows what proper outcome should be in your application's case. I.e. admins at the receiving side cannot provide you spec and therefore they are also unable to check that you are signing stuff correctly and that they are performing proper validation of input.
IMHO xml-crypto
's bug ticket is not correct place to have this discussion. Instead of having this bug ticket #472 this should have been discussion.
from xml-crypto.
Related Issues (20)
- refactor: deprecate `SignedXml.signingKey` in favor of `SignedXml.publicKey` and `SignedXml.privateKey` HOT 1
- `xpath` dependency "problem" HOT 10
- [ENHANCEMENT]: Signature compliant to http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1 HOT 5
- [ENHANCEMENT]: Export `C14nCanonicalization`, `ExclusiveCanonicalization` HOT 1
- [ENHANCEMENT]: Remove files, folders not needed on the release HOT 2
- Add Reference for the KeyInfo node
- [BUG]: keyInfo usage HOT 4
- invalid signature: for uri calculated digest is '*' but the xml to validate supplies digest '*' HOT 9
- Issue with Signature Verification When 'Transforms' Tag is Absent in 'Reference' Element HOT 5
- How to sign a SAML assertion? HOT 1
- Potentially unsafe default impl for `getKeyInfo()` HOT 2
- [BUG?]: duplicate reference in signature HOT 6
- The declared digest does not match the actual calculated digest HOT 3
- Bug/Outdated README: unclear whether signatureAlgorithm required or not HOT 2
- [ENHANCEMENT]: AddObject to SignedXml instance HOT 4
- [ENHANCEMENT]: wssecurity - getCertFromKeyInfo not possible HOT 1
- [ENHANCEMENT]: Improve experience of adding a `Reference` to the `Signature`.
- [ENHANCEMENT]: Making the signature wrap the content that it's signing HOT 4
- [BUG]: Sign signature on xml that included tag <![CDATA[.....]]> HOT 8
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from xml-crypto.