C# Asp.Net核心SAML响应签名验证
我正在开发一个需要使用第三方idP(SP启动)实现SAML SSO的web应用程序。我已经到了接收来自idP的相同响应的地步,如下所示:C# Asp.Net核心SAML响应签名验证,c#,asp.net-core,saml-2.0,xml-signature,C#,Asp.net Core,Saml 2.0,Xml Signature,我正在开发一个需要使用第三方idP(SP启动)实现SAML SSO的web应用程序。我已经到了接收来自idP的相同响应的地步,如下所示: <?xml version="1.0" encoding="UTF-8"?> <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" InResponseTo="63622fa6-9a00-4d3
<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" InResponseTo="63622fa6-9a00-4d39-9c92-791c3a1efc3f" IssueInstant="2017-12-04T13:47:30Z" ID="mjmobamignjdlgkpmkiijfbknamlbkadhkjcamhp" Version="2.0">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="gkifgihgclegelojncjfgegcddfncgdaefcjgbod" IssueInstant="2017-12-04T13:47:30Z" Version="2.0">
<saml:Issuer>https://idp.com</saml:Issuer>
<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/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#gkifgihgclegelojncjfgegcddfncgdaefcjgbod">
<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/2000/09/xmldsig#sha1"/>
<ds:DigestValue>nyU3iydIomlY9+D+YO7E6zNyq1A=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>1AVSFcmgaPMFZvPHYyZDz1oFWzgiMCHI6yMfe6yCSK1pw6bkbZd/yZys8DuySi3Q75bnu3FmbrJQ
L9eEfoXK7kJEut79f9xrBwScNYQ21AZdYh5Rdzm7jRsbugYuQpfUUWasR6U37+bStVPpsCYEo4+C
Y1arLC/9ujj7aGxF7H+EMk7X0L4059+2v711X7a/3biowx2CyNOgjNRcrri3cyX/0soryyCA6/zH
fO2wcQi4udMXcZwXtZpAsluah7DjGp9MSTS5NInKm3Is4VIS9fN3KmKKTJYYZI27N0lFAxgHGVXc
GPWsh4hAd1CqQvuM0P5YlBfgPBD6Mu6tmZ9VLg==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509IssuerSerial>
<ds:X509IssuerName>CN=Symantec Class 3 Secure Server CA - G4,OU=Symantec Trust Network,O=Symantec Corporation,C=US</ds:X509IssuerName>
<ds:X509SerialNumber>142421751065451577073995987482935596892</ds:X509SerialNumber>
</ds:X509IssuerSerial>
<ds:X509Data>
<ds:X509Certificate>MIIGfDCCBWSgAwIBAgIQayVud3+bDrNKrbQphkCXXDANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQG
EwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRy
dXN0IE5ldHdvcmsxLzAtBgNVBAMTJlN5bWFudGVjIENsYXNzIDMgU2VjdXJlIFNlcnZlciBDQSAt
IEc0MB4XDTE2MTEyNTAwMDAwMFoXDTE4MTEyNjIzNTk1OVowgYExCzAJBgNVBAYTAlVTMREwDwYD
VQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsxGDAWBgNVBAoMD1Rob21zb24gUmV1dGVy
czEMMAoGA1UECwwDTUlTMSQwIgYDVQQDDBtzYWZlc2FtbC50aG9tc29ucmV1dGVycy5jb20wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDakNsHCqwMaX1VO11VQwzS3eFIOEYr78EMvX3v
lxYO5F41NBEslkFVUD5RzFOXwpUhNzHPHd7IkECUtdrJlkmwWdpdIPC2exfojRSdQsLRFJFSm6sp
JnXBDiY3hzxwUiwe4ZQF2pxAVFXSmBXxbigvOpPeOargfbvNGJtn6VKClQDJdBPQXaj8JcqzV+GR
uc0XgiLZ+rkKLM3nx17wFq4pOWaDnEomxBEHFvw0t+T2sTgXJ0mG2gAugdz24+ImOHLQfYnrvDdJ
OV5R3TXTUTqfnNWP8AHv60bauL2SxEALNw6RpToBN30pIYN55X0aS/KR2Jv2f3AgoVjzeObTKjV/
AgMBAAGjggLwMIIC7DAmBgNVHREEHzAdghtzYWZlc2FtbC50aG9tc29ucmV1dGVycy5jb20wCQYD
VR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMGEG
A1UdIARaMFgwVgYGZ4EMAQICMEwwIwYIKwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3Bz
MCUGCCsGAQUFBwICMBkMF2h0dHBzOi8vZC5zeW1jYi5jb20vcnBhMB8GA1UdIwQYMBaAFF9gz2GQ
Vd+EQxSKYCqy9Xr0QxjvMCsGA1UdHwQkMCIwIKAeoByGGmh0dHA6Ly9zcy5zeW1jYi5jb20vc3Mu
Y3JsMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYTaHR0cDovL3NzLnN5bWNkLmNvbTAmBggr
BgEFBQcwAoYaaHR0cDovL3NzLnN5bWNiLmNvbS9zcy5jcnQwggF8BgorBgEEAdZ5AgQCBIIBbASC
AWgBZgB1AN3rHSt6DU+mIIuBrYFocH4ujp0B1VyIjT0RxM227L7MAAABWJtuTccAAAQDAEYwRAIg
TnarbbJerkWL2KzLU3wv5YYzCkKsn1oSlJz8L4v+H94CIB3bX2g1VDE1r1ieojPqJ0adVVMycO6P
6BPvdBP1EGKLAHYA7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/csAAAFYm25OGAAABAMA
RzBFAiAv03fuYpOk+OhnprzQDUtf1OHwxCZbMxLcxHPvPSFVZgIhANurB8rz4rAPmnEENCIK1Kdr
t6iDAF15THY8lWuGtFS3AHUAvHjh38X2PGhGSTNNoQ+hXwl5aSAJwIG08/aRfz7ZuKUAAAFYm25O
wwAABAMARjBEAiBMFlg9dANwKJ8vMltapsWGeQotN3tklnlApUxlVduOwwIgA0HHsKr1qgryF6fY
04k53uYxoeVoqk1elaAHi+K6JmMwDQYJKoZIhvcNAQELBQADggEBAByVHCZzKL9iVhg2Ypw6Xqxl
UcetruvMZJHUCZeH1eHmre4EMw97JQ5JH/QAftjoqN/mxa9DlSxaOBDMmVlFcLjOs60UVHFb8FVV
ScBpuogrztg8oPc+XRhaKTLmdsL32agQUdH+TAvhs8TOqxJlENk50iILrAxnYcadOWo1A0nJnZIF
N8qfbyTFoojQj0jBnIThNeDP8RR4m7kAba2Y9PiE7YeQWUPUGepUhQT76zivX81TmdGJo0IZ4Jjd
xdtyyK90STS73tOq1jUnUUqkb8zyTPgkSC/MDnFzuWSie4CWgfw0KSKPNEmra6nlH/2y+YckVYMi
TyU0Bbc2VGLlcP8=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified">C229699</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData InResponseTo="63622fa6-9a00-4d39-9c92-791c3a1efc3f" NotOnOrAfter="2017-12-04T13:57:30Z" Recipient="http://my-app.net/saml"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2017-12-04T13:42:30Z" NotOnOrAfter="2017-12-04T13:57:30Z">
<saml:AudienceRestriction>
<saml:Audience>http://my-app.net</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2017-12-04T13:47:30Z" SessionIndex="gkifgihgclegelojncjfgegcddfncgdaefcjgbod">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="UserID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue>D100000</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
仅为快速参考,无需打开上述链接,这是代码的样子(在适用的情况下使用我的示例值/变量):
尝试像这样验证签名(您的签名没有为我验证,但这可能是由于在此处发布签名时所做的更改造成的): 如果这不起作用,也可以尝试同样的方法,但打电话
return signedXml.CheckSignature();
而不是
return signedXml.CheckSignature(cert);
请注意,仅验证此签名不足以确保响应未被篡改。您可以使用响应本身提供的密钥(
X509Data
)来验证签名,这意味着攻击者可能截获了响应,提取了信息,并使用自己的密钥放弃了响应,所以签名将是有效的,但签名时使用的密钥将是无效的。因此,在提取证书(或者可以使用signedXml.CheckSignatureReturningKey
方法获取与签名相关的公钥)之后,您需要验证它是否有效以及它是否是您期望的证书(例如,通过将其哈希值与您期望的证书哈希值进行比较).签名应通过身份提供者提供的证书进行验证。从更新中,您将存储来自IdP的元数据xml,然后从中检索证书
另一种选择是从IdP获取证书,作为.cer
文件,并将其安装在本地计算机的受信任根证书存储中。一旦安装,就可以在代码中访问它,并用于验证签名。这样,您就不必担心XML中证书的欺骗
在这种方法中,响应XML中的证书用于获取序列号,通过该序列号可以在存储中找到保存的证书
在@Evk的响应之上构建
public static bool VerifyXml(XmlDocument Doc)
{
if (Doc == null)
throw new ArgumentException("Doc");
SignedXml signedXml = new SignedXml(Doc);
var nsManager = new XmlNamespaceManager(Doc.NameTable);
nsManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
var node = Doc.SelectSingleNode("//ds:Signature", nsManager);
// find signature node
var certElement = Doc.SelectSingleNode("//ds:X509Certificate", nsManager);
// find certificate node
var cert = new X509Certificate2(Convert.FromBase64String(certElement.InnerText));
signedXml.LoadXml((XmlElement)node);
//Find installed certificate from store
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 storeCert = store.Certificates.Find(X509FindType.FindBySerialNumber, cert.SerialNumber, true)[0];
return signedXml.CheckSignature(storeCert, true);
//^^^ If certificate is installed in the Root location then
//this method returns true after validating it as well
//In addition to validating the signature
}
也
那么,您是否在响应xml上尝试过该代码?是的,我在它上尝试过,但仍然无法使验证返回true。此外,我还更新了我的问题,包括在我尝试这段代码之前如何将响应表单数据转换为字符串,以澄清我是如何尝试使用它的。很难给出太多建议,因为在这种情况下,SignedXml是一种方法。如果没有示例saml响应(带有签名和所有其他东西,您用XXX替换了它们——这并不是真正必要的,因为我认为这不是敏感的数据),很难判断它返回false的原因。旁注:如果值已经在base64中,为什么要使用UrlDecode?似乎不需要。如果有帮助的话,我更新了saml响应以包括签名、证书等(抱歉,我不确定这是否是敏感数据,因为这是我第一次使用saml)。此外,从我收集的数据来看,转换后的值仍然是Url编码的,没有Url解码,我无法收集正确格式的字符串来生成xml.Update-在进行更多测试后,问题似乎是由我调用VerifyXml(doc,key)引起的代码>。我正在设置远程调试以查看它(只能通过发布的web应用程序进行调试)。您的第一个答案非常有效(您的备选答案也很有效)。非常感谢你的帮助,这正是我所需要的@Jared如果第二个有效-使用它,它会更好(并且删除找到证书节点的代码,因为它不是必需的)。谢谢!为了澄清起见,您能否向我解释一下为什么第二个更好?@Jared well它也这样做,但代码更少(例如,不需要找到证书节点)。但请注意,我用更多信息更新了答案,因为在这种情况下,仅仅验证签名是不够的。
public static bool VerifyXml(XmlDocument Doc) {
if (Doc == null)
throw new ArgumentException("Doc");
SignedXml signedXml = new SignedXml(Doc);
var nsManager = new XmlNamespaceManager(Doc.NameTable);
nsManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
var node = Doc.SelectSingleNode("//ds:Signature", nsManager);
// find signature node
var certElement = Doc.SelectSingleNode("//ds:X509Certificate", nsManager);
// find certificate node
var cert = new X509Certificate2(Convert.FromBase64String(certElement.InnerText));
signedXml.LoadXml((XmlElement)node);
return signedXml.CheckSignature(cert);
}
return signedXml.CheckSignature();
return signedXml.CheckSignature(cert);
public static bool VerifyXml(XmlDocument Doc)
{
if (Doc == null)
throw new ArgumentException("Doc");
SignedXml signedXml = new SignedXml(Doc);
var nsManager = new XmlNamespaceManager(Doc.NameTable);
nsManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
var node = Doc.SelectSingleNode("//ds:Signature", nsManager);
// find signature node
var certElement = Doc.SelectSingleNode("//ds:X509Certificate", nsManager);
// find certificate node
var cert = new X509Certificate2(Convert.FromBase64String(certElement.InnerText));
signedXml.LoadXml((XmlElement)node);
//Find installed certificate from store
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 storeCert = store.Certificates.Find(X509FindType.FindBySerialNumber, cert.SerialNumber, true)[0];
return signedXml.CheckSignature(storeCert, true);
//^^^ If certificate is installed in the Root location then
//this method returns true after validating it as well
//In addition to validating the signature
}
return signedXml.CheckSignature(cert);
//^^^^ This will not work.
//CheckSignature(X509Certificate2 certificate, bool verifySignatureOnly)
//needs a boolean flag as well