none
Generating a SAML2 Assertion programically without an STS for a third party web service. RRS feed

  • General discussion

  • I had a developer that needed to send a self-signed SAML2 Assertion to a third party web service.

    based on code from the article at Dev Pro Article I came up with the following solution.  

    1.  First I created a console application to act as the client.

    2.  Then I created a default WCF service application that I could use for testing the client.

    3.  Rather than use a self-signed certificate I used a ssl certificate that I typically use for development.

    The result is a SOAP request (using fiddler) that looks like the following

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
      <s:Header>
        <a:Action s:mustUnderstand="1">http://tempuri.org/IService1/GetData</a:Action>
        <a:MessageID>urn:uuid:5f976b9f-2e4e-47c0-a91d-dfb36bed9aaa</a:MessageID>
        <ActivityId CorrelationId="6e8d985b-83be-4726-ace6-237101c3a3e0" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">50113a37-85f3-4696-af15-02b43bd55e71</ActivityId>
        <a:ReplyTo>
          <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>
        <VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPoyyoLer4/W9FgYLH+1XIiWoAAAAAHClHyTLDIEuoK1AHHcVGd7EpZR4aJjZPlU0ar2CTqLIACQAA</VsDebuggerCausalityData>
        <a:To s:mustUnderstand="1">https://bmukes.insurancetechnologies.com/TestSALMAssertionWebService/Service1.svc</a:To>
        <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
          <u:Timestamp u:Id="_0">
            <u:Created>2013-07-09T04:11:49.730Z</u:Created>
            <u:Expires>2013-07-09T04:16:49.730Z</u:Expires>
          </u:Timestamp>
          <Assertion ID="_07398a79-fe1f-43b8-a3da-57db9a509c59" IssueInstant="2013-07-09T04:11:48.874Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
            <Issuer>https://bmukes.insurancetechnologies.com/test/sts/</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/2001/04/xmldsig-more#rsa-sha256"/>
                <ds:Reference URI="#_07398a79-fe1f-43b8-a3da-57db9a509c59">
                  <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/2001/04/xmlenc#sha256"/>
                  <ds:DigestValue>HFnQ3hOTRz54XH5zxx4YCw0ItlnqIzYWP9pybOoOJXQ=</ds:DigestValue>
                </ds:Reference>
              </ds:SignedInfo>
              <ds:SignatureValue>UYK28O8rUQ3VAi2gSBDMCWGU8ou7faecX4FeaMD4WM5LXm5zNWVtTWIykxiacTrVtb1/ZhUMDb3AVUb8KIvBH6CdTT649MuwlmaM49E0jhidWySIYNEerzpa+7zBEcctRwPDRIL3kT0in/XeoJmpS3TX8psLdCvOK1tySHLAeoI=</ds:SignatureValue>
              <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
                  <X509Certificate>MIIFFTCCA/2gAwIBAgIDY9dBMA0GCSqGSIb3DQEBBQUAMIHdMQswCQYDVQQGEwJVUzERMA8GA1UECBMIQ29sb3JhZG8xGTAXBgNVBAcTEENvbG9yYWRvIFNwcmluZ3MxJjAkBgNVBAMTHUluc1RlY2guUHJvZHVjdERldmVsb3BtZW50LkNBMTkwNwYJKoZIhvcNAQkBFipXZWJBZG1pbmlzdHJhdG9yQEluc3VyYW5jZVRlY2hub2xvZ2llcy5jb20xHzAdBgNVBAoTFkluc3VyYW5jZSBUZWNobm9sb2dpZXMxHDAaBgNVBAsTE1Byb2R1Y3QgRGV2ZWxvcG1lbnQwHhcNMTExMTIyMTczMDE0WhcNMzkxMTE1MTczMDE0WjCB4DELMAkGA1UEBhMCVVMxETAPBgNVBAgTCENvbG9yYWRvMRkwFwYDVQQHExBDb2xvcmFkbyBTcHJpbmdzMR8wHQYDVQQKExZJbnN1cmFuY2UgVGVjaG5vbG9naWVzMRwwGgYDVQQLExNQcm9kdWN0IERldmVsb3BtZW50MSkwJwYDVQQDEyBibXVrZXMuaW5zdXJhbmNldGVjaG5vbG9naWVzLmNvbTE5MDcGCSqGSIb3DQEJARYqV2ViQWRtaW5pc3RyYXRvckBJbnN1cmFuY2VUZWNobm9sb2dpZXMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9p13n5BUJ0X9X3InYJqTAyRQbqwRop1hVnwGIl0+jFwa4Vw2t79DIV3x7C13LxC4Z2cXocwSzrt3ytE8Hyt+d0jR9mgKMcQHv0dP2vzG+BywMU6LX6L6+FW2aBmrQSnNEjEi3MoeX3ZNG1G2cJLrDlu+U/UZa9S2UF413M7+hfQIDAQABo4IBWzCCAVcwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFDHFfhxo2SheDGbhLq2J428nhG3bMIH8BgNVHSMEgfQwgfGhgeOkgeAwgd0xCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEZMBcGA1UEBxMQQ29sb3JhZG8gU3ByaW5nczEmMCQGA1UEAxMdSW5zVGVjaC5Qcm9kdWN0RGV2ZWxvcG1lbnQuQ0ExOTA3BgkqhkiG9w0BCQEWKldlYkFkbWluaXN0cmF0b3JASW5zdXJhbmNlVGVjaG5vbG9naWVzLmNvbTEfMB0GA1UEChMWSW5zdXJhbmNlIFRlY2hub2xvZ2llczEcMBoGA1UECxMTUHJvZHVjdCBEZXZlbG9wbWVudIIJAJeX+RFdLgCYMA0GCSqGSIb3DQEBBQUAA4IBAQAGk6M4FmJVS51pRZ2yoM7ck6RedknJGjOXNdf9hZZlxYvKPk3nO7UUW2bWTEsH8s/18e8lDJrXS+WwXT8EDkL0JUly2lDupZRtX4aydZmIXqrxRuxfWCdTjk6tflRyFCOSLP9TuX69yPPU0W0hY5duGe8nfjPy3xf2CuBMXeKq/5vHRdUJI0EfYYeFjoH+iRD73d7yA1Oqwpbg3vtxDtTJrvo41sSww9Ft53ThbcH5W05L3dPNfuO6mn8yUwuf2vl9zQCljg74+sfGUPSK8JdMjVvWqHWtPDeE1vruAGtLhc4hcVHZ2F3WIyWebmMazzVQqj7I7oWXXtb1n+wqJKOG</X509Certificate>
                </X509Data>
              </KeyInfo>
            </ds:Signature>
            <Subject>
              <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"/>
            </Subject>
            <Conditions NotBefore="2013-07-09T04:11:48.837Z" NotOnOrAfter="2013-07-09T12:11:48.837Z">
              <AudienceRestriction>
                <Audience>https://bmukes.insurancetechnologies.com/TestSALMAssertionWebService/Service1.svc</Audience>
              </AudienceRestriction>
            </Conditions>
            <AttributeStatement>
              <Attribute Name="User">
                <AttributeValue>Michele</AttributeValue>
              </Attribute>
              <Attribute Name="Permission">
                <AttributeValue>Read</AttributeValue>
                <AttributeValue>Write</AttributeValue>
                <AttributeValue>Update</AttributeValue>
                <AttributeValue>Delete</AttributeValue>
              </Attribute>
            </AttributeStatement>
            <AuthnStatement AuthnInstant="2013-07-09T04:11:48.848Z">
              <AuthnContext>
                <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:X509</AuthnContextClassRef>
              </AuthnContext>
            </AuthnStatement>
          </Assertion>
        </o:Security>
      </s:Header>
      <s:Body>
        <GetData xmlns="http://tempuri.org/">
          <value>3</value>
        </GetData>
      </s:Body>
    </s:Envelope>
    1. *** Here is the client program.cs file ***

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.IdentityModel; using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Protocols.WSTrust; using Microsoft.IdentityModel.Claims; using System.Security.Cryptography.X509Certificates; using Microsoft.IdentityModel.SecurityTokenService; using System.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens.Saml2; namespace TestSAMLAssertionClient { class Program { static void Main(string[] args) { //Test With SALM2 token ChannelFactory<TestSALMAssertionWebService.IService1> factory = new ChannelFactory<TestSALMAssertionWebService.IService1>("WS2007HttpBinding_IService1"); ChannelFactoryOperations.ConfigureChannelFactory<TestSALMAssertionWebService.IService1>(factory); TestSALMAssertionWebService.IService1 c = factory.CreateChannelWithIssuedToken(GetSaml2Token()); Console.WriteLine( c.GetData(3)); Console.ReadLine(); } /* * http://devproconnections.com/development/generate-saml-tokens-using-windows-identity-foundation */ static Saml2SecurityToken GetSaml2Token() { SecurityTokenDescriptor descriptor = new SecurityTokenDescriptor(); descriptor.TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"; DateTime issueInstant = DateTime.UtcNow; descriptor.Lifetime = new Lifetime(issueInstant, issueInstant + new TimeSpan(8, 0, 0)); descriptor.AppliesToAddress = "https://bmukes.insurancetechnologies.com/TestSALMAssertionWebService/Service1.svc"; List<Claim> claims = new List<Claim>(){ new Claim("User", "Michele"), new Claim("Permission", "Read"), new Claim("Permission", "Write"), new Claim("Permission", "Update"), new Claim("Permission", "Delete") }; descriptor.Subject = new ClaimsIdentity(claims); descriptor.AddAuthenticationClaims("urn:oasis:names:tc:SAML:2.0:ac:classes:X509"); //who issued the token descriptor.TokenIssuerName = "https://bmukes.insurancetechnologies.com/test/sts/"; X509Certificate2 signingCert = GetCertificateByThumbprint(StoreName.My, StoreLocation.LocalMachine, "68 94 27 1e 8a 0e 09 a7 fd a1 8b 18 18 ba 2f f8 f4 0c f2 6f"); SecurityKeyIdentifier ski = new SecurityKeyIdentifier(new SecurityKeyIdentifierClause[] { new X509SecurityToken(signingCert).CreateKeyIdentifierClause<X509RawDataKeyIdentifierClause>() }); X509SigningCredentials signingCreds = new X509SigningCredentials(signingCert, ski); descriptor.SigningCredentials = signingCreds; Saml2SecurityTokenHandler tokenHandler = new Saml2SecurityTokenHandler(); Saml2SecurityToken token = tokenHandler.CreateToken(descriptor) as Saml2SecurityToken; return token; } static X509Certificate2 GetCertificateByThumbprint(StoreName name, StoreLocation location, string thumbprint) { String validthumbprint = thumbprint.Replace(" ", null); X509Store store = new X509Store(name, location); X509Certificate2Collection certificates = null; store.Open(OpenFlags.ReadOnly); try { X509Certificate2 result = null; // // Every time we call store.Certificates property, a new collection will be returned. // certificates = store.Certificates; for (int i = 0; i < certificates.Count; i++) { X509Certificate2 cert = certificates[i]; if (cert.Thumbprint.ToLower() == validthumbprint.ToLower()) { if (result != null) { throw new ApplicationException(string.Format("There are multiple certificates for thumbprint {0}", thumbprint)); } result = new X509Certificate2(cert); } } if (result == null) { throw new ApplicationException(string.Format("No certificate was found for thumbprint {0}", thumbprint)); } return result; } finally { if (certificates != null) { for (int i = 0; i < certificates.Count; i++) { X509Certificate2 cert = certificates[i]; cert.Reset(); } } store.Close(); } } } }

    *** Here is the app.config for the client ***

    <configuration>
      <system.serviceModel>
        <bindings>
         <customBinding>
            <!-- This binding is a WS2007FederationHttpBinding without Secure Sessions that uses Text message encoding. -->
            <binding
                name="WS2007FederationHttpBinding_NoSecureSession_Text"
                closeTimeout="00:01:00"
                openTimeout="00:01:00"
                receiveTimeout="00:10:00"
                sendTimeout="00:01:00">
              <security
                  authenticationMode="IssuedTokenOverTransport"
                  requireSignatureConfirmation="true"
                  securityHeaderLayout="Lax"
                  messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
                  keyEntropyMode="CombinedEntropy"
                  includeTimestamp="true">
                <issuedTokenParameters
                    tokenType="urn:oasis:names:tc:SAML:2.0:assertion" keyType="BearerKey">
                </issuedTokenParameters>
    
                <!-- This basically says "Don't use Secure Conversation" -->
                <secureConversationBootstrap/>
              </security>
    
              <!-- Use Text Encoding -->
              <textMessageEncoding/>
    
              <!-- This says to use HTTPS when communicating with the remote service -->
              <httpsTransport
                  requireClientCertificate="false"
                  maxBufferPoolSize="134217728"
                  maxReceivedMessageSize="134217728"
                  maxBufferSize="134217728"/>
            </binding>
          </customBinding>
        </bindings>
        <client>
          <endpoint address="https://bmukes.insurancetechnologies.com/TestSALMAssertionWebService/Service1.svc"
              binding="customBinding" bindingConfiguration="WS2007FederationHttpBinding_NoSecureSession_Text"
              contract="TestSALMAssertionWebService.IService1" name="WS2007HttpBinding_IService1" behaviorConfiguration="MyBehaviorName" />
        </client>
        <behaviors>
            <endpointBehaviors >
              <behavior name="MyBehaviorName">
                <clientCredentials>
                  <serviceCertificate>
                    <defaultCertificate findValue="68 94 27 1e 8a 0e 09 a7 fd a1 8b 18 18 ba 2f f8 f4 0c f2 6f"
                    storeLocation="LocalMachine" x509FindType="FindByThumbprint"
                    storeName="My"/>
                  </serviceCertificate>
                </clientCredentials>
              </behavior>
            </endpointBehaviors>
        </behaviors>
      </system.serviceModel>
    </configuration>

    ** and here is the web.config for the test web service.

    <configuration>
      <configSections>
        <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </configSections>
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
      </system.web>
    
      <microsoft.identityModel>
        <service >
          <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35">
            <trustedIssuers>
              <add name="https://bmukes.insurancetechnologies.com/test/sts/" thumbprint="6894271e8a0e09a7fda18b1818ba2ff8f40cf26f"/>
            </trustedIssuers>
          </issuerNameRegistry>
          <audienceUris mode="Never">
            <add value="https://bmukes.insurancetechnologies.com/TestSALMAssertionWebService/Service1.svc"/>
          </audienceUris>
          <serviceCertificate>
            <certificateReference findValue="6894271e8a0e09a7fda18b1818ba2ff8f40cf26f"                               
                    storeLocation="LocalMachine" x509FindType="FindByThumbprint"
                    storeName="My"/>
          </serviceCertificate>
        </service>
    
      </microsoft.identityModel>
      
      <system.serviceModel>
        <services>
          <service name="TestSALMAssertionWebService.Service1" behaviorConfiguration="TestSALMAssertionWebService.Service1.Behavior">
            <endpoint contract="TestSALMAssertionWebService.IService1" binding="customBinding" bindingConfiguration="WS2007FederationHttpBinding_NoSecureSession_Text">
            </endpoint>
          </service>
        </services>
        <bindings>
          <customBinding>
            <!-- This binding is a WS2007FederationHttpBinding without Secure Sessions that uses Text message encoding. -->
            <binding
                name="WS2007FederationHttpBinding_NoSecureSession_Text"
                closeTimeout="00:01:00"
                openTimeout="00:01:00"
                receiveTimeout="00:10:00"
                sendTimeout="00:01:00">
              <security
                  authenticationMode="IssuedTokenOverTransport"
                  requireSignatureConfirmation="true"
                  securityHeaderLayout="Lax"
                  messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
                  keyEntropyMode="CombinedEntropy"
                  includeTimestamp="true">
                <issuedTokenParameters
                    tokenType="urn:oasis:names:tc:SAML:2.0:assertion" keyType="BearerKey">
                </issuedTokenParameters>
    
                <!-- This basically says "Don't use Secure Conversation" -->
                <secureConversationBootstrap/>
              </security>
    
              <!-- Use Text Encoding -->
              <textMessageEncoding/>
    
              <!-- This says to use HTTPS when communicating with the remote service -->
              <httpsTransport
                  requireClientCertificate="false"
                  maxBufferPoolSize="134217728"
                  maxReceivedMessageSize="134217728"
                  maxBufferSize="134217728"/>
            </binding>
          </customBinding>
        </bindings>
        <extensions>
          <behaviorExtensions>
            <add name="federatedServiceHostConfiguration" type="Microsoft.IdentityModel.Configuration.ConfigureServiceHostBehaviorExtensionElement, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
          </behaviorExtensions>
        </extensions>
        
        <behaviors>
          <serviceBehaviors>
            <behavior name="TestSALMAssertionWebService.Service1.Behavior">
              <federatedServiceHostConfiguration  />
              <!-- To avoid disclosing metadata information, set the value below to false before deployment -->
              <serviceMetadata httpGetEnabled="true"/>
              <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
              <serviceDebug includeExceptionDetailInFaults="true"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
      </system.serviceModel>
     <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
        <!--
            To browse web app root directory during debugging, set the value below to true.
            Set to false before deployment to avoid disclosing web app folder information.
          -->
        <directoryBrowse enabled="true"/>
      </system.webServer>
    </configuration>

    I did not include code for the service as its simply the default service created when you create a WCF service application.

    This was developed and tested using the .NET 4.0 framework and WIF 3.5.

    Hopefully this will save some other developer 2 days of Google searches.

    To Recap the secret sauce to making this work:

    1. Create a self-signed certificate or using an existing one for development.
    2. Write the code to create a SAML 2 token (see program.cs) do not forget to reference Microsoft.IdentityModel and System.IdentityModel.
    3. Use ChannelFactoryOperations.ConfigureChannelFactory and CreateChannelWithIssuedToken to send the token to the service.
    4. Use a WS2007FederationHttpBinding without Secure Sessions. 

    Tuesday, July 9, 2013 4:58 AM

All replies