Commit 0e75897d authored by Alberto Inch's avatar Alberto Inch
Browse files

Firma digital de documentos XML con DSIG.

parents
Pipeline #329 failed with stages
node_modules
LPGBolivia.md
\ No newline at end of file
This diff is collapsed.
# pkcs11-xml
Esta librería se creó con el propósito de firmar documentos XML mediante el estándar DSIG con contenedores PKCS#12 desde aplicaciones escritas para nodejs.
## Instalación
```
$ npm install pkcs11-xml
```
## Ejemplo
### Firmar el tag "book" del documento xml
```javascript
const Dsig = require('pkcs12-xml');
var dsig = new Dsig('store.p12');
try {
dsig.openSession('12345678');
var xml = '<library><book><name>Julio Berne</name></book></library>';
console.log(dsig.computeSignature(xml, 'book'));
} catch(e) {
console.error(e);
} finally {
dsig.closeSession();
}
```
const convert = require('xml-js');
const crypto = require('crypto');
const Token = require('./token');
class Dsig {
constructor(file) {
this.token = new Token(file);
this.signOpt = {
compact: true,
ignoreComment: true,
spaces: 2,
fullTagEmptyElement: true
};
this.signedInfo = {
CanonicalizationMethod: {
_attributes: {
Algorithm: 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
}
},
SignatureMethod: {
_attributes: {
Algorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
}
},
Reference: {
_attributes: {
URI: ''
},
Transforms: {
Transform: [
{
_attributes: {
Algorithm: 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'
}
}, {
_attributes: {
Algorithm: 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'
}
}
]
},
DigestMethod: {
_attributes: {
Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
}
},
DigestValue: ''
}
};
}
openSession(pin) {
this.token.openSession(pin);
}
closeSession() {
try {
this.token.closeSession();
} catch (error) {
}
}
computeSignature(xml, tag) {
const doc = convert.xml2js(xml, this.signOpt);
const root = Object.keys(doc);
const _attributes = {
xmlns: 'http://www.w3.org/2000/09/xmldsig#'
};
if (tag) {
if (doc[root][tag]) {
doc[root][tag]._attributes = { id: tag };
} else {
throw new Error('Tag no encontrado.');
}
this.signedInfo.Reference._attributes.URI = `#${tag}`;
} else {
_attributes['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance';
}
const hash = this.digest(convert.js2xml(doc, this.signOpt));
this.signedInfo.Reference.DigestValue = hash;
const signedInfoXML = convert.js2xml({
SignedInfo: {
_attributes,
CanonicalizationMethod: this.signedInfo.CanonicalizationMethod,
SignatureMethod: this.signedInfo.SignatureMethod,
Reference: this.signedInfo.Reference
}
}, this.signOpt).split('\n').map(e => e.trim()).join('\n');
console.log(signedInfoXML);
let signature = this.signatureValue(signedInfoXML);
doc[root].Signature = {
_attributes: {
xmlns: 'http://www.w3.org/2000/09/xmldsig#'
},
SignedInfo: this.signedInfo,
SignatureValue: signature,
KeyInfo: {
X509Data: {
X509Certificate: this.certificate
}
}
};
let flag = false;
return convert.js2xml(doc, this.signOpt).split('\n').map(e => {
if (e.indexOf('<Signature') >= 0) {
flag = true;
}
if (e.indexOf('</Signature>') >= 0) {
flag = false;
return e.trim();
}
if (flag) {
return e.trim();
} else {
return e;
}
}).join('\n').replace('</Signature>\n', '</Signature>');
}
digest(xml) {
var sha256 = crypto.createHash('sha256');
sha256.update(xml, 'utf8');
return sha256.digest('base64');
}
signatureValue(xml) {
let privateKey = this.token.getPrivateKey();
this.certificate = this.token.getCertificate();
let signature = this.token.signature(xml, privateKey);
return Buffer.from(signature, 'binary').toString('base64');
}
}
module.exports = Dsig;
const fs = require('fs');
const forge = require('node-forge');
class Token {
constructor(file) {
const pkcs12 = fs.readFileSync(file);
this.p12Der = forge.asn1.fromDer(pkcs12.toString('binary'));
}
openSession(pin, index = this.index) {
this.p12Asn1 = forge.pkcs12.pkcs12FromAsn1(this.p12Der, false, pin);
}
closeSession() {
this.p12Asn1 = undefined;
}
getPrivateKey() {
for (let i = 0; i < this.p12Asn1.safeContents.length; i++) {
if (this.p12Asn1.safeContents[i].safeBags[0].key) {
return forge.pki.privateKeyToPem(this.p12Asn1.safeContents[i].safeBags[0].key);
}
}
return null;
}
getCertificate() {
for (let i = 0; i < this.p12Asn1.safeContents.length; i++) {
if (this.p12Asn1.safeContents[i].safeBags[0].cert) {
return forge.pki.certificateToPem(this.p12Asn1.safeContents[i].safeBags[0].cert);
}
}
return null;
}
signature(xml, privateKey) {
const md = forge.md.sha256.create();
md.update(xml, 'utf8');
var key = forge.pki.privateKeyFromPem(privateKey);
return key.sign(md);
}
}
module.exports = Token;
{
"name": "pkcs12-xml",
"version": "1.0.0",
"description": "Librería para firma de documentos XML con DSIG",
"main": "./lib/dsig",
"scripts": {
"test": "node test/index.js"
},
"repository": {
"type": "git",
"url": "https://gitlab.softwarelibre.gob.bo/adsib/pkcs12-xml"
},
"keywords": [
"pkcs12",
"xml"
],
"author": "ADSIB",
"maintainers": [
"<ainch@adsib.gob.bo>"
],
"license": "GPL-3.0-or-later",
"dependencies": {
"node-forge": "^0.8.5",
"xml-js": "^1.6.9"
}
}
const Dsig = require('../lib/dsig');
var dsig = new Dsig(`${__dirname}/../token/token.pfx`);
try {
dsig.openSession('12345678');
var xml = '<library><book><name>Harry Potter</name></book></library>';
console.log(dsig.computeSignature(xml, 'book'));
} catch(e) {
console.error(e);
} finally {
dsig.closeSession();
}
File added
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment