Skip to content

Commit 17bbd13

Browse files
committed
Add callback options to sign/verify asynchronously
-Hardware Security Module (HSM) signing/verification usually takes place over an async connection. -Used callbacks instead of async/await for minimal code changes and backward compatibility
1 parent f1e7ddb commit 17bbd13

File tree

2 files changed

+154
-21
lines changed

2 files changed

+154
-21
lines changed

lib/signed-xml.js

Lines changed: 89 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -333,28 +333,56 @@ SignedXml.defaultNsForPrefix = {
333333

334334
SignedXml.findAncestorNs = findAncestorNs;
335335

336-
SignedXml.prototype.checkSignature = function(xml) {
336+
SignedXml.prototype.checkSignature = function(xml, callback) {
337+
if (callback != null && typeof callback !== 'function') {
338+
throw new Error("Last paramater must be a callback function")
339+
}
340+
337341
this.validationErrors = []
338342
this.signedXml = xml
339343

340344
if (!this.keyInfoProvider) {
341-
throw new Error("cannot validate signature since no key info resolver was provided")
345+
var err = new Error("cannot validate signature since no key info resolver was provided")
346+
if (!callback){
347+
throw err
348+
}else{
349+
callback(err)
350+
return
351+
}
342352
}
343353

344354
this.signingKey = this.keyInfoProvider.getKey(this.keyInfo)
345-
if (!this.signingKey) throw new Error("key info provider could not resolve key info " + this.keyInfo)
355+
if (!this.signingKey) {
356+
var err = new Error("key info provider could not resolve key info " + this.keyInfo)
357+
if (!callback){
358+
throw err
359+
}else{
360+
callback(err)
361+
return
362+
}
363+
}
346364

347365
var doc = new Dom().parseFromString(xml)
348366

349367
if (!this.validateReferences(doc)) {
350-
return false;
351-
}
352-
353-
if (!this.validateSignatureValue(doc)) {
354-
return false;
368+
if (!callback){
369+
return false;
370+
}else{
371+
callback(new Error('Could not validate references'))
372+
}
355373
}
356374

357-
return true
375+
var ret = this.validateSignatureValue(doc)
376+
if (ret instanceof Promise){
377+
ret.then((asyncValidateSig)=>{
378+
callback(null, asyncValidateSig)
379+
})
380+
}else{
381+
if (!ret) {
382+
return false;
383+
}
384+
return true
385+
}
358386
}
359387

360388
SignedXml.prototype.getCanonSignedInfoXml = function(doc) {
@@ -411,7 +439,7 @@ SignedXml.prototype.validateSignatureValue = function(doc) {
411439
SignedXml.prototype.calculateSignatureValue = function(doc) {
412440
var signedInfoCanon = this.getCanonSignedInfoXml(doc)
413441
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm)
414-
this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey)
442+
return this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey)
415443
}
416444

417445
SignedXml.prototype.findSignatureAlgorithm = function(name) {
@@ -654,7 +682,15 @@ SignedXml.prototype.addReference = function(xpath, transforms, digestAlgorithm,
654682
* `append`, `prepend`, `before`, `after`
655683
*
656684
*/
657-
SignedXml.prototype.computeSignature = function(xml, opts) {
685+
SignedXml.prototype.computeSignature = function(xml, opts, callback) {
686+
if (typeof opts === 'function' && callback == null){
687+
callback = opts
688+
}
689+
690+
if (callback != null && typeof callback !== 'function'){
691+
throw new Error("Last paramater must be a callback function")
692+
}
693+
658694
var doc = new Dom().parseFromString(xml),
659695
xmlNsAttr = "xmlns",
660696
signatureAttrs = [],
@@ -676,8 +712,14 @@ SignedXml.prototype.computeSignature = function(xml, opts) {
676712
location.action = location.action || "append";
677713

678714
if (validActions.indexOf(location.action) === -1) {
679-
throw new Error("location.action option has an invalid action: " + location.action +
680-
", must be any of the following values: " + validActions.join(", "));
715+
var err = new Error("location.action option has an invalid action: " + location.action +
716+
", must be any of the following values: " + validActions.join(", "));
717+
if (!callback){
718+
throw err;
719+
}else{
720+
callback(err, null)
721+
return
722+
}
681723
}
682724

683725
// automatic insertion of `:`
@@ -719,7 +761,13 @@ SignedXml.prototype.computeSignature = function(xml, opts) {
719761
var referenceNode = xpath.select(location.reference, doc);
720762

721763
if (!referenceNode || referenceNode.length === 0) {
722-
throw new Error("the following xpath cannot be used because it was not found: " + location.reference);
764+
var err = new Error("the following xpath cannot be used because it was not found: " + location.reference);
765+
if (!callback){
766+
throw err
767+
}else{
768+
callback(err, null)
769+
return
770+
}
723771
}
724772

725773
referenceNode = referenceNode[0];
@@ -735,16 +783,36 @@ SignedXml.prototype.computeSignature = function(xml, opts) {
735783
}
736784

737785
this.signatureNode = signatureDoc
738-
this.calculateSignatureValue(doc)
739-
740786
var signedInfoNode = utils.findChilds(this.signatureNode, "SignedInfo")
741-
if (signedInfoNode.length==0) throw new Error("could not find SignedInfo element in the message")
742-
787+
if (signedInfoNode.length == 0) {
788+
var err = new Error("could not find SignedInfo element in the message")
789+
if (!callback){
790+
throw err
791+
}else{
792+
callback(err)
793+
return
794+
}
795+
}
743796
signedInfoNode = signedInfoNode[0];
744-
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling)
745797

746-
this.signatureXml = signatureDoc.toString()
747-
this.signedXml = doc.toString()
798+
var ret = this.calculateSignatureValue(doc)
799+
if (ret instanceof Promise){
800+
ret.then((asyncSig)=>{
801+
this.signatureValue = asyncSig
802+
803+
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling)
804+
this.signatureXml = signatureDoc.toString()
805+
this.signedXml = doc.toString()
806+
807+
if (callback) callback()
808+
}).catch(function(err){
809+
if (callback) callback(err)
810+
})
811+
}else{
812+
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling)
813+
this.signatureXml = signatureDoc.toString()
814+
this.signedXml = doc.toString()
815+
}
748816
}
749817

750818
SignedXml.prototype.getKeyInfo = function(prefix) {

test/signature-unit-tests.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var select = require('xpath').select
33
, SignedXml = require('../lib/signed-xml.js').SignedXml
44
, FileKeyInfo = require('../lib/signed-xml.js').FileKeyInfo
55
, fs = require('fs')
6+
, crypto = require('crypto')
67

78
module.exports = {
89

@@ -473,6 +474,70 @@ module.exports = {
473474
test.done();
474475
},
475476

477+
"signer creates correct signature values using async callback": function (test) {
478+
479+
function DummySignatureAlgorithm() {
480+
this.getSignature = function (signedInfo, signingKey) {
481+
return new Promise((resolve, reject) => {
482+
var signer = crypto.createSign("RSA-SHA1")
483+
signer.update(signedInfo)
484+
var res = signer.sign(signingKey, 'base64')
485+
resolve(res)
486+
})
487+
}
488+
this.getAlgorithmName = function () {
489+
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
490+
}
491+
}
492+
493+
var xml = "<root><x xmlns=\"ns\" Id=\"_0\"></x><y attr=\"value\" Id=\"_1\"></y><z><w Id=\"_2\"></w></z></root>"
494+
SignedXml.SignatureAlgorithms["http://dummySignatureAlgorithmAsync"] = DummySignatureAlgorithm
495+
var sig = new SignedXml()
496+
sig.signatureAlgorithm = "http://dummySignatureAlgorithmAsync"
497+
sig.signingKey = fs.readFileSync("./test/static/client.pem")
498+
sig.keyInfoProvider = null
499+
500+
sig.addReference("//*[local-name(.)='x']")
501+
sig.addReference("//*[local-name(.)='y']")
502+
sig.addReference("//*[local-name(.)='w']")
503+
504+
sig.computeSignature(xml, function(err){
505+
var signedXml = sig.getSignedXml()
506+
var expected = "<root><x xmlns=\"ns\" Id=\"_0\"/><y attr=\"value\" Id=\"_1\"/><z><w Id=\"_2\"/></z>" +
507+
"<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">" +
508+
"<SignedInfo>" +
509+
"<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>" +
510+
"<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>" +
511+
"<Reference URI=\"#_0\">" +
512+
"<Transforms>" +
513+
"<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></Transforms>" +
514+
"<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>" +
515+
"<DigestValue>b5GCZ2xpP5T7tbLWBTkOl4CYupQ=</DigestValue>" +
516+
"</Reference>" +
517+
"<Reference URI=\"#_1\">" +
518+
"<Transforms>" +
519+
"<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>" +
520+
"</Transforms>" +
521+
"<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>" +
522+
"<DigestValue>4Pq/sBri+AyOtxtSFsPSOyylyzk=</DigestValue>" +
523+
"</Reference>" +
524+
"<Reference URI=\"#_2\">" +
525+
"<Transforms>" +
526+
"<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>" +
527+
"</Transforms>" +
528+
"<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>" +
529+
"<DigestValue>6I7SDu1iV2YOajTlf+iMLIBfLnE=</DigestValue>" +
530+
"</Reference>" +
531+
"</SignedInfo>" +
532+
"<SignatureValue>NejzGB9MDUddKCt3GL2vJhEd5q6NBuhLdQc3W4bJI5q34hk7Hk6zBRoW3OliX+/f7Hpi9y0INYoqMSUfrsAVm3IuPzUETKlI6xiNZo07ULRj1DwxRo6cU66ar1EKUQLRuCZas795FjB8jvUI2lyhcax/00uMJ+Cjf4bwAQ+9gOQ=</SignatureValue>" +
533+
"</Signature>" +
534+
"</root>"
535+
536+
test.equal(expected, signedXml, "wrong signature format")
537+
test.done();
538+
})
539+
},
540+
476541
"correctly loads signature": function(test) {
477542
passLoadSignature(test, "./test/static/valid_signature.xml")
478543
passLoadSignature(test, "./test/static/valid_signature.xml", true)

0 commit comments

Comments
 (0)