Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signature did not verify #216

Open
VaLThieL opened this issue Aug 16, 2020 · 13 comments
Open

Signature did not verify #216

VaLThieL opened this issue Aug 16, 2020 · 13 comments

Comments

@VaLThieL
Copy link

VaLThieL commented Aug 16, 2020

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.

@erwn2793
Copy link

erwn2793 commented Apr 4, 2021

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

@JhostinOsorio
Copy link

Lograron solucionar sobre el error Incorrect reference digest value?

@eduardowin
Copy link

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

@iaguedo
Copy link

iaguedo commented Nov 17, 2022

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

@djaqua
Copy link
Contributor

djaqua commented May 8, 2023

@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.

@iaguedo
Copy link

iaguedo commented May 8, 2023

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

@VaLThieL
Copy link
Author

VaLThieL commented May 8, 2023

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

@cjbarth
Copy link
Contributor

cjbarth commented May 29, 2023

@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.

@djaqua
Copy link
Contributor

djaqua commented Jun 9, 2023

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

@erwn2793
Copy link

erwn2793 commented Jun 9, 2023

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

@djaqua
Copy link
Contributor

djaqua commented Jun 11, 2023

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 😄

@erwn2793
Copy link

erwn2793 commented Jun 11, 2023

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();

@djaqua
Copy link
Contributor

djaqua commented Jun 11, 2023

@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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants