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:
- Create a class that derives from the SecurityToken class.
- Apply an appropriate security permission attribute.
- Implement constructors with parameters that reflect your token's data.
- Override the appropriate methods and properties of your class.
To create a class that derives from the SecurityToken class
Create a new Class Library project in Visual Studio 2005.
Add references to the Microsoft.Web.Services3, System.Web.Services, System.Security, and System.Xml assemblies.
- In Solution Explorer, right-click References, and then click Add Reference.
- 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.
- Click OK.
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;
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
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; } }
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
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); }
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; } }
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; } }
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; }
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