Skip to content

Commit 147f99a

Browse files
committed
Move signXML function to xml module
1 parent 53ab58f commit 147f99a

File tree

5 files changed

+57
-60
lines changed

5 files changed

+57
-60
lines changed
+3-26
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,19 @@
1-
import { SignedXml } from "xml-crypto";
2-
import * as algorithms from "./algorithms";
31
import { SamlSigningOptions } from "./types";
2+
import { signXml } from "./xml";
43

54
const authnRequestXPath =
65
'/*[local-name(.)="AuthnRequest" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
76
const issuerXPath =
87
'/*[local-name(.)="Issuer" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:assertion"]';
9-
const defaultTransforms = [
10-
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
11-
"http://www.w3.org/2001/10/xml-exc-c14n#",
12-
];
138

149
export function signSamlPost(
1510
samlMessage: string,
1611
xpath: string,
1712
options: SamlSigningOptions
1813
): string {
19-
if (!samlMessage) throw new Error("samlMessage is required");
20-
if (!xpath) throw new Error("xpath is required");
21-
if (!options) {
22-
options = {} as SamlSigningOptions;
23-
}
24-
25-
if (options.privateKey == null) throw new Error("options.privateKey is required");
26-
27-
const transforms = options.xmlSignatureTransforms || defaultTransforms;
28-
const sig = new SignedXml();
29-
if (options.signatureAlgorithm) {
30-
sig.signatureAlgorithm = algorithms.getSigningAlgorithm(options.signatureAlgorithm);
31-
}
32-
sig.addReference(xpath, transforms, algorithms.getDigestAlgorithm(options.digestAlgorithm));
33-
sig.signingKey = options.privateKey;
34-
sig.computeSignature(samlMessage, {
35-
location: { reference: xpath + issuerXPath, action: "after" },
36-
});
37-
return sig.getSignedXml();
14+
return signXml(samlMessage, xpath, { reference: xpath + issuerXPath, action: "after" }, options);
3815
}
3916

40-
export function signAuthnRequestPost(authnRequest: string, options: SamlSigningOptions) {
17+
export function signAuthnRequestPost(authnRequest: string, options: SamlSigningOptions): string {
4118
return signSamlPost(authnRequest, authnRequestXPath, options);
4219
}

src/passport-saml/saml.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
XMLOutput,
2929
SamlConfig,
3030
ErrorWithXmlStatus,
31+
isValidSamlSigningOptions,
3132
} from "./types";
3233
import { assertRequired } from "./utility";
3334
import {
@@ -353,7 +354,7 @@ class SAML {
353354
}
354355

355356
let stringRequest = buildXmlBuilderObject(request, false);
356-
if (isHttpPostBinding && this.options.privateKey != null) {
357+
if (isHttpPostBinding && isValidSamlSigningOptions(this.options)) {
357358
stringRequest = signAuthnRequestPost(stringRequest, this.options);
358359
}
359360
return stringRequest;

src/passport-saml/types.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ export interface AuthorizeOptions extends AuthenticateOptions {
1818
}
1919

2020
export interface SamlSigningOptions {
21-
privateKey?: string | Buffer;
21+
privateKey: string | Buffer;
2222
signatureAlgorithm?: SignatureAlgorithm;
2323
xmlSignatureTransforms?: string[];
2424
digestAlgorithm?: string;
2525
}
2626

27+
export const isValidSamlSigningOptions = (
28+
options: Partial<SamlSigningOptions>
29+
): options is SamlSigningOptions => {
30+
return options.privateKey != null;
31+
};
32+
2733
/**
2834
* These are SAML options that must be provided to construct a new SAML Strategy
2935
*/
@@ -35,7 +41,7 @@ export interface MandatorySamlOptions {
3541
* The options required to use a SAML strategy
3642
* These may be provided by means of defaults specified in the constructor
3743
*/
38-
export interface SamlOptions extends SamlSigningOptions, MandatorySamlOptions {
44+
export interface SamlOptions extends Partial<SamlSigningOptions>, MandatorySamlOptions {
3945
// Core
4046
callbackUrl?: string;
4147
path: string;

src/passport-saml/utility.ts

+7-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { SignedXml } from "xml-crypto";
21
import { SamlSigningOptions } from "./types";
3-
import * as algorithms from "./algorithms";
2+
import { signXml } from "./xml";
43

54
export function assertRequired<T>(value: T | null | undefined, error?: string): T {
65
if (value === undefined || value === null || (typeof value === "string" && value.length === 0)) {
@@ -10,37 +9,14 @@ export function assertRequired<T>(value: T | null | undefined, error?: string):
109
}
1110
}
1211

13-
export function signXml(samlMessage: string, xpath: string, options: SamlSigningOptions): string {
14-
const defaultTransforms = [
15-
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
16-
"http://www.w3.org/2001/10/xml-exc-c14n#",
17-
];
18-
19-
if (!samlMessage) throw new Error("samlMessage is required");
20-
if (!xpath) throw new Error("xpath is required");
21-
if (!options) {
22-
options = {} as SamlSigningOptions;
23-
}
24-
25-
if (!options.privateKey) throw new Error("options.privateKey is required");
26-
27-
const transforms = options.xmlSignatureTransforms || defaultTransforms;
28-
const sig = new SignedXml();
29-
if (options.signatureAlgorithm) {
30-
sig.signatureAlgorithm = algorithms.getSigningAlgorithm(options.signatureAlgorithm);
31-
}
32-
sig.addReference(xpath, transforms, algorithms.getDigestAlgorithm(options.digestAlgorithm));
33-
sig.signingKey = options.privateKey;
34-
sig.computeSignature(samlMessage, {
35-
location: { reference: xpath, action: "append" },
36-
});
37-
38-
return sig.getSignedXml();
39-
}
40-
4112
export function signXmlResponse(samlMessage: string, options: SamlSigningOptions): string {
4213
const responseXpath =
4314
'//*[local-name(.)="Response" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
4415

45-
return signXml(samlMessage, responseXpath, options);
16+
return signXml(
17+
samlMessage,
18+
responseXpath,
19+
{ reference: responseXpath, action: "append" },
20+
options
21+
);
4622
}

src/passport-saml/xml.ts

+37
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import * as xmlenc from "xml-encryption";
44
import * as xmldom from "xmldom";
55
import * as xml2js from "xml2js";
66
import * as xmlbuilder from "xmlbuilder";
7+
import { isValidSamlSigningOptions, SamlSigningOptions } from "./types";
8+
import * as algorithms from "./algorithms";
79

810
type SelectedValue = string | number | boolean | Node;
911

@@ -93,6 +95,41 @@ export const validateXmlSignatureForCert = (
9395
return sig.checkSignature(fullXml);
9496
};
9597

98+
interface XmlSignatureLocation {
99+
reference: string;
100+
action: "append" | "prepend" | "before" | "after";
101+
}
102+
103+
export const signXml = (
104+
xml: string,
105+
xpath: string,
106+
location: XmlSignatureLocation,
107+
options: SamlSigningOptions
108+
): string => {
109+
const defaultTransforms = [
110+
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
111+
"http://www.w3.org/2001/10/xml-exc-c14n#",
112+
];
113+
114+
if (!xml) throw new Error("samlMessage is required");
115+
if (!location) throw new Error("location is required");
116+
if (!options) throw new Error("options is required");
117+
if (!isValidSamlSigningOptions(options)) throw new Error("options.privateKey is required");
118+
119+
const transforms = options.xmlSignatureTransforms || defaultTransforms;
120+
const sig = new xmlCrypto.SignedXml();
121+
if (options.signatureAlgorithm != null) {
122+
sig.signatureAlgorithm = algorithms.getSigningAlgorithm(options.signatureAlgorithm);
123+
}
124+
sig.addReference(xpath, transforms, algorithms.getDigestAlgorithm(options.digestAlgorithm));
125+
sig.signingKey = options.privateKey;
126+
sig.computeSignature(xml, {
127+
location,
128+
});
129+
130+
return sig.getSignedXml();
131+
};
132+
96133
export const parseDomFromString = (xml: string): Document => {
97134
return new xmldom.DOMParser().parseFromString(xml);
98135
};

0 commit comments

Comments
 (0)