import { DOMParser as Dom } from '@xmldom/xmldom'; import { promisify } from 'util'; import { certToPEM } from 'utils'; import { SignedXml, xpath as select } from 'xml-crypto'; import xml2js from 'xml2js'; import { inflateRaw } from 'zlib'; const inflateRawAsync = promisify(inflateRaw); // Parse XML const parseXML = (xml: string): Promise> => { return new Promise((resolve, reject) => { xml2js.parseString(xml, (err: Error, result: any) => { if (err) { reject(err); } resolve(result); }); }); }; // Decode the base64 string const decodeBase64 = async (string: string, isDeflated: boolean) => { return isDeflated ? (await inflateRawAsync(Buffer.from(string, 'base64'))).toString() : Buffer.from(string, 'base64').toString(); }; // Parse SAMLRequest attributes const extractSAMLRequestAttributes = async (rawRequest: string) => { const result = await parseXML(rawRequest); const attributes = result['samlp:AuthnRequest']['$']; const issuer = result['samlp:AuthnRequest']['saml:Issuer']; return { id: attributes.ID, acsUrl: attributes.AssertionConsumerServiceURL, providerName: attributes.ProviderName, audience: issuer[0]['_'], publicKey: result['samlp:AuthnRequest']['Signature'][0]['KeyInfo'][0]['X509Data'][0]['X509Certificate'][0], }; }; // Validate signature const hasValidSignature = async (xml: string, certificate: string): Promise => { return new Promise((resolve, reject) => { const doc = new Dom().parseFromString(xml); const signature = select( doc, "/*/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']" )[0] || select( doc, "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']" )[0] || select( doc, "/*/*/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']" )[0]; const signed = new SignedXml(); signed.keyInfoProvider = { file: '', getKey: function getKey(keyInfo: any) { return Buffer.from(certToPEM(certificate), 'utf8'); }, getKeyInfo: function getKeyInfo(key: any) { return ''; }, }; signed.loadSignature(signature.toString()); const response = signed.checkSignature(xml); return !response ? reject(false) : resolve(true); }); }; export { extractSAMLRequestAttributes, hasValidSignature, decodeBase64 };