islandis-login
Version:
Island.is Identification and Authentication Services (Login) for Node.js
125 lines (99 loc) • 3.46 kB
JavaScript
const { xpath } = require("xml-crypto");
const { DOMParser } = require("@xmldom/xmldom");
const { SignedXml } = require("xml-crypto");
const { Certificate } = require("@fidm/x509");
const { readFileSync } = require("fs");
/**
*
* A key info provider implementation
*
*/
function FileKeyInfo(key) {
this.key = key;
this.getKeyInfo = function(key, prefix) {
prefix = prefix || "";
prefix = prefix ? prefix + ":" : prefix;
return `<${prefix}X509Data></${prefix}X509Data>`;
};
this.getKey = function() {
return this.key;
};
}
function isCertificateDataValid(cert) {
const { serialName } = cert.subject;
const { organizationName } = cert.issuer;
const { validFrom, validTo } = cert;
if (serialName !== "6503760649" || organizationName !== "Audkenni hf.") {
return false;
}
const timestamp = Date.now();
if (
timestamp < new Date(validFrom).getTime() ||
timestamp > new Date(validTo).getTime()
) {
return false;
}
return true;
}
function checkSignature(doc, pem, xml) {
const signature = xpath(
doc,
"/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']"
)[0];
const sig = new SignedXml();
sig.keyInfoProvider = new FileKeyInfo(pem);
sig.loadSignature(signature);
const isValid = sig.checkSignature(xml);
return isValid;
}
function isCertificateValid(certificate, certPath) {
// Reference: https://www.audkenni.is/adstod/skilriki-kortum/skilrikjakedjur/
const FullgiltAudkenni = Certificate.fromPEM(readFileSync(certPath));
// we only need to verify the authority cert because that is the cert used
// to sign the message from Island.is
if (
FullgiltAudkenni.verifySubjectKeyIdentifier() &&
certificate.verifySubjectKeyIdentifier() &&
FullgiltAudkenni.checkSignature(certificate) === null &&
certificate.isIssuer(FullgiltAudkenni)
) {
return true;
}
return false;
}
function certToPEM(cert) {
return `-----BEGIN CERTIFICATE-----\n${cert}\n-----END CERTIFICATE-----`;
}
/*
Validates x509 certificate validity, checks certificate
and validates digital signature of XML.
*/
function validate(xml, signature, certPath) {
return new Promise((resolve, reject) => {
const doc = new DOMParser().parseFromString(xml);
// construct x509 certificate
const pem = certToPEM(signature);
const cert = Certificate.fromPEM(new Buffer.from(pem));
// Verify certificate data, i.e.
// serialNumber & organization name is Auðkenni etc.
if (!isCertificateDataValid(cert)) {
return reject("XML message is not signed by Auðkenni.");
}
// Verify that the XML document provided by the request was signed by the
// certificate provided with the request.
if (!checkSignature(doc, pem, xml)) {
return reject("XML signature is invalid.");
}
// Verify that the certificate we get from the Island.is request
// is signed and issued by the certificate provided via 'certPath'.
if (!isCertificateValid(cert, certPath)) {
return reject(
"The XML document is not signed by Þjóðskrá Íslands."
);
}
return resolve();
});
}
module.exports = {
validateCert: validate,
};