diff --git a/src/binding-redirect.ts b/src/binding-redirect.ts index 4ff402d..9669723 100644 --- a/src/binding-redirect.ts +++ b/src/binding-redirect.ts @@ -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 diff --git a/src/binding-simplesign.ts b/src/binding-simplesign.ts index e40cc0d..fc6f259 100644 --- a/src/binding-simplesign.ts +++ b/src/binding-simplesign.ts @@ -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 @@ -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, @@ -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), diff --git a/src/flow.ts b/src/flow.ts index f58a536..ef99ede 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -53,7 +53,7 @@ function getDefaultExtractorFields(parserType: ParserType, assertion?: any): Ext async function redirectFlow(options): Promise { 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; @@ -109,6 +109,9 @@ async function redirectFlow(options): Promise { 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); @@ -296,9 +299,7 @@ async function postFlow(options): Promise { async function postSimpleSignFlow(options): Promise { const { request, parserType, self, checkSignature = true, from } = options; - - const { body, octetString } = request; - + const { body } = request; const targetEntityMetadata = from.entityMeta; // ?SAMLRequest= or ?SAMLResponse= @@ -354,6 +355,19 @@ async function postSimpleSignFlow(options): Promise { 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'); diff --git a/src/libsaml.ts b/src/libsaml.ts index 56f1b4b..f21dbcc 100644 --- a/src/libsaml.ts +++ b/src/libsaml.ts @@ -240,6 +240,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 { + 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, @@ -250,6 +299,7 @@ const libSaml = () => { defaultAttributeTemplate, defaultLogoutRequestTemplate, defaultLogoutResponseTemplate, + octetStringBuilder, /** * @desc Replace the tag (e.g. {tag}) inside the raw XML diff --git a/test/flow.ts b/test/flow.ts index 19bf553..9c12d23 100644 --- a/test/flow.ts +++ b/test/flow.ts @@ -82,31 +82,16 @@ const parseRedirectUrlContextCallBack = (_context: string) => { const _SAMLResponse = originalURL.query.SAMLResponse; const _Signature = originalURL.query.Signature; const _SigAlg = originalURL.query.SigAlg; - delete originalURL.query.Signature; - const _octetString = Object.keys(originalURL.query).map(q => q + '=' + encodeURIComponent(originalURL.query[q] as string)).join('&'); + const octetString = libsaml.octetStringBuilder('redirect','SAMLResponse', originalURL.query); return { query: { SAMLResponse: _SAMLResponse, Signature: _Signature, SigAlg: _SigAlg, }, - octetString: _octetString, + octetString, }; }; -// Build SimpleSign octetString -const buildSimpleSignOctetString = (type:string, context:string, sigAlg:string|undefined, relayState:string|undefined, signature: string|undefined) =>{ - const rawRequest = String(utility.base64Decode(context, true)); - let octetString:string = ''; - octetString += type + '=' + rawRequest; - if (relayState !== undefined && relayState.length > 0){ - octetString += '&RelayState=' + relayState; - } - if (signature !== undefined && signature.length >0 && sigAlg && sigAlg.length > 0){ - octetString += '&SigAlg=' + sigAlg; - } - return octetString; -}; - // Define of metadata const defaultIdpConfig = { @@ -182,11 +167,10 @@ test('create login request with redirect binding using default template and pars }); test('create login request with post simpleSign binding using default template and parse it', async t => { - const { relayState, id, context: SAMLRequest, type, sigAlg, signature } = sp.createLoginRequest(idp, 'simpleSign') as SimpleSignBindingContext; + const { id, context: SAMLRequest, type, sigAlg, signature } = sp.createLoginRequest(idp, 'simpleSign') as SimpleSignBindingContext; t.is(typeof id, 'string'); t.is(typeof SAMLRequest, 'string'); - const octetString = buildSimpleSignOctetString(type, SAMLRequest, sigAlg, relayState,signature); - const { samlContent, extract } = await idp.parseLoginRequest(sp, 'simpleSign', { body: { SAMLRequest, Signature: signature, SigAlg:sigAlg }, octetString}); + const { extract } = await idp.parseLoginRequest(sp, 'simpleSign', { body: { SAMLRequest, Signature: signature, SigAlg:sigAlg }}); t.is(extract.issuer, 'https://sp.example.org/metadata'); t.is(typeof extract.request.id, 'string'); t.is(extract.nameIDPolicy.format, 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'); @@ -194,7 +178,7 @@ test('create login request with post simpleSign binding using default template a }); test('create login request with post binding using default template and parse it', async t => { - const { relayState, type, entityEndpoint, id, context: SAMLRequest } = sp.createLoginRequest(idp, 'post') as PostBindingContext; + const { type, entityEndpoint, id, context: SAMLRequest } = sp.createLoginRequest(idp, 'post') as PostBindingContext; t.is(typeof id, 'string'); t.is(typeof SAMLRequest, 'string'); t.is(typeof entityEndpoint, 'string'); @@ -262,10 +246,11 @@ test('create login request with redirect binding signing with unencrypted PKCS#8 const { context } = _sp.createLoginRequest(idp, 'redirect'); - const parsed = parseRedirectUrlContextCallBack(context) - const signature = Buffer.from(parsed.query.Signature as string, 'base64'); + const parsed: Record = url.parse(context, true).query; + const octetString = libsaml.octetStringBuilder('redirect','SAMLRequest', parsed) + const signature = Buffer.from(parsed.Signature, 'base64'); - const valid = libsaml.verifyMessageSignature(_sp.entityMeta, parsed.octetString, signature, parsed.query.SigAlg as string); + const valid = libsaml.verifyMessageSignature(_sp.entityMeta, octetString, signature, parsed.SigAlg); t.true(valid, 'signature did not validate'); }); @@ -279,10 +264,11 @@ test('create login request with redirect binding signing with encrypted PKCS#8', const { context } = _sp.createLoginRequest(idp, 'redirect'); - const parsed = parseRedirectUrlContextCallBack(context) - const signature = Buffer.from(parsed.query.Signature as string, 'base64'); + const parsed: Record = url.parse(context, true).query; + const octetString = libsaml.octetStringBuilder('redirect','SAMLRequest', parsed) + const signature = Buffer.from(parsed.Signature, 'base64'); - const valid = libsaml.verifyMessageSignature(_sp.entityMeta, parsed.octetString, signature, parsed.query.SigAlg as string); + const valid = libsaml.verifyMessageSignature(_sp.entityMeta, octetString, signature, parsed.SigAlg); t.true(valid, 'signature did not validate'); }); @@ -442,8 +428,7 @@ test('send response with signed assertion by post simplesign and parse it', asyn 'relaystate' ) as SimpleSignBindingContext; // receiver (caution: only use metadata and public key when declare pair-up in oppoent entity) - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); - const { samlContent, extract } = await sp.parseLoginResponse(idpNoEncrypt, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + const { samlContent, extract } = await sp.parseLoginResponse(idpNoEncrypt, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg: sigAlg }}); t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); @@ -525,7 +510,7 @@ test('send response with signed assertion + custom transformation algorithms by ); const user = { email: 'user@esaml2.com' } - const { id, context: SAMLResponse, type, sigAlg, signature, relayState } = await idpNoEncrypt.createLoginResponse( + const { id, context: SAMLResponse, sigAlg, signature, relayState } = await idpNoEncrypt.createLoginResponse( signedAssertionSp, sampleRequestInfo, 'simpleSign', @@ -535,8 +520,7 @@ test('send response with signed assertion + custom transformation algorithms by 'relaystate' ) as SimpleSignBindingContext; // receiver (caution: only use metadata and public key when declare pair-up in oppoent entity) - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); - const { samlContent, extract } = await sp.parseLoginResponse(idpNoEncrypt, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + const { samlContent, extract } = await sp.parseLoginResponse(idpNoEncrypt, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); @@ -606,7 +590,7 @@ test('send response with [custom template] signed assertion by post simpleSign a // sender (caution: only use metadata and public key when declare pair-up in oppoent entity) const requestInfo = { extract: { request: { id: 'request_id' } } }; const user = { email: 'user@esaml2.com'}; - const { id, context: SAMLResponse, type, sigAlg, signature, entityEndpoint, relayState } = await idpcustomNoEncrypt.createLoginResponse( + const { id, context: SAMLResponse, sigAlg, signature, entityEndpoint, relayState } = await idpcustomNoEncrypt.createLoginResponse( sp, requestInfo, 'simpleSign', @@ -617,8 +601,7 @@ test('send response with [custom template] signed assertion by post simpleSign a 'relaystate' ) as SimpleSignBindingContext; // receiver (caution: only use metadata and public key when declare pair-up in oppoent entity) - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); - const { samlContent, extract } = await sp.parseLoginResponse(idpcustomNoEncrypt, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + const { samlContent, extract } = await sp.parseLoginResponse(idpcustomNoEncrypt, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); @@ -673,7 +656,7 @@ test('send response with signed message by redirect and parse it', async t => { test('send response with signed message by post simplesign and parse it', async t => { // sender (caution: only use metadata and public key when declare pair-up in oppoent entity) const user = { email: 'user@esaml2.com' }; - const { id, context: SAMLResponse, type, sigAlg, signature, relayState } = await idpNoEncrypt.createLoginResponse( + const { id, context: SAMLResponse, sigAlg, signature, relayState } = await idpNoEncrypt.createLoginResponse( spNoAssertSign, sampleRequestInfo, 'simpleSign', @@ -683,8 +666,7 @@ test('send response with signed message by post simplesign and parse it', async 'relaystate' ) as SimpleSignBindingContext; // receiver (caution: only use metadata and public key when declare pair-up in oppoent entity) - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); - const { samlContent, extract } = await spNoAssertSign.parseLoginResponse(idpNoEncrypt, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + const { samlContent, extract } = await spNoAssertSign.parseLoginResponse(idpNoEncrypt, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); @@ -745,9 +727,8 @@ test('send response with [custom template] and signed message by redirect and pa test('send response with [custom template] and signed message by post simplesign and parse it', async t => { // sender (caution: only use metadata and public key when declare pair-up in oppoent entity) - const requestInfo = { extract: { authnrequest: { id: 'request_id' } } }; const user = { email: 'user@esaml2.com'}; - const { id, context: SAMLResponse, type, sigAlg, signature, relayState } = await idpcustomNoEncrypt.createLoginResponse( + const { id, context: SAMLResponse, sigAlg, signature, relayState } = await idpcustomNoEncrypt.createLoginResponse( spNoAssertSign, { extract: { authnrequest: { id: 'request_id' } } }, 'simpleSign', { email: 'user@esaml2.com' }, @@ -756,8 +737,7 @@ test('send response with [custom template] and signed message by post simplesign 'relaystate' ) as SimpleSignBindingContext; // receiver (caution: only use metadata and public key when declare pair-up in oppoent entity) - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); - const { samlContent, extract } = await spNoAssertSign.parseLoginResponse(idpcustomNoEncrypt, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + const { samlContent, extract } = await spNoAssertSign.parseLoginResponse(idpcustomNoEncrypt, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); @@ -821,15 +801,14 @@ test('send login response with signed assertion + signed message by post simples wantMessageSigned: true, }); const user = { email: 'user@esaml2.com' }; - const { id, context: SAMLResponse, type, sigAlg, signature, relayState } = await idpNoEncrypt.createLoginResponse(spWantMessageSign, sampleRequestInfo, + const { id, context: SAMLResponse, sigAlg, signature, relayState } = await idpNoEncrypt.createLoginResponse(spWantMessageSign, sampleRequestInfo, 'simpleSign', user, createTemplateCallback(idpNoEncrypt, spWantMessageSign, binding.simpleSign, user), undefined, 'relaystate' ) as SimpleSignBindingContext; // receiver (caution: only use metadata and public key when declare pair-up in oppoent entity) - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); - const { samlContent, extract } = await spWantMessageSign.parseLoginResponse (idpNoEncrypt, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + const { samlContent, extract } = await spWantMessageSign.parseLoginResponse (idpNoEncrypt, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); @@ -900,7 +879,7 @@ test('send login response with [custom template] and signed assertion + signed m wantMessageSigned: true, }); const user = { email: 'user@esaml2.com'}; - const { id, context: SAMLResponse, type, sigAlg, signature, relayState } = await idpcustomNoEncrypt.createLoginResponse( + const { id, context: SAMLResponse, sigAlg, signature, relayState } = await idpcustomNoEncrypt.createLoginResponse( spWantMessageSign, { extract: { authnrequest: { id: 'request_id' } } }, 'simpleSign', @@ -910,8 +889,7 @@ test('send login response with [custom template] and signed assertion + signed m 'relaystate' ) as SimpleSignBindingContext; // receiver (caution: only use metadata and public key when declare pair-up in oppoent entity) - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); - const { samlContent, extract } = await spWantMessageSign.parseLoginResponse(idpcustomNoEncrypt, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + const { samlContent, extract } = await spWantMessageSign.parseLoginResponse(idpcustomNoEncrypt, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); @@ -1160,15 +1138,14 @@ test('avoid malformatted response with redirect binding', async t => { } }); -test('avoid malformatted response with simplesign binding', async t => { +test('avoid malformed response with simplesign binding', async t => { // sender (caution: only use metadata and public key when declare pair-up in oppoent entity) const user = { email: 'user@email.com' }; - const { context: SAMLResponse, type, sigAlg, signature, relayState } = await idpNoEncrypt.createLoginResponse(sp, sampleRequestInfo, 'simpleSign', user, createTemplateCallback(idpNoEncrypt, sp, binding.simpleSign, user), undefined, 'relaystate'); + const { context: SAMLResponse, sigAlg, signature, relayState } = await idpNoEncrypt.createLoginResponse(sp, sampleRequestInfo, 'simpleSign', user, createTemplateCallback(idpNoEncrypt, sp, binding.simpleSign, user), undefined, 'relaystate'); const rawResponse = String(utility.base64Decode(SAMLResponse, true)); const attackResponse = `evil@evil.com${rawResponse}`; - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); try { - await sp.parseLoginResponse(idpNoEncrypt, 'simpleSign', { body: { SAMLResponse: utility.base64Encode(attackResponse), Signature: signature, SigAlg:sigAlg }, octetString }); + await sp.parseLoginResponse(idpNoEncrypt, 'simpleSign', { body: { SAMLResponse: utility.base64Encode(attackResponse), RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); } catch (e) { // it must throw an error t.is(true, true); @@ -1305,11 +1282,10 @@ test.serial('should throw ERR_SUBJECT_UNCONFIRMED for the expired SAML response const user = { email: 'user@esaml2.com' }; try { - const { context: SAMLResponse, type, sigAlg, signature, relayState } = await idp.createLoginResponse(sp, sampleRequestInfo, 'simpleSign', user, createTemplateCallback(idp, sp, binding.simpleSign, user), undefined, 'relaystate'); - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); + const { context: SAMLResponse, sigAlg, signature, relayState } = await idp.createLoginResponse(sp, sampleRequestInfo, 'simpleSign', user, createTemplateCallback(idp, sp, binding.simpleSign, user), undefined, 'relaystate'); // simulate the time on client side when response arrives after 5.1 sec tk.freeze(fiveMinutesOneSecLater); - await sp.parseLoginResponse(idp, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + await sp.parseLoginResponse(idp, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); // test failed, it shouldn't happen t.is(true, false); } catch (e) { @@ -1368,11 +1344,10 @@ test.serial('should not throw ERR_SUBJECT_UNCONFIRMED for the expired SAML respo const user = { email: 'user@esaml2.com' }; try { - const { context: SAMLResponse, type, signature, sigAlg, relayState } = await idp.createLoginResponse(spWithClockDrift, sampleRequestInfo, 'simpleSign', user, createTemplateCallback(idp, spWithClockDrift, binding.simpleSign, user), undefined, 'relaystate'); - const octetString = buildSimpleSignOctetString(type, SAMLResponse, sigAlg, relayState, signature); + const { context: SAMLResponse, signature, sigAlg, relayState } = await idp.createLoginResponse(spWithClockDrift, sampleRequestInfo, 'simpleSign', user, createTemplateCallback(idp, spWithClockDrift, binding.simpleSign, user), undefined, 'relaystate'); // simulate the time on client side when response arrives after 5.1 sec tk.freeze(fiveMinutesOneSecLater); - await spWithClockDrift.parseLoginResponse(idp, 'simpleSign', { body: { SAMLResponse, Signature: signature, SigAlg:sigAlg }, octetString }); + await spWithClockDrift.parseLoginResponse(idp, 'simpleSign', { body: { SAMLResponse, RelayState: relayState, Signature: signature, SigAlg:sigAlg }}); t.is(true, true); } catch (e) { // test failed, it shouldn't happen diff --git a/test/issues.ts b/test/issues.ts index 3c768f0..8340de7 100644 --- a/test/issues.ts +++ b/test/issues.ts @@ -1,5 +1,5 @@ import esaml2 = require('../index'); -import { readFileSync, writeFileSync } from 'fs'; +import { readFileSync } from 'fs'; import test from 'ava'; import * as fs from 'fs'; import * as url from 'url'; @@ -10,8 +10,6 @@ import { extract } from '../src/extractor'; const { IdentityProvider: identityProvider, ServiceProvider: serviceProvider, - IdPMetadata: idpMetadata, - SPMetadata: spMetadata, Utility: utility, SamlLib: libsaml, Constants: ref, @@ -43,17 +41,17 @@ test('#31 query param for sso/slo returns error', t => { nameIDFormat: ['urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'], assertionConsumerService: [{ Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - Location: 'sp.example.com/acs', + Location: 'https://sp.example.com/acs', }, { Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - Location: 'sp.example.com/acs', + Location: 'https://sp.example.com/acs', }], singleLogoutService: [{ Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - Location: 'sp.example.com/slo', + Location: 'https://sp.example.com/slo', }, { Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - Location: 'sp.example.com/slo', + Location: 'https://sp.example.com/slo', }], }; const idpcfg = { @@ -61,17 +59,17 @@ test('#31 query param for sso/slo returns error', t => { nameIDFormat: ['urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'], singleSignOnService: [{ Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - Location: 'idp.example.com/sso', + Location: 'https://idp.example.com/sso', }, { Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - Location: 'idp.example.com/sso', + Location: 'https://idp.example.com/sso', }], singleLogoutService: [{ Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - Location: 'idp.example.com/sso/slo', + Location: 'https://idp.example.com/sso/slo', }, { Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - Location: 'idp.example.com/sso/slo', + Location: 'https://idp.example.com/sso/slo', }], }; const idp = identityProvider(idpcfg); @@ -151,11 +149,11 @@ test('#31 query param for sso/slo returns error', t => { }); test('#91 idp gets single sign on service from the metadata', t => { - t.is(idp.entityMeta.getSingleSignOnService('post'), 'idp.example.com/sso'); + t.is(idp.entityMeta.getSingleSignOnService('post'), 'https://idp.example.com/sso'); }); - + test('#98 undefined AssertionConsumerServiceURL with redirect request', t => { - const { id, context } = sp98.createLoginRequest(idp, 'redirect'); + const { context } = sp98.createLoginRequest(idp, 'redirect'); const originalURL = url.parse(context, true); const request = originalURL.query.SAMLRequest as string; const rawRequest = utility.inflateString(decodeURIComponent(request)); @@ -164,4 +162,4 @@ test('#31 query param for sso/slo returns error', t => { const index = Object.keys(authnRequest.attributes).find((i: string) => authnRequest.attributes[i].nodeName === 'AssertionConsumerServiceURL') as any; t.is(authnRequest.attributes[index].nodeValue, 'https://example.org/response'); }); -})(); \ No newline at end of file +})();