Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

octet string builder #509

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 38 additions & 37 deletions src/binding-redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,58 +25,59 @@ export interface BuildRedirectConfig {

/**
* @private
* @desc Helper of generating URL param/value pair
* @param {string} param key
* @param {string} value value of key
* @param {boolean} first determine whether the param is the starting one in order to add query header '?'
* @return {string}
*/
function pvPair(param: string, value: string, first?: boolean): string {
return (first === true ? '?' : '&') + param + '=' + value;
}
/**
* @private
* @desc Refractored part of URL generation for login/logout request
* @desc Refactored part of URL generation for login/logout request
* @param {string} type
* @param {boolean} isSigned
* @param {string} rawSamlRequest
* @param {object} entitySetting
* @return {string}
*/
function buildRedirectURL(opts: BuildRedirectConfig) {
function buildRedirectURL(opts: BuildRedirectConfig): string {
const {
baseUrl,
type,
isSigned,
context,
entitySetting,
relayState,
isSigned,
entitySetting
} = opts;
let { relayState = '' } = opts;
const noParams = (url.parse(baseUrl).query || []).length === 0;
const queryParam = libsaml.getQueryParamByType(type);
// In general, this xmlstring is required to do deflate -> base64 -> urlencode
const samlRequest = encodeURIComponent(utility.base64Encode(utility.deflateString(context)));
if (relayState !== '') {
relayState = pvPair(urlParams.relayState, encodeURIComponent(relayState));

const redirectUrl = new url.URL(baseUrl)

const direction = libsaml.getQueryParamByType(type);
// In general, this XML string is required to do deflate -> base64 -> URL encode
const encodedContext = utility.base64Encode(utility.deflateString(context));
redirectUrl.searchParams.set(direction, encodedContext);

if (relayState) {
redirectUrl.searchParams.set(urlParams.relayState, relayState);
}

if (isSigned) {
const sigAlg = pvPair(urlParams.sigAlg, encodeURIComponent(entitySetting.requestSignatureAlgorithm));
const octetString = samlRequest + relayState + sigAlg;
return baseUrl
+ pvPair(queryParam, octetString, noParams)
+ pvPair(urlParams.signature, encodeURIComponent(
libsaml.constructMessageSignature(
queryParam + '=' + octetString,
entitySetting.privateKey,
entitySetting.privateKeyPass,
undefined,
entitySetting.requestSignatureAlgorithm
).toString()
)
);
const octetString = libsaml.octetStringBuilder(
binding.redirect,
direction,
{ // can just be `Object.fromEntries(redirectUrl.searchParams)` when the targeted Node version is updated.
[direction]: encodedContext,
[urlParams.relayState]: relayState,
[urlParams.sigAlg]: entitySetting.requestSignatureAlgorithm,
}
);
const signature = libsaml.constructMessageSignature(
octetString,
entitySetting.privateKey,
entitySetting.privateKeyPass,
true,
entitySetting.requestSignatureAlgorithm
).toString();

redirectUrl.searchParams.set(urlParams.signature, signature);
redirectUrl.searchParams.set(urlParams.sigAlg, entitySetting.requestSignatureAlgorithm);
}
return baseUrl + pvPair(queryParam, samlRequest + relayState, noParams);

return redirectUrl.toString();
}

/**
* @desc Redirect URL for login request
* @param {object} entity object includes both idp and sp
Expand Down
30 changes: 9 additions & 21 deletions src/binding-simplesign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,6 @@ export interface BindingSimpleSignContext {
sigAlg: string;
}

/**
* @private
* @desc Helper of generating URL param/value pair
* @param {string} param key
* @param {string} value value of key
* @param {boolean} first determine whether the param is the starting one in order to add query header '?'
* @return {string}
*/
function pvPair(param: string, value: string, first?: boolean): string {
return (first === true ? '?' : '&') + param + '=' + value;
}
/**
* @private
* @desc Refactored part of simple signature generation for login/logout request
Expand All @@ -51,17 +40,16 @@ function buildSimpleSignature(opts: BuildSimpleSignConfig) : string {
context,
entitySetting,
} = opts;
let { relayState = '' } = opts;
const queryParam = libsaml.getQueryParamByType(type);

if (relayState !== '') {
relayState = pvPair(urlParams.relayState, relayState);
}
// ?SAMLRequest= or ?SAMLResponse=
const direction = libsaml.getQueryParamByType(type);
const octetString = libsaml.octetStringBuilder(binding.simpleSign, direction, {
[direction]: context,
[urlParams.relayState]: opts.relayState,
[urlParams.sigAlg]: entitySetting.requestSignatureAlgorithm,
});

const sigAlg = pvPair(urlParams.sigAlg, entitySetting.requestSignatureAlgorithm);
const octetString = context + relayState + sigAlg;
return libsaml.constructMessageSignature(
queryParam + '=' + octetString,
octetString,
entitySetting.privateKey,
entitySetting.privateKeyPass,
undefined,
Expand Down Expand Up @@ -117,7 +105,7 @@ function base64LoginRequest(entity: any, customTagReplacement?: (template: strin
sigAlg: spSetting.requestSignatureAlgorithm,
};
}
// No need to embeded XML signature
// No need to embed XML signature
return {
id,
context: utility.base64Encode(rawSamlRequest),
Expand Down
22 changes: 18 additions & 4 deletions src/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function getDefaultExtractorFields(parserType: ParserType, assertion?: any): Ext
async function redirectFlow(options): Promise<FlowResult> {

const { request, parserType, self, checkSignature = true, from } = options;
const { query, octetString } = request;
const { query } = request;
const { SigAlg: sigAlg, Signature: signature } = query;

const targetEntityMetadata = from.entityMeta;
Expand Down Expand Up @@ -109,6 +109,9 @@ async function redirectFlow(options): Promise<FlowResult> {
return Promise.reject('ERR_MISSING_SIG_ALG');
}

// Look for the octet string on the request object first as a backwards compat feature
const octetString = request.octetString || libsaml.octetStringBuilder(bindDict.redirect, direction, query)

// put the below two assignments into verifyMessageSignature function
const base64Signature = Buffer.from(decodeURIComponent(signature), 'base64');
const decodeSigAlg = decodeURIComponent(sigAlg);
Expand Down Expand Up @@ -296,9 +299,7 @@ async function postFlow(options): Promise<FlowResult> {
async function postSimpleSignFlow(options): Promise<FlowResult> {

const { request, parserType, self, checkSignature = true, from } = options;

const { body, octetString } = request;

const { body } = request;
const targetEntityMetadata = from.entityMeta;

// ?SAMLRequest= or ?SAMLResponse=
Expand Down Expand Up @@ -354,6 +355,19 @@ async function postSimpleSignFlow(options): Promise<FlowResult> {
return Promise.reject('ERR_MISSING_SIG_ALG');
}

// Look for the octet string on the request object first as a backwards compat feature
const octetString = request.octetString || libsaml.octetStringBuilder(
bindDict.simpleSign,
direction,
{
...body,
// SimpleSign wants the XML already base64-decoded before computing the octet string.
// For encapsulation, it would be nice to have the helper decode it,
// but for optimization, lets not decode it twice.
[direction]: xmlString
}
)

// put the below two assignments into verifyMessageSignature function
const base64Signature = Buffer.from(signature, 'base64');

Expand Down
50 changes: 50 additions & 0 deletions src/libsaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,55 @@ const libSaml = () => {
return prefix + camelContent.charAt(0).toUpperCase() + camelContent.slice(1);
}

/**
* Create the octet string for the signature algorithms.
*
* Used in both Redirect and POST-SimpleSign bindings.
* HTTP-Redirect Binding as defined in the Bindings OASIS Standard, Section 3.4.4.1.
* http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
* HTTP-POST-SimpleSign Binding as defined in the "SimpleSign" Binding OASIS Committee Draft.
* https://www.oasis-open.org/committees/download.php/30234/sstc-saml-binding-simplesign-cd-04.pdf
*
* @public
* @param {string} binding "redirect" or "simpleSign"
* @param {string} direction "SAMLRequest" or "SAMLResponse"
* @param {object} values Object that includes SAMLRequest/SAMLResponse, SigAlg, and optional RelayState. All other values are ignored.
* @return {string}
*/
function octetStringBuilder(binding: string, direction: string, values: Record<string, unknown>): string {
const content = values[direction]
const sigAlg = values[urlParams.sigAlg]
const relayState = values[urlParams.relayState]

if (typeof content !== 'string') {
throw Error('ERR_OCTET_BAD_ARGS_CONTENT')
}

if (typeof sigAlg !== 'string') {
throw Error('ERR_MISSING_SIG_ALG')
}

const params: string[][] = [[direction, content]]

if (typeof relayState === 'string' && relayState.length !== 0) {
params.push([urlParams.relayState, relayState])
}

params.push([urlParams.sigAlg, sigAlg])

// Redirect binding needs each param URL encoded, `URLSearchParams` gives us this out of the box and maintains order.
if (binding === wording.binding.redirect){
return new URLSearchParams(params).toString()
}

// The SimpleSign octet string combines the params with &,= but doesn't encode for URLs.
if (binding === wording.binding.simpleSign){
return params.map(([k,v]) => `${k}=${v}`).join('&')
}

throw Error('ERR_OCTET_UNDEFINED_BINDING')
}

return {

createXPath,
Expand All @@ -248,6 +297,7 @@ const libSaml = () => {
defaultAttributeTemplate,
defaultLogoutRequestTemplate,
defaultLogoutResponseTemplate,
octetStringBuilder,

/**
* @desc Replace the tag (e.g. {tag}) inside the raw XML
Expand Down
Loading