mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-25 00:22:43 -05:00
feat(saml): validate SAML crypto algorithms during initial phase (#6785)
This commit is contained in:
committed by
GitHub
parent
c0c9444a19
commit
b56d7b8eaa
@@ -1066,6 +1066,39 @@ sso({
|
||||
- **"SAML assertion has expired"** — Current time is after the `NotOnOrAfter` timestamp (plus clock skew)
|
||||
- **"SAML assertion missing required timestamp conditions"** — Assertion has no timestamps and `requireTimestamps` is enabled
|
||||
|
||||
### Algorithm Validation
|
||||
|
||||
Better Auth validates SAML cryptographic algorithms and warns about deprecated ones (SHA-1, RSA 1.5, 3DES) by default.
|
||||
|
||||
```ts title="auth.ts"
|
||||
sso({
|
||||
saml: {
|
||||
algorithms: {
|
||||
// "warn" (default) | "reject" | "allow"
|
||||
onDeprecated: "warn",
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
| Value | Behavior |
|
||||
|-------|----------|
|
||||
| `"warn"` | Log warning, allow authentication (default) |
|
||||
| `"reject"` | Throw error, block authentication |
|
||||
| `"allow"` | Silent, no validation |
|
||||
|
||||
For strict security (production):
|
||||
|
||||
```ts title="auth.ts"
|
||||
sso({
|
||||
saml: {
|
||||
algorithms: {
|
||||
onDeprecated: "reject",
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Domain verification
|
||||
|
||||
Domain verification allows your application to automatically trust a new SSO provider
|
||||
@@ -1358,6 +1391,18 @@ If you want to allow account linking for specific trusted providers, enable the
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
algorithms: {
|
||||
description: "Algorithm validation options.",
|
||||
type: "object",
|
||||
properties: {
|
||||
onDeprecated: {
|
||||
description: "Behavior for deprecated algorithms (SHA-1, RSA 1.5, 3DES).",
|
||||
type: "string",
|
||||
enum: ["reject", "warn", "allow"],
|
||||
default: "warn",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
modelName: {
|
||||
|
||||
@@ -29,6 +29,15 @@ export {
|
||||
validateSAMLTimestamp,
|
||||
} from "./routes/sso";
|
||||
|
||||
export {
|
||||
type AlgorithmValidationOptions,
|
||||
DataEncryptionAlgorithm,
|
||||
type DeprecatedAlgorithmBehavior,
|
||||
DigestAlgorithm,
|
||||
KeyEncryptionAlgorithm,
|
||||
SignatureAlgorithm,
|
||||
} from "./saml";
|
||||
|
||||
import type { OIDCConfig, SAMLConfig, SSOOptions, SSOProvider } from "./types";
|
||||
|
||||
export type { SAMLConfig, OIDCConfig, SSOOptions, SSOProvider };
|
||||
|
||||
@@ -30,8 +30,8 @@ import {
|
||||
discoverOIDCConfig,
|
||||
mapDiscoveryErrorToAPIError,
|
||||
} from "../oidc";
|
||||
import { validateSAMLAlgorithms } from "../saml";
|
||||
import type { OIDCConfig, SAMLConfig, SSOOptions, SSOProvider } from "../types";
|
||||
|
||||
import { safeJsonParse, validateEmailDomain } from "../utils";
|
||||
|
||||
const AUTHN_REQUEST_KEY_PREFIX = "saml-authn-request:";
|
||||
@@ -1808,6 +1808,8 @@ export const callbackSSOSAML = (options?: SSOOptions) => {
|
||||
|
||||
const { extract } = parsedResponse!;
|
||||
|
||||
validateSAMLAlgorithms(parsedResponse, options?.saml?.algorithms);
|
||||
|
||||
validateSAMLTimestamp((extract as any).conditions, {
|
||||
clockSkew: options?.saml?.clockSkew,
|
||||
requireTimestamps: options?.saml?.requireTimestamps,
|
||||
@@ -2246,6 +2248,8 @@ export const acsEndpoint = (options?: SSOOptions) => {
|
||||
|
||||
const { extract } = parsedResponse!;
|
||||
|
||||
validateSAMLAlgorithms(parsedResponse, options?.saml?.algorithms);
|
||||
|
||||
validateSAMLTimestamp((extract as any).conditions, {
|
||||
clockSkew: options?.saml?.clockSkew,
|
||||
requireTimestamps: options?.saml?.requireTimestamps,
|
||||
|
||||
205
packages/sso/src/saml/algorithms.test.ts
Normal file
205
packages/sso/src/saml/algorithms.test.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
/* cspell:ignore xenc */
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import * as alg from "./algorithms";
|
||||
|
||||
const encryptedAssertionXml = `
|
||||
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
<saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
|
||||
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
|
||||
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
|
||||
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||
<xenc:EncryptedKey>
|
||||
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
|
||||
</xenc:EncryptedKey>
|
||||
</ds:KeyInfo>
|
||||
</xenc:EncryptedData>
|
||||
</saml:EncryptedAssertion>
|
||||
</samlp:Response>
|
||||
`;
|
||||
|
||||
const deprecatedEncryptionXml = `
|
||||
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
<saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
|
||||
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
|
||||
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
|
||||
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||
<xenc:EncryptedKey>
|
||||
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
|
||||
</xenc:EncryptedKey>
|
||||
</ds:KeyInfo>
|
||||
</xenc:EncryptedData>
|
||||
</saml:EncryptedAssertion>
|
||||
</samlp:Response>
|
||||
`;
|
||||
|
||||
const plainAssertionXml = `
|
||||
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
|
||||
<saml:Subject>test</saml:Subject>
|
||||
</saml:Assertion>
|
||||
</samlp:Response>
|
||||
`;
|
||||
|
||||
describe("validateSAMLAlgorithms", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("signature validation", () => {
|
||||
it("should accept secure signature algorithms", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms({
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
|
||||
samlContent: plainAssertionXml,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("should warn by default for deprecated signature algorithms", () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms({
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA1,
|
||||
samlContent: plainAssertionXml,
|
||||
}),
|
||||
).not.toThrow();
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("SAML Security Warning"),
|
||||
);
|
||||
});
|
||||
|
||||
it("should reject deprecated signature with onDeprecated: reject", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms(
|
||||
{
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA1,
|
||||
samlContent: plainAssertionXml,
|
||||
},
|
||||
{ onDeprecated: "reject" },
|
||||
),
|
||||
).toThrow(/deprecated/i);
|
||||
});
|
||||
|
||||
it("should silently allow deprecated with onDeprecated: allow", () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms(
|
||||
{
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA1,
|
||||
samlContent: plainAssertionXml,
|
||||
},
|
||||
{ onDeprecated: "allow" },
|
||||
),
|
||||
).not.toThrow();
|
||||
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should enforce custom signature allow-list", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms(
|
||||
{
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
|
||||
samlContent: plainAssertionXml,
|
||||
},
|
||||
{ allowedSignatureAlgorithms: [alg.SignatureAlgorithm.RSA_SHA512] },
|
||||
),
|
||||
).toThrow(/not in allow-list/i);
|
||||
});
|
||||
|
||||
it("should pass null/undefined sigAlg without error", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms({
|
||||
sigAlg: null,
|
||||
samlContent: plainAssertionXml,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("should reject unknown signature algorithms", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms({
|
||||
sigAlg: "http://example.com/unknown-algo",
|
||||
samlContent: plainAssertionXml,
|
||||
}),
|
||||
).toThrow(/not recognized/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("encryption validation", () => {
|
||||
it("should accept secure encryption algorithms", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms({
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
|
||||
samlContent: encryptedAssertionXml,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("should warn by default for deprecated encryption algorithms", () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms({
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
|
||||
samlContent: deprecatedEncryptionXml,
|
||||
}),
|
||||
).not.toThrow();
|
||||
|
||||
expect(warnSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should reject deprecated encryption with onDeprecated: reject", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms(
|
||||
{
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
|
||||
samlContent: deprecatedEncryptionXml,
|
||||
},
|
||||
{ onDeprecated: "reject" },
|
||||
),
|
||||
).toThrow(/deprecated/i);
|
||||
});
|
||||
|
||||
it("should skip encryption validation for plain assertions", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms({
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
|
||||
samlContent: plainAssertionXml,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("should handle malformed XML gracefully", () => {
|
||||
expect(() =>
|
||||
alg.validateSAMLAlgorithms({
|
||||
sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
|
||||
samlContent: "not valid xml",
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("algorithm constants", () => {
|
||||
it("should export signature algorithm constants", () => {
|
||||
expect(alg.SignatureAlgorithm.RSA_SHA256).toBe(
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
);
|
||||
expect(alg.SignatureAlgorithm.RSA_SHA1).toBe(
|
||||
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
|
||||
);
|
||||
});
|
||||
|
||||
it("should export encryption algorithm constants", () => {
|
||||
expect(alg.KeyEncryptionAlgorithm.RSA_OAEP).toBe(
|
||||
"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
|
||||
);
|
||||
expect(alg.DataEncryptionAlgorithm.AES_256_GCM).toBe(
|
||||
"http://www.w3.org/2009/xmlenc11#aes256-gcm",
|
||||
);
|
||||
});
|
||||
});
|
||||
259
packages/sso/src/saml/algorithms.ts
Normal file
259
packages/sso/src/saml/algorithms.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import { APIError } from "better-auth/api";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
|
||||
export const SignatureAlgorithm = {
|
||||
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",
|
||||
ECDSA_SHA256: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
|
||||
ECDSA_SHA384: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
|
||||
ECDSA_SHA512: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
|
||||
} as const;
|
||||
|
||||
export const DigestAlgorithm = {
|
||||
SHA1: "http://www.w3.org/2000/09/xmldsig#sha1",
|
||||
SHA256: "http://www.w3.org/2001/04/xmlenc#sha256",
|
||||
SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384",
|
||||
SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
|
||||
} as const;
|
||||
|
||||
export const KeyEncryptionAlgorithm = {
|
||||
RSA_1_5: "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
|
||||
RSA_OAEP: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
|
||||
RSA_OAEP_SHA256: "http://www.w3.org/2009/xmlenc11#rsa-oaep",
|
||||
} as const;
|
||||
|
||||
export const DataEncryptionAlgorithm = {
|
||||
TRIPLEDES_CBC: "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
|
||||
AES_128_CBC: "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
|
||||
AES_192_CBC: "http://www.w3.org/2001/04/xmlenc#aes192-cbc",
|
||||
AES_256_CBC: "http://www.w3.org/2001/04/xmlenc#aes256-cbc",
|
||||
AES_128_GCM: "http://www.w3.org/2009/xmlenc11#aes128-gcm",
|
||||
AES_192_GCM: "http://www.w3.org/2009/xmlenc11#aes192-gcm",
|
||||
AES_256_GCM: "http://www.w3.org/2009/xmlenc11#aes256-gcm",
|
||||
} as const;
|
||||
|
||||
const DEPRECATED_SIGNATURE_ALGORITHMS: readonly string[] = [
|
||||
SignatureAlgorithm.RSA_SHA1,
|
||||
];
|
||||
|
||||
const DEPRECATED_KEY_ENCRYPTION_ALGORITHMS: readonly string[] = [
|
||||
KeyEncryptionAlgorithm.RSA_1_5,
|
||||
];
|
||||
|
||||
const DEPRECATED_DATA_ENCRYPTION_ALGORITHMS: readonly string[] = [
|
||||
DataEncryptionAlgorithm.TRIPLEDES_CBC,
|
||||
];
|
||||
|
||||
const SECURE_SIGNATURE_ALGORITHMS: readonly string[] = [
|
||||
SignatureAlgorithm.RSA_SHA256,
|
||||
SignatureAlgorithm.RSA_SHA384,
|
||||
SignatureAlgorithm.RSA_SHA512,
|
||||
SignatureAlgorithm.ECDSA_SHA256,
|
||||
SignatureAlgorithm.ECDSA_SHA384,
|
||||
SignatureAlgorithm.ECDSA_SHA512,
|
||||
];
|
||||
|
||||
export type DeprecatedAlgorithmBehavior = "reject" | "warn" | "allow";
|
||||
|
||||
export interface AlgorithmValidationOptions {
|
||||
onDeprecated?: DeprecatedAlgorithmBehavior;
|
||||
allowedSignatureAlgorithms?: string[];
|
||||
allowedDigestAlgorithms?: string[];
|
||||
allowedKeyEncryptionAlgorithms?: string[];
|
||||
allowedDataEncryptionAlgorithms?: string[];
|
||||
}
|
||||
|
||||
const xmlParser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
removeNSPrefix: true,
|
||||
});
|
||||
|
||||
function findNode(obj: unknown, nodeName: string): unknown {
|
||||
if (!obj || typeof obj !== "object") return null;
|
||||
|
||||
const record = obj as Record<string, unknown>;
|
||||
|
||||
if (nodeName in record) {
|
||||
return record[nodeName];
|
||||
}
|
||||
|
||||
for (const value of Object.values(record)) {
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
const found = findNode(item, nodeName);
|
||||
if (found) return found;
|
||||
}
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
const found = findNode(value, nodeName);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractEncryptionAlgorithms(xml: string): {
|
||||
keyEncryption: string | null;
|
||||
dataEncryption: string | null;
|
||||
} {
|
||||
try {
|
||||
const parsed = xmlParser.parse(xml);
|
||||
|
||||
const encryptedKey = findNode(parsed, "EncryptedKey") as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
const keyEncMethod = encryptedKey?.EncryptionMethod as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
const keyAlg = keyEncMethod?.["@_Algorithm"] as string | undefined;
|
||||
|
||||
const encryptedData = findNode(parsed, "EncryptedData") as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
const dataEncMethod = encryptedData?.EncryptionMethod as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
const dataAlg = dataEncMethod?.["@_Algorithm"] as string | undefined;
|
||||
|
||||
return {
|
||||
keyEncryption: keyAlg || null,
|
||||
dataEncryption: dataAlg || null,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
keyEncryption: null,
|
||||
dataEncryption: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function hasEncryptedAssertion(xml: string): boolean {
|
||||
try {
|
||||
const parsed = xmlParser.parse(xml);
|
||||
return findNode(parsed, "EncryptedAssertion") !== null;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeprecatedAlgorithm(
|
||||
message: string,
|
||||
behavior: DeprecatedAlgorithmBehavior,
|
||||
errorCode: string,
|
||||
): void {
|
||||
switch (behavior) {
|
||||
case "reject":
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message,
|
||||
code: errorCode,
|
||||
});
|
||||
case "warn":
|
||||
console.warn(`[SAML Security Warning] ${message}`);
|
||||
break;
|
||||
case "allow":
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function validateSignatureAlgorithm(
|
||||
algorithm: string | null | undefined,
|
||||
options: AlgorithmValidationOptions = {},
|
||||
): void {
|
||||
if (!algorithm) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { onDeprecated = "warn", allowedSignatureAlgorithms } = options;
|
||||
|
||||
if (allowedSignatureAlgorithms) {
|
||||
if (!allowedSignatureAlgorithms.includes(algorithm)) {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: `SAML signature algorithm not in allow-list: ${algorithm}`,
|
||||
code: "SAML_ALGORITHM_NOT_ALLOWED",
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEPRECATED_SIGNATURE_ALGORITHMS.includes(algorithm)) {
|
||||
handleDeprecatedAlgorithm(
|
||||
`SAML response uses deprecated signature algorithm: ${algorithm}. Please configure your IdP to use SHA-256 or stronger.`,
|
||||
onDeprecated,
|
||||
"SAML_DEPRECATED_ALGORITHM",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SECURE_SIGNATURE_ALGORITHMS.includes(algorithm)) {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: `SAML signature algorithm not recognized: ${algorithm}`,
|
||||
code: "SAML_UNKNOWN_ALGORITHM",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function validateEncryptionAlgorithms(
|
||||
algorithms: { keyEncryption: string | null; dataEncryption: string | null },
|
||||
options: AlgorithmValidationOptions = {},
|
||||
): void {
|
||||
const {
|
||||
onDeprecated = "warn",
|
||||
allowedKeyEncryptionAlgorithms,
|
||||
allowedDataEncryptionAlgorithms,
|
||||
} = options;
|
||||
|
||||
const { keyEncryption, dataEncryption } = algorithms;
|
||||
|
||||
if (keyEncryption) {
|
||||
if (allowedKeyEncryptionAlgorithms) {
|
||||
if (!allowedKeyEncryptionAlgorithms.includes(keyEncryption)) {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: `SAML key encryption algorithm not in allow-list: ${keyEncryption}`,
|
||||
code: "SAML_ALGORITHM_NOT_ALLOWED",
|
||||
});
|
||||
}
|
||||
} else if (DEPRECATED_KEY_ENCRYPTION_ALGORITHMS.includes(keyEncryption)) {
|
||||
handleDeprecatedAlgorithm(
|
||||
`SAML response uses deprecated key encryption algorithm: ${keyEncryption}. Please configure your IdP to use RSA-OAEP.`,
|
||||
onDeprecated,
|
||||
"SAML_DEPRECATED_ALGORITHM",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataEncryption) {
|
||||
if (allowedDataEncryptionAlgorithms) {
|
||||
if (!allowedDataEncryptionAlgorithms.includes(dataEncryption)) {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: `SAML data encryption algorithm not in allow-list: ${dataEncryption}`,
|
||||
code: "SAML_ALGORITHM_NOT_ALLOWED",
|
||||
});
|
||||
}
|
||||
} else if (DEPRECATED_DATA_ENCRYPTION_ALGORITHMS.includes(dataEncryption)) {
|
||||
handleDeprecatedAlgorithm(
|
||||
`SAML response uses deprecated data encryption algorithm: ${dataEncryption}. Please configure your IdP to use AES-GCM.`,
|
||||
onDeprecated,
|
||||
"SAML_DEPRECATED_ALGORITHM",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validateSAMLAlgorithms(
|
||||
response: { sigAlg?: string | null; samlContent: string },
|
||||
options?: AlgorithmValidationOptions,
|
||||
): void {
|
||||
validateSignatureAlgorithm(response.sigAlg, options);
|
||||
|
||||
if (hasEncryptedAssertion(response.samlContent)) {
|
||||
const encAlgs = extractEncryptionAlgorithms(response.samlContent);
|
||||
validateEncryptionAlgorithms(encAlgs, options);
|
||||
}
|
||||
}
|
||||
9
packages/sso/src/saml/index.ts
Normal file
9
packages/sso/src/saml/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export {
|
||||
type AlgorithmValidationOptions,
|
||||
DataEncryptionAlgorithm,
|
||||
type DeprecatedAlgorithmBehavior,
|
||||
DigestAlgorithm,
|
||||
KeyEncryptionAlgorithm,
|
||||
SignatureAlgorithm,
|
||||
validateSAMLAlgorithms,
|
||||
} from "./algorithms";
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { OAuth2Tokens, User } from "better-auth";
|
||||
import type { AuthnRequestStore } from "./authn-request-store";
|
||||
import type { AlgorithmValidationOptions } from "./saml/algorithms";
|
||||
|
||||
export interface OIDCMapping {
|
||||
id?: string | undefined;
|
||||
@@ -339,5 +340,19 @@ export interface SSOOptions {
|
||||
* @default false
|
||||
*/
|
||||
requireTimestamps?: boolean;
|
||||
/**
|
||||
* Algorithm validation options for SAML responses.
|
||||
*
|
||||
* Controls behavior when deprecated algorithms (SHA-1, RSA1_5, 3DES)
|
||||
* are detected in SAML responses.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* algorithms: {
|
||||
* onDeprecated: "reject" // Reject deprecated algorithms
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
algorithms?: AlgorithmValidationOptions;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user