Skip to content

Commit e9942f3

Browse files
authored
Merge pull request #206 from troyfactor4/master
Add callback options to sign/verify asynchronously
2 parents f1e7ddb + ddc17c9 commit e9942f3

File tree

5 files changed

+2190
-28
lines changed

5 files changed

+2190
-28
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,33 @@ Now do the signing. Note how we configure the signature to use the above algorit
365365

366366
You can always look at the actual code as a sample (or drop me a [mail](mailto:[email protected])).
367367

368+
## Asynchronous signing and verification
369+
370+
If the private key is not stored locally and you wish to use a signing server or Hardware Security Module (HSM) to sign documents you can create a custom signing algorithm that uses an asynchronous callback.
371+
372+
`````javascript
373+
function AsyncSignatureAlgorithm() {
374+
this.getSignature = function (signedInfo, signingKey, callback) {
375+
var signer = crypto.createSign("RSA-SHA1")
376+
signer.update(signedInfo)
377+
var res = signer.sign(signingKey, 'base64')
378+
//Do some asynchronous things here
379+
callback(null, res)
380+
}
381+
this.getAlgorithmName = function () {
382+
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
383+
}
384+
}
385+
386+
SignedXml.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm
387+
var sig = new SignedXml()
388+
sig.signatureAlgorithm = "http://asyncSignatureAlgorithm"
389+
sig.computeSignature(xml, opts, function(err){
390+
var signedResponse = sig.getSignedXml()
391+
})
392+
`````
393+
394+
The function `sig.checkSignature` may also use a callback if asynchronous verification is needed.
368395

369396
## X.509 / Key formats
370397
Xml-Crypto internally relies on node's crypto module. This means pem encoded certificates are supported. So to sign an xml use key.pem that looks like this (only the begining of the key content is shown):

lib/signed-xml.js

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -333,28 +333,64 @@ 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+
return
373+
}
355374
}
356375

357-
return true
376+
if (!callback) {
377+
//Syncronous flow
378+
if (!this.validateSignatureValue(doc)) {
379+
return false
380+
}
381+
return true
382+
} else {
383+
//Asyncronous flow
384+
this.validateSignatureValue(doc, function (err, isValidSignature) {
385+
if (err) {
386+
this.validationErrors.push("invalid signature: the signature value " +
387+
this.signatureValue + " is incorrect")
388+
callback(err)
389+
} else {
390+
callback(null, isValidSignature)
391+
}
392+
})
393+
}
358394
}
359395

360396
SignedXml.prototype.getCanonSignedInfoXml = function(doc) {
@@ -399,19 +435,19 @@ SignedXml.prototype.getCanonReferenceXml = function(doc, ref, node) {
399435
return this.getCanonXml(ref.transforms, node, c14nOptions)
400436
}
401437

402-
SignedXml.prototype.validateSignatureValue = function(doc) {
438+
SignedXml.prototype.validateSignatureValue = function(doc, callback) {
403439
var signedInfoCanon = this.getCanonSignedInfoXml(doc)
404440
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm)
405-
var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue)
406-
if (!res) this.validationErrors.push("invalid signature: the signature value " +
441+
var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue, callback)
442+
if (!res && !callback) this.validationErrors.push("invalid signature: the signature value " +
407443
this.signatureValue + " is incorrect")
408444
return res
409445
}
410446

411-
SignedXml.prototype.calculateSignatureValue = function(doc) {
447+
SignedXml.prototype.calculateSignatureValue = function(doc, callback) {
412448
var signedInfoCanon = this.getCanonSignedInfoXml(doc)
413449
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm)
414-
this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey)
450+
this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey, callback)
415451
}
416452

417453
SignedXml.prototype.findSignatureAlgorithm = function(name) {
@@ -654,7 +690,15 @@ SignedXml.prototype.addReference = function(xpath, transforms, digestAlgorithm,
654690
* `append`, `prepend`, `before`, `after`
655691
*
656692
*/
657-
SignedXml.prototype.computeSignature = function(xml, opts) {
693+
SignedXml.prototype.computeSignature = function(xml, opts, callback) {
694+
if (typeof opts === 'function' && callback == null) {
695+
callback = opts
696+
}
697+
698+
if (callback != null && typeof callback !== 'function') {
699+
throw new Error("Last paramater must be a callback function")
700+
}
701+
658702
var doc = new Dom().parseFromString(xml),
659703
xmlNsAttr = "xmlns",
660704
signatureAttrs = [],
@@ -676,8 +720,14 @@ SignedXml.prototype.computeSignature = function(xml, opts) {
676720
location.action = location.action || "append";
677721

678722
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(", "));
723+
var err = new Error("location.action option has an invalid action: " + location.action +
724+
", must be any of the following values: " + validActions.join(", "));
725+
if (!callback) {
726+
throw err;
727+
} else {
728+
callback(err, null)
729+
return
730+
}
681731
}
682732

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

721771
if (!referenceNode || referenceNode.length === 0) {
722-
throw new Error("the following xpath cannot be used because it was not found: " + location.reference);
772+
var err = new Error("the following xpath cannot be used because it was not found: " + location.reference);
773+
if (!callback) {
774+
throw err
775+
} else {
776+
callback(err, null)
777+
return
778+
}
723779
}
724780

725781
referenceNode = referenceNode[0];
@@ -735,16 +791,39 @@ SignedXml.prototype.computeSignature = function(xml, opts) {
735791
}
736792

737793
this.signatureNode = signatureDoc
738-
this.calculateSignatureValue(doc)
739-
740794
var signedInfoNode = utils.findChilds(this.signatureNode, "SignedInfo")
741-
if (signedInfoNode.length==0) throw new Error("could not find SignedInfo element in the message")
742-
795+
if (signedInfoNode.length == 0) {
796+
var err = new Error("could not find SignedInfo element in the message")
797+
if (!callback) {
798+
throw err
799+
} else {
800+
callback(err)
801+
return
802+
}
803+
}
743804
signedInfoNode = signedInfoNode[0];
744-
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling)
745805

746-
this.signatureXml = signatureDoc.toString()
747-
this.signedXml = doc.toString()
806+
if (!callback) {
807+
//Synchronous flow
808+
this.calculateSignatureValue(doc)
809+
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling)
810+
this.signatureXml = signatureDoc.toString()
811+
this.signedXml = doc.toString()
812+
} else {
813+
var self = this
814+
//Asynchronous flow
815+
this.calculateSignatureValue(doc, function(err, signature) {
816+
if (err) {
817+
callback(err)
818+
} else {
819+
self.signatureValue = signature
820+
signatureDoc.insertBefore(self.createSignature(prefix), signedInfoNode.nextSibling)
821+
self.signatureXml = signatureDoc.toString()
822+
self.signedXml = doc.toString()
823+
callback()
824+
}
825+
})
826+
}
748827
}
749828

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

0 commit comments

Comments
 (0)