dummyidp/src/lib/saml.ts
2024-05-13 16:19:21 -07:00

78 lines
4.4 KiB
TypeScript

export interface AssertionData {
idpEntityId: string;
subjectId: string;
sessionId: string;
now: string;
expire: string;
spEntityId: string;
}
export async function encodeAssertion(
key: CryptoKey,
assertionData: AssertionData,
): Promise<string> {
return btoa(await signAssertion(key, assertionData));
}
async function signAssertion(
key: CryptoKey,
assertionData: AssertionData,
): Promise<string> {
const digest = await digestValue(assertionData);
const signature = await signatureValue(key, assertionData);
return signedAssertion(assertionData, digest, signature);
}
function signedAssertion(
assertionData: AssertionData,
digest: string,
signature: string,
): string {
return `<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"><saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>${digest}</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>${signature}</ds:SignatureValue></ds:Signature><saml2:Issuer>${assertionData.idpEntityId}</saml2:Issuer><saml2:Subject><saml2:NameID>${assertionData.subjectId}</saml2:NameID><saml2:SubjectConfirmation><saml2:SubjectConfirmationData InResponseTo="${assertionData.sessionId}"></saml2:SubjectConfirmationData></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="${assertionData.now}" NotOnOrAfter="${assertionData.expire}"><saml2:AudienceRestriction><saml2:Audience>${assertionData.spEntityId}</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions></saml2:Assertion></saml2p:Response>`;
}
async function signatureValue(
key: CryptoKey,
assertionData: AssertionData,
): Promise<string> {
const digest = await digestValue(assertionData);
const enc = new TextEncoder();
console.log("signature part");
console.log(signaturePart(digest));
const signatureData = await crypto.subtle.sign(
"RSASSA-PKCS1-v1_5",
key,
enc.encode(signaturePart(digest)),
);
return arrayBufferToBase64(signatureData);
}
function signaturePart(digest: string): string {
return `<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></ds:SignatureMethod><ds:Reference><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod><ds:DigestValue>${digest}</ds:DigestValue></ds:Reference></ds:SignedInfo>`;
}
async function digestValue(assertionData: AssertionData): Promise<string> {
const enc = new TextEncoder();
console.log("digest part");
console.log(digestPart(assertionData));
const digestData = await crypto.subtle.digest(
"SHA-256",
enc.encode(digestPart(assertionData)),
);
return arrayBufferToBase64(digestData);
}
function digestPart(assertionData: AssertionData): string {
return `<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:Issuer>${assertionData.idpEntityId}</saml2:Issuer><saml2:Subject><saml2:NameID>${assertionData.subjectId}</saml2:NameID><saml2:SubjectConfirmation><saml2:SubjectConfirmationData InResponseTo="${assertionData.sessionId}"></saml2:SubjectConfirmationData></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="${assertionData.now}" NotOnOrAfter="${assertionData.expire}"><saml2:AudienceRestriction><saml2:Audience>${assertionData.spEntityId}</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions></saml2:Assertion>`;
}
function arrayBufferToBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
let binary = "";
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}