Code through the pain Ladislav Mrnka's professional blog about software development

13Apr/110

Last week I was working on some SOAP message preprocessing in our current project. We needed to extract raw information about security tokens used in SOAP message and because of that we decided to use WSSecurityTokenSerializer class from System.ServiceModel.Security namespace. This class provides public method ReadKeyIdentifierClause inherited from SecurityTokenSerializer. The method was working fine until we used it to read EncryptedKey token with included ReferenceList. In this scenario the pair method CanReadKeyIdentifierClause returns true, but ReadKeyIdentifierClause is throwing an unexpected XmlException because the method implementation expects the end element for EncryptedKey instead of the start element for ReferenceList. I asked related question on MSDN but I haven't got any answer yet. I think this is a bug.

Using ReferenceList in EncryptedKey is allowed by both WS-Security 1.0 and WS-Security 1.1 specifications and moreover it is result of many security configurations in WCF including BasicHttpBinding with security mode set to BasicHttpSecurityMode.Message and client credentials set to BasicHttpMessageCredential.Certificate. This configuration creates mutual certificate asymmetric security binding which uses exactly that problematic token. The rest of the article shows the test fixture to reproduce the issue.

Test fixture to reproduce the issue:

using System.IdentityModel.Tokens;
using System.IO;
using System.ServiceModel.Security;
using System.Xml;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace WsSecurityTokenSerializerTest
{
  [TestClass]
  public class WsSecurity
  {
    // Source of this testing EncryptedKey is BasicHttpBinding +
    // BasicHttpSecurityMode.Message + BasicHttpMessageCreadentialType.Certificate
    // => WSS 1.0 Mutual Certificate (asymmetric) security
    // The cipher value was replaced with shorter one just for this blog post.
    private const string EncryptedKeyWithReferenceList =
      @"<e:EncryptedKey Id='_0' xmlns:e='http://www.w3.org/2001/04/xmlenc#'>
          <e:EncryptionMethod Algorithm='http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'>
            <DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'
                          xmlns='http://www.w3.org/2000/09/xmldsig#'/>
          </e:EncryptionMethod>
          <KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'>
            <o:SecurityTokenReference>
              xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
              <X509Data>
                <X509IssuerSerial>
                  <X509IssuerName>CN=Some name</X509IssuerName>
                  <X509SerialNumber>200</X509SerialNumber>
                </X509IssuerSerial>
              </X509Data>
            </o:SecurityTokenReference>
          </KeyInfo>
         <e:CipherData>
           <e:CipherValue>QUJDREVGR0hJSktMKw==</e:CipherValue>
         </e:CipherData>
         <e:ReferenceList>
           <e:DataReference URI='#_2'/>
           <e:DataReference URI='#_3'/>
         </e:ReferenceList>
       </e:EncryptedKey>";

    [TestMethod]
    public void CanReadKeyIdentifierClause_EncryptedKey_ReferenceList_WSS10()
    {
      var wss = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity10);
      using (var reader = XmlReader.Create(new StringReader(EncryptedKeyWithReferenceList)))
      {
        Assert.IsTrue(wss.CanReadKeyIdentifierClause(reader));
      }
    }

    [TestMethod]
    public void CanReadKeyIdentifierClause_EncryptedKey_ReferenceList_WSS11()
    {
      var wss = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity11);
      using (var reader = XmlReader.Create(new StringReader(EncryptedKeyWithReferenceList)))
      {
        Assert.IsTrue(wss.CanReadKeyIdentifierClause(reader));
      }
    }

    [TestMethod]
    public void ReadKeyIdentifierClause_EncryptedKey_ReferenceList_WSS10()
    {
      var wss = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity10);
      using (var reader = XmlReader.Create(new StringReader(EncryptedKeyWithReferenceList)))
      {
        // THROWS EXCEPTION:
        // System.Xml.XmlException: 'Element' is an invalid XmlNodeType. Line 20, position 19.
        var clause = wss.ReadKeyIdentifierClause(reader);
        Assert.IsTrue(clause is EncryptedKeyIdentifierClause);
      }
    }

    [TestMethod]
    public void ReadKeyIdentifierClause_EncryptedKey_ReferenceList_WSS11()
    {
      var wss = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity11);
      using (var reader = XmlReader.Create(new StringReader(EncryptedKeyWithReferenceList)))
      {
        // THROWS EXCEPTION:
        // System.Xml.XmlException: 'Element' is an invalid XmlNodeType. Line 20, position 19.
        var clause = wss.ReadKeyIdentifierClause(reader);
        Assert.IsTrue(clause is EncryptedKeyIdentifierClause);
      }
    }

    private const string EncryptedKey =
      @"<e:EncryptedKey Id='_0' xmlns:e='http://www.w3.org/2001/04/xmlenc#'>
          <e:EncryptionMethod Algorithm='http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'>
            <DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'
                          xmlns='http://www.w3.org/2000/09/xmldsig#'/>
          </e:EncryptionMethod>
          <KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'>
            <o:SecurityTokenReference>
              xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
              <X509Data>
                <X509IssuerSerial>
                  <X509IssuerName>CN=Some name</X509IssuerName>
                  <X509SerialNumber>200</X509SerialNumber>
                </X509IssuerSerial>
              </X509Data>
            </o:SecurityTokenReference>
          </KeyInfo>
          <e:CipherData>
            <e:CipherValue>QUJDREVGR0hJSktMKw==</e:CipherValue>
          </e:CipherData>
        </e:EncryptedKey>";

    [TestMethod]
    public void CanReadKeyIdentifierClause_EncryptedKey_WSS10()
    {
      var wss = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity10);
      using (var reader = XmlReader.Create(new StringReader(EncryptedKey)))
      {
        Assert.IsTrue(wss.CanReadKeyIdentifierClause(reader));
      }
    }

    [TestMethod]
    public void CanReadKeyIdentifierClause_EncryptedKey_WSS11()
    {
      var wss = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity11);
      using (var reader = XmlReader.Create(new StringReader(EncryptedKey)))
      {
        Assert.IsTrue(wss.CanReadKeyIdentifierClause(reader));
      }
    }

    [TestMethod]
    public void ReadKeyIdentifierClause_EncryptedKey_WSS10()
    {
      var wss = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity10);
      using (var reader = XmlReader.Create(new StringReader(EncryptedKey)))
      {
        // NO exception here
        var clause = wss.ReadKeyIdentifierClause(reader);
        Assert.IsTrue(clause is EncryptedKeyIdentifierClause);
      }
    }

    [TestMethod]
    public void ReadKeyIdentifierClause_EncryptedKey_WSS11()
    {
      var wss = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity11);
      using (var reader = XmlReader.Create(new StringReader(EncryptedKey)))
      {
        // NO exception here
        var clause = wss.ReadKeyIdentifierClause(reader);
        Assert.IsTrue(clause is EncryptedKeyIdentifierClause);
      }
    }
  }
}
Posted on April 13, 2011 by Ladislav Mrnka
Filed under: WCF
Leave a comment
Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

(required)

No trackbacks yet.