如何验证 HTTP 重定向绑定的 SAML 签名

Posted

技术标签:

【中文标题】如何验证 HTTP 重定向绑定的 SAML 签名【英文标题】:How to verify a SAML signature for HTTP-redirect binding 【发布时间】:2017-05-18 21:51:17 【问题描述】:

我通过 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 ==” P>

它也有一个签名值

WkDaGzC6vPTlzh + EnFA5 / 8IMmV7LviyRh2DA5EHF0K0nl + xzBlKfNCYRnunpwoEvGhereGdI5xBpv + mc9IguiCaLZSZjDh6lIDdpvctCnmSNzORqzWQwQGeZ9vjgtCLjUn35VZLNs3WgEqbi2cL + ObrUDS2gV1XvBA3Q3RRhoDmi + XE89Ztnd1cNpR3XdA + EL2ENbMI2XAD9qSgMufUJY / 3GBBpT7Vg1ODtPxBudq + sXrgPh / + + WtUUitLkkfC8tdRTCS1EZPv h27I5g / VNza23Xl8w2HdAuYP0F2FjREo8VV2aUtaOUd / jAF9 + bfkGV93y1PzFttLxdBbFoxp6qBg == P>

但我不明白如何验证此签名是否正确。

第 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>

这里有任何帮助。

【问题讨论】:

【参考方案1】:

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

封装的 XMLDSign 签名

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

&lt;ds:SignatureValue&gt; 包含签名,&lt;ds:SignedInfo&gt; 包含签名数据和对消息的引用,&lt;ds:KeyInfo&gt; 通常包含带有签名者身份的 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的公钥验证签名与签名消息是否匹配,确保签名者身份且消息未被篡改 p>

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

生成 SAML 响应:使用 SAML 数据生成 XML 消息,并使用您的私钥对其进行签名以发送到 SP

大多数编程语言都支持 XMLDsig 签名,但在您的情况下使用的是 deflated encoding,这是 SAML 绑定的特定特征,因此如果您的 SAML 库不支持它,您有手动验证签名。根据specification,这些或多或少是要遵循的步骤

 //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);  

【讨论】:

几件事。首先它是LogoutRequest 而不是 SAML AuthRequest(我想我忘了提)。其次,绑定是 HTTP 重定向,而不是 HTTP-POST,请参阅 SAML 绑定的第 3.4.4.1 节。 如果我错了,也请纠正我,但这就是 SAML 签名和验证的正确工作方式。 IDP(拥有自己的公共/私人对)SP(拥有自己的公共/私人对)。如果 IDP 必须对数据进行签名,它会使用自己的私钥对其进行签名。然后,SP 可以使用 IDP 的 Public/cert(通过 IDP 的元数据共享)解密签名数据。同样,如果 SP 必须使用自己的私钥签署数据,则 IDP 使用 SP 的公共/证书对其进行解密(可通过 SP 元数据访问) AuthRequest 只是一个例子。 IdP&SP 的签名模型是正确的。只有术语decrypt 不合适。消息已签名,未加密(内容未隐藏),使用公钥检查的操作为verification 如果我是正确的,您说的是嵌入签名,我从 SP 收到的请求是通过 HTTP 重定向绑定与没有嵌入签名的 SAML 请求的请求。添加了我的 SAML 注销请求的片段。 您的 SAML 注销包括 &lt;samlp:SessionIndex&gt; 以识别要注销的会话,因此在这种情况下,我猜不需要数字签名,因为之前已识别客户端并且拥有 sessionIndex 足以验证请求【参考方案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);

【讨论】:

什么教程?你能编辑你的答案并给出更好的解释吗? @MehdiBounya 正确的术语是“回答”没有教程。【参考方案3】:

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

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

【讨论】:

【参考方案4】:

根据绑定(POST 或重定向)不同,验证 SAML 2.0 签名的方式也不同。如果使用 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.");

从Saml2RedirectBinding复制的代码 RawSaml2QueryString Saml2SignedText

【讨论】:

【参考方案5】:

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

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);

【讨论】:

【参考方案6】:

我们可以使用one login saml library 来验证 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

`

【讨论】:

以上是关于如何验证 HTTP 重定向绑定的 SAML 签名的主要内容,如果未能解决你的问题,请参考以下文章

使用 Java 创建“HTTP 重定向绑定”SAML 请求

如何正确解码 Java 中的 SAML 请求(HTTP 重定向)?

OneLogin 签名的 Authnrequest HTTP 重定向方法

SAML HTTP 重定向绑定中未保留查询字符串

AADSTS750054:SAMLRequest 或 SAMLResponse 必须作为查询字符串参数出现在 SAML 重定向绑定的 HTTP 请求中

在 ELB 后面使用带有 Spring Boot 的 SAML 重定向到 http 而不是 https