Add response signing - wip
This commit is contained in:
parent
4022901ea9
commit
364cd92ffb
@ -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
28
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user