Skip to content

Commit

Permalink
Merge pull request #206 from troyfactor4/master
Browse files Browse the repository at this point in the history
Add callback options to sign/verify asynchronously
  • Loading branch information
LoneRifle committed Apr 12, 2020
2 parents f1e7ddb + ddc17c9 commit e9942f3
Show file tree
Hide file tree
Showing 5 changed files with 2,190 additions and 28 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,33 @@ Now do the signing. Note how we configure the signature to use the above algorit

You can always look at the actual code as a sample (or drop me a [mail](mailto:yaronn01@gmail.com)).

## Asynchronous signing and verification

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.

`````javascript
function AsyncSignatureAlgorithm() {
this.getSignature = function (signedInfo, signingKey, callback) {
var signer = crypto.createSign("RSA-SHA1")
signer.update(signedInfo)
var res = signer.sign(signingKey, 'base64')
//Do some asynchronous things here
callback(null, res)
}
this.getAlgorithmName = function () {
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
}
}

SignedXml.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm
var sig = new SignedXml()
sig.signatureAlgorithm = "http://asyncSignatureAlgorithm"
sig.computeSignature(xml, opts, function(err){
var signedResponse = sig.getSignedXml()
})
`````

The function `sig.checkSignature` may also use a callback if asynchronous verification is needed.

## X.509 / Key formats
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):
Expand Down
129 changes: 104 additions & 25 deletions lib/signed-xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,28 +333,64 @@ SignedXml.defaultNsForPrefix = {

SignedXml.findAncestorNs = findAncestorNs;

SignedXml.prototype.checkSignature = function(xml) {
SignedXml.prototype.checkSignature = function(xml, callback) {
if (callback != null && typeof callback !== 'function') {
throw new Error("Last paramater must be a callback function")
}

this.validationErrors = []
this.signedXml = xml

if (!this.keyInfoProvider) {
throw new Error("cannot validate signature since no key info resolver was provided")
var err = new Error("cannot validate signature since no key info resolver was provided")
if (!callback) {
throw err
} else {
callback(err)
return
}
}

this.signingKey = this.keyInfoProvider.getKey(this.keyInfo)
if (!this.signingKey) throw new Error("key info provider could not resolve key info " + this.keyInfo)
if (!this.signingKey) {
var err = new Error("key info provider could not resolve key info " + this.keyInfo)
if (!callback) {
throw err
} else {
callback(err)
return
}
}

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

if (!this.validateReferences(doc)) {
return false;
}

if (!this.validateSignatureValue(doc)) {
return false;
if (!callback) {
return false;
} else {
callback(new Error('Could not validate references'))
return
}
}

return true
if (!callback) {
//Syncronous flow
if (!this.validateSignatureValue(doc)) {
return false
}
return true
} else {
//Asyncronous flow
this.validateSignatureValue(doc, function (err, isValidSignature) {
if (err) {
this.validationErrors.push("invalid signature: the signature value " +
this.signatureValue + " is incorrect")
callback(err)
} else {
callback(null, isValidSignature)
}
})
}
}

SignedXml.prototype.getCanonSignedInfoXml = function(doc) {
Expand Down Expand Up @@ -399,19 +435,19 @@ SignedXml.prototype.getCanonReferenceXml = function(doc, ref, node) {
return this.getCanonXml(ref.transforms, node, c14nOptions)
}

SignedXml.prototype.validateSignatureValue = function(doc) {
SignedXml.prototype.validateSignatureValue = function(doc, callback) {
var signedInfoCanon = this.getCanonSignedInfoXml(doc)
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm)
var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue)
if (!res) this.validationErrors.push("invalid signature: the signature value " +
var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue, callback)
if (!res && !callback) this.validationErrors.push("invalid signature: the signature value " +
this.signatureValue + " is incorrect")
return res
}

SignedXml.prototype.calculateSignatureValue = function(doc) {
SignedXml.prototype.calculateSignatureValue = function(doc, callback) {
var signedInfoCanon = this.getCanonSignedInfoXml(doc)
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm)
this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey)
this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey, callback)
}

SignedXml.prototype.findSignatureAlgorithm = function(name) {
Expand Down Expand Up @@ -654,7 +690,15 @@ SignedXml.prototype.addReference = function(xpath, transforms, digestAlgorithm,
* `append`, `prepend`, `before`, `after`
*
*/
SignedXml.prototype.computeSignature = function(xml, opts) {
SignedXml.prototype.computeSignature = function(xml, opts, callback) {
if (typeof opts === 'function' && callback == null) {
callback = opts
}

if (callback != null && typeof callback !== 'function') {
throw new Error("Last paramater must be a callback function")
}

var doc = new Dom().parseFromString(xml),
xmlNsAttr = "xmlns",
signatureAttrs = [],
Expand All @@ -676,8 +720,14 @@ SignedXml.prototype.computeSignature = function(xml, opts) {
location.action = location.action || "append";

if (validActions.indexOf(location.action) === -1) {
throw new Error("location.action option has an invalid action: " + location.action +
", must be any of the following values: " + validActions.join(", "));
var err = new Error("location.action option has an invalid action: " + location.action +
", must be any of the following values: " + validActions.join(", "));
if (!callback) {
throw err;
} else {
callback(err, null)
return
}
}

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

if (!referenceNode || referenceNode.length === 0) {
throw new Error("the following xpath cannot be used because it was not found: " + location.reference);
var err = new Error("the following xpath cannot be used because it was not found: " + location.reference);
if (!callback) {
throw err
} else {
callback(err, null)
return
}
}

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

this.signatureNode = signatureDoc
this.calculateSignatureValue(doc)

var signedInfoNode = utils.findChilds(this.signatureNode, "SignedInfo")
if (signedInfoNode.length==0) throw new Error("could not find SignedInfo element in the message")

if (signedInfoNode.length == 0) {
var err = new Error("could not find SignedInfo element in the message")
if (!callback) {
throw err
} else {
callback(err)
return
}
}
signedInfoNode = signedInfoNode[0];
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling)

this.signatureXml = signatureDoc.toString()
this.signedXml = doc.toString()
if (!callback) {
//Synchronous flow
this.calculateSignatureValue(doc)
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling)
this.signatureXml = signatureDoc.toString()
this.signedXml = doc.toString()
} else {
var self = this
//Asynchronous flow
this.calculateSignatureValue(doc, function(err, signature) {
if (err) {
callback(err)
} else {
self.signatureValue = signature
signatureDoc.insertBefore(self.createSignature(prefix), signedInfoNode.nextSibling)
self.signatureXml = signatureDoc.toString()
self.signedXml = doc.toString()
callback()
}
})
}
}

SignedXml.prototype.getKeyInfo = function(prefix) {
Expand Down
Loading

0 comments on commit e9942f3

Please sign in to comment.