From 147f99a9ca2418ec90fb8694f53d37175ffeab17 Mon Sep 17 00:00:00 2001 From: Quentin Barbe Date: Mon, 22 Mar 2021 18:01:08 +0100 Subject: [PATCH] Move signXML function to xml module --- src/passport-saml/saml-post-signing.ts | 29 ++------------------ src/passport-saml/saml.ts | 3 +- src/passport-saml/types.ts | 10 +++++-- src/passport-saml/utility.ts | 38 +++++--------------------- src/passport-saml/xml.ts | 37 +++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/passport-saml/saml-post-signing.ts b/src/passport-saml/saml-post-signing.ts index df55ec2df..74a036ce4 100644 --- a/src/passport-saml/saml-post-signing.ts +++ b/src/passport-saml/saml-post-signing.ts @@ -1,42 +1,19 @@ -import { SignedXml } from "xml-crypto"; -import * as algorithms from "./algorithms"; import { SamlSigningOptions } from "./types"; +import { signXml } from "./xml"; const authnRequestXPath = '/*[local-name(.)="AuthnRequest" 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 defaultTransforms = [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", -]; export function signSamlPost( samlMessage: string, xpath: string, options: SamlSigningOptions ): string { - if (!samlMessage) throw new Error("samlMessage is required"); - if (!xpath) throw new Error("xpath is required"); - if (!options) { - options = {} as SamlSigningOptions; - } - - if (options.privateKey == null) throw new Error("options.privateKey is required"); - - const transforms = options.xmlSignatureTransforms || defaultTransforms; - const sig = new SignedXml(); - if (options.signatureAlgorithm) { - sig.signatureAlgorithm = algorithms.getSigningAlgorithm(options.signatureAlgorithm); - } - sig.addReference(xpath, transforms, algorithms.getDigestAlgorithm(options.digestAlgorithm)); - sig.signingKey = options.privateKey; - sig.computeSignature(samlMessage, { - location: { reference: xpath + issuerXPath, action: "after" }, - }); - return sig.getSignedXml(); + return signXml(samlMessage, xpath, { reference: xpath + issuerXPath, action: "after" }, options); } -export function signAuthnRequestPost(authnRequest: string, options: SamlSigningOptions) { +export function signAuthnRequestPost(authnRequest: string, options: SamlSigningOptions): string { return signSamlPost(authnRequest, authnRequestXPath, options); } diff --git a/src/passport-saml/saml.ts b/src/passport-saml/saml.ts index a9d110339..b0bc41cca 100644 --- a/src/passport-saml/saml.ts +++ b/src/passport-saml/saml.ts @@ -28,6 +28,7 @@ import { XMLOutput, SamlConfig, ErrorWithXmlStatus, + isValidSamlSigningOptions, } from "./types"; import { assertRequired } from "./utility"; import { @@ -353,7 +354,7 @@ class SAML { } let stringRequest = buildXmlBuilderObject(request, false); - if (isHttpPostBinding && this.options.privateKey != null) { + if (isHttpPostBinding && isValidSamlSigningOptions(this.options)) { stringRequest = signAuthnRequestPost(stringRequest, this.options); } return stringRequest; diff --git a/src/passport-saml/types.ts b/src/passport-saml/types.ts index 3a0fbab8f..88a538f6a 100644 --- a/src/passport-saml/types.ts +++ b/src/passport-saml/types.ts @@ -18,12 +18,18 @@ export interface AuthorizeOptions extends AuthenticateOptions { } export interface SamlSigningOptions { - privateKey?: string | Buffer; + privateKey: string | Buffer; signatureAlgorithm?: SignatureAlgorithm; xmlSignatureTransforms?: string[]; digestAlgorithm?: string; } +export const isValidSamlSigningOptions = ( + options: Partial +): options is SamlSigningOptions => { + return options.privateKey != null; +}; + /** * These are SAML options that must be provided to construct a new SAML Strategy */ @@ -35,7 +41,7 @@ export interface MandatorySamlOptions { * The options required to use a SAML strategy * These may be provided by means of defaults specified in the constructor */ -export interface SamlOptions extends SamlSigningOptions, MandatorySamlOptions { +export interface SamlOptions extends Partial, MandatorySamlOptions { // Core callbackUrl?: string; path: string; diff --git a/src/passport-saml/utility.ts b/src/passport-saml/utility.ts index e789271f2..5e31302c9 100644 --- a/src/passport-saml/utility.ts +++ b/src/passport-saml/utility.ts @@ -1,6 +1,5 @@ -import { SignedXml } from "xml-crypto"; import { SamlSigningOptions } from "./types"; -import * as algorithms from "./algorithms"; +import { signXml } from "./xml"; export function assertRequired(value: T | null | undefined, error?: string): T { if (value === undefined || value === null || (typeof value === "string" && value.length === 0)) { @@ -10,37 +9,14 @@ export function assertRequired(value: T | null | undefined, error?: string): } } -export function signXml(samlMessage: string, xpath: string, options: SamlSigningOptions): string { - const defaultTransforms = [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", - ]; - - if (!samlMessage) throw new Error("samlMessage is required"); - if (!xpath) throw new Error("xpath is required"); - if (!options) { - options = {} as SamlSigningOptions; - } - - if (!options.privateKey) throw new Error("options.privateKey is required"); - - const transforms = options.xmlSignatureTransforms || defaultTransforms; - const sig = new SignedXml(); - if (options.signatureAlgorithm) { - sig.signatureAlgorithm = algorithms.getSigningAlgorithm(options.signatureAlgorithm); - } - sig.addReference(xpath, transforms, algorithms.getDigestAlgorithm(options.digestAlgorithm)); - sig.signingKey = options.privateKey; - sig.computeSignature(samlMessage, { - location: { reference: xpath, action: "append" }, - }); - - return sig.getSignedXml(); -} - export function signXmlResponse(samlMessage: string, options: SamlSigningOptions): string { const responseXpath = '//*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]'; - return signXml(samlMessage, responseXpath, options); + return signXml( + samlMessage, + responseXpath, + { reference: responseXpath, action: "append" }, + options + ); } diff --git a/src/passport-saml/xml.ts b/src/passport-saml/xml.ts index 03cf7415f..f1100a0b8 100644 --- a/src/passport-saml/xml.ts +++ b/src/passport-saml/xml.ts @@ -4,6 +4,8 @@ import * as xmlenc from "xml-encryption"; import * as xmldom from "xmldom"; import * as xml2js from "xml2js"; import * as xmlbuilder from "xmlbuilder"; +import { isValidSamlSigningOptions, SamlSigningOptions } from "./types"; +import * as algorithms from "./algorithms"; type SelectedValue = string | number | boolean | Node; @@ -93,6 +95,41 @@ export const validateXmlSignatureForCert = ( return sig.checkSignature(fullXml); }; +interface XmlSignatureLocation { + reference: string; + action: "append" | "prepend" | "before" | "after"; +} + +export const signXml = ( + xml: string, + xpath: string, + location: XmlSignatureLocation, + options: SamlSigningOptions +): string => { + const defaultTransforms = [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ]; + + if (!xml) throw new Error("samlMessage is required"); + if (!location) throw new Error("location is required"); + if (!options) throw new Error("options is required"); + if (!isValidSamlSigningOptions(options)) throw new Error("options.privateKey is required"); + + const transforms = options.xmlSignatureTransforms || defaultTransforms; + const sig = new xmlCrypto.SignedXml(); + if (options.signatureAlgorithm != null) { + sig.signatureAlgorithm = algorithms.getSigningAlgorithm(options.signatureAlgorithm); + } + sig.addReference(xpath, transforms, algorithms.getDigestAlgorithm(options.digestAlgorithm)); + sig.signingKey = options.privateKey; + sig.computeSignature(xml, { + location, + }); + + return sig.getSignedXml(); +}; + export const parseDomFromString = (xml: string): Document => { return new xmldom.DOMParser().parseFromString(xml); };