Skip to content

Commit

Permalink
Allow arrays of certs to be provided (#499)
Browse files Browse the repository at this point in the history
* feat: allow arrays of certs to be provided

Brings the `signingCert` and `encryptCert` args on the entities up to parity with raw metadata when multiple KeyDescriptors are desired for each use.

fixes #497

* docs: typos

Items my IDE pointed out as I poked around.
  • Loading branch information
mastermatt authored Dec 27, 2022
1 parent 18ef139 commit 87aa1cf
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 72 deletions.
4 changes: 2 additions & 2 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export function getContext() {
export function setSchemaValidator(params: ValidatorContext) {

if (typeof params.validate !== 'function') {
throw new Error('validate must be a callback function having one arguemnt as xml input');
throw new Error('validate must be a callback function having one argument as xml input');
}

// assign the validate function to the context
context.validate = params.validate;

}
}
5 changes: 2 additions & 3 deletions src/binding-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { wording, namespace, StatusCode } from './urn';
import { BindingContext } from './entity';
import libsaml from './libsaml';
import utility, { get } from './utility';
import { LogoutResponseTemplate } from './libsaml';

const binding = wording.binding;

Expand Down Expand Up @@ -145,7 +144,7 @@ async function base64LoginResponse(requestInfo: any = {}, entity: any, user: any
...config,
rawSamlMessage: rawSamlResponse,
transformationAlgorithms: spSetting.transformationAlgorithms,
referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
signatureConfig: {
prefix: 'ds',
location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
Expand Down Expand Up @@ -315,7 +314,7 @@ function base64LogoutResponse(requestInfo: any, entity: any, customTagReplacemen
reference: "/*[local-name(.)='LogoutResponse']/*[local-name(.)='Issuer']",
action: 'after'
}
}
}
}),
};
}
Expand Down
10 changes: 5 additions & 5 deletions src/binding-simplesign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function pvPair(param: string, value: string, first?: boolean): string {
}
/**
* @private
* @desc Refractored part of simple signature generation for login/logout request
* @desc Refactored part of simple signature generation for login/logout request
* @param {string} type
* @param {string} rawSamlRequest
* @param {object} entitySetting
Expand All @@ -61,10 +61,10 @@ function buildSimpleSignature(opts: BuildSimpleSignConfig) : string {
const sigAlg = pvPair(urlParams.sigAlg, entitySetting.requestSignatureAlgorithm);
const octetString = context + relayState + sigAlg;
return libsaml.constructMessageSignature(
queryParam + '=' + octetString,
entitySetting.privateKey,
entitySetting.privateKeyPass,
undefined,
queryParam + '=' + octetString,
entitySetting.privateKey,
entitySetting.privateKeyPass,
undefined,
entitySetting.requestSignatureAlgorithm
).toString();
}
Expand Down
4 changes: 2 additions & 2 deletions src/entity-idp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ import { isString } from './utility';
import { BindingContext } from './entity';

/**
* Identity prvider can be configured using either metadata importing or idpSetting
* Identity provider can be configured using either metadata importing or idpSetting
*/
export default function(props: IdentityProviderSettings) {
return new IdentityProvider(props);
}

/**
* Identity prvider can be configured using either metadata importing or idpSetting
* Identity provider can be configured using either metadata importing or idpSetting
*/
export class IdentityProvider extends Entity {

Expand Down
17 changes: 8 additions & 9 deletions src/flow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { inflateString, base64Decode, isNonEmptyArray } from './utility';
import { inflateString, base64Decode } from './utility';
import { verifyTime } from './validator';
import libsaml from './libsaml';
import {
Expand All @@ -19,7 +19,6 @@ import {
MessageSignatureOrder,
StatusCode
} from './urn';
import simpleSignBinding from './binding-simplesign';

const bindDict = wording.binding;
const urlParams = wording.urlParams;
Expand Down Expand Up @@ -110,7 +109,7 @@ async function redirectFlow(options): Promise<FlowResult> {
return Promise.reject('ERR_MISSING_SIG_ALG');
}

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

Expand All @@ -125,7 +124,7 @@ async function redirectFlow(options): Promise<FlowResult> {
}

/**
* Validation part: validate the context of response after signature is verified and decrpyted (optional)
* Validation part: validate the context of response after signature is verified and decrypted (optional)
*/
const issuer = targetEntityMetadata.getEntityID();
const extractedProperties = parseResult.extract;
Expand Down Expand Up @@ -207,7 +206,7 @@ async function postFlow(options): Promise<FlowResult> {
// check status based on different scenarios
await checkStatus(samlContent, parserType);

// verify the signatures (the repsonse is encrypted then signed, then verify first then decrypt)
// verify the signatures (the response is encrypted then signed, then verify first then decrypt)
if (
checkSignature &&
from.entitySetting.messageSigningOrder === MessageSignatureOrder.ETS
Expand All @@ -227,7 +226,7 @@ async function postFlow(options): Promise<FlowResult> {
extractorFields = getDefaultExtractorFields(parserType, result[1]);
}

// verify the signatures (the repsonse is signed then encrypted, then decrypt first then verify)
// verify the signatures (the response is signed then encrypted, then decrypt first then verify)
if (
checkSignature &&
from.entitySetting.messageSigningOrder === MessageSignatureOrder.STE
Expand All @@ -246,7 +245,7 @@ async function postFlow(options): Promise<FlowResult> {
};

/**
* Validation part: validate the context of response after signature is verified and decrpyted (optional)
* Validation part: validate the context of response after signature is verified and decrypted (optional)
*/
const targetEntityMetadata = from.entityMeta;
const issuer = targetEntityMetadata.getEntityID();
Expand Down Expand Up @@ -355,7 +354,7 @@ async function postSimpleSignFlow(options): Promise<FlowResult> {
return Promise.reject('ERR_MISSING_SIG_ALG');
}

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

const verified = libsaml.verifyMessageSignature(targetEntityMetadata, octetString, base64Signature, sigAlg);
Expand All @@ -369,7 +368,7 @@ async function postSimpleSignFlow(options): Promise<FlowResult> {
}

/**
* Validation part: validate the context of response after signature is verified and decrpyted (optional)
* Validation part: validate the context of response after signature is verified and decrypted (optional)
*/
const issuer = targetEntityMetadata.getEntityID();
const extractedProperties = parseResult.extract;
Expand Down
13 changes: 6 additions & 7 deletions src/libsaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ const libSaml = () => {
context: '<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status><saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{AssertionID}" Version="2.0" IssueInstant="{IssueInstant}"><saml:Issuer>{Issuer}</saml:Issuer><saml:Subject><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="{SubjectConfirmationDataNotOnOrAfter}" Recipient="{SubjectRecipient}" InResponseTo="{InResponseTo}"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="{ConditionsNotBefore}" NotOnOrAfter="{ConditionsNotOnOrAfter}"><saml:AudienceRestriction><saml:Audience>{Audience}</saml:Audience></saml:AudienceRestriction></saml:Conditions>{AuthnStatement}{AttributeStatement}</saml:Assertion></samlp:Response>',
attributes: [],
additionalTemplates: {
"attributeStatementTemplate": defaultAttributeStatementTemplate,
"attributeTemplate": defaultAttributeTemplate
'attributeStatementTemplate': defaultAttributeStatementTemplate,
'attributeTemplate': defaultAttributeTemplate
}
};
/**
Expand Down Expand Up @@ -252,7 +252,7 @@ const libSaml = () => {
defaultLogoutResponseTemplate,

/**
* @desc Repalce the tag (e.g. {tag}) inside the raw XML
* @desc Replace the tag (e.g. {tag}) inside the raw XML
* @param {string} rawXML raw XML string used to do keyword replacement
* @param {array} tagValues tag values
* @return {string}
Expand All @@ -266,8 +266,8 @@ const libSaml = () => {
/**
* @desc Helper function to build the AttributeStatement tag
* @param {LoginResponseAttribute} attributes an array of attribute configuration
* @param {AttributeTemplate} attributeTemplate the attribut tag template to be used
* @param {AttributeStatementTemplate} attributeStatementTemplate the attributStatement tag template to be used
* @param {AttributeTemplate} attributeTemplate the attribute tag template to be used
* @param {AttributeStatementTemplate} attributeStatementTemplate the attributeStatement tag template to be used
* @return {string}
*/
attributeStatementBuilder(
Expand Down Expand Up @@ -351,7 +351,6 @@ const libSaml = () => {
/**
* @desc Verify the XML signature
* @param {string} xml xml
* @param {signature} signature context of XML signature
* @param {SignatureVerifierOptions} opts cert declares the X509 certificate
* @return {boolean} verification result
*/
Expand Down Expand Up @@ -639,7 +638,7 @@ const libSaml = () => {
return resolve(utility.base64Encode(doc.toString()));
});
} else {
return resolve(utility.base64Encode(xml)); // No need to do encrpytion
return resolve(utility.base64Encode(xml)); // No need to do encryption
}
});
},
Expand Down
14 changes: 5 additions & 9 deletions src/metadata-idp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Metadata, { MetadataInterface } from './metadata';
import { MetadataIdpOptions, MetadataIdpConstructor } from './types';
import { namespace } from './urn';
import libsaml from './libsaml';
import { isNonEmptyArray, isString } from './utility';
import { castArrayOpt, isNonEmptyArray, isString } from './utility';
import xml from 'xml';

export interface IdpMetadataInterface extends MetadataInterface {
Expand Down Expand Up @@ -46,16 +46,12 @@ export class IdpMetadata extends Metadata {
},
}];

if (signingCert) {
IDPSSODescriptor.push(libsaml.createKeySection('signing', signingCert));
} else {
//console.warn('Construct identity provider - missing signing certificate');
for(const cert of castArrayOpt(signingCert)) {
IDPSSODescriptor.push(libsaml.createKeySection('signing', cert));
}

if (encryptCert) {
IDPSSODescriptor.push(libsaml.createKeySection('encryption', encryptCert));
} else {
//console.warn('Construct identity provider - missing encrypt certificate');
for(const cert of castArrayOpt(encryptCert)) {
IDPSSODescriptor.push(libsaml.createKeySection('encryption', cert));
}

if (isNonEmptyArray(nameIDFormat)) {
Expand Down
18 changes: 7 additions & 11 deletions src/metadata-sp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Metadata, { MetadataInterface } from './metadata';
import { MetadataSpConstructor, MetadataSpOptions } from './types';
import { namespace, elementsOrder as order } from './urn';
import libsaml from './libsaml';
import { isNonEmptyArray, isString } from './utility';
import { castArrayOpt, isNonEmptyArray, isString } from './utility';
import xml from 'xml';

export interface SpMetadataInterface extends MetadataInterface {
Expand Down Expand Up @@ -36,14 +36,14 @@ export default function(meta: MetadataSpConstructor) {
export class SpMetadata extends Metadata {

/**
* @param {object/string} meta (either xml string or configuation in object)
* @param {object/string} meta (either xml string or configuration in object)
* @return {object} prototypes including public functions
*/
constructor(meta: MetadataSpConstructor) {

const isFile = isString(meta) || meta instanceof Buffer;

// use object configuation instead of importing metadata file directly
// use object configuration instead of importing metadata file directly
if (!isFile) {

const {
Expand Down Expand Up @@ -80,16 +80,12 @@ export class SpMetadata extends Metadata {
console.warn('Construct service provider - missing signatureConfig');
}

if (signingCert) {
descriptors.KeyDescriptor!.push(libsaml.createKeySection('signing', signingCert).KeyDescriptor);
} else {
//console.warn('Construct service provider - missing signing certificate');
for(const cert of castArrayOpt(signingCert)) {
descriptors.KeyDescriptor!.push(libsaml.createKeySection('signing', cert).KeyDescriptor);
}

if (encryptCert) {
descriptors.KeyDescriptor!.push(libsaml.createKeySection('encryption', encryptCert).KeyDescriptor);
} else {
//console.warn('Construct service provider - missing encrypt certificate');
for(const cert of castArrayOpt(encryptCert)) {
descriptors.KeyDescriptor!.push(libsaml.createKeySection('encryption', cert).KeyDescriptor);
}

if (isNonEmptyArray(nameIDFormat)) {
Expand Down
4 changes: 2 additions & 2 deletions src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class Metadata implements MetadataInterface {
meta: any;

/**
* @param {string | Buffer} metadata xml
* @param {string | Buffer} xml
* @param {object} extraParse for custom metadata extractor
*/
constructor(xml: string | Buffer, extraParse: any = []) {
Expand Down Expand Up @@ -140,7 +140,7 @@ export default class Metadata implements MetadataInterface {
if (!(singleLogoutService instanceof Array)) {
singleLogoutService = [singleLogoutService];
}
const service = singleLogoutService.find(obj => obj.binding === bindType);
const service = singleLogoutService.find(obj => obj.binding === bindType);
if (service) {
return service.location;
}
Expand Down
16 changes: 8 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ type SSOService = {

export interface MetadataIdpOptions {
entityID?: string;
signingCert?: string | Buffer;
encryptCert?: string | Buffer;
signingCert?: string | Buffer | (string | Buffer)[];
encryptCert?: string | Buffer | (string | Buffer)[];
wantAuthnRequestsSigned?: boolean;
nameIDFormat?: string[];
singleSignOnService?: SSOService[];
Expand All @@ -31,8 +31,8 @@ export type MetadataIdpConstructor =

export interface MetadataSpOptions {
entityID?: string;
signingCert?: string | Buffer;
encryptCert?: string | Buffer;
signingCert?: string | Buffer | (string | Buffer)[];
encryptCert?: string | Buffer | (string | Buffer)[];
authnRequestsSigned?: boolean;
wantAssertionsSigned?: boolean;
wantMessageSigned?: boolean;
Expand Down Expand Up @@ -81,8 +81,8 @@ export type ServiceProviderSettings = {
signatureConfig?: SignatureConfig;
loginRequestTemplate?: SAMLDocumentTemplate;
logoutRequestTemplate?: SAMLDocumentTemplate;
signingCert?: string | Buffer;
encryptCert?: string | Buffer;
signingCert?: string | Buffer | (string | Buffer)[];
encryptCert?: string | Buffer | (string | Buffer)[];
transformationAlgorithms?: string[];
nameIDFormat?: string[];
allowCreate?: boolean;
Expand Down Expand Up @@ -110,8 +110,8 @@ export type IdentityProviderSettings = {
entityID?: string;
privateKey?: string | Buffer;
privateKeyPass?: string;
signingCert?: string | Buffer;
encryptCert?: string | Buffer; /** todo */
signingCert?: string | Buffer | (string | Buffer)[];
encryptCert?: string | Buffer | (string | Buffer)[];
nameIDFormat?: string[];
singleSignOnService?: SSOService[];
singleLogoutService?: SSOService[];
Expand Down
Loading

0 comments on commit 87aa1cf

Please sign in to comment.