How to: Verify Digital Signatures of SOAP Messages Signed Using a User Name and Password

WSE validates that digital signatures are cryptographically correct. However, you must use code to verify that a signature exists and that the signature applies to the expected set of XML elements. Signature validation is done by WSE before recipient code executes when WSE is enabled to run with the recipient.

Note

The UsernameToken security token should only be used as a token that identifies the client and not for digital signing or encrypting SOAP messages. When a SOAP message is digitally signed or encrypted by using a UsernameToken security token, it is susceptible to a dictionary attack. Instead, use the UsernameToken security token for identity and use an EncryptedKeyToken security token to digitally sign or encrypt the SOAP messages. The <usernameForCertificateSecurity> Element turnkey security assertion uses this model.

To validate digital signatures for incoming SOAP messages created using a UsernameToken, WSE must be configured to process the incoming SOAP messages. An additional two steps are required when the UsernameToken does not represent a Windows account. When the UsernameToken is a Windows account and WSE is processing the incoming SOAP messages, WSE attempts to authenticate the account and if successful, sets the Prinicpal property of the token.

To configure WSE to validate digital signatures created using a UsernameToken for incoming SOAP messages

  1. In the Web.config file for the Web application that is hosting the Web service, include an <soapServerProtocolFactory> Element element in the <webServices> section.

    This step is only required when the recipient of the SOAP message is hosted in ASP.NET. When the SOAP message recipient is a Web service client or the TCP protocol is used with SOAP messaging, this configuration entry is not required. For Web service clients, the base class of the proxy class must be changed to derive from the WebServicesClientProtocol class.

    The following code example shows the configuration entry that must be placed in the Web.config file for WSE to run with a Web service. The type attribute of the <soapServerProtocolFactory> Element element must be on one line, even though the following sample shows it split across multiple lines for readability.

    <configuration>
       <system.web>
            <webServices>
                <soapServerProtocolFactory type="Microsoft.Web.Services3.WseProtocolFactory, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
            </webServices>
        </system.web>
       </system.web>
    </configuration>
    
  2. Optionally, create and register a custom security token manager for UsernameToken security tokens.

    When you do not want WSE to authenticate UsernameToken security tokens against Active Directory™ or the UsernameToken is not based on a Windows account, you must derive a class from UsernameTokenManager.

    When you do want WSE to authenticate the UsernameToken against Active Directory, you do not need to create your own class that derives from the UsernameTokenManager class. However, incoming SOAP messages must contain the password in plain text (PasswordOption.SendPlainText). Because the password is being sent in plain text, it is highly recommended that the UsernameToken be encrypted.

    1. Add a new class to the project containing the SOAP message recipient. Derive the class from the UsernameTokenManager class.
      Once the security token manager class is registered in the application's configuration file, WSE calls the class's AuthenticateToken method to get the password for a given user name of the class whenever incoming SOAP messages are received that contain UsernameToken security tokens.

      Public Class CustomUsernameTokenManager
          Inherits UsernameTokenManager
      public class CustomUsernameTokenManager : UsernameTokenManager 
      
    2. Ensure that an arbitrary assembly cannot access the class deriving from the UsernameTokenManager class.
      It is recommended that you demand that an assembly accessing this class already have permission to call unmanaged code. Because Microsoft.Web.Services3 is the only assembly that should call this class, you might consider further restricting access to assemblies only signed by Microsoft.
      The following code example applies the SecurityPermissionAttribute attribute to the class deriving from the UsernameTokenManager class, demanding the UnmanagedCode permission.

      <SecurityPermission(SecurityAction.Demand, _
          Flags:=SecurityPermissionFlag.UnmanagedCode)> _
      Public Class CustomUsernameTokenManager
          Inherits UsernameTokenManager
      
      [SecurityPermission(SecurityAction.Demand,
           Flags= SecurityPermissionFlag.UnmanagedCode)]
      public class CustomUsernameTokenManager : UsernameTokenManager 
      
    3. Override the AuthenticateToken method of the UsernameTokenManager class.
      When a SOAP message is received that contains a UsernameToken, WSE deserializes the UsernameToken and calls the VerifyToken method. The default implementation of the VerifyToken method of the UsernameTokenManager class calls the AuthenticateToken method, and then compares the returned password with the one passed in the SOAP message. The password returned from the AuthenticateToken method must match the password specified by the SOAP sender or a SecurityFault exception is thrown.

      Note

      The AuthenticateToken method is equivalent to the PasswordProvider.GetPassword method in WSE 1.0.

      The following code example overrides the AuthenticateToken method by providing an implementation of the method that gets the UTF-8 encoding of the user name.

      Protected Overrides Function AuthenticateToken(ByVal userName As UsernameToken) _
          As String
      
          ' Ensure that the SOAP message sender passed a UsernameToken.
          If userName Is Nothing Then
              Throw New ArgumentNullException()
          End If
      
          ' This is a very simple provider.
          ' In most production systems the following code 
          ' typically consults an external database of (userName,hash)
          ' pairs. For this example, it is the UTF-8
          ' encoding of the user name.
          Dim password As Byte() = _
              System.Text.Encoding.UTF8.GetBytes(userName.Username)
      
          Array.Reverse(password)
      
          Return Convert.ToBase64String(password)
      End Function
      
      protected override string AuthenticateToken(UsernameToken userName)
      {
          // Ensure that the SOAP message sender passed a UsernameToken.
          if (userName == null)
              throw new ArgumentNullException();
      
          // This is a very simple provider.
          // In most production systems the following code 
          // typically consults an external database of (userName,hash)
          // pairs. For this example, it is the UTF-8
          // encoding of the user name.
          byte[] password =
              System.Text.Encoding.UTF8.GetBytes(userName.Username);
          Array.Reverse( password );
      
          return Convert.ToBase64String( password );
      }
      
    4. Configure the microsoft.web.services3 configuration section handler for the application's configuration file by adding a <section> Element element to the <configuration> section.
      The following code example shows how to add the microsoft.web.services3 configuration section handler.

      <configuration>
        <configSections>
          <section name="microsoft.web.services3"
            type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </configSections>
      </configuration>
      
    5. Configure the custom UsernameToken security token manager in the application's configuration file.
      In the <configuration> section, add the <microsoft.web.services3>, <security>, and <securityTokenManager> elements.
      The following code example configures the MyNamespace.CustomUsernameTokenProvider security token manager to be called whenever a SOAP message containing a UsernameToken is received for recipients affected by this configuration file. The value of the type attribute must all appear on one line; it appears on multiple lines in the following example for readability.

      <microsoft.web.services3>
        <security>
          <securityTokenManager>
            <add localName="UsernameToken"
                 type="MyNamespace.CustomUsernameTokenProvider, MyAssemblyName,
            Version=3.0.0.0,
            Culture=neutral,
            PublicKeyToken=81f0828a1c0bb867"
                 namespace="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" >
            </add>
          </securityTokenManager>
        </security>
      </microsoft.web.services3>
      

    Note

    Because the type attribute does not need to reference a type residing in a strong-named assembly, the Version, Culture, and PublicKeyToken parts of the assembly name are optional.

To use code to require that incoming SOAP messages be signed using a UsernameToken and that the required XML elements are signed

  1. Create a custom policy assertion.

    For more information about creating custom policy assertions, see How to: Create a Custom Policy Assertion that Secures SOAP Messages.

  2. In the input SOAP filter for the client or the Web service that receives the signed SOAP messages, override the ValidateMessageSecurity method.

    The following code example overrides the ValidateMessageSecurity method for the Web service input SOAP filter.

    Public Overrides Sub ValidateMessageSecurity(ByVal envelope As SoapEnvelope, ByVal security As Security)
    
    public override void  ValidateMessageSecurity(SoapEnvelope envelope, Security security)
    {
    
  3. Verify that the expected XML elements are signed using a UsernameToken security token.

    The following code example verifies that the <Body> element and the WS-Addressing headers are signed of the SOAP request are signed using a UsernameToken security token.

    Dim IsSigned As Boolean = False
    Dim element As ISecurityElement
    For Each element In security.Elements
        If (TypeOf (element) Is MessageSignature) Then
            ' The SoapContext contains a Signature element. 
            Dim sign As MessageSignature = element
            Dim expectedOptions As SignatureOptions = SignatureOptions.IncludeTimestamp Or _
                                                      SignatureOptions.IncludeSoapBody Or _
                                                      SignatureOptions.IncludeTo Or _
                                                      SignatureOptions.IncludeAction Or _
                                                      SignatureOptions.IncludeMessageId
    
            If ((sign.SignatureOptions And expectedOptions) = expectedOptions) Then
                If TypeOf sign.SigningToken Is UsernameToken Then
                    ' The SOAP message is signed with a
                    ' UsernameToken.
                    IsSigned = True
                End If
            End If
        End If
    Next
    If (Not IsSigned) Then
        Throw New SecurityFault("Message did not meet security requirements.")
    End If
    
    bool IsSigned = false;
    foreach (ISecurityElement element in security.Elements)
    {
        if (element is MessageSignature)
        {
            // The given context contains a Signature element.
            MessageSignature sign = element as MessageSignature;
    
            SignatureOptions expectedOptions = SignatureOptions.IncludeTimestamp |
                                               SignatureOptions.IncludeSoapBody |
                                               SignatureOptions.IncludeTo |
                                               SignatureOptions.IncludeAction |
                                               SignatureOptions.IncludeMessageId;
            if ((sign.SignatureOptions & expectedOptions) == expectedOptions)
            {
                // The SOAP message is signed.
                if (sign.SigningToken is UsernameToken)
                    // The SOAP message is signed 
                    // with a UsernameToken.
                    IsSigned = true;
            }
        }
    }
    if (!IsSigned)
        throw new SecurityFault("Message did not meet security requirements.");
    

Example

The following code example verifies that the <Body> element and WS-Addressing headers for SOAP requests are signed using a UsernameToken security token

Public Overrides Sub ValidateMessageSecurity(ByVal envelope As SoapEnvelope, ByVal security As Security)
    Dim IsSigned As Boolean = False
    Dim element As ISecurityElement
    For Each element In security.Elements
        If (TypeOf (element) Is MessageSignature) Then
            ' The SoapContext contains a Signature element. 
            Dim sign As MessageSignature = element
            Dim expectedOptions As SignatureOptions = SignatureOptions.IncludeTimestamp Or _
                                                      SignatureOptions.IncludeSoapBody Or _
                                                      SignatureOptions.IncludeTo Or _
                                                      SignatureOptions.IncludeAction Or _
                                                      SignatureOptions.IncludeMessageId

            If ((sign.SignatureOptions And expectedOptions) = expectedOptions) Then
                If TypeOf sign.SigningToken Is UsernameToken Then
                    ' The SOAP message is signed with a
                    ' UsernameToken.
                    IsSigned = True
                End If
            End If
        End If
    Next
    If (Not IsSigned) Then
        Throw New SecurityFault("Message did not meet security requirements.")
    End If
End Sub 'ValidateMessageSecurity
public override void  ValidateMessageSecurity(SoapEnvelope envelope, Security security)
{

    bool IsSigned = false;
    foreach (ISecurityElement element in security.Elements)
    {
        if (element is MessageSignature)
        {
            // The given context contains a Signature element.
            MessageSignature sign = element as MessageSignature;

            SignatureOptions expectedOptions = SignatureOptions.IncludeTimestamp |
                                               SignatureOptions.IncludeSoapBody |
                                               SignatureOptions.IncludeTo |
                                               SignatureOptions.IncludeAction |
                                               SignatureOptions.IncludeMessageId;
            if ((sign.SignatureOptions & expectedOptions) == expectedOptions)
            {
                // The SOAP message is signed.
                if (sign.SigningToken is UsernameToken)
                    // The SOAP message is signed 
                    // with a UsernameToken.
                    IsSigned = true;
            }
        }
    }
    if (!IsSigned)
        throw new SecurityFault("Message did not meet security requirements.");
}

See Also

Tasks

How to: Sign a SOAP Message by Using a User Name and Password
How to: Digitally Sign a SOAP Message

Reference

UsernameToken
UsernameTokenManager
<securityTokenManager> Element