Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions .changeset/swift-waves-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/core-typings': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Adds a new setting to select which algorithm to use when signing SAML requests and responses
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { SAMLSignatureAlgorithm } from '@rocket.chat/core-typings';

export interface IServiceProviderOptions {
provider: string;
entryPoint: string;
Expand All @@ -6,6 +8,7 @@ export interface IServiceProviderOptions {
cert: string;
privateCert: string;
privateKey: string;
signatureAlgorithm: SAMLSignatureAlgorithm;
customAuthnContext: string;
authnContextComparison: string;
defaultUserRole: string;
Expand Down
56 changes: 39 additions & 17 deletions apps/meteor/app/meteor-accounts-saml/server/lib/ServiceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ import { LogoutRequestParser } from './parsers/LogoutRequest';
import { LogoutResponseParser } from './parsers/LogoutResponse';
import { ResponseParser } from './parsers/Response';

const signatureAlgorithms = {
'RSA-SHA1': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
'RSA-SHA256': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
'RSA-SHA384': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384',
'RSA-SHA512': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512',
} as const;

export class SAMLServiceProvider {
serviceProviderOptions: IServiceProviderOptions;

Expand All @@ -29,10 +36,35 @@ export class SAMLServiceProvider {
this.serviceProviderOptions = serviceProviderOptions;
}

private signRequest(xml: string): string {
const signer = crypto.createSign('RSA-SHA1');
private getSignatureAlgorithm(): keyof typeof signatureAlgorithms {
const algorithm = `RSA-${this.serviceProviderOptions.signatureAlgorithm}`;

if (algorithm in signatureAlgorithms) {
return algorithm as keyof typeof signatureAlgorithms;
}

return 'RSA-SHA256';
}

private maybeSignRequest(samlObject: Record<string, any>): Record<string, any> {
if (!this.serviceProviderOptions.privateKey) {
return samlObject;
}

const algorithm = this.getSignatureAlgorithm();

const alg = signatureAlgorithms[algorithm];
const xml = querystring.stringify({ ...samlObject, SigAlg: alg });

const signer = crypto.createSign(algorithm);
signer.update(xml);
return signer.sign(this.serviceProviderOptions.privateKey, 'base64');
const signature = signer.sign(this.serviceProviderOptions.privateKey, 'base64');

return {
...samlObject,
SigAlg: alg,
Signature: signature,
};
}

public generateAuthorizeRequest(credentialToken: string): string {
Expand Down Expand Up @@ -78,15 +110,10 @@ export class SAMLServiceProvider {
// TBD. We should really include a proper RelayState here
const relayState = Meteor.absoluteUrl();

const samlResponse: Record<string, any> = {
const samlResponse = this.maybeSignRequest({
SAMLResponse: base64,
RelayState: relayState,
};

if (this.serviceProviderOptions.privateCert) {
samlResponse.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
samlResponse.Signature = this.signRequest(querystring.stringify(samlResponse));
}
});

target += querystring.stringify(samlResponse);

Expand Down Expand Up @@ -127,15 +154,10 @@ export class SAMLServiceProvider {
relayState = this.serviceProviderOptions.provider;
}

const samlRequest: Record<string, any> = {
const samlRequest = this.maybeSignRequest({
SAMLRequest: base64,
RelayState: relayState,
};

if (this.serviceProviderOptions.privateCert) {
samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
samlRequest.Signature = this.signRequest(querystring.stringify(samlRequest));
}
});

target += querystring.stringify(samlRequest);

Expand Down
14 changes: 14 additions & 0 deletions apps/meteor/app/meteor-accounts-saml/server/lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const getSamlConfigs = function (service: string): SAMLConfiguration {
publicCert: settings.get(`${service}_public_cert`),
// People often overlook the instruction to remove the header and footer of the certificate on this specific setting, so let's do it for them.
cert: SAMLUtils.normalizeCert((settings.get(`${service}_cert`) as string) || ''),
algorithm: settings.get(`${service}_signature_algorithm`) || 'SHA1',
},
signatureValidationType: settings.get(`${service}_signature_validation_type`),
userDataFieldMap: settings.get(`${service}_user_data_fieldmap`),
Expand Down Expand Up @@ -89,6 +90,7 @@ const configureSamlService = function (samlConfigs: Record<string, any>): IServi
cert: samlConfigs.secret.cert,
privateCert,
privateKey,
signatureAlgorithm: samlConfigs.secret.algorithm,
customAuthnContext: samlConfigs.customAuthnContext,
authnContextComparison: samlConfigs.authnContextComparison,
defaultUserRole: samlConfigs.defaultUserRole,
Expand Down Expand Up @@ -213,6 +215,18 @@ export const addSettings = async function (name: string): Promise<void> {
i18nLabel: 'SAML_Custom_Private_Key',
secret: true,
});
await this.add(`SAML_Custom_${name}_signature_algorithm`, 'SHA1', {
type: 'select',
values: [
{ key: 'SHA1', i18nLabel: 'SAML_Custom_Signature_Algorithm_SHA1' },
{ key: 'SHA256', i18nLabel: 'SAML_Custom_Signature_Algorithm_SHA256' },
{ key: 'SHA384', i18nLabel: 'SAML_Custom_Signature_Algorithm_SHA384' },
{ key: 'SHA512', i18nLabel: 'SAML_Custom_Signature_Algorithm_SHA512' },
],
i18nLabel: 'SAML_Custom_Signature_Algorithm',
i18nDescription: 'SAML_Custom_Signature_Algorithm_description',
secret: true,
});
});
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,5 @@
'metadata.sign.privatekey_pass' => null,
'metadata.sign.certificate' => null,
'proxy' => null,
'trusted.url.domains' => array(),
'trusted.url.domains' => array('localhost:3000'),
);
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@
),
),
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
// Other settings that may be useful for testing, but are not compatible with the existing e2e tests
// 'privatekey' => 'server.pem',
// 'certificate' => 'server.crt',
// 'redirect.sign' => true,
// 'redirect.validate' => true,
// 'validate.logout' => true,
// 'assertion.encryption' => true,
);
5 changes: 4 additions & 1 deletion apps/meteor/tests/unit/app/meteor-accounts-saml/data.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export const serviceProviderOptions = {
import type { IServiceProviderOptions } from '../../../../app/meteor-accounts-saml/server/definition/IServiceProviderOptions';

export const serviceProviderOptions: IServiceProviderOptions = {
provider: '[test-provider]',
entryPoint: '[entry-point]',
idpSLORedirectURL: '[idpSLORedirectURL]',
issuer: '[issuer]',
cert: '',
privateCert: '',
privateKey: '',
signatureAlgorithm: 'SHA256',
customAuthnContext: 'Password',
authnContextComparison: 'Whatever',
defaultUserRole: 'user',
Expand Down
3 changes: 3 additions & 0 deletions packages/core-typings/src/ILoginServiceConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export type CASConfiguration = {
autoclose: boolean;
};

export type SAMLSignatureAlgorithm = 'SHA1' | 'SHA256' | 'SHA384' | 'SHA512';

export type SAMLConfiguration = {
buttonLabelText: string;
buttonLabelColor: string;
Expand All @@ -84,6 +86,7 @@ export type SAMLConfiguration = {
privateKey: string;
publicCert: string;
cert: string;
algorithm: SAMLSignatureAlgorithm;
};
signatureValidationType: 'All' | 'Response' | 'Assertion' | 'Either';
userDataFieldMap: string;
Expand Down
6 changes: 6 additions & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -4398,6 +4398,12 @@
"SAML_Custom_Private_Key": "Private Key Contents",
"SAML_Custom_Provider": "Custom Provider",
"SAML_Custom_Public_Cert": "Public Cert Contents",
"SAML_Custom_Signature_Algorithm": "Signature Algorithm",
"SAML_Custom_Signature_Algorithm_description": "What Algorithm to use when signing requests and responses from Rocket.Chat",
"SAML_Custom_Signature_Algorithm_SHA1": "SHA1",
"SAML_Custom_Signature_Algorithm_SHA256": "SHA256",
"SAML_Custom_Signature_Algorithm_SHA384": "SHA384",
"SAML_Custom_Signature_Algorithm_SHA512": "SHA512",
Comment thread
pierre-lehnen-rc marked this conversation as resolved.
Outdated
"SAML_Custom_Username_Field": "Username field name",
"SAML_Custom_Username_Normalize": "Normalize username",
"SAML_Custom_Username_Normalize_Lowercase": "To Lowercase",
Expand Down