xml-crypto icon indicating copy to clipboard operation
xml-crypto copied to clipboard

Signature did not verify

Open VaLThieL opened this issue 4 years ago • 13 comments

I'm stuck by several days on this. The digest is correct but when I verify the xml generated, I got the signature is invalid

here's my code

const soap = require('soap');
const fs = require('fs');
const JSZip = require("jszip");
const SignedXml = require('xml-crypto').SignedXml;
const select = require('xml-crypto').xpath;
const dom = require('xmldom').DOMParser;
const forge = require('node-forge');
const pki = forge.pki;

function KeyInfoProvider(certificatePEM) {
	if (!this instanceof KeyInfoProvider) {
		return new KeyInfoProvider();
	}
	if (Buffer.isBuffer(certificatePEM)) {
		certificatePEM = certificatePEM.toString('ascii');
	}
	if (certificatePEM == null || typeof certificatePEM !== 'string') {
		throw new Error('certificatePEM must be a valid certificate in PEM format');
	}

	this._certificatePEM = certificatePEM;
	this.getKeyInfo = function (key, prefix) {
		let keyInfoXml,
			certObj,
			certBodyInB64;

		prefix = prefix || '';
		prefix = prefix ? prefix + ':' : prefix;
		certBodyInB64 = forge.util.encode64(forge.pem.decode(this._certificatePEM)[1].body);
		certObj = pki.certificateFromPem(`-----BEGIN CERTIFICATE-----\n${certBodyInB64}\n-----END CERTIFICATE-----`);

		keyInfoXml = `<${prefix}X509Data>`;
		keyInfoXml += `<${prefix}X509SubjectName>`;
		keyInfoXml += `${getSubjectName(certObj)}`;
		keyInfoXml += `</${prefix}X509SubjectName>`;
		keyInfoXml += `<${prefix}X509Certificate>`;
		keyInfoXml += `${certBodyInB64}`;
		keyInfoXml += `</${prefix}X509Certificate>`;
		keyInfoXml += `</${prefix}X509Data>`;

		return keyInfoXml;
	};

	this.getKey = function () {
		return this._certificatePEM;
	};
};
function getSubjectName(certObj) {
	const fields = ['CN', 'OU', 'O', 'L', 'ST', 'C'];
	let subjectFields;

	if (certObj.subject) {
		subjectFields = fields.reduce(function (subjects, fieldName) {
			const certAttr = certObj.subject.getField(fieldName);

			if (certAttr) {
				subjects.push(fieldName + '=' + certAttr.value);
			}

			return subjects;
		}, []);
	}
	return Array.isArray(subjectFields) ? subjectFields.join(',') : '';
};
function normalizeLineEndings(string) {
	return string.replace(/\r\n?/g, '\n').replace(/[\r\n\t ]+/g, ' ');
}
const transforms = [
	'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
	"http://www.w3.org/2001/10/xml-exc-c14n#"
];
const digests = [
	"http://www.w3.org/2001/04/xmlenc#sha256"
];
const xpath = "//*[local-name(.)='ExtensionContent']";		
const options = {};

const url = 'https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService?wsdl';
const certFile = "./cert/20529473169.pem";

const data = req.param("data");
const fileName = `${data.other.companyRuc}-${data.other.documentType}-${data.other.serie}-${data.other.correlative}`;
const soapOptions = { forceSoap12Headers: false };
const certBuffer = fs.readFileSync(certFile);

let zip = new JSZip();

let sig = new SignedXml(null, options);
sig.addReference(xpath, transforms, digests);
sig.keyInfoProvider = new KeyInfoProvider(certBuffer);
sig.signingKey = certBuffer;
sig.canonicalizationAlgorithm = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
sig.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
sig.computeSignature(normalizeLineEndings(zipfiles.createXML(data)), {
	prefix: "ds",
	attrs: {
		Id: `SignatureMM`
	},
	location: { reference: "//*[local-name(.)='ExtensionContent']", action: "append" }
});

const xmlSigned = sig.getSignedXml();

// CHECK
let xml = xmlSigned;
const doc = new dom().parseFromString(xml);
const signature = select(doc, "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];
let sig2 = new SignedXml(null, options);
sig2.keyInfoProvider = new KeyInfoProvider(certBuffer);
sig2.loadSignature(signature);
const resxml = sig2.checkSignature(xml);
console.log("resXML", resxml);
if (!resxml) console.log(sig2.validationErrors);
// CHECK

zip.file(`${fileName}.xml`, xmlSigned);

I'm putting the generated zip too

20529473169-01-F001-256.zip

NOTE: I was using this link too besides the company service to check the xml generated

https://tools.chilkat.io/xmlDsigVerify.cshtml

Please I really need help, I'm stuck and I don't know what to do, add normalizeLineEndings function for check if is something with the break lines, try with implicit transforms too, but nothing.

VaLThieL avatar Aug 16 '20 07:08 VaLThieL

Hola compatriota, lograste solucionarlo? Tengo el mismo problema, obtengo este error como respuesta Incorrect reference digest value

erwn2793 avatar Apr 04 '21 22:04 erwn2793

Lograron solucionar sobre el error Incorrect reference digest value?

JhostinOsorio avatar Sep 10 '21 15:09 JhostinOsorio

Hola, alguien pudo resolver ese problema de "reference digest value"?

eduardowin avatar Aug 17 '22 21:08 eduardowin

Buenas, lograron resolver este problema?, hasta ahora me valida que la firma esta mal

iaguedo avatar Nov 17 '22 15:11 iaguedo

@VaLThieL did you ever figure this out? If not, would you mind elaborating on what reference(s) failed signature validation?

I was getting a similar problem, but I don't want to post my solution if the problem I was having is from a different root cause.

djaqua avatar May 08 '23 05:05 djaqua

hasta el momento no e podido solucionarlo, me devuelve error al validar la firma

iaguedo avatar May 08 '23 15:05 iaguedo

Hola compatriota, lograste solucionarlo? Tengo el mismo problema, obtengo este error como respuesta Incorrect reference digest value

Si estimado, al final use un servicio de python para encriptar, en si es mi middleware por así decirlo

@VaLThieL did you ever figure this out? If not, would you mind elaborating on what reference(s) failed signature validation?

I was getting a similar problem, but I don't want to post my solution if the problem I was having is from a different root cause.

Cant with this library, at the end I use a python complement

VaLThieL avatar May 08 '23 21:05 VaLThieL

@djaqua , if you have a solution, please feel free to post it so we can discuss (preferably in English). We are eager to have PRs with test suites to help make this library better for everyone so that @VaLThieL and others don't have to use a Python complement or maintain their own forks.

cjbarth avatar May 29 '23 21:05 cjbarth

@cjbarth sorry - been pretty busy. Give me a few minutes to catch up and figure out where all this is at

djaqua avatar Jun 09 '23 02:06 djaqua

Hello guys, I managed to solve it, tomorrow I will share my solution

erwn2793 avatar Jun 09 '23 02:06 erwn2793

Again, sorry for the late response!

When I wasn't able to verify the signatures that I was generating, it was because I rolled my own implementation of WS-Security and the Timestamp element existed in the same namespace alias as the Id attributes that xml-crypto uses when adding references ("wsu"). My solution: put Timestamp in it's own namespace alias ("wsu0" is what I used).

I'm honestly unsure whether this is a "bug" or a valid interpretation of the XML digital signature specification.

I'm curious and eager to see how you solved your problem :smile:

djaqua avatar Jun 11 '23 02:06 djaqua

this was my solution

const signature = new SignedXml();
    signature.addReference(
      "//*[local-name(.)='Invoice']",
      [
        'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
        'http://www.w3.org/2001/10/xml-exc-c14n#',
      ],
      'http://www.w3.org/2001/04/xmlenc#sha256',
      '',
      '',
      '',
      true,
    );
    signature.signingKey = Buffer.from(certificate.key);
    signature.canonicalizationAlgorithm =
      'http://www.w3.org/2001/10/xml-exc-c14n#';
    signature.signatureAlgorithm =
      'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
    signature.keyInfoProvider = this.keyInfoProvider(certificate);
    signature.computeSignature(xml, {
      prefix: 'ds',
      location: {
        reference: ".//*[local-name(.)='ExtensionContent']",
        action: 'prepend',
      },
      attrs: {
        Id: 'SignatureSP',
      },
    });
    return signature.getSignedXml();

erwn2793 avatar Jun 11 '23 02:06 erwn2793

@erwn2793 - is one of the elements your signing in the 'wsu' namespace? Setting the namespace prefix explicitly in computeSignature is what I did as part of my solution, but I don't think it was strictly necessary.

As I mentioned, I had to alias the xml-dsig namespace to 'wsu0' for my Timestamp element, but I also explicitly set the prefix to 'wsu1' for computeSignature so that I could add the comment "must be a different namespace than the Timestamp element"

Also, in your code, does this.keyInfoProvider(certificate) return a specific instance of KeyInfoProvider based on the certificate you specify?

djaqua avatar Jun 11 '23 23:06 djaqua