Add response signing - wip

This commit is contained in:
Kiran 2022-02-22 11:30:14 +05:30
parent 4022901ea9
commit 364cd92ffb
4 changed files with 64 additions and 13 deletions

View File

@ -1,3 +1,5 @@
// import { fetchPrivateKey, fetchPublicKey } from "utils";
const appUrl = process.env.APP_URL || 'http://localhost:4000'; const appUrl = process.env.APP_URL || 'http://localhost:4000';
const entityId = process.env.ENTITY_ID || 'http://saml.example.com'; const entityId = process.env.ENTITY_ID || 'http://saml.example.com';
const ssoUrl = `${appUrl}/api/saml/sso`; const ssoUrl = `${appUrl}/api/saml/sso`;

28
package-lock.json generated
View File

@ -6,10 +6,10 @@
"": { "": {
"name": "fake", "name": "fake",
"dependencies": { "dependencies": {
"@types/xml-crypto": "^1.4.2",
"axios": "^0.24.0", "axios": "^0.24.0",
"next": "12.1.0", "next": "12.1.0",
"node-fetch": "^3.2.0", "node-fetch": "^3.2.0",
"node-forge": "^1.2.1",
"rambda": "^7.0.2", "rambda": "^7.0.2",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
@ -21,6 +21,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "17.0.8", "@types/node": "17.0.8",
"@types/react": "17.0.38", "@types/react": "17.0.38",
"@types/xml-crypto": "^1.4.2",
"@types/xml2js": "0.4.9", "@types/xml2js": "0.4.9",
"autoprefixer": "10.4.2", "autoprefixer": "10.4.2",
"eslint": "8.6.0", "eslint": "8.6.0",
@ -429,7 +430,8 @@
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "17.0.8", "version": "17.0.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz",
"integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==",
"dev": true
}, },
"node_modules/@types/parse-json": { "node_modules/@types/parse-json": {
"version": "4.0.0", "version": "4.0.0",
@ -464,6 +466,7 @@
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.2.tgz", "resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.2.tgz",
"integrity": "sha512-1kT+3gVkeBDg7Ih8NefxGYfCApwZViMIs5IEs5AXF6Fpsrnf9CLAEIRh0DYb1mIcRcvysVbe27cHsJD6rJi36w==", "integrity": "sha512-1kT+3gVkeBDg7Ih8NefxGYfCApwZViMIs5IEs5AXF6Fpsrnf9CLAEIRh0DYb1mIcRcvysVbe27cHsJD6rJi36w==",
"dev": true,
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"xpath": "0.0.27" "xpath": "0.0.27"
@ -473,6 +476,7 @@
"version": "0.0.27", "version": "0.0.27",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz",
"integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==",
"dev": true,
"engines": { "engines": {
"node": ">=0.6.0" "node": ">=0.6.0"
} }
@ -4817,6 +4821,14 @@
"url": "https://opencollective.com/node-fetch" "url": "https://opencollective.com/node-fetch"
} }
}, },
"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": ">= 6.13.0"
}
},
"node_modules/node-libs-browser": { "node_modules/node-libs-browser": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
@ -8288,7 +8300,8 @@
"@types/node": { "@types/node": {
"version": "17.0.8", "version": "17.0.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz",
"integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==",
"dev": true
}, },
"@types/parse-json": { "@types/parse-json": {
"version": "4.0.0", "version": "4.0.0",
@ -8323,6 +8336,7 @@
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.2.tgz", "resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.2.tgz",
"integrity": "sha512-1kT+3gVkeBDg7Ih8NefxGYfCApwZViMIs5IEs5AXF6Fpsrnf9CLAEIRh0DYb1mIcRcvysVbe27cHsJD6rJi36w==", "integrity": "sha512-1kT+3gVkeBDg7Ih8NefxGYfCApwZViMIs5IEs5AXF6Fpsrnf9CLAEIRh0DYb1mIcRcvysVbe27cHsJD6rJi36w==",
"dev": true,
"requires": { "requires": {
"@types/node": "*", "@types/node": "*",
"xpath": "0.0.27" "xpath": "0.0.27"
@ -8331,7 +8345,8 @@
"xpath": { "xpath": {
"version": "0.0.27", "version": "0.0.27",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz",
"integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==",
"dev": true
} }
} }
}, },
@ -11787,6 +11802,11 @@
"formdata-polyfill": "^4.0.10" "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": { "node-libs-browser": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",

View File

@ -11,6 +11,7 @@
"axios": "^0.24.0", "axios": "^0.24.0",
"next": "12.1.0", "next": "12.1.0",
"node-fetch": "^3.2.0", "node-fetch": "^3.2.0",
"node-forge": "^1.2.1",
"rambda": "^7.0.2", "rambda": "^7.0.2",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
@ -22,13 +23,13 @@
"devDependencies": { "devDependencies": {
"@types/node": "17.0.8", "@types/node": "17.0.8",
"@types/react": "17.0.38", "@types/react": "17.0.38",
"@types/xml-crypto": "^1.4.2",
"@types/xml2js": "0.4.9", "@types/xml2js": "0.4.9",
"autoprefixer": "10.4.2", "autoprefixer": "10.4.2",
"eslint": "8.6.0", "eslint": "8.6.0",
"eslint-config-next": "12.0.7", "eslint-config-next": "12.0.7",
"postcss": "8.4.6", "postcss": "8.4.6",
"tailwindcss": "3.0.23", "tailwindcss": "3.0.23",
"typescript": "4.5.4", "typescript": "4.5.4"
"@types/xml-crypto": "^1.4.2"
} }
} }

View File

@ -2,6 +2,7 @@ import { User } from '../types';
import xmlbuilder from 'xmlbuilder'; import xmlbuilder from 'xmlbuilder';
import crypto from 'crypto'; import crypto from 'crypto';
import { SignedXml, FileKeyInfo } from 'xml-crypto'; import { SignedXml, FileKeyInfo } from 'xml-crypto';
import { pki, util, asn1 } from 'node-forge';
const createResponseXML = async (params: { const createResponseXML = async (params: {
idpIdentityId: string, idpIdentityId: string,
@ -66,15 +67,15 @@ const createResponseXML = async (params: {
'@Destination': acsUrl, '@Destination': acsUrl,
'@InResponseTo': inResponseTo, '@InResponseTo': inResponseTo,
'@IssueInstant': authTimestamp, '@IssueInstant': authTimestamp,
'saml:Issuer': {
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'#text': idpIdentityId,
},
'samlp:Status': { 'samlp:Status': {
'samlp:StatusCode': { 'samlp:StatusCode': {
'@Value': 'urn:oasis:names:tc:SAML:2.0:status:Success' '@Value': 'urn:oasis:names:tc:SAML:2.0:status:Success'
} }
}, },
'saml:Issuer': {
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'#text': idpIdentityId,
},
'saml:Assertion': { 'saml:Assertion': {
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion', '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'@Version': '2.0', '@Version': '2.0',
@ -141,15 +142,42 @@ const createResponseForm = (relayState: string, encodedSamlResponse: string, acs
return formElements.join(''); 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}</${prefix}X509Certificate></${prefix}X509Data>`;
};
this.getKey = () => {
return getPublicKeyPemFromCertificate(x509Certificate).toString();
};
}
const signResponseXML = async (xml: string, signingKey: any, publicKey: any): Promise<string> => { const signResponseXML = async (xml: string, signingKey: any, publicKey: any): Promise<string> => {
const sig = new SignedXml(); const sig = new SignedXml();
const responseXPath = '/*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]'; 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"]'; const issuerXPath = '/*[local-name(.)="Issuer" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:assertion"]';
console.log({publicKey, signingKey})
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; 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.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');