diff --git a/lib/env.ts b/lib/env.ts index ee2001f..716128b 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -2,8 +2,10 @@ const appUrl = process.env.APP_URL || 'http://localhost:4000'; const entityId = process.env.ENTITY_ID || 'http://saml.example.com'; const ssoUrl = `${appUrl}/api/saml/sso`; -export default { +const config = { appUrl, entityId, ssoUrl, }; + +export default config; diff --git a/package-lock.json b/package-lock.json index cad1290..c69d4c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,10 +6,8 @@ "": { "name": "fake", "dependencies": { - "axios": "^0.24.0", "next": "12.1.0", - "node-fetch": "^3.2.0", - "rambda": "^7.0.2", + "node-forge": "^1.2.1", "react": "17.0.2", "react-dom": "17.0.2", "webpack-filter-warnings-plugin": "^1.2.1", @@ -19,6 +17,7 @@ }, "devDependencies": { "@types/node": "17.0.8", + "@types/node-forge": "^1.0.0", "@types/react": "17.0.38", "@types/xml-crypto": "^1.4.2", "@types/xml2js": "0.4.9", @@ -432,6 +431,15 @@ "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", "dev": true }, + "node_modules/@types/node-forge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.0.0.tgz", + "integrity": "sha512-h0bgwPKq5u99T9Gor4qtV1lCZ41xNkai0pie1n/a2mh2/4+jENWOlo7AJ4YKxTZAnSZ8FRurUpdIN7ohaPPuHA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -1222,14 +1230,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", - "dependencies": { - "follow-redirects": "^1.14.4" - } - }, "node_modules/axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -2008,14 +2008,6 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", - "engines": { - "node": ">= 12" - } - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3310,28 +3302,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-blob": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", - "integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -3428,25 +3398,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -3456,17 +3407,6 @@ "node": ">=0.10.0" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/fraction.js": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz", @@ -4785,39 +4725,12 @@ "url": "https://opencollective.com/postcss/" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], + "node_modules/node-forge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", + "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.0.tgz", - "integrity": "sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "node": ">= 6.13.0" } }, "node_modules/node-libs-browser": { @@ -5725,11 +5638,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rambda": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.0.2.tgz", - "integrity": "sha512-oM1In0rXyFIzhYDIwREhdOm+oTDwtGW5WIgo/m8TqzS5kSBvsu8UoqzQFrSA0SJCdWJQCqOsvK0pKEINTwcEIA==" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7644,14 +7552,6 @@ "node": ">=0.10.0" } }, - "node_modules/web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/webpack": { "version": "4.46.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", @@ -8294,6 +8194,15 @@ "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", "dev": true }, + "@types/node-forge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.0.0.tgz", + "integrity": "sha512-h0bgwPKq5u99T9Gor4qtV1lCZ41xNkai0pie1n/a2mh2/4+jENWOlo7AJ4YKxTZAnSZ8FRurUpdIN7ohaPPuHA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -8912,14 +8821,6 @@ "integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==", "dev": true }, - "axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", - "requires": { - "follow-redirects": "^1.14.4" - } - }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -9593,11 +9494,6 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, - "data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -10636,15 +10532,6 @@ "reusify": "^1.0.4" } }, - "fetch-blob": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", - "integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==", - "requires": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - } - }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -10734,25 +10621,12 @@ } } }, - "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" - }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "peer": true }, - "formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "requires": { - "fetch-blob": "^3.1.2" - } - }, "fraction.js": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz", @@ -11778,20 +11652,10 @@ } } }, - "node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" - }, - "node-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.0.tgz", - "integrity": "sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw==", - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } + "node-forge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", + "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==" }, "node-libs-browser": { "version": "2.2.1", @@ -12515,11 +12379,6 @@ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true }, - "rambda": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.0.2.tgz", - "integrity": "sha512-oM1In0rXyFIzhYDIwREhdOm+oTDwtGW5WIgo/m8TqzS5kSBvsu8UoqzQFrSA0SJCdWJQCqOsvK0pKEINTwcEIA==" - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -14044,11 +13903,6 @@ } } }, - "web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" - }, "webpack": { "version": "4.46.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", diff --git a/package.json b/package.json index 6ac6f65..7b485a8 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,8 @@ "lint": "next lint" }, "dependencies": { - "axios": "^0.24.0", "next": "12.1.0", - "node-fetch": "^3.2.0", - "rambda": "^7.0.2", + "node-forge": "^1.2.1", "react": "17.0.2", "react-dom": "17.0.2", "webpack-filter-warnings-plugin": "^1.2.1", @@ -21,14 +19,15 @@ }, "devDependencies": { "@types/node": "17.0.8", + "@types/node-forge": "^1.0.0", "@types/react": "17.0.38", + "@types/xml-crypto": "^1.4.2", "@types/xml2js": "0.4.9", "autoprefixer": "10.4.2", "eslint": "8.6.0", "eslint-config-next": "12.0.7", "postcss": "8.4.6", "tailwindcss": "3.0.23", - "typescript": "4.5.4", - "@types/xml-crypto": "^1.4.2" + "typescript": "4.5.4" } } diff --git a/pages/_app.tsx b/pages/_app.tsx index 46ea5dd..0aa1cbb 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,6 +1,6 @@ -import "styles/globals.css"; -import type { AppProps } from "next/app"; -import Layout from "components/Layout"; +import 'styles/globals.css'; +import type { AppProps } from 'next/app'; +import Layout from 'components/Layout'; function MyApp({ Component, pageProps }: AppProps) { return ( diff --git a/pages/_document.tsx b/pages/_document.tsx index c045cfd..9c030f6 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -1,22 +1,18 @@ -import Document, { Html, Head, Main, NextScript } from "next/document"; +import Document, { Html, Head, Main, NextScript } from 'next/document'; class MyDocument extends Document { render() { return ( - + + - - +
diff --git a/pages/saml/sso.tsx b/pages/saml/sso.tsx index 1143e66..b8bde97 100644 --- a/pages/saml/sso.tsx +++ b/pages/saml/sso.tsx @@ -1,13 +1,10 @@ import type { GetServerSideProps } from 'next'; -import React from "react"; -import { AuthNRequest } from '../../types' -import { extractSAMLRequestAttributes, createResponseForm } from '../../utils' +import React from 'react'; +import { AuthNRequest } from '../../types'; +import { extractSAMLRequestAttributes, createResponseForm } from '../../utils'; -const ProcessRequest: React.FC = ({relayState, samlRequest}) => { - return ( -
Processing request
- ); -} +const ProcessRequest: React.FC = ({ relayState, samlRequest }) => { + return
Processing request
; +}; export default ProcessRequest; - diff --git a/utils/certificate.ts b/utils/certificate.ts index 30b24fe..a3623a8 100644 --- a/utils/certificate.ts +++ b/utils/certificate.ts @@ -7,7 +7,7 @@ const fetchPublicKey = async (): Promise => { const fetchPrivateKey = async (): Promise => { return await fs.readFile(path.join('data', 'key.pem'), 'ascii'); -} +}; const stripCertHeaderAndFooter = (cert: string): string => { cert = cert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, ''); @@ -17,8 +17,4 @@ const stripCertHeaderAndFooter = (cert: string): string => { return cert; }; -export { - fetchPublicKey, - fetchPrivateKey, - stripCertHeaderAndFooter, -} \ No newline at end of file +export { fetchPublicKey, fetchPrivateKey, stripCertHeaderAndFooter }; diff --git a/utils/idp.ts b/utils/idp.ts index b83bb91..7bf0d66 100644 --- a/utils/idp.ts +++ b/utils/idp.ts @@ -13,7 +13,7 @@ const createIdPMetadataXML = async ({ }): Promise => { const xmlPath = path.join('data', 'idp-metadata.xml'); const xml = await fs.readFile(xmlPath, 'utf8'); - certificate = stripCertHeaderAndFooter(certificate) + certificate = stripCertHeaderAndFooter(certificate); return xml .replace('idp_entity_id', idpEntityId) @@ -21,6 +21,4 @@ const createIdPMetadataXML = async ({ .replace(/idp_sso_url/g, idpSsoUrl); }; -export { - createIdPMetadataXML, -} \ No newline at end of file +export { createIdPMetadataXML }; diff --git a/utils/index.ts b/utils/index.ts index cb79829..943cb83 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,4 +1,4 @@ export * from './certificate'; export * from './request'; export * from './response'; -export * from './idp'; \ No newline at end of file +export * from './idp'; diff --git a/utils/request.ts b/utils/request.ts index fd63f42..b7cd78c 100644 --- a/utils/request.ts +++ b/utils/request.ts @@ -1,6 +1,6 @@ -import xml2js from "xml2js"; -import { promisify } from "util"; -import { inflateRaw } from "zlib"; +import xml2js from 'xml2js'; +import { promisify } from 'util'; +import { inflateRaw } from 'zlib'; const inflateRawAsync = promisify(inflateRaw); @@ -19,18 +19,16 @@ const parseXML = (xml: string): Promise> => { // Parse SAMLRequest attributes const extractSAMLRequestAttributes = async (samlRequest: string) => { - const request = ( - await inflateRawAsync(Buffer.from(samlRequest, "base64")) - ).toString(); + const request = (await inflateRawAsync(Buffer.from(samlRequest, 'base64'))).toString(); const result = await parseXML(request); - const attributes = result["samlp:AuthnRequest"]["$"]; - const issuer = result["samlp:AuthnRequest"]["saml:Issuer"]; + 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]["_"], + audience: issuer[0]['_'], }; }; diff --git a/utils/response.ts b/utils/response.ts index 5d3b732..9515e2d 100644 --- a/utils/response.ts +++ b/utils/response.ts @@ -2,14 +2,15 @@ import { User } from '../types'; import xmlbuilder from 'xmlbuilder'; import crypto from 'crypto'; import { SignedXml, FileKeyInfo } from 'xml-crypto'; +import { pki, util, asn1 } from 'node-forge'; const createResponseXML = async (params: { - idpIdentityId: string, - audience: string, - acsUrl: string, - user: User + idpIdentityId: string; + audience: string; + acsUrl: string; + user: User; }): Promise => { - const {idpIdentityId, audience, acsUrl, user} = params; + const { idpIdentityId, audience, acsUrl, user } = params; const authDate = new Date(); const authTimestamp = authDate.toISOString(); @@ -20,61 +21,61 @@ const createResponseXML = async (params: { authDate.setMinutes(authDate.getMinutes() + 10); const notAfter = authDate.toISOString(); - const inResponseTo = '_1234' + const inResponseTo = '_1234'; const responseId = crypto.randomBytes(10).toString('hex'); const attributeStatement = { '@xmlns:xs': 'http://www.w3.org/2001/XMLSchema', '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', - 'saml:Attribute' : [ + 'saml:Attribute': [ { '@Name': 'id', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '#text': user.id, - } + }, }, { '@Name': 'email', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '#text': user.email, - } + }, }, { '@Name': 'firstName', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '#text': user.firstName, - } + }, }, { '@Name': 'lastName', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '#text': user.lastName, - } + }, }, - ] - } + ], + }; const nodes = { - 'samlp:Response':{ + 'samlp:Response': { '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol', '@Version': '2.0', '@ID': responseId, '@Destination': acsUrl, '@InResponseTo': inResponseTo, '@IssueInstant': authTimestamp, + 'samlp:Status': { + 'samlp:StatusCode': { + '@Value': 'urn:oasis:names:tc:SAML:2.0:status:Success', + }, + }, 'saml:Issuer': { '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion', '#text': idpIdentityId, }, - 'samlp:Status': { - 'samlp:StatusCode': { - '@Value': 'urn:oasis:names:tc:SAML:2.0:status:Success' - } - }, 'saml:Assertion': { '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion', '@Version': '2.0', @@ -87,7 +88,7 @@ const createResponseXML = async (params: { 'saml:NameID': { '@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', '#text': user.email, - } + }, }, 'saml:Conditions': { '@NotBefore': notBefore, @@ -95,24 +96,24 @@ const createResponseXML = async (params: { 'saml:AudienceRestriction': { 'saml:Audience': { '#text': audience, - } - } + }, + }, }, 'saml:AuthnStatement': { '@AuthnInstant': authTimestamp, '@SessionIndex': '_YIlFoNFzLMDYxdwf-T_BuimfkGa5qhKg', 'saml:AuthnContext': { 'saml:AuthnContextClassRef': { - '#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified' - } - } + '#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified', + }, + }, }, 'saml:AttributeStatement': attributeStatement, }, - } - } + }, + }; - return xmlbuilder.create(nodes).end({ pretty: true}); + return xmlbuilder.create(nodes).end({ pretty: true }); }; // Create the HTML form to submit the response @@ -141,28 +142,57 @@ const createResponseForm = (relayState: string, encodedSamlResponse: string, acs return formElements.join(''); }; +function getPublicKeyPemFromCertificate(x509Certificate: string) { + const certDerBytes = util.decode64(x509Certificate); + const obj = asn1.fromDer(certDerBytes); + const cert = pki.certificateFromAsn1(obj); + return pki.publicKeyToPem(cert.publicKey); +} + +const stripCertHeaderAndFooter = (cert: string): string => { + cert = cert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, ''); + cert = cert.replace(/-+END CERTIFICATE-+\r?\n?/, ''); + cert = cert.replace(/\r\n/g, '\n'); + return cert; +}; + +function GetKeyInfo(x509Certificate: string, signatureConfig: any = {}) { + x509Certificate = stripCertHeaderAndFooter(x509Certificate); + + this.getKeyInfo = () => { + const prefix = signatureConfig.prefix ? `${signatureConfig.prefix}:` : ''; + return `<${prefix}X509Data><${prefix}X509Certificate>${x509Certificate}`; + }; + + this.getKey = () => { + return getPublicKeyPemFromCertificate(x509Certificate).toString(); + }; +} + const signResponseXML = async (xml: string, signingKey: any, publicKey: any): Promise => { const sig = new SignedXml(); - const responseXPath = '/*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]'; - const issuerXPath = '/*[local-name(.)="Issuer" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:assertion"]'; - - console.log({publicKey, signingKey}) + const responseXPath = + '/*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]'; + const issuerXPath = + '/*[local-name(.)="Issuer" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:assertion"]'; sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; - sig.keyInfoProvider = new FileKeyInfo(publicKey); + + // @ts-ignore + sig.keyInfoProvider = new GetKeyInfo(publicKey, {}); sig.signingKey = signingKey; - sig.addReference(responseXPath, ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'], 'http://www.w3.org/2001/04/xmlenc#sha256'); + sig.addReference( + responseXPath, + ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'], + 'http://www.w3.org/2001/04/xmlenc#sha256' + ); sig.computeSignature(xml, { location: { reference: responseXPath + issuerXPath, action: 'after' }, }); return sig.getSignedXml(); -} +}; -export { - createResponseXML, - createResponseForm, - signResponseXML -} \ No newline at end of file +export { createResponseXML, createResponseForm, signResponseXML };