Как проверить подпись SAML для привязки HTTP-перенаправления
Я получаю запрос SAML через HTTP-перенаправление, связывающее содержимое запроса SAML выглядит следующим образом
{ "SigAlg" => " http://www.w3.org/2000/09/xmldsig ", "SAMLRequest"=>"lVLLaoQwFP0VyT5jEqPG4AiFoSDMtNApXXQzxDxaQRObRDqfX3XoolAKXd7DPQ/uuXUQ4zDxo3tzc3zSH7MOMWkPe3DpcixzVVVQl4RBqoiCncEYEmkoY7k00hCQvGgfemf3gOwQSNoQZt3aEIWNC4RwCRGGiD6jkmPMs2KHUPYKksPi0lsRN+Z7jFPgafqpvejtbtQpSK7jYAPfsu3B7C13IvSBWzHqwKPk57vTkS+WfPIuOukG0NSbub9R/yaJELRfzUGzrhmtFut15qdeeheciY926K2u05toUz8sIu0huXd+FPFv9RXpFTTbKp/WA4WobQT/jEYrykwhNaQ66yDNMwY7wijEtMCmysqqo6xOb8Ga+tbjWYe1jtYqfW0uCucoYwWCHS3F0kRGoajWTpAiiJRZJRmu01+Y3+CPt2i+ АА =="}
Он также имеет значение подписи
WkDaGzC6vPTlzh+EnFA5/8IMmV7LviyRh2DA5EHF0K0nl+xzBlKfNCYRnunpwoEvGhereGdI5xBpv+mc9IguiCaLZSZjDh6lIDdpvctCnmSNzORqzWQwQGeZ9vjgtCLjUn35VZLNs3WgEqbi2cL+ObrUDS2gV1XvBA3Q3RRhoDmi+XE89Ztnd1cNpR3XdA+EL2ENbMI2XAD9qSgMufUJY/3GBBpT7Vg1ODtPxBudq+sXrgPh/+WtUUitLkkfC8tdRTCS1EZPv+h27I5g/VNza23Xl8w2HdAuYP0F2FjREo8VV2aUtaOUd/jAF9+bfkGV93y1PzFttLxdBbFoxp6qBg==
Но я не понимаю, как проверить правильность этой подписи.
Раздел 3.4.4.1 о привязке SAML https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
To construct the signature, a string consisting of the concatenation of the RelayState (if present),
SigAlg, and SAMLRequest (or SAMLResponse) query string parameters (each one URLencoded)
is constructed in one of the following ways (ordered as below):
SAMLRequest=value&RelayState=value&SigAlg=value
SAMLResponse=value&RelayState=value&SigAlg=value
Я попробовал подход, но
Сигнатура, сгенерированная мной с использованием закрытого ключа, не соответствует той, которую я получил от своего SP. (размещено выше)
Кроме того, я не могу расшифровать подписанное сообщение с помощью закрытого ключа (я предполагаю, что подпись была создана с использованием общедоступного, с которым я ее объединил).
<samlp:LogoutRequest ID="_36167d94-d868-4c04-aee3-8bbd4ed91317" Version="2.0" IssueInstant="2017-01-05T16:21:55.704Z" Destination="https://werain.me/" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">urn:federation:MicrosoftOnline</Issuer><NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">4948f6ce-4e3b-4538-b284-1461f9379b48</NameID><samlp:SessionIndex>_eafbb730-b590-0134-a918-00d202739c81</samlp:SessionIndex></samlp:LogoutRequest>
Любая помощь здесь.
7 ответов
Сообщение аутентификации SAML - это документ XML со встроенной (конвертированной) подписью XMLDSig или дефлированной подписью кодировки.
Конвертированная подпись XMLDSign
<samlp:LogoutRequest>
<...saml message...>
<ds:Signature>
<ds:SignedInfo />
<ds:SignatureValue />
<ds:KeyInfo />
</ds:Signature>
</samlp:LogoutRequest>
<ds:SignatureValue>
содержит подпись, <ds:SignedInfo>
подписанные данные и ссылка на сообщение и <ds:KeyInfo>
обычно содержит сертификат X509 с указанием лица, подписавшего документ, или ссылку на этот сертификат
Дефлированная кодировка в URL
SAMLRequest=value&RelayState=value&SigAlg=value&Signature=value
Где каждое значение закодировано в URL
SAMLRequest=urlencode(base64(<samlp:LogoutRequest> <...saml message...> </samlp:LogoutRequest>))
И подпись делается на конкатенации алгоритма строки запроса с использованием алгоритма SigAlg
Signature = urlencode( base64 ( SigAlg ("SAMLRequest=value&RelayState=value&SigAlg=value")))
Цифровая подпись сообщений SAML
Сообщение SAML имеет цифровую подпись (без шифрования) с закрытым ключом эмитента (SP) и может быть проверено с помощью открытого ключа SP. Ответ SAML должен быть подписан закрытым ключом провайдера идентификации (IdP), и SP может проверить сообщение с открытым ключом IdP.
Если вы действуете как IdP и хотите проверить запрос SAML от SP, вам необходимо:
Проверьте цифровую подпись: с помощью открытого ключа SP убедитесь, что подпись совпадает с подписанным сообщением, чтобы удостовериться, что личность подписавшего и сообщение не были изменены
Авторизуйте запрос: убедитесь, что личность подписавшего может выполнить запрошенную операцию. Обычно необходимо сопоставить серийный номер или тему сертификата с существующим списком или проверить, что сертификат был выдан доверенным центром сертификации.
Сгенерируйте ответ SAML: сгенерируйте XML-сообщение с данными SAML и подпишите его своим закрытым ключом для отправки в SP
Большинство языков программирования поддерживают подписи XMLDsig, но в вашем случае используется дефлированная кодировка, которая является специфической характеристикой привязки SAML, поэтому, если ваша библиотека SAML не поддерживает ее, вам необходимо проверить подпись вручную. Это более или менее шаги, которые необходимо выполнить в соответствии со спецификацией
//get params from query string
String samlrequest = getQueryParam("SAMLRequest");
String relaystate = getQueryParam("RelayState");
String sigalg = getQueryParam("SigAlg");
String signature = getQueryParam("Signature");
//The signature
byte signature[] = URLDecoder.decode(Base64.getDecoder().decode(signature ), "UTF-8");
//The signed data. build the following string checking if RelayState is null
//SAMLRequest=samlrequest&RelayState=relaystate&SigAlg=sigalg
byte signedData[] = concat(samlrequest,relaystate,sigalg);
//The signature algorithm could be "SHA1WithRSA" or "SHA1withDSA" depending on sigalg is http://www.w3.org/2000/09/xmldsig#rsa-sha1 or http://www.w3.org/2000/09/xmldsig#dsa-sha1
String signatureAlgorithm = extractSignatureAlgorithm(sigalg);
//get the public key of the SP. It must be registered before this process
PublicKey publicKey = ...
//Verify the signature
Signature sig = Signature.getInstance(signatureAlgorithm);
sig.initVerify(publicKey);
sig.update(signedData);
boolean verifies = sig.verify(signature);
Мы можем использовать библиотеку saml для одного входа в систему для проверки подписи запроса авторизации. Они предоставляют множество методов оболочки для SAML. Это его реализация на Ruby.`
def verify_signature(params)
saml_request = URI.decode(params[:SAMLRequest])
relay_state_string = URI.decode(params[:RelayState])
signature = URI.decode(params[:Signature])
sign_alg = URI.decode(params[:SigAlg])
query_params,sig_params={},{}
query_params[:type] = "SAMLRequest"
query_params[:data] = saml_request
query_params[:relay_state] = relay_state_string
query_params[:sig_alg] = sign_alg
query = OneLogin::RubySaml::Utils.build_query(query_params)
sig_params[:cert] = getPublicKeyFromCertificate
sig_params[:sig_alg] = sign_alg
sig_params[:signature] = signature
sig_params[:query_string] = query
OneLogin::RubySaml::Utils.verify_signature(sig_params)
end
`
Подпись SAML 2.0 проверяется по-разному в зависимости от привязки (POST или Redirect). Если используется привязка POST, подпись проверяется в XML SAML. Если используется привязка Redirect, строка запроса проверяется с помощью подписи.
Этот LogoutRequest отправляется с привязкой перенаправления. Следующий пример кода C# скопирован из компонента https://github.com/ITfoxtec/ITfoxtec.Identity.Saml2 и показывает, как проверить подпись.
var queryString = request.QueryString;
var signatureValue = Convert.FromBase64String(request.Query["Signature"]);
var messageName = "SAMLRequest";
var signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
var signatureValidationCertificate = new X509Certificate2("path-to-service-provider-x509-certificate");
var saml2Sign = new Saml2SignedText(signatureValidationCertificate, signatureAlgorithm);
if (saml2Sign.CheckSignature(Encoding.UTF8.GetBytes(new RawSaml2QueryString(queryString, messageName).SignedQueryString), signatureValue))
{
// Signature is valid.
}
else
{
throw new InvalidSignatureException("Signature is invalid.");
}
- Код скопирован из Saml2RedirectBinding
- RawSaml2QueryString
- Saml2SignedText
Я пытаюсь использовать приведенный выше ответ, но безуспешно.
Затем, прочитайте документацию и немного времени, у меня есть успех, чтобы проверить подпись с Java, и быстрый ответ:
final String samlRequest = request.getParameter("SAMLRequest");
final String relayState = request.getParameter("RelayState");
final String sigAlg = request.getParameter("SigAlg");
final String signature = request.getParameter("Signature");
FileInputStream fis = new FileInputStream(new File("path-to-service-provider-x509-certificate"));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(fis);
// ps: java.net.URLEncoder;
String query = "SAMLRequest=" + URLEncoder.encode(samlRequest, "UTF-8");
query += "&RelayState=" +URLEncoder.encode(relayState, "UTF-8");
query += "&SigAlg=" + URLEncoder.encode(sigAlg, "UTF-8");
// ps: org.opensaml.xml.util.Base64
byte[] signatureBytes = Base64.decode(signature);
org.apache.xml.security.Init.init();
Signature sig = Signature.getInstance("SHA1withRSA"); // or other alg (i, e: SHA256WithRSA or others)
sig.initVerify(cert.getPublicKey());
sig.update(query.getBytes());
Boolean valid = sig.verify(signatureBytes);
Еще один момент, который я хотел бы добавить к приведенным выше ответам: кодирование / декодирование URL-адресов не является каноническим, что означает, что на самом деле каждый фреймворк / язык может по-разному это делать. Я застрял на проверке привязки HTTP-Redirect в течение многих дней, оказалось, что платформа Java Play 1.x, которую мы используем для URL, декодирует вещи не так, как ожидает среда SAML.
Мы решили эту проблему, вместо этого взяв параметры запроса непосредственно из строки запроса, вместо того, чтобы позволить инфраструктуре Play декодировать ее для нас (только для нас, чтобы нам нужно было перекодировать ее обратно). Поэтому, если ваш код соответствует Александру, но среда SAML говорит, что подпись недействительна, убедитесь, что вы вводите в алгоритм строки, которые напрямую взяты из параметров URL GET.
Для тех, кто все еще застрял, вот полный метод
public static void verifySignature(boolean isResponse, String samlQueryString, String relayStateString, String sigAlgString, String signature, X509Certificate cert) throws Exception {
String type = isResponse ? "SAMLResponse" : "SAMLRequest";
String query = type + "=" + URLEncoder.encode(samlQueryString, "UTF-8");
query += relayStateString == null ? "" : "&RelayState=" + URLEncoder.encode(relayStateString, "UTF-8");
query += "&SigAlg=" + URLEncoder.encode(sigAlgString, "UTF-8");
String javaSigAlgName = null;
if(sigAlgString.equals("http://www.w3.org/2000/09/xmldsig#rsa-sha1")) {
javaSigAlgName = "SHA1withRSA";
} else if(sigAlgString.equals("http://www.w3.org/2000/09/xmldsig#rsa-sha256")) {
javaSigAlgName = "SHA256withRSA";
} else {
throw new Exception("signature: " + sigAlgString + " not supported by SP/IDP");
}
byte[] signatureBytes = Base64.getDecoder().decode(signature);
Signature sig = Signature.getInstance(javaSigAlgName);
sig.initVerify(cert.getPublicKey());
sig.update(query.getBytes());
Boolean valid = sig.verify(signatureBytes);
System.out.println("is valid: " + valid);
}
Мы потратили часы, пытаясь разобраться в этом, и наконец наткнулись на эту тему. Просто хотел прикрепить следующее изображение, которое я создал, чтобы наглядно показать, как создать подписанный запрос http-перенаправления SAML AuthNRequest, чтобы внести свой вклад в эту очень ценную тему.