How to: Create a Class Representing a Custom XML Security Token

The first step to using custom XML security credentials within a SOAP message is to create a class that represents the custom XML security token. This topic details how to do that. After that class is created, another class that represents a security token manager must be created. For more information about creating a security token manager, see How to: Create a Security Token Manager for a Custom Security Token.

WSE 3.0 allows a security token service to change the type of security token that is being issued without having to update clients when the security token is an opaque token. Previous versions of WSE required the client to be updated to accommodate a different type of issued security token. Specifically, WSE 2.0 required a client application to register all security token managers for issued security tokens; with WSE 3.0, that is no longer necessary. For more details about creating an opaque security token, see SAML STS.

The following procedures create a class that represents a custom XML security token:

  1. Create a class that derives from the SecurityToken class.
  2. Apply an appropriate security permission attribute.
  3. Implement constructors with parameters that reflect your token's data.
  4. Override the appropriate methods and properties of your class.

To create a class that derives from the SecurityToken class

  1. Create a new Class Library project in Visual Studio 2005.

  2. Add references to the Microsoft.Web.Services3, System.Web.Services, System.Security, and System.Xml assemblies.

    1. In Solution Explorer, right-click References, and then click Add Reference.
    2. Click the .NET tab, press and hold down the CTRL key, click Microsoft.Web.Services3.dll, System.Web.Services.dll, System.Security.dll, and System.Xml.dll, and then click Select.
    3. Click OK.
  3. Add the Imports statements or using directives shown in the following code example to the top of the file for Class1.cs.

    Imports System
    Imports System.Security.Cryptography
    Imports System.Security.Permissions
    Imports System.Text
    Imports System.Globalization
    Imports System.Xml
    Imports System.Configuration
    
    Imports Microsoft.Web.Services3.Addressing
    Imports Microsoft.Web.Services3.Design
    Imports Microsoft.Web.Services3.Security
    Imports Microsoft.Web.Services3.Security.Cryptography
    Imports Microsoft.Web.Services3.Security.Utility
    Imports Microsoft.Web.Services3.Security.Tokens
    
    using System;
    using System.Security.Cryptography;
    using System.Security.Permissions;
    using System.Text;
    using System.Globalization;
    using System.Xml;
    using System.Configuration;
    
    using Microsoft.Web.Services3.Addressing;
    using Microsoft.Web.Services3.Design;
    using Microsoft.Web.Services3.Security;
    using Microsoft.Web.Services3.Security.Cryptography;
    using Microsoft.Web.Services3.Security.Utility;
    using Microsoft.Web.Services3.Security.Tokens;
    
  4. Add a class that derives from the SecurityToken class.

    If the custom security token will be issued using a security token service, also implement the IIssuedToken interface.

    <SecurityPermission(SecurityAction.Demand, Flags:=SecurityPermissionFlag.UnmanagedCode)> _
    Public Class XmlToken
        Inherits SecurityToken
        Implements IIssuedToken
    
    [SecurityPermission(SecurityAction.Demand, Flags= SecurityPermissionFlag.UnmanagedCode)] 
    public class XmlToken : SecurityToken, IIssuedToken
    

To apply an appropriate security permission attribute

  • Apply the SecurityPermissionAttribute attribute to your class to help prevent an arbitrary assembly from accessing your token.

    It is recommended that you demand that an assembly accessing this class already have permission to call unmanaged code. This helps prevent partially trusted code from gaining access to the cryptographic key information that the security token provides

    The following code example applies the SecurityPermissionAttribute attribute to the class derived from the SecurityToken class, demanding the UnmanagedCode permission.

    <SecurityPermission(SecurityAction.Demand, Flags:=SecurityPermissionFlag.UnmanagedCode)> _
    Public Class XmlToken
        Inherits SecurityToken
        Implements IIssuedToken
    
    [SecurityPermission(SecurityAction.Demand, Flags= SecurityPermissionFlag.UnmanagedCode)] 
    public class XmlToken : SecurityToken, IIssuedToken
    

To implement constructors with parameters that reflect your token's data

  1. Add one or more constructors that take the data necessary to represent your custom security credentials.

    When an encrypted SOAP message is received by WSE, WSE calls the security token manager to retrieve the security token required to decrypt the SOAP message. The security token manager then calls this constructor to create an instance of the security token. The following code example implements a constructor and properties for the token's lifetime and request proof token.

        ' <summary>
        ' The security token manager uses this constructor to instantiate
        ' a security token from KeyInfo clauses.
        ' </summary>
        Public Sub New(ByVal serviceToken As SecurityToken)
            MyBase.New(XmlTokenNames.TypeNames.TokenType)
    
            ' Set the lift time
            _lifeTime = New LifeTime(DateTime.Now, 8 * 60 * 60)
    
            ' Now generate a key.
            _key = CType(KeyAlgorithm.Create("AES128"), SymmetricKeyAlgorithm)
            _key.GenerateKey()
    
            ' Generate the encrypted form of the key.
            _serviceToken = serviceToken
        End Sub
    
    
    
    ...
    
    
            ' <summary>
        ' Get/Set a life time for this binary token, including creation
        ' time and expiration time.
        ' </summary>
        Public Property LifeTime() As LifeTime Implements IIssuedToken.LifeTime
            Get
                Return _lifeTime
            End Get
            Set(ByVal Value As LifeTime)
                _lifeTime = Value
            End Set
        End Property
    
    
    
    ...
    
    
            ' <summary>
        ' Get/Set the proof token for this binary token. This is the 
        ' encrypted form of the key for the token requestor.
        ' </summary>
        Public Property ProofToken() As RequestedProofToken Implements IIssuedToken.ProofToken
            Get
                Return _proofToken
            End Get
            Set(ByVal Value As RequestedProofToken)
                _proofToken = Value
            End Set
        End Property
    
        /// <summary>
        /// The security token manager uses this constructor to instantiate
        /// a security token from KeyInfo clauses.
        /// </summary>
        public XmlToken(SecurityToken serviceToken) : base(XmlTokenNames.TypeNames.TokenType) 
        {
            // Set the lift time
            _lifeTime   = new LifeTime(DateTime.Now, 8 * 60 * 60);
    
            // Now generate a key.
            _key = (SymmetricKeyAlgorithm)KeyAlgorithm.Create("AES128");
            _key.GenerateKey();          
    
            // Generate the encrypted form of the key.
            _serviceToken = serviceToken;
        }
    
    
    
    ...
    
    
            /// <summary>
        /// Get/Set a life time for this binary token, including creation
        /// time and expiration time.
        /// </summary>
        public LifeTime LifeTime 
        {
            get
            {
                return _lifeTime;
            }
            set 
            {
                _lifeTime = value;
            }
        }
    
    
    
    ...
    
    
            /// <summary>
        /// Get/Set the proof token for this binary token. This is the 
        /// encrypted form of the key for the token requestor.
        /// </summary>
        public RequestedProofToken ProofToken
        {
            get
            {
                return _proofToken;
            }
            set
            {
                _proofToken = value;
            }
        }
    
  2. Implement a constructor that takes an XmlElement argument.

    When a SOAP message is received that contains an element in the WS-Security header matching the qname attribute of the <securityTokenManager> Element configuration element, WSE calls the LoadTokenFromXml method of the security token manager. The LoadTokenFromXml method can then call this constructor to deserialize the XML into your custom XML security token.

    ' <summary>
    ' This constructor deserializes an XmlToken from its xml representation.
    ' </summary>
    ' <param name="element"></param>
    Public Sub New(ByVal element As XmlElement)
        MyBase.New(XmlTokenNames.TypeNames.TokenType)
        LoadXml(element)
    End Sub
    
    /// <summary>
    /// This constructor deserializes an XmlToken from its xml representation.
    /// </summary>
    /// <param name="element"></param>
    public XmlToken(XmlElement element) : base(XmlTokenNames.TypeNames.TokenType)
    {
        LoadXml(element);            
    }
    

To override the appropriate methods and properties of your class

  1. Override the LoadXml and GetXml methods.

    The LoadXml method initializes the properties of the security token by parsing the XmlElement argument passed to it. The constructor that accepts an XmlElement argument can call the LoadXml method to parse the XmlElement argument passed to it.

    The GetXml method is called by WSE when the custom XML security token is serialized into the SOAP message.

    The following code example demonstrates how to populate a custom XML security token from an XmlElement by using the LoadXml method and how to serialize a custom XML security token by using the GetXml method.

    ' <summary>
    '  Serialize this class to an Xmlelement
    ' </summary>
    ' <param name="document">the newly generated Xmlelment belongs to this Xmldocument</param>
    ' <returns></returns>
    Public Overloads Overrides Function GetXml(ByVal document As XmlDocument) As XmlElement
        If (document Is Nothing) Then
            Throw New ArgumentNullException("document")
        End If
    
        Dim element As XmlElement = _
            document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.Token, XmlTokenNames.MyNamespace)
    
        '
        ' The id attribute
        '
        If (Not Id Is Nothing AndAlso _
            Id.Length <> 0) Then
            Dim attr As XmlAttribute = _
                document.CreateAttribute(WSUtility.Prefix, WSUtility.AttributeNames.Id, WSUtility.NamespaceURI)
            attr.InnerText = Id
            element.Attributes.Append(attr)
        End If
    
        '
        ' CreatedAt
        '
        If (Not _lifeTime Is Nothing AndAlso _
            _lifeTime.Created <> DateTime.MinValue) Then
            Dim createdAt As XmlElement = _
                document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.CreatedAt, XmlTokenNames.MyNamespace)
            createdAt.InnerText = _
                XmlConvert.ToString(_lifeTime.Created.ToUniversalTime(), WSUtility.TimeFormat)
            element.AppendChild(createdAt)
        End If
    
        '
        ' ExpiresAt
        '
        If (Not _lifeTime Is Nothing AndAlso _
            _lifeTime.Expires <> DateTime.MinValue) Then
            Dim expiresAt As XmlElement = _
                document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.ExpiresAt, XmlTokenNames.MyNamespace)
            expiresAt.InnerText = _
                XmlConvert.ToString(_lifeTime.Expires.ToUniversalTime(), WSUtility.TimeFormat)
            element.AppendChild(expiresAt)
        End If
    
        '
        ' _keys: use _keysXml if we don't understand it
        '
        If (Not _keysXml Is Nothing) Then
            element.AppendChild(document.ImportNode(_keysXml, True))
        ElseIf (Not _serviceToken Is Nothing) Then
            element.AppendChild(New EncryptedKey( _
                _serviceToken, _key.KeyBytes).GetXml(document))
        End If
    
        '
        ' envelope signature
        '
        If (Not _signedXml Is Nothing) Then
            element.AppendChild(document.ImportNode(_signedXml, True))
        End If
    
        Return element
    End Function
    
    ' <summary>
    ' Load an instance of this class through an XmlElement.
    ' </summary>
    ' <param name="element">The XmlElement version of the token</param>
    Public Overrides Sub LoadXml(ByVal element As XmlElement)
        If (element Is Nothing) Then
            Throw New ArgumentNullException("element")
        End If
    
        ' Reset existing contents
        _lifeTime = Nothing
        _key = Nothing
        _keysXml = Nothing
        _proofToken = Nothing
    
        Dim createdAt As DateTime = DateTime.MinValue
        Dim expiresAt As DateTime = DateTime.MaxValue
    
        Select Case element.LocalName
            Case XmlTokenNames.ElementNames.Token
                ' get the Id out of the token
                Id = element.GetAttribute(WSUtility.AttributeNames.Id, WSUtility.NamespaceURI)
    
                Dim child As XmlNode
                For Each child In element.ChildNodes
                    Select Case child.LocalName
                        Case XmlTokenNames.ElementNames.CreatedAt
                            createdAt = Convert.ToDateTime(child.InnerXml, CultureInfo.InvariantCulture)
                        Case XmlTokenNames.ElementNames.ExpiresAt
                            expiresAt = Convert.ToDateTime(child.InnerXml, CultureInfo.InvariantCulture)
                        Case XmlEncryption.ElementNames.EncryptedKey
                            _keysXml = CType(New XmlDocument().ImportNode(child, True), XmlElement)
                        Case XmlSignature.ElementNames.Signature
                            _signedXml = CType(New XmlDocument().ImportNode(child, True), XmlElement)
                    End Select
                Next
        End Select
    
        If (createdAt <> DateTime.MinValue OrElse _
            expiresAt <> DateTime.MaxValue) Then
            _lifeTime = New LifeTime(createdAt, expiresAt)
        End If
    End Sub
    
    /// <summary>
    ///  Serialize this class to an Xmlelement
    /// </summary>
    /// <param name="document">the newly generated Xmlelment belongs to this Xmldocument</param>
    /// <returns></returns>
    public override XmlElement GetXml(XmlDocument document) 
    {
        if (document == null)
            throw new ArgumentNullException("document");
    
        XmlElement element = document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.Token, XmlTokenNames.Namespace);
    
        //
        // The id attribute
        //
        if ( Id != null && Id.Length != 0 )
        {
            XmlAttribute attr = document.CreateAttribute(WSUtility.Prefix, WSUtility.AttributeNames.Id, WSUtility.NamespaceURI);
            attr.InnerText = Id;
            element.Attributes.Append(attr);
        }
    
        //
        // CreatedAt
        //
        if (_lifeTime != null && _lifeTime.Created != DateTime.MinValue)
        {
            XmlElement createdAt = document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.CreatedAt, XmlTokenNames.Namespace);
            createdAt.InnerText = XmlConvert.ToString(_lifeTime.Created.ToUniversalTime(), WSUtility.TimeFormat);
            element.AppendChild(createdAt);
        }
    
        //
        // ExpiresAt
        //
        if (_lifeTime != null && _lifeTime.Expires != DateTime.MinValue)
        {
            XmlElement expiresAt = document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.ExpiresAt, XmlTokenNames.Namespace );
            expiresAt.InnerText = XmlConvert.ToString(_lifeTime.Expires.ToUniversalTime(), WSUtility.TimeFormat);
            element.AppendChild(expiresAt);
        }
    
        //
        // _keys: use _keysXml if we don't understand it
        //
        if (_keysXml != null )
            element.AppendChild(document.ImportNode(_keysXml,true));
        else if ( _serviceToken != null )
            element.AppendChild(new EncryptedKey(_serviceToken, _key.KeyBytes).GetXml(document));
    
        //
        // envelope signature
        //
        if ( _signedXml != null )
            element.AppendChild(document.ImportNode(_signedXml,true));
    
        return element;
    }
    
    /// <summary>
    /// Load an instance of this class through an XmlElement.
    /// </summary>
    /// <param name="element">The XmlElement version of the token</param>
    public override void LoadXml(XmlElement element) 
    {
        if ( null == element )
            throw new ArgumentNullException("element");
    
        // Reset existing contents
        _lifeTime       = null;
        _key            = null;
        _keysXml        = null;
        _proofToken     = null;
    
        DateTime createdAt = DateTime.MinValue;
        DateTime expiresAt = DateTime.MaxValue;
    
        switch ( element.LocalName ) 
        {
            case XmlTokenNames.ElementNames.Token:
                // get the Id out of the token
                Id = element.GetAttribute(WSUtility.AttributeNames.Id, WSUtility.NamespaceURI);
    
                foreach(XmlNode child in element.ChildNodes ) 
                {
                    switch ( child.LocalName ) 
                    {
                        case XmlTokenNames.ElementNames.CreatedAt:
                            createdAt = Convert.ToDateTime(child.InnerXml, CultureInfo.InvariantCulture);
                            break;
                        case XmlTokenNames.ElementNames.ExpiresAt:
                            expiresAt = Convert.ToDateTime(child.InnerXml, CultureInfo.InvariantCulture);                                
                            break;
                        case XmlEncryption.ElementNames.EncryptedKey:
                            _keysXml = new XmlDocument().ImportNode(child, true) as XmlElement;
                            break;
                        case XmlSignature.ElementNames.Signature:
                            _signedXml = new XmlDocument().ImportNode(child, true) as XmlElement;
                            break;
                        default:
                            break;                                    
                    }
                }
                break;
        }
    
        if ( createdAt != DateTime.MinValue || expiresAt != DateTime.MaxValue )
            _lifeTime = new LifeTime(createdAt, expiresAt);
    }
    
  2. Override the SupportsDigitalSignature and SupportsDataEncryption properties.

    These Boolean properties indicate whether the custom XML security can be used for digital signatures and data encryption.

    The following code example specifies that the custom XML security token can be used for both digitally signing and encrypting SOAP messages.

    ' <summary>
    ' Returns true if the token supports Data Encryption.
    ' </summary>
    Public Overrides ReadOnly Property SupportsDataEncryption() As Boolean
        Get
            Return True
        End Get
    End Property
    
    ' <summary>
    ' Returns true if the token supports Digital Signature.
    ' </summary>
    Public Overrides ReadOnly Property SupportsDigitalSignature() As Boolean
        Get
            Return True
        End Get
    End Property
    
    /// <summary>
    /// Returns true if the token supports Data Encryption.
    /// </summary>
    public override bool SupportsDataEncryption
    {
        get
        {
            return true;
        }
    }
    
    /// <summary>
    /// Returns true if the token supports Digital Signature.
    /// </summary>
    public override bool SupportsDigitalSignature
    {
        get
        {
            return true;
        }
    }
    
  3. Override the Key property.

    The Key property is accessed by WSE to specify the key that is used to perform cryptographic operations on a SOAP message, including signing and encryption.

    The following code example returns a key for the security token that is generated using the AES-128 key algorithm.

    ' <summary>
    ' Returns the key for the security token.
    ' </summary>
    Public Overrides ReadOnly Property Key() As KeyAlgorithm
        Get
            If (Not _proofToken Is Nothing) Then
                '
                ' Attempt recovery from the proof token.
                '    
                _key = CType(KeyAlgorithm.Create("AES128"), SymmetricKeyAlgorithm)
                _key.KeyBytes = _proofToken.KeyBytes
            ElseIf (Not _keysXml Is Nothing) Then
                '
                ' Attempt key recovery from the encrypted form.
                '
                Dim encryptedKey As EncryptedKey = New EncryptedKey(_keysXml)
    
                encryptedKey.Decrypt()
    
                _key = CType(KeyAlgorithm.Create("AES128"), SymmetricKeyAlgorithm)
                _key.KeyBytes = encryptedKey.KeyBytes
            End If
    
            Return _key
        End Get
    End Property
    
    /// <summary>
    /// Returns the key for the security token.
    /// </summary>
    public override KeyAlgorithm Key
    {
        get
        {
            if ( _proofToken != null )
            {
                //
                // Attempt recovery from the proof token.
                //    
                _key          = (SymmetricKeyAlgorithm)KeyAlgorithm.Create("AES128");
                _key.KeyBytes = _proofToken.KeyBytes;
            }
            else if ( _keysXml != null )
            {
                //
                // Attempt key recovery from the encrypted form.
                //
                EncryptedKey encryptedKey = new EncryptedKey(_keysXml);
    
                encryptedKey.Decrypt();
    
                _key          = (SymmetricKeyAlgorithm)KeyAlgorithm.Create("AES128");
                _key.KeyBytes = encryptedKey.KeyBytes;
            }
    
            return _key;
        }
    }
    
  4. Override the GetHashCode and Equals methods.

    The GetHashCode method serves as a hash function for the custom XML security token when the type is added to a Hashtable collection. Override the Equals method so that the Hashtable collection works correctly. The Equals method guarantees that two objects, which are considered equal, have the same hash code. Equality for this example is based on the key bytes. For more information about these two methods, see Object.GetHashCode.

    The following code example creates a hash code based on the security token's expiration date and time, key, and the name of the key container.

    ' <summary>
    ' Return true if two tokens have the same key material.
    ' </summary>
    Public Overloads Overrides Function Equals(ByVal token As SecurityToken) As Boolean
        If (token Is Nothing OrElse Not (TypeOf token Is XmlToken)) Then
            Return False
        End If
    
        Dim xmlToken As XmlToken = CType(token, XmlToken)
    
        If (Not (_key.KeyBytes Is Nothing) AndAlso _
            Not (xmlToken._key.KeyBytes Is Nothing) AndAlso _
            (_key.KeyBytes.Length = xmlToken._key.KeyBytes.Length)) Then
            Dim index As Integer = _key.KeyBytes.Length
            While (--index > -1)
                If (_key.KeyBytes(index) <> xmlToken._key.KeyBytes(index)) Then
                    Return False
                End If
            End While
            Return True
        ElseIf ((_key.KeyBytes Is Nothing) AndAlso _
            (xmlToken._key.KeyBytes Is Nothing)) Then
            Return True
        Else
            Return False
        End If
    End Function
    
    ' <summary>
    ' Return the hash of the key material for this token.
    ' </summary>
    Public Overrides Function GetHashCode() As Integer
        Dim h As Integer = 0
    
        If (Not _key Is Nothing) Then
            h += _key.KeyBytes.GetHashCode()
        End If
        Return h
    End Function
    
    /// <summary>
    /// Return true if two tokens have the same key material.
    /// </summary>
    public override bool Equals(SecurityToken token)
    {
        if (token == null || !(token is XmlToken))
            return false;
    
        XmlToken xmlToken = (XmlToken)token;
    
        if (_key.KeyBytes != null && xmlToken._key.KeyBytes != null &&
            _key.KeyBytes.Length == xmlToken._key.KeyBytes.Length)
        {
            int index = _key.KeyBytes.Length;
            while (--index > -1)
                if (_key.KeyBytes[index] != xmlToken._key.KeyBytes[index])
                    return false;
            return true;
        }
        else if (_key.KeyBytes == null && xmlToken._key.KeyBytes == null)
            return true;
        else
            return false;
    }
    
    /// <summary>
    /// Return the hash of the key material for this token.
    /// </summary>
    public override int GetHashCode()
    {
        int h = 0;
    
        if (_key != null)
            h += _key.KeyBytes.GetHashCode();
    
        return h;
    }
    
  5. Override the IsCurrent method.

    The IsCurrent method determines whether the security token is valid at the current moment.

    The following code example verifies that the security token has not expired.

    ' <summary>
    ' Determines if the current token is valid for security operations.
    ' </summary>
    Public Overrides ReadOnly Property IsCurrent() As Boolean
        Get
            Return (_lifeTime Is Nothing OrElse _
                _lifeTime.IsCurrent)
        End Get
    End Property
    
    /// <summary>
    /// Determines if the current token is valid for security operations.
    /// </summary>
    public override bool IsCurrent
    {
        get
        {
            return (_lifeTime == null || _lifeTime.IsCurrent);
        }
    }      
    

Example

The following code example demonstrates how to build a class that represents a custom XML security token.

Imports System
Imports System.Security.Cryptography
Imports System.Security.Permissions
Imports System.Text
Imports System.Globalization
Imports System.Xml
Imports System.Configuration

Imports Microsoft.Web.Services3.Addressing
Imports Microsoft.Web.Services3.Design
Imports Microsoft.Web.Services3.Security
Imports Microsoft.Web.Services3.Security.Cryptography
Imports Microsoft.Web.Services3.Security.Utility
Imports Microsoft.Web.Services3.Security.Tokens
Namespace CustomXmlSecTokenCode
    <SecurityPermission(SecurityAction.Demand, Flags:=SecurityPermissionFlag.UnmanagedCode)> _
    Public Class XmlToken
        Inherits SecurityToken
        Implements IIssuedToken

        ' The encrypted form of the key for the token.
        Private _keysXml As XmlElement = Nothing
        ' The encrypted form of the key supplied as a proof token.
        Private _proofToken As RequestedProofToken = Nothing
        ' _serviceToken is used to encrypt the key for the target service.
        Private _serviceToken As SecurityToken = Nothing
        ' The key for the token. This will be an AES128 key.
        ' We use AES128 as our Symmetric Algorithm.
        Private _key As SymmetricKeyAlgorithm = Nothing
        ' life time of this token
        Private _lifeTime As LifeTime = Nothing

        ' For any other extensible element
        Private _signedXml As XmlElement = Nothing

        ' <summary>
        ' The security token manager uses this constructor to instantiate
        ' a security token from KeyInfo clauses.
        ' </summary>
        Public Sub New(ByVal serviceToken As SecurityToken)
            MyBase.New(XmlTokenNames.TypeNames.TokenType)

            ' Set the lift time
            _lifeTime = New LifeTime(DateTime.Now, 8 * 60 * 60)

            ' Now generate a key.
            _key = CType(KeyAlgorithm.Create("AES128"), SymmetricKeyAlgorithm)
            _key.GenerateKey()

            ' Generate the encrypted form of the key.
            _serviceToken = serviceToken
        End Sub

        ' <summary>
        ' This constructor deserializes an XmlToken from its xml representation.
        ' </summary>
        ' <param name="element"></param>
        Public Sub New(ByVal element As XmlElement)
            MyBase.New(XmlTokenNames.TypeNames.TokenType)
            LoadXml(element)
        End Sub

        ' <summary>
        ' Not used.
        ' </summary>
        Public Property AppliesTo() As AppliesTo Implements IIssuedToken.AppliesTo
            Get
                Return Nothing
            End Get
            Set(ByVal Value As AppliesTo)

            End Set
        End Property

        ' <summary>
        ' Not used.
        ' </summary>
        Public Property TokenIssuer() As EndpointReference Implements IIssuedToken.TokenIssuer
            Get
                Return Nothing
            End Get
            Set(ByVal Value As EndpointReference)

            End Set
        End Property

        ' <summary>
        ' Get/Set a life time for this binary token, including creation
        ' time and expiration time.
        ' </summary>
        Public Property LifeTime() As LifeTime Implements IIssuedToken.LifeTime
            Get
                Return _lifeTime
            End Get
            Set(ByVal Value As LifeTime)
                _lifeTime = Value
            End Set
        End Property
 
        ' <summary>
        ' Not used.
        ' </summary>
        Public Property BaseToken() As SecurityToken Implements IIssuedToken.BaseToken
            Get
                Return Nothing
            End Get
            Set(ByVal Value As SecurityToken)

            End Set
        End Property

        ' <summary>
        ' Not used.
        ' </summary>
        Public ReadOnly Property SupportingTokens() As SecurityTokenCollection Implements IIssuedToken.SupportingTokens
            Get
                Return Nothing
            End Get
        End Property

        ' <summary>
        ' Get/Set the proof token for this binary token. This is the 
        ' encrypted form of the key for the token requestor.
        ' </summary>
        Public Property ProofToken() As RequestedProofToken Implements IIssuedToken.ProofToken
            Get
                Return _proofToken
            End Get
            Set(ByVal Value As RequestedProofToken)
                _proofToken = Value
            End Set
        End Property

        ' <summary>
        ' Return true if two tokens have the same key material.
        ' </summary>
        Public Overloads Overrides Function Equals(ByVal token As SecurityToken) As Boolean
            If (token Is Nothing OrElse Not (TypeOf token Is XmlToken)) Then
                Return False
            End If

            Dim xmlToken As XmlToken = CType(token, XmlToken)

            If (Not (_key.KeyBytes Is Nothing) AndAlso _
                Not (xmlToken._key.KeyBytes Is Nothing) AndAlso _
                (_key.KeyBytes.Length = xmlToken._key.KeyBytes.Length)) Then
                Dim index As Integer = _key.KeyBytes.Length
                While (--index > -1)
                    If (_key.KeyBytes(index) <> xmlToken._key.KeyBytes(index)) Then
                        Return False
                    End If
                End While
                Return True
            ElseIf ((_key.KeyBytes Is Nothing) AndAlso _
                (xmlToken._key.KeyBytes Is Nothing)) Then
                Return True
            Else
                Return False
            End If
        End Function

        ' <summary>
        ' Return the hash of the key material for this token.
        ' </summary>
        Public Overrides Function GetHashCode() As Integer
            Dim h As Integer = 0

            If (Not _key Is Nothing) Then
                h += _key.KeyBytes.GetHashCode()
            End If
            Return h
        End Function

        ' <summary>
        ' Returns the key for the security token.
        ' </summary>
        Public Overrides ReadOnly Property Key() As KeyAlgorithm
            Get
                If (Not _proofToken Is Nothing) Then
                    '
                    ' Attempt recovery from the proof token.
                    '    
                    _key = CType(KeyAlgorithm.Create("AES128"), SymmetricKeyAlgorithm)
                    _key.KeyBytes = _proofToken.KeyBytes
                ElseIf (Not _keysXml Is Nothing) Then
                    '
                    ' Attempt key recovery from the encrypted form.
                    '
                    Dim encryptedKey As EncryptedKey = New EncryptedKey(_keysXml)

                    encryptedKey.Decrypt()

                    _key = CType(KeyAlgorithm.Create("AES128"), SymmetricKeyAlgorithm)
                    _key.KeyBytes = encryptedKey.KeyBytes
                End If

                Return _key
            End Get
        End Property

        ' <summary>
        '  Serialize this class to an Xmlelement
        ' </summary>
        ' <param name="document">the newly generated Xmlelment belongs to this Xmldocument</param>
        ' <returns></returns>
        Public Overloads Overrides Function GetXml(ByVal document As XmlDocument) As XmlElement
            If (document Is Nothing) Then
                Throw New ArgumentNullException("document")
            End If

            Dim element As XmlElement = _
                document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.Token, XmlTokenNames.MyNamespace)

            '
            ' The id attribute
            '
            If (Not Id Is Nothing AndAlso _
                Id.Length <> 0) Then
                Dim attr As XmlAttribute = _
                    document.CreateAttribute(WSUtility.Prefix, WSUtility.AttributeNames.Id, WSUtility.NamespaceURI)
                attr.InnerText = Id
                element.Attributes.Append(attr)
            End If

            '
            ' CreatedAt
            '
            If (Not _lifeTime Is Nothing AndAlso _
                _lifeTime.Created <> DateTime.MinValue) Then
                Dim createdAt As XmlElement = _
                    document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.CreatedAt, XmlTokenNames.MyNamespace)
                createdAt.InnerText = _
                    XmlConvert.ToString(_lifeTime.Created.ToUniversalTime(), WSUtility.TimeFormat)
                element.AppendChild(createdAt)
            End If

            '
            ' ExpiresAt
            '
            If (Not _lifeTime Is Nothing AndAlso _
                _lifeTime.Expires <> DateTime.MinValue) Then
                Dim expiresAt As XmlElement = _
                    document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.ExpiresAt, XmlTokenNames.MyNamespace)
                expiresAt.InnerText = _
                    XmlConvert.ToString(_lifeTime.Expires.ToUniversalTime(), WSUtility.TimeFormat)
                element.AppendChild(expiresAt)
            End If

            '
            ' _keys: use _keysXml if we don't understand it
            '
            If (Not _keysXml Is Nothing) Then
                element.AppendChild(document.ImportNode(_keysXml, True))
            ElseIf (Not _serviceToken Is Nothing) Then
                element.AppendChild(New EncryptedKey( _
                    _serviceToken, _key.KeyBytes).GetXml(document))
            End If

            '
            ' envelope signature
            '
            If (Not _signedXml Is Nothing) Then
                element.AppendChild(document.ImportNode(_signedXml, True))
            End If

            Return element
        End Function

        ' <summary>
        ' Load an instance of this class through an XmlElement.
        ' </summary>
        ' <param name="element">The XmlElement version of the token</param>
        Public Overrides Sub LoadXml(ByVal element As XmlElement)
            If (element Is Nothing) Then
                Throw New ArgumentNullException("element")
            End If

            ' Reset existing contents
            _lifeTime = Nothing
            _key = Nothing
            _keysXml = Nothing
            _proofToken = Nothing

            Dim createdAt As DateTime = DateTime.MinValue
            Dim expiresAt As DateTime = DateTime.MaxValue

            Select Case element.LocalName
                Case XmlTokenNames.ElementNames.Token
                    ' get the Id out of the token
                    Id = element.GetAttribute(WSUtility.AttributeNames.Id, WSUtility.NamespaceURI)

                    Dim child As XmlNode
                    For Each child In element.ChildNodes
                        Select Case child.LocalName
                            Case XmlTokenNames.ElementNames.CreatedAt
                                createdAt = Convert.ToDateTime(child.InnerXml, CultureInfo.InvariantCulture)
                            Case XmlTokenNames.ElementNames.ExpiresAt
                                expiresAt = Convert.ToDateTime(child.InnerXml, CultureInfo.InvariantCulture)
                            Case XmlEncryption.ElementNames.EncryptedKey
                                _keysXml = CType(New XmlDocument().ImportNode(child, True), XmlElement)
                            Case XmlSignature.ElementNames.Signature
                                _signedXml = CType(New XmlDocument().ImportNode(child, True), XmlElement)
                        End Select
                    Next
            End Select

            If (createdAt <> DateTime.MinValue OrElse _
                expiresAt <> DateTime.MaxValue) Then
                _lifeTime = New LifeTime(createdAt, expiresAt)
            End If
        End Sub

        ' <summary>
        ' Determines if the current token is valid for security operations.
        ' </summary>
        Public Overrides ReadOnly Property IsCurrent() As Boolean
            Get
                Return (_lifeTime Is Nothing OrElse _
                    _lifeTime.IsCurrent)
            End Get
        End Property

        ' <summary>
        ' Returns true if the token supports Data Encryption.
        ' </summary>
        Public Overrides ReadOnly Property SupportsDataEncryption() As Boolean
            Get
                Return True
            End Get
        End Property

        ' <summary>
        ' Returns true if the token supports Digital Signature.
        ' </summary>
        Public Overrides ReadOnly Property SupportsDigitalSignature() As Boolean
            Get
                Return True
            End Get
        End Property
    End Class
End Namespace
using System;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Globalization;
using System.Xml;
using System.Configuration;

using Microsoft.Web.Services3.Addressing;
using Microsoft.Web.Services3.Design;
using Microsoft.Web.Services3.Security;
using Microsoft.Web.Services3.Security.Cryptography;
using Microsoft.Web.Services3.Security.Utility;
using Microsoft.Web.Services3.Security.Tokens;
namespace CustomXmlSecTokenCode
{
    [SecurityPermission(SecurityAction.Demand, Flags= SecurityPermissionFlag.UnmanagedCode)] 
    public class XmlToken : SecurityToken, IIssuedToken
    {
        // The encrypted form of the key for the token.
        private XmlElement              _keysXml        = null;
        // The encrypted form of the key supplied as a proof token.
        private RequestedProofToken     _proofToken     = null;
        // _serviceToken is used to encrypt the key for the target service.
        private SecurityToken           _serviceToken   = null;  
        // The key for the token. This will be an AES128 key.
        // We use AES128 as our Symmetric Algorithm.
        private SymmetricKeyAlgorithm   _key            = null;
        // life time of this token
        private LifeTime                _lifeTime       = null;

        // For any other extensible element
        private XmlElement              _signedXml      = null;

        /// <summary>
        /// The security token manager uses this constructor to instantiate
        /// a security token from KeyInfo clauses.
        /// </summary>
        public XmlToken(SecurityToken serviceToken) : base(XmlTokenNames.TypeNames.TokenType) 
        {
            // Set the lift time
            _lifeTime   = new LifeTime(DateTime.Now, 8 * 60 * 60);
    
            // Now generate a key.
            _key = (SymmetricKeyAlgorithm)KeyAlgorithm.Create("AES128");
            _key.GenerateKey();          

            // Generate the encrypted form of the key.
            _serviceToken = serviceToken;
        }

        /// <summary>
        /// This constructor deserializes an XmlToken from its xml representation.
        /// </summary>
        /// <param name="element"></param>
        public XmlToken(XmlElement element) : base(XmlTokenNames.TypeNames.TokenType)
        {
            LoadXml(element);            
        }

        /// <summary>
        /// Not used.
        /// </summary>
        public AppliesTo AppliesTo
        {
            get
            {
                return null;
            }
            set
            {
            }
        } 

        /// <summary>
        /// Not used.
        /// </summary>
        public EndpointReference TokenIssuer
        {
            get
            {
                return null;
            }
            set
            {
            }
        }

        /// <summary>
        /// Get/Set a life time for this binary token, including creation
        /// time and expiration time.
        /// </summary>
        public LifeTime LifeTime 
        {
            get
            {
                return _lifeTime;
            }
            set 
            {
                _lifeTime = value;
            }
        }
        /// <summary>
        /// Not used.
        /// </summary>
        public SecurityToken BaseToken
        {
            get
            {
                return null;
            }
            set
            {
            }
        }

        /// <summary>
        /// Not used.
        /// </summary>
        public SecurityTokenCollection SupportingTokens
        {
            get
            {
                return null;
            }
            set
            {
            }
        }
        
        /// <summary>
        /// Get/Set the proof token for this binary token. This is the 
        /// encrypted form of the key for the token requestor.
        /// </summary>
        public RequestedProofToken ProofToken
        {
            get
            {
                return _proofToken;
            }
            set
            {
                _proofToken = value;
            }
        }

        /// <summary>
        /// Return true if two tokens have the same key material.
        /// </summary>
        public override bool Equals(SecurityToken token)
        {
            if (token == null || !(token is XmlToken))
                return false;

            XmlToken xmlToken = (XmlToken)token;

            if (_key.KeyBytes != null && xmlToken._key.KeyBytes != null &&
                _key.KeyBytes.Length == xmlToken._key.KeyBytes.Length)
            {
                int index = _key.KeyBytes.Length;
                while (--index > -1)
                    if (_key.KeyBytes[index] != xmlToken._key.KeyBytes[index])
                        return false;
                return true;
            }
            else if (_key.KeyBytes == null && xmlToken._key.KeyBytes == null)
                return true;
            else
                return false;
        }

        /// <summary>
        /// Return the hash of the key material for this token.
        /// </summary>
        public override int GetHashCode()
        {
            int h = 0;
    
            if (_key != null)
                h += _key.KeyBytes.GetHashCode();

            return h;
        }

        /// <summary>
        /// Returns the key for the security token.
        /// </summary>
        public override KeyAlgorithm Key
        {
            get
            {
                if ( _proofToken != null )
                {
                    //
                    // Attempt recovery from the proof token.
                    //    
                    _key          = (SymmetricKeyAlgorithm)KeyAlgorithm.Create("AES128");
                    _key.KeyBytes = _proofToken.KeyBytes;
                }
                else if ( _keysXml != null )
                {
                    //
                    // Attempt key recovery from the encrypted form.
                    //
                    EncryptedKey encryptedKey = new EncryptedKey(_keysXml);

                    encryptedKey.Decrypt();

                    _key          = (SymmetricKeyAlgorithm)KeyAlgorithm.Create("AES128");
                    _key.KeyBytes = encryptedKey.KeyBytes;
                }

                return _key;
            }
        }

        /// <summary>
        ///  Serialize this class to an Xmlelement
        /// </summary>
        /// <param name="document">the newly generated Xmlelment belongs to this Xmldocument</param>
        /// <returns></returns>
        public override XmlElement GetXml(XmlDocument document) 
        {
            if (document == null)
                throw new ArgumentNullException("document");

            XmlElement element = document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.Token, XmlTokenNames.Namespace);

            //
            // The id attribute
            //
            if ( Id != null && Id.Length != 0 )
            {
                XmlAttribute attr = document.CreateAttribute(WSUtility.Prefix, WSUtility.AttributeNames.Id, WSUtility.NamespaceURI);
                attr.InnerText = Id;
                element.Attributes.Append(attr);
            }

            //
            // CreatedAt
            //
            if (_lifeTime != null && _lifeTime.Created != DateTime.MinValue)
            {
                XmlElement createdAt = document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.CreatedAt, XmlTokenNames.Namespace);
                createdAt.InnerText = XmlConvert.ToString(_lifeTime.Created.ToUniversalTime(), WSUtility.TimeFormat);
                element.AppendChild(createdAt);
            }

            //
            // ExpiresAt
            //
            if (_lifeTime != null && _lifeTime.Expires != DateTime.MinValue)
            {
                XmlElement expiresAt = document.CreateElement(XmlTokenNames.Prefix, XmlTokenNames.ElementNames.ExpiresAt, XmlTokenNames.Namespace );
                expiresAt.InnerText = XmlConvert.ToString(_lifeTime.Expires.ToUniversalTime(), WSUtility.TimeFormat);
                element.AppendChild(expiresAt);
            }

            //
            // _keys: use _keysXml if we don't understand it
            //
            if (_keysXml != null )
                element.AppendChild(document.ImportNode(_keysXml,true));
            else if ( _serviceToken != null )
                element.AppendChild(new EncryptedKey(_serviceToken, _key.KeyBytes).GetXml(document));

            //
            // envelope signature
            //
            if ( _signedXml != null )
                element.AppendChild(document.ImportNode(_signedXml,true));

            return element;
        }

        /// <summary>
        /// Load an instance of this class through an XmlElement.
        /// </summary>
        /// <param name="element">The XmlElement version of the token</param>
        public override void LoadXml(XmlElement element) 
        {
            if ( null == element )
                throw new ArgumentNullException("element");

            // Reset existing contents
            _lifeTime       = null;
            _key            = null;
            _keysXml        = null;
            _proofToken     = null;

            DateTime createdAt = DateTime.MinValue;
            DateTime expiresAt = DateTime.MaxValue;

            switch ( element.LocalName ) 
            {
                case XmlTokenNames.ElementNames.Token:
                    // get the Id out of the token
                    Id = element.GetAttribute(WSUtility.AttributeNames.Id, WSUtility.NamespaceURI);

                    foreach(XmlNode child in element.ChildNodes ) 
                    {
                        switch ( child.LocalName ) 
                        {
                            case XmlTokenNames.ElementNames.CreatedAt:
                                createdAt = Convert.ToDateTime(child.InnerXml, CultureInfo.InvariantCulture);
                                break;
                            case XmlTokenNames.ElementNames.ExpiresAt:
                                expiresAt = Convert.ToDateTime(child.InnerXml, CultureInfo.InvariantCulture);                                
                                break;
                            case XmlEncryption.ElementNames.EncryptedKey:
                                _keysXml = new XmlDocument().ImportNode(child, true) as XmlElement;
                                break;
                            case XmlSignature.ElementNames.Signature:
                                _signedXml = new XmlDocument().ImportNode(child, true) as XmlElement;
                                break;
                            default:
                                break;                                    
                        }
                    }
                    break;
            }

            if ( createdAt != DateTime.MinValue || expiresAt != DateTime.MaxValue )
                _lifeTime = new LifeTime(createdAt, expiresAt);
        }

        /// <summary>
        /// Determines if the current token is valid for security operations.
        /// </summary>
        public override bool IsCurrent
        {
            get
            {
                return (_lifeTime == null || _lifeTime.IsCurrent);
            }
        }      

        /// <summary>
        /// Returns true if the token supports Data Encryption.
        /// </summary>
        public override bool SupportsDataEncryption
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Returns true if the token supports Digital Signature.
        /// </summary>
        public override bool SupportsDigitalSignature
        {
            get
            {
                return true;
            }
        }
    }
}

See Also

Tasks

How to: Create a Security Token Manager for a Custom Security Token

Reference

SecurityToken Class

Other Resources

Creating Custom Security Tokens