9

我通过 HTTP 重定向绑定接收 SAML 请求,SAML 请求的内容如下所示

{"SigAlg"=>" http://www.w3.org/2000/09/xmldsig#rsa-sha1 ", "SAMLRequest"=>"lVLLaoQwFP0VyT5jEqPG4AiFoSDMtNApXXQzxDxaQRObRDqfX3XoolAKXd7DPQ/uuXUQ4zDxo3tzc3zSH7MOMWkPe3DpcixzVVVQl4RBqoiCncEYEmkoY7k00hCQvGgfemf3gOwQSNoQZt3aEIWNC4RwCRGGiD6jkmPMs2KHUPYKksPi0lsRN+Z7jFPgafqpvejtbtQpSK7jYAPfsu3B7C13IvSBWzHqwKPk57vTkS+WfPIuOukG0NSbub9R/yaJELRfzUGzrhmtFut15qdeeheciY926K2u05toUz8sIu0huXd+FPFv9RXpFTTbKp/WA4WobQT/jEYrykwhNaQ66yDNMwY7wijEtMCmysqqo6xOb8Ga+ tbjWYe1jtYqfW0uCucoYwWCHS3F0kRGoajWTpAiiJRZJRmu01+Y3+CPt2i+AA=="}

它也有一个签名值

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>

这里有任何帮助。

4

6 回答 6

13

SAML 身份验证消息是具有嵌入式(封装)XMLDSig 签名或压缩编码签名的 XML 文档

封装的 XMLDSign 签名

<samlp:LogoutRequest>
    <...saml message...> 
    <ds:Signature>
         <ds:SignedInfo />
         <ds:SignatureValue /> 
         <ds:KeyInfo /> 
    </ds:Signature> 
</samlp:LogoutRequest>

<ds:SignatureValue>包含签名、<ds:SignedInfo>签名数据和对消息的引用,<ds:KeyInfo>通常包含带有签名者身份的 X509Certificate,或对该证书的引用

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 并且想要验证 SP 的 SAML 请求,您需要:

  • 验证数字签名:使用SP的公钥验证签名与签名消息是否匹配,以确保签名者的身份和消息未被更改

  • 授权请求:验证签名者的身份是否可以执行请求的操作。通常您必须将证书的序列号或主题与预先存在的列表进行匹配,或者验证证书是否由受信任的证书颁发机构颁发

  • 生成 SAML 响应:使用 SAML 数据生成 XML 消息并使用您的私钥对其进行签名以发送到 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);  
于 2017-01-04T17:24:52.933 回答
2

我正在尝试使用上述答案,但没有成功。

然后,阅读文档并花一点时间,我已经成功使用 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);
于 2018-04-01T13:33:59.080 回答
2

SAML 2.0 签名的验证方式因绑定(POST 或重定向)而异。如果使用 POST 绑定,则在 SAML XML 中验证签名。如果使用重定向绑定,则使用签名验证查询字符串。

此 LogoutRequest 使用重定向绑定发送。以下 C# 示例代码是从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.");
}
于 2018-11-22T11:06:51.613 回答
1

对于那些仍然卡住的人,这里是完整的方法

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);
}
于 2019-05-14T09:36:31.207 回答
1

我们可以使用一个登录 saml 库来验证 auth-request 签名。它们为 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

`

于 2019-09-24T07:26:32.967 回答
0

我想在上述答案中补充一点:URL 编码/解码是非规范的,这意味着每种框架/语言实际上可能有不同的处理方式。我被困在验证 HTTP-Redirect 绑定很多天了,结果发现我们使用的 Java Play 1.x 框架以不同于 SAML 框架预期的方式对事物进行解码。

我们解决了这个问题,而是直接从查询字符串中取出查询参数,而不是让 Play 框架为我们解码(只是我们需要重新编码)。因此,如果您的代码与 Alexandre 的代码匹配,但 SAML 框架表示签名无效,请确保您将直接取自 URL GET 参数的字符串输入算法。

于 2018-11-05T17:32:03.090 回答