【问题标题】:How to sign SAMLResponse and encrypt assertion with C#?如何使用 C# 签署 SAMLResponse 并加密断言?
【发布时间】:2017-10-02 16:14:31
【问题描述】:

我有一个 IDP,它生成带有签名断言的 SAMLResponse。我不会添加生成的 XML 文件的示例,因为它会使问题变得太长。但如果这真的有帮助,请告诉我,我会添加它。

SP 要求对断言进行加密并对响应进行签名,目前情况并非如此。经过研究,我找不到如何做到这一点,我会发布一些我尝试过的代码,但老实说我有点无能为力,我尝试的一切都无济于事。

问题是,我如何签署响应并加密断言?

以下是响应的创建和签名方式:

public class SAML
{
    private const int tokenLifetime = 30;
    private const string issuer = "https://some.domain/IdP";
    private const string CertificateSerialNumber = "XXXXXXXXXXXXX";

    private static string _RequestId;
    private static string _RequestIssueInstant;
    private static string _RequestProviderName;
    private static string _RequestACS;
    private static Dictionary<string, string> _claimDescriptors = new Dictionary<string, string>();


    public static string CreateSamlResponse(string RequestId, string RequestIssueInstant, string RequestProviderName, string RequestACS, Dictionary<string,string> claimDescriptors)
    {
        _RequestId = RequestId;
        _RequestIssueInstant = RequestIssueInstant;
        _RequestProviderName = RequestProviderName;
        _RequestACS = RequestACS;
        _claimDescriptors = claimDescriptors;

        var claims = CreateClaims();
        var tokenHandler = new Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler();
        var token = CreateToken(claims, tokenHandler);

        return CreateSamlResponseXml(tokenHandler, token);
    }

    private static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken CreateToken(IEnumerable<Microsoft.IdentityModel.Claims.Claim> claims,
        Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler tokenHandler)
    {
        var descriptor = CreateTokenDescriptor(claims);
        var token = tokenHandler.CreateToken(descriptor) as Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken;

        AddAuthenticationStatement(token);
        AddConfirmationData(token);

        return token;
    }

    private static void AddConfirmationData(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken token)
    {
        var confirmationData = new Microsoft.IdentityModel.Tokens.Saml2.Saml2SubjectConfirmationData
        {
            Recipient = new Uri(_RequestACS),
            NotOnOrAfter = DateTime.UtcNow.AddSeconds(tokenLifetime),
            InResponseTo = new Microsoft.IdentityModel.Tokens.Saml2.Saml2Id(_RequestId),
        };

        token.Assertion.Subject.SubjectConfirmations.Add(new Microsoft.IdentityModel.Tokens.Saml2.Saml2SubjectConfirmation(
            Microsoft.IdentityModel.Tokens.Saml2.Saml2Constants.ConfirmationMethods.Bearer, confirmationData));
    }

    private static void AddAuthenticationStatement(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken token)
    {
        var authenticationMethod = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password";
        var authenticationContext = new Microsoft.IdentityModel.Tokens.Saml2.Saml2AuthenticationContext(new Uri(authenticationMethod));
        var authenticationStatement = new Microsoft.IdentityModel.Tokens.Saml2.Saml2AuthenticationStatement(authenticationContext);
        token.Assertion.Statements.Add(authenticationStatement);
    }

    private static string CreateSamlResponseXml(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler tokenHandler, Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken token)
    {
        var buffer = new StringBuilder();

        using (var stringWriter = new StringWriter(buffer))
        using (var xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings()))
        {
            xmlWriter.WriteStartElement("Response", "urn:oasis:names:tc:SAML:2.0:protocol");
            xmlWriter.WriteAttributeString("IssueInstant", DateTime.UtcNow.ToString("o"));
            xmlWriter.WriteAttributeString("ID", "_" + Guid.NewGuid());
            xmlWriter.WriteAttributeString("Version", "2.0");

            xmlWriter.WriteStartElement("Status");
            xmlWriter.WriteStartElement("StatusCode");
            xmlWriter.WriteAttributeString("Value", "urn:oasis:names:tc:SAML:2.0:status:Success");
            xmlWriter.WriteEndElement();
            xmlWriter.WriteEndElement();

            tokenHandler.WriteToken(xmlWriter, token);

            xmlWriter.WriteEndElement();
        }
        return buffer.ToString();
    }

    private static Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor CreateTokenDescriptor(IEnumerable<Microsoft.IdentityModel.Claims.Claim> claims)
    {
        var descriptor = new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor()
        {
            TokenType = Microsoft.IdentityModel.Tokens.SecurityTokenTypes.OasisWssSaml2TokenProfile11,
            Lifetime = new Microsoft.IdentityModel.Protocols.WSTrust.Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddMinutes(1)),
            //AppliesToAddress = appliesTo,
            AppliesToAddress = _RequestACS,
            TokenIssuerName = issuer,
            Subject = new Microsoft.IdentityModel.Claims.ClaimsIdentity(claims),
            SigningCredentials = GetSigningCredentials()
        };

        return descriptor;
    }

    private static System.IdentityModel.Tokens.SigningCredentials GetSigningCredentials()
    {    
        System.Security.Cryptography.X509Certificates.X509Certificate2 myCertificate = null;

        X509Certificate2Collection selectedCerts = new X509Certificate2Collection();

        X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);

        foreach (System.Security.Cryptography.X509Certificates.X509Certificate2 cert in store.Certificates)
        {
            if (cert.SerialNumber.Trim().ToLower().Equals(CertificateSerialNumber.ToLower())){ myCertificate = cert; }
        }
        return new Microsoft.IdentityModel.SecurityTokenService.X509SigningCredentials(myCertificate, System.IdentityModel.Tokens.SecurityAlgorithms.RsaSha1Signature, System.IdentityModel.Tokens.SecurityAlgorithms.Sha1Digest);
    }

    private static IEnumerable<Microsoft.IdentityModel.Claims.Claim> CreateClaims()
    {
        foreach (var claimDescriptor in _claimDescriptors)
        {
            yield return new Microsoft.IdentityModel.Claims.Claim(claimDescriptor.Key, claimDescriptor.Value);
        }
    }

}

【问题讨论】:

  • 你有没有想过这个问题?如果是这样,有什么办法可以在这里发布代码,有完全相同的要求。

标签: c# .net authentication saml saml-2.0


【解决方案1】:

SecurityTokenDescriptor 和 Saml2SecurityToken 类除了 SigningCredentials 属性外,还有一个 EncryptingCredentials 属性。设置它,断言将被加密。

签名响应可能比较棘手。获得带有加密断言的响应 xml 后,您可以为此目的使用 SignedXml class。一个例子是https://github.com/Safewhere/CHTestSigningService/blob/86a66950d1ffa5208b8bf80d03868a073ba29f12/Kombit.Samples.CHTestSigningService/Code/TokenSigningService.cs#L344

【讨论】:

  • 谢谢 Thuan!这真的很有帮助!我有一个问题。我正在使用 EncryptingCredentials 属性,就像你提到的那样,并且 Assertion 正在被加密,但是 X509Certificate 元素没有被添加到 X509Data 部分,我找不到原因。有什么建议吗?
  • 可以查看SecurityKeyIdentifier属性:msdn.microsoft.com/en-us/library/…IIRC,它负责添加keyinfo。
猜你喜欢
  • 1970-01-01
  • 2021-10-28
  • 1970-01-01
  • 2014-07-22
  • 2016-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-28
相关资源
最近更新 更多