samlp
Version:
SAML Protocol server middleware
157 lines (128 loc) • 5.12 kB
JavaScript
var SignedXml = require('xml-crypto').SignedXml;
var thumbprint = require('@auth0/thumbprint');
var xmlCrypto = require('xml-crypto');
var crypto = require('crypto');
var encoders = require('./encoders');
var constants = require('./constants');
var algorithms = {
signature: {
'rsa-sha256': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
'rsa-sha1': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
},
digest: {
'sha256': 'http://www.w3.org/2001/04/xmlenc#sha256',
'sha1': 'http://www.w3.org/2000/09/xmldsig#sha1'
}
};
var DEFAULT_SIG_ALG = 'rsa-sha256';
var DEFAULT_DIGEST_ALG = 'sha256';
module.exports.getSigAlg = function (options) {
return algorithms.signature[options.signatureAlgorithm || DEFAULT_SIG_ALG];
};
module.exports.signXml = function (options, xml) {
var signatureAlgorithm = options.signatureAlgorithm || DEFAULT_SIG_ALG;
var digestAlgorithm = options.digestAlgorithm || DEFAULT_DIGEST_ALG;
var sig = new SignedXml(null, {
signatureAlgorithm: algorithms.signature[signatureAlgorithm]
});
sig.addReference(options.reference || constants.ELEMENTS.LOGOUT_REQUEST.SIGNATURE_LOCATION_PATH,
["http://www.w3.org/2000/09/xmldsig#enveloped-signature",
"http://www.w3.org/2001/10/xml-exc-c14n#"],
algorithms.digest[digestAlgorithm]);
sig.signingKey = options.key;
var pem = encoders.removeHeaders(options.cert);
sig.keyInfoProvider = {
getKeyInfo: function () {
return "<X509Data><X509Certificate>" + pem + "</X509Certificate></X509Data>";
}
};
sig.computeSignature(xml, {
location: {
reference: "//*[local-name(.)='Issuer']",
action: 'after'
}
});
return sig.getSignedXml();
};
module.exports.validateXmlEmbeddedSignature = function (xml, options) {
var calculatedThumbprint = '';
var signature = xmlCrypto.xpath(xml, "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];
if (!signature){
return ['Signature is missing'];
}
if (options.thumprints){
// Make sure thumprints is an array for the validation
options.thumbprints = options.thumbprints instanceof Array ? options.thumbprints : [options.thumbprints];
}
var sig = new SignedXml();
sig.keyInfoProvider = {
getKeyInfo: function () {
return "<X509Data></X509Data>";
},
getKey: function (keyInfo) {
//If there's no embedded signing cert, use the configured cert through options
if(!keyInfo || keyInfo.length===0){
if(!options.signingCert) throw new Error('options.signingCert must be specified for SAMLResponses with no embedded signing certificate');
return certToPEM(options.signingCert);
}
//If there's an embedded signature and thumprints are provided check that
if (options.thumbprints && options.thumbprints.length > 0) {
var embeddedSignature = keyInfo[0].getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "X509Certificate");
if (embeddedSignature.length > 0) {
var base64cer = embeddedSignature[0].firstChild.toString();
calculatedThumbprint = thumbprint.calculate(base64cer);
// using embedded cert, so options.cert is not used anymore
delete options.signingCert;
return certToPEM(base64cer);
}
}
// If there's an embedded signature, but no thumprints are supplied, use options.cert
// either options.cert or options.thumbprints must be specified so at this point there
// must be an options.cert
return certToPEM(options.signingCert);
}
};
var valid;
try {
sig.loadSignature(signature);
valid = sig.checkSignature(xml.toString());
} catch (e) {
return [e];
}
if (!valid) {
return sig.validationErrors;
}
if (options.cert) {
return;
}
if (options.thumbprints) {
var valid_thumbprint = options.thumbprints.some(function (thumbprint) {
return calculatedThumbprint.toUpperCase() === thumbprint.toUpperCase();
});
if (!valid_thumbprint) {
return ['Invalid thumbprint (configured: ' + options.thumbprints.join(', ').toUpperCase() + '. calculated: ' + calculatedThumbprint.toUpperCase() + ')'];
}
return;
}
return;
};
module.exports.sign = function (options, content) {
var signatureAlgorithm = options.signatureAlgorithm || DEFAULT_SIG_ALG;
var signer = crypto.createSign(signatureAlgorithm.toUpperCase());
signer.update(content);
return signer.sign(options.key, 'base64');
};
module.exports.isValidContentAndSignature = function (content, signature, options) {
var verifier = crypto.createVerify(options.signatureAlgorithm.split('#')[1].toUpperCase());
verifier.update(content);
return verifier.verify(certToPEM(options.signingCert), signature, 'base64');
};
function certToPEM(cert) {
if (/-----BEGIN CERTIFICATE-----/.test(cert)) {
return cert;
}
cert = cert.match(/.{1,64}/g).join('\n');
cert = "-----BEGIN CERTIFICATE-----\n" + cert;
cert = cert + "\n-----END CERTIFICATE-----\n";
return cert;
}