Naner
Naner

Reputation: 1292

How to sign SAMLResponse and encrypt assertion with C#?

I have an IDP that generates a SAMLResponse with a signed assertion. I'm not going to add an example of the generated XML file because it will make the question too long. But if that would be really helpful, let me know, and I'll add it.

An SP is requiring that the assertion be encrypted and response signed, which, currently is not the case. After research, I couldn't find how to do it, and I'd post some code that I tried, but I'm a little clueless to be honest, and everything I tried got nowhere.

Question is, how do I sign the response and encrypt the assertion?

Here's how the response is being created and signed:

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

}

Upvotes: 3

Views: 2447

Answers (1)

Thuan
Thuan

Reputation: 1628

The SecurityTokenDescriptor and Saml2SecurityToken classes, in addition to SigningCredentials property, have an EncryptingCredentials property. Set it and the assertion will be encrypted.

Signing response might be trickier. After you have the response xml with encrypted assertion, you can use the SignedXml class for that purpose. One example is https://github.com/Safewhere/CHTestSigningService/blob/86a66950d1ffa5208b8bf80d03868a073ba29f12/Kombit.Samples.CHTestSigningService/Code/TokenSigningService.cs#L344

Upvotes: 1

Related Questions