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 entityId = process.env.ENTITY_ID || 'http://saml.example.com';
const ssoUrl = `${appUrl}/api/saml/sso`;

28
package-lock.json generated
View File

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

View File

@ -2,6 +2,7 @@ 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,
@ -66,15 +67,15 @@ const createResponseXML = async (params: {
'@Destination': acsUrl,
'@InResponseTo': inResponseTo,
'@IssueInstant': authTimestamp,
'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:Issuer': {
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'#text': idpIdentityId,
},
'saml:Assertion': {
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
'@Version': '2.0',
@ -141,15 +142,42 @@ 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}</${prefix}X509Certificate></${prefix}X509Data>`;
};
this.getKey = () => {
return getPublicKeyPemFromCertificate(x509Certificate).toString();
};
}
const signResponseXML = async (xml: string, signingKey: any, publicKey: any): Promise<string> => {
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})
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');