SAML Detailed

Что такое saml detailed: определение, основные принципы, примеры и практические советы. Изучайте фундаментальной защите информации с подробными объяснениями для начинающих специалистов.

SAML Detailed - Security Assertion Markup Language

Что такое SAML Detailed?

SAML (Security Assertion Markup Language) — это XML-based стандарт для обмена данными аутентификации и авторизации между сторонами. SAML Detailed включает детальное изучение протокола, его компонентов, профилей и best practices для реализации Single Sign-On (SSO).

Основные принципы

  • Assertions — утверждения
  • Protocols — протоколы
  • Bindings — привязки
  • Profiles — профили
  • Metadata — метаданные

Архитектура SAML Detailed

1. SAML Core

// Система SAML Core
class SAMLCore {
  constructor() {
    this.assertions = new Map();
    this.protocols = new Map();
    this.bindings = new Map();
    this.profiles = new Map();
    this.metadata = new Map();
  }

  // Создание SAML Assertion
  createAssertion(assertionData) {
    const assertion = {
      id: this.generateAssertionId(),
      version: "2.0",
      issueInstant: new Date().toISOString(),
      issuer: assertionData.issuer,
      subject: assertionData.subject,
      conditions: assertionData.conditions,
      statements: assertionData.statements,
      signature: null,
    };

    // Валидация данных assertion
    const validation = this.validateAssertionData(assertion);
    if (!validation.isValid) {
      throw new Error(
        `Assertion validation failed: ${validation.errors.join(", ")}`
      );
    }

    // Генерация подписи
    assertion.signature = this.generateSignature(assertion);

    // Сохранение assertion
    this.assertions.set(assertion.id, assertion);

    return assertion;
  }

  // Валидация данных assertion
  validateAssertionData(assertion) {
    const errors = [];

    if (!assertion.issuer || assertion.issuer.trim() === ") {
      errors.push("Issuer is required");
    }

    if (!assertion.subject || !assertion.subject.nameId) {
      errors.push("Subject with NameID is required");
    }

    if (!assertion.conditions) {
      errors.push("Conditions are required");
    }

    if (!assertion.statements || assertion.statements.length === 0) {
      errors.push("At least one statement is required");
    }

    return {
      isValid: errors.length === 0,
      errors: errors,
    };
  }

  // Создание Subject
  createSubject(subjectData) {
    const subject = {
      nameId: {
        format:
          subjectData.nameIdFormat ||
          "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
        value: subjectData.nameIdValue,
      },
      subjectConfirmations: [],
    };

    // Добавление SubjectConfirmation
    if (subjectData.subjectConfirmation) {
      subject.subjectConfirmations.push({
        method: "urn:oasis:names:tc:SAML:2.0:cm:bearer",
        subjectConfirmationData: {
          notBefore: subjectData.notBefore,
          notOnOrAfter: subjectData.notOnOrAfter,
          recipient: subjectData.recipient,
          inResponseTo: subjectData.inResponseTo,
        },
      });
    }

    return subject;
  }

  // Создание Conditions
  createConditions(conditionsData) {
    const conditions = {
      notBefore: conditionsData.notBefore,
      notOnOrAfter: conditionsData.notOnOrAfter,
      audienceRestrictions: [],
      oneTimeUse: conditionsData.oneTimeUse || false,
      proxyRestrictions: null,
    };

    // Добавление AudienceRestriction
    if (conditionsData.audiences && conditionsData.audiences.length > 0) {
      conditions.audienceRestrictions.push({
        audiences: conditionsData.audiences,
      });
    }

    // Добавление ProxyRestriction
    if (conditionsData.proxyRestrictions) {
      conditions.proxyRestrictions = {
        count: conditionsData.proxyRestrictions.count,
        audience: conditionsData.proxyRestrictions.audience,
      };
    }

    return conditions;
  }

  // Создание AttributeStatement
  createAttributeStatement(attributes) {
    const attributeStatement = {
      type: "AttributeStatement",
      attributes: [],
    };

    for (const attr of attributes) {
      attributeStatement.attributes.push({
        name: attr.name,
        nameFormat:
          attr.nameFormat ||
          "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
        values: attr.values || [],
      });
    }

    return attributeStatement;
  }

  // Создание AuthnStatement
  createAuthnStatement(authnData) {
    const authnStatement = {
      type: "AuthnStatement",
      authnInstant: authnData.authnInstant,
      sessionIndex: authnData.sessionIndex,
      subjectLocality: authnData.subjectLocality,
      authnContext: {
        authnContextClassRef: authnData.authnContextClassRef,
        authnContextDecl: authnData.authnContextDecl,
        authnContextDeclRef: authnData.authnContextDeclRef,
        authenticatingAuthorities: authnData.authenticatingAuthorities || [],
      },
    };

    return authnStatement;
  }

  // Создание AuthorizationDecisionStatement
  createAuthorizationDecisionStatement(authzData) {
    const authzStatement = {
      type: "AuthorizationDecisionStatement",
      resource: authzData.resource,
      decision: authzData.decision, // Permit, Deny, Indeterminate
      actions: authzData.actions || [],
      evidence: authzData.evidence || null,
    };

    return authzStatement;
  }

  // Генерация подписи
  generateSignature(assertion) {
    const signature = {
      id: this.generateSignatureId(),
      signedInfo: {
        canonicalizationMethod: {
          algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
        },
        signatureMethod: {
          algorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
        },
        reference: {
          uri: `#${assertion.id}`,
          transforms: [
            {
              algorithm:
                "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
            },
          ],
          digestMethod: {
            algorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
          },
          digestValue: this.calculateDigest(assertion),
        },
      },
      signatureValue: this.calculateSignature(assertion),
      keyInfo: {
        x509Data: {
          x509Certificate: this.getX509Certificate(),
        },
      },
    };

    return signature;
  }

  // Генерация ID assertion
  generateAssertionId() {
    return "_" + Date.now() + "_" + Math.random().toString(36).substr(2, 8);
  }

  // Генерация ID подписи
  generateSignatureId() {
    return "_" + Date.now() + "_" + Math.random().toString(36).substr(2, 8);
  }
}

2. SAML Protocols

// Система SAML Protocols
class SAMLProtocols {
  constructor() {
    this.authnRequests = new Map();
    this.responses = new Map();
    self.logoutRequests = new Map();
    this.logoutResponses = new Map();
  }

  // Создание AuthnRequest
  createAuthnRequest(requestData) {
    const authnRequest = {
      id: this.generateRequestId(),
      version: "2.0",
      issueInstant: new Date().toISOString(),
      destination: requestData.destination,
      consent:
        requestData.consent ||
        "urn:oasis:names:tc:SAML:2.0:consent:unspecified",
      issuer: requestData.issuer,
      subject: requestData.subject,
      nameIdPolicy: requestData.nameIdPolicy,
      requestedAuthnContext: requestData.requestedAuthnContext,
      forceAuthn: requestData.forceAuthn || false,
      isPassive: requestData.isPassive || false,
      protocolBinding: requestData.protocolBinding,
      assertionConsumerServiceURL: requestData.assertionConsumerServiceURL,
      assertionConsumerServiceIndex: requestData.assertionConsumerServiceIndex,
      attributeConsumingServiceIndex:
        requestData.attributeConsumingServiceIndex,
      providerName: requestData.providerName,
      signature: null,
    };

    // Валидация данных запроса
    const validation = this.validateAuthnRequest(authnRequest);
    if (!validation.isValid) {
      throw new Error(
        `AuthnRequest validation failed: ${validation.errors.join(", ")}`
      );
    }

    // Генерация подписи
    authnRequest.signature = this.generateSignature(authnRequest);

    // Сохранение запроса
    this.authnRequests.set(authnRequest.id, authnRequest);

    return authnRequest;
  }

  // Валидация AuthnRequest
  validateAuthnRequest(authnRequest) {
    const errors = [];

    if (!authnRequest.destination || authnRequest.destination.trim() === ") {
      errors.push("Destination is required");
    }

    if (!authnRequest.issuer || authnRequest.issuer.trim() === ") {
      errors.push("Issuer is required");
    }

    if (
      !authnRequest.assertionConsumerServiceURL &&
      !authnRequest.assertionConsumerServiceIndex
    ) {
      errors.push(
        "Either AssertionConsumerServiceURL or AssertionConsumerServiceIndex is required"
      );
    }

    return {
      isValid: errors.length === 0,
      errors: errors,
    };
  }

  // Создание Response
  createResponse(responseData) {
    const response = {
      id: this.generateResponseId(),
      version: "2.0",
      issueInstant: new Date().toISOString(),
      destination: responseData.destination,
      inResponseTo: responseData.inResponseTo,
      issuer: responseData.issuer,
      status: responseData.status,
      assertions: responseData.assertions || [],
      signature: null,
    };

    // Валидация данных ответа
    const validation = this.validateResponse(response);
    if (!validation.isValid) {
      throw new Error(
        `Response validation failed: ${validation.errors.join(", ")}`
      );
    }

    // Генерация подписи
    response.signature = this.generateSignature(response);

    // Сохранение ответа
    this.responses.set(response.id, response);

    return response;
  }

  // Валидация Response
  validateResponse(response) {
    const errors = [];

    if (!response.destination || response.destination.trim() === ") {
      errors.push("Destination is required");
    }

    if (!response.issuer || response.issuer.trim() === ") {
      errors.push("Issuer is required");
    }

    if (!response.status) {
      errors.push("Status is required");
    }

    return {
      isValid: errors.length === 0,
      errors: errors,
    };
  }

  // Создание Status
  createStatus(statusData) {
    const status = {
      statusCode: {
        value: statusData.statusCode,
        subStatusCode: statusData.subStatusCode,
      },
      statusMessage: statusData.statusMessage,
      statusDetail: statusData.statusDetail,
    };

    return status;
  }

  // Создание LogoutRequest
  createLogoutRequest(requestData) {
    const logoutRequest = {
      id: this.generateRequestId(),
      version: "2.0",
      issueInstant: new Date().toISOString(),
      destination: requestData.destination,
      issuer: requestData.issuer,
      nameId: requestData.nameId,
      sessionIndex: requestData.sessionIndex,
      reason: requestData.reason,
      notOnOrAfter: requestData.notOnOrAfter,
      signature: null,
    };

    // Валидация данных запроса
    const validation = this.validateLogoutRequest(logoutRequest);
    if (!validation.isValid) {
      throw new Error(
        `LogoutRequest validation failed: ${validation.errors.join(", ")}`
      );
    }

    // Генерация подписи
    logoutRequest.signature = this.generateSignature(logoutRequest);

    // Сохранение запроса
    this.logoutRequests.set(logoutRequest.id, logoutRequest);

    return logoutRequest;
  }

  // Валидация LogoutRequest
  validateLogoutRequest(logoutRequest) {
    const errors = [];

    if (!logoutRequest.destination || logoutRequest.destination.trim() === ") {
      errors.push("Destination is required");
    }

    if (!logoutRequest.issuer || logoutRequest.issuer.trim() === ") {
      errors.push("Issuer is required");
    }

    if (!logoutRequest.nameId) {
      errors.push("NameID is required");
    }

    return {
      isValid: errors.length === 0,
      errors: errors,
    };
  }

  // Создание LogoutResponse
  createLogoutResponse(responseData) {
    const logoutResponse = {
      id: this.generateResponseId(),
      version: "2.0",
      issueInstant: new Date().toISOString(),
      destination: responseData.destination,
      inResponseTo: responseData.inResponseTo,
      issuer: responseData.issuer,
      status: responseData.status,
      signature: null,
    };

    // Валидация данных ответа
    const validation = this.validateLogoutResponse(logoutResponse);
    if (!validation.isValid) {
      throw new Error(
        `LogoutResponse validation failed: ${validation.errors.join(", ")}`
      );
    }

    // Генерация подписи
    logoutResponse.signature = this.generateSignature(logoutResponse);

    // Сохранение ответа
    this.logoutResponses.set(logoutResponse.id, logoutResponse);

    return logoutResponse;
  }

  // Валидация LogoutResponse
  validateLogoutResponse(logoutResponse) {
    const errors = [];

    if (
      !logoutResponse.destination ||
      logoutResponse.destination.trim() === "
    ) {
      errors.push("Destination is required");
    }

    if (!logoutResponse.issuer || logoutResponse.issuer.trim() === ") {
      errors.push("Issuer is required");
    }

    if (!logoutResponse.status) {
      errors.push("Status is required");
    }

    return {
      isValid: errors.length === 0,
      errors: errors,
    };
  }

  // Генерация ID запроса
  generateRequestId() {
    return "_" + Date.now() + "_" + Math.random().toString(36).substr(2, 8);
  }

  // Генерация ID ответа
  generateResponseId() {
    return "_" + Date.now() + "_" + Math.random().toString(36).substr(2, 8);
  }
}

3. SAML Bindings

// Система SAML Bindings
class SAMLBindings {
  constructor() {
    this.bindings = new Map();
    this.transports = new Map();
  }

  // HTTP Redirect Binding
  createHTTPRedirectBinding(message, destination) {
    const binding = {
      type: "HTTP-Redirect",
      destination: destination,
      message: message,
      relayState: message.relayState,
      signature: message.signature,
    };

    // Создание URL для редиректа
    const url = this.createRedirectURL(binding);

    return {
      binding: binding,
      url: url,
    };
  }

  // Создание URL для редиректа
  createRedirectURL(binding) {
    const params = new URLSearchParams();

    if (binding.message.type === "AuthnRequest") {
      params.set("SAMLRequest", this.encodeMessage(binding.message));
    } else if (binding.message.type === "Response") {
      params.set("SAMLResponse", this.encodeMessage(binding.message));
    }

    if (binding.relayState) {
      params.set("RelayState", binding.relayState);
    }

    if (binding.signature) {
      params.set("Signature", binding.signature);
    }

    return `${binding.destination}?${params.toString()}`;
  }

  // HTTP POST Binding
  createHTTPPostBinding(message, destination) {
    const binding = {
      type: "HTTP-POST",
      destination: destination,
      message: message,
      relayState: message.relayState,
      signature: message.signature,
    };

    // Создание HTML формы
    const form = this.createPostForm(binding);

    return {
      binding: binding,
      form: form,
    };
  }

  // Создание HTML формы для POST
  createPostForm(binding) {
    const form = document.createElement("form");
    form.method = "POST";
    form.action = binding.destination;

    // Добавление скрытых полей
    const samlField = document.createElement("input");
    samlField.type = "hidden";
    samlField.name =
      binding.message.type === "AuthnRequest" ? "SAMLRequest" : "SAMLResponse";
    samlField.value = this.encodeMessage(binding.message);
    form.appendChild(samlField);

    if (binding.relayState) {
      const relayStateField = document.createElement("input");
      relayStateField.type = "hidden";
      relayStateField.name = "RelayState";
      relayStateField.value = binding.relayState;
      form.appendChild(relayStateField);
    }

    if (binding.signature) {
      const signatureField = document.createElement("input");
      signatureField.type = "hidden";
      signatureField.name = "Signature";
      signatureField.value = binding.signature;
      form.appendChild(signatureField);
    }

    return form;
  }

  // HTTP Artifact Binding
  createHTTPArtifactBinding(message, destination) {
    const binding = {
      type: "HTTP-Artifact",
      destination: destination,
      message: message,
      artifact: this.generateArtifact(),
      relayState: message.relayState,
    };

    // Сохранение сообщения по артефакту
    this.artifacts.set(binding.artifact, message);

    return binding;
  }

  // Генерация артефакта
  generateArtifact() {
    const sourceId = this.generateSourceId();
    const messageHandle = this.generateMessageHandle();
    const typeCode = "0004"; // SAML 2.0

    return `${typeCode}${sourceId}${messageHandle}`;
  }

  // Генерация Source ID
  generateSourceId() {
    return this.sha1(this.issuer).substring(0, 20);
  }

  // Генерация Message Handle
  generateMessageHandle() {
    return this.generateRandomString(20);
  }

  // SOAP Binding
  createSOAPBinding(message, destination) {
    const binding = {
      type: "SOAP",
      destination: destination,
      message: message,
      soapEnvelope: this.createSOAPEnvelope(message),
    };

    return binding;
  }

  // Создание SOAP конверта
  createSOAPEnvelope(message) {
    const envelope = {
      "soap:Envelope": {
        "@xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/",
        "soap:Header": {},
        "soap:Body": {
          [message.type]: message,
        },
      },
    };

    return envelope;
  }

  // Кодирование сообщения
  encodeMessage(message) {
    const xml = this.serializeToXML(message);
    const compressed = this.compress(xml);
    const encoded = this.base64Encode(compressed);

    return encoded;
  }

  // Декодирование сообщения
  decodeMessage(encodedMessage) {
    const compressed = this.base64Decode(encodedMessage);
    const xml = this.decompress(compressed);
    const message = this.parseFromXML(xml);

    return message;
  }

  // Генерация случайной строки
  generateRandomString(length) {
    const chars =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    let result = ";

    for (let i = 0; i < length; i++) {
      result += chars.charAt(Math.floor(Math.random() * chars.length));
    }

    return result;
  }
}

Основные компоненты SAML Detailed

1. SAML Core

  • Assertions — утверждения
  • Subjects — субъекты
  • Conditions — условия
  • Statements — утверждения

2. SAML Protocols

  • AuthnRequest — запрос аутентификации
  • Response — ответ
  • LogoutRequest — запрос выхода
  • LogoutResponse — ответ выхода

3. SAML Bindings

  • HTTP Redirect — HTTP редирект
  • HTTP POST — HTTP POST
  • HTTP Artifact — HTTP артефакт
  • SOAP — SOAP

Best Practices

1. Security

  • Use HTTPS — используйте HTTPS
  • Validate Signatures — валидируйте подписи
  • Check Timestamps — проверяйте временные метки
  • Implement Replay Protection — реализуйте защиту от повторного использования

2. Implementation

  • Follow Standards — следуйте стандартам
  • Handle Errors — обрабатывайте ошибки
  • Implement Logging — реализуйте логирование
  • Regular Updates — регулярные обновления

3. Monitoring

  • Message Flow — поток сообщений
  • Error Rates — частота ошибок
  • Performance — производительность
  • Security Events — события безопасности

Заключение

SAML Detailed — это критически важный стандарт для корпоративной аутентификации, который требует:

  • Глубокого понимания — XML и протоколов SAML
  • Специализированных навыков — в области реализации
  • Правильных инструментов — для разработки и тестирования
  • Системного подхода — к обеспечению безопасности

Помните: SAML Detailed — это не разовое мероприятие, а постоянный процесс. Регулярно обновляйте реализацию, следите за новыми угрозами и адаптируйте методы защиты.


Совет: Начните с базовых компонентов SAML, затем переходите к более сложным профилям. Не забывайте о валидации подписей и правильной обработке ошибок!