Tuesday, June 9, 2009

Steps to use VS 2008 with WSE 3.0 to create client for calling to Axis2 Web Services WSDL file

I've been working on this case a few days and finally made it work.
The case need to create .net framework 3 web site client to refer the Axis2 web services with WSS4J Security (WS-Security specification 1.0). Need to custom SOAP Header and signature.
As all knows, visual studio 2008 doesn't integrated with WSE 3.0, also since the current VS project is web site instead of web application, the proxy file of web service is dynamic with DLL file which means you are not able to change it. Here find a new way to tackle with it.

Here's the main steps and code reference.
  1. Setup WSE 3.0 (Download from Microsoft web site and install)
  2. Convert the JKS to PFX keystore file. ( Mentioned at last blog post)
  3. Create a new Class Library project into the solution.
  4. Add web reference and designate the WSDL (Provided from Axis2 web service) file. ( You will find the proxy file named reference.vb in the same folder with wsdl file at this Class Library project.
  5. Click to open WSE 3.0 configuration tool and click menu open the file app.config which in Class Library project folder.
  6. Click on "Enable this project for Web Services Enhancements" under General panel, and Enable Policy and Add a new Policy with a new name under Policy panel. If you want to get debugging soap packet, Enable Message Trace and designate Input and Output File under Diagnostics panel and then click menu File and Save it.
  7. Update part of the app.config as follows snapshot:
  8. Find the file named wse3policyCache.config and update it as snapshot:
  9. Create a new class file as bottom coding:
  10. Build the Class Library project and debugging.
  11. From main web site, add reference for this new project and there should be a new dll happens in Bin folder.
  12. Refer to the DLL project namespace with web service name to access.
  13. Rebuild main web site and test them out.
File: CustomSecurityAssertion.vb

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Xml
Imports System.Security.Cryptography.X509Certificates

Imports Microsoft.Web.Services3
Imports Microsoft.Web.Services3.Design
Imports Microsoft.Web.Services3.Security
Imports Microsoft.Web.Services3.Security.Tokens
Namespace GiftRegistryProxy

Class CustomSecurityAssertion
Inherits SecurityPolicyAssertion
Dim serviceX509TokenProviderValue As TokenProvider(Of X509SecurityToken)
Dim clientX509TokenProviderValue As TokenProvider(Of X509SecurityToken)

Public Property ClientX509TokenProvider() As TokenProvider(Of X509SecurityToken)

Get

Return clientX509TokenProviderValue
End Get
Set(ByVal value As TokenProvider(Of X509SecurityToken))

clientX509TokenProviderValue = value
End Set
End Property

Public Property ServiceX509TokenProvider() As TokenProvider(Of X509SecurityToken)

Get

Return serviceX509TokenProviderValue
End Get
Set(ByVal value As TokenProvider(Of X509SecurityToken))

serviceX509TokenProviderValue = value
End Set
End Property

Public Sub New()
End Sub 'New

Public Overrides Function CreateClientOutputFilter(ByVal context As FilterCreationContext) As SoapFilter
Return New CustomSecurityClientOutputFilter(Me)
End Function 'CreateClientOutputFilter

Public Overrides Function CreateClientInputFilter(ByVal context As FilterCreationContext) As SoapFilter
Return Nothing
End Function 'CreateClientInputFilter

Public Overrides Function CreateServiceInputFilter(ByVal context As FilterCreationContext) As SoapFilter
Return New CustomSecurityServerInputFilter(Me)
End Function 'CreateServiceInputFilter

Public Overrides Function CreateServiceOutputFilter(ByVal context As FilterCreationContext) As SoapFilter
Return New CustomSecurityServerOutputFilter(Me)
End Function 'CreateServiceOutputFilter

Public Overrides Sub ReadXml(ByVal reader As XmlReader, ByVal extensions As IDictionary(Of String, Type))
If reader Is Nothing Then
Throw New ArgumentNullException("reader")
End If
If extensions Is Nothing Then
Throw New ArgumentNullException("extensions")
End If
Dim isEmpty As Boolean = reader.IsEmptyElement
MyBase.ReadAttributes(reader)
reader.ReadStartElement("CustomSecurityAssertion")

If Not isEmpty Then
' Read the contents of the element.
If reader.MoveToContent() = XmlNodeType.Element AndAlso reader.Name = "clientToken" Then
reader.ReadStartElement()
reader.MoveToContent()

' Get the registed security token provider for X.509 certificate security credentials.
Dim type As Type = extensions(reader.Name)
Dim instance As Object = Activator.CreateInstance(type)

If instance Is Nothing Then
Throw New InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, "Unable to instantiate policy extension of type 0End.", type.AssemblyQualifiedName))
End If
Dim clientProvider As TokenProvider(Of X509SecurityToken) = CType(instance, TokenProvider(Of X509SecurityToken))

' Read the child elements that provide the details about the client's X.509 certificate.
clientProvider.ReadXml(reader, extensions)
Me.ClientX509TokenProvider = clientProvider
reader.ReadEndElement()
End If
' Read the contents of the element.
If reader.MoveToContent() = XmlNodeType.Element AndAlso reader.Name = "serviceToken" Then
reader.ReadStartElement()
reader.MoveToContent()

' Get the registed security token provider for X.509 certificate security credentials.
Dim type As Type = extensions(reader.Name)
Dim instance As Object = Activator.CreateInstance(type)

If instance Is Nothing Then
Throw New InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, "Unable to instantiate policy extension of type 0End.", type.AssemblyQualifiedName))
End If
Dim serviceProvider As TokenProvider(Of X509SecurityToken) = CType(instance, TokenProvider(Of X509SecurityToken))

' Read the child elements that provide the details about the client's X.509 certificate.
serviceProvider.ReadXml(reader, extensions)
Me.ServiceX509TokenProvider = serviceProvider
reader.ReadEndElement()
End If
MyBase.ReadElements(reader, extensions)
reader.ReadEndElement()
End If
End Sub
Public Overrides Function GetExtensions() As IEnumerable(Of KeyValuePair(Of String, Type))

' Add the CustomSecurityAssertion custom policy assertion to the list of registered
' policy extensions.
Dim extensions As New List(Of KeyValuePair(Of String, Type))
extensions.Add(New KeyValuePair(Of String, Type)("CustomSecurityAssertion", Me.GetType()))
If (Not serviceX509TokenProviderValue Is Nothing) Then

' Add any policy extensions that read child elements of the element
' to the list of registered policy extensions.
Dim innerExtensions As IEnumerable(Of KeyValuePair(Of String, Type)) = serviceX509TokenProviderValue.GetExtensions()
If (Not innerExtensions Is Nothing) Then
Dim extension As KeyValuePair(Of String, Type)
For Each extension In innerExtensions
extensions.Add(extension)
Next
End If
End If
If (Not clientX509TokenProviderValue Is Nothing) Then

' Add any policy extensions that read child elements of the element
' to the list of registered policy extensions.
Dim innerExtensions As IEnumerable(Of KeyValuePair(Of String, Type)) = clientX509TokenProviderValue.GetExtensions()
If (Not innerExtensions Is Nothing) Then
Dim extension As KeyValuePair(Of String, Type)
For Each extension In innerExtensions
extensions.Add(extension)
Next
End If
End If
Return extensions
End Function

End Class 'CustomSecurityAssertion

Class RequestState
Private clientTokenValue As SecurityToken
Private serverTokenValue As SecurityToken


Public Sub New(ByVal cToken As SecurityToken, ByVal sToken As SecurityToken)
clientTokenValue = cToken
serverTokenValue = sToken
End Sub 'New


Public ReadOnly Property ClientToken() As SecurityToken
Get
Return clientTokenValue
End Get
End Property

Public ReadOnly Property ServerToken() As SecurityToken
Get
Return serverTokenValue
End Get
End Property
End Class 'RequestState

Class CustomSecurityServerInputFilter
Inherits ReceiveSecurityFilter

Public Sub New(ByVal parentAssertion As CustomSecurityAssertion)
MyBase.New(parentAssertion.ServiceActor, False)
End Sub 'New


Public Overrides Sub ValidateMessageSecurity(ByVal envelope As SoapEnvelope, ByVal security As Security)
Dim clientToken As SecurityToken = Nothing
Dim serverToken As SecurityToken = Nothing

' Ensure incoming SOAP messages are signed and encrypted.
Dim elem As ISecurityElement
For Each elem In security.Elements
If TypeOf elem Is MessageSignature Then
Dim sig As MessageSignature = CType(elem, MessageSignature)
clientToken = sig.SigningToken
End If

If TypeOf elem Is EncryptedData Then
Dim enc As EncryptedData = CType(elem, EncryptedData)
serverToken = enc.SecurityToken
End If
Next elem

If clientToken Is Nothing OrElse serverToken Is Nothing Then
Throw New Exception("Incoming message did not meet security requirements")
End If
Dim state As New RequestState(clientToken, serverToken)

envelope.Context.OperationState.Set(state)
End Sub 'ValidateMessageSecurity
End Class 'CustomSecurityServerInputFilter

Class CustomSecurityServerOutputFilter
Inherits SendSecurityFilter
Public Sub New(ByVal parentAssertion As CustomSecurityAssertion)
MyBase.New(parentAssertion.ServiceActor, False)
End Sub 'New

Public Overrides Sub SecureMessage(ByVal envelope As SoapEnvelope, ByVal security As Security)
Dim state As RequestState = envelope.Context.OperationState.Get(Of RequestState)()

' Sign the message with the Web service's security token.
security.Tokens.Add(state.ServerToken)
security.Elements.Add(New MessageSignature(state.ServerToken))

' Encrypt the message with the client's security token.
security.Elements.Add(New EncryptedData(state.ClientToken))
End Sub 'SecureMessage
End Class 'CustomSecurityServerOutputFilter

Class CustomSecurityClientInputFilter
Inherits ReceiveSecurityFilter

Public Sub New(ByVal parentAssertion As CustomSecurityAssertion)
MyBase.New(parentAssertion.ServiceActor, True)
End Sub 'New

Public Overrides Sub ValidateMessageSecurity(ByVal envelope As SoapEnvelope, ByVal security As Security)
Dim state As RequestState
Dim signed As Boolean = False
Dim encrypted As Boolean = False

' Get the request state out of the operation state.
state = envelope.Context.OperationState.Get(Of RequestState)()

' Make sure the message was signed with the server's security token.
Dim elem As ISecurityElement
For Each elem In security.Elements
If TypeOf elem Is MessageSignature Then
Dim sig As MessageSignature = CType(elem, MessageSignature)

If sig.SigningToken.Equals(state.ServerToken) Then
signed = True
End If
End If
If TypeOf elem Is EncryptedData Then
Dim enc As EncryptedData = CType(elem, EncryptedData)

If enc.SecurityToken.Equals(state.ClientToken) Then
encrypted = True
End If
End If
Next elem
If Not signed OrElse Not encrypted Then
Throw New Exception("Response message does not meet security requirements")
End If
End Sub 'ValidateMessageSecurity
End Class 'CustomSecurityClientInputFilter

Class CustomSecurityClientOutputFilter
Inherits SendSecurityFilter
Private clientToken As SecurityToken
Private serverToken As SecurityToken
Protected keystore As String = System.Configuration.ConfigurationManager.AppSettings("keystore")

Public Sub New(ByVal parentAssertion As CustomSecurityAssertion)
MyBase.New(parentAssertion.ServiceActor, True)
' Get the client security token.
'clientToken = X509TokenProvider.CreateToken(StoreLocation.CurrentUser, StoreName.My, "CN=Customretailergrworksservice, OU=Custom, O=MarCoole, L=Walnut Creek, S=California, C=US")

'####################### Start update for key handler
Dim cert As X509Certificate2 = New X509Certificate2(keystore, "CustomGRWorksServiceRetailer")
clientToken = New X509SecurityToken(cert)
'####################### End update for key handler

' Get the server security token.
serverToken = X509TokenProvider.CreateToken(StoreLocation.LocalMachine, StoreName.My, "CN=Customretailergrworksservice, OU=Custom, O=MarCoole, L=Walnut Creek, S=California, C=US")
End Sub 'New

Public Overrides Sub SecureMessage(ByVal envelope As SoapEnvelope, ByVal security As Security)
'Dim token As X509SecurityToken = GetSecurityToken("CN=Customretailergrworksservice, OU=Custom, O=MarCoole, L=Walnut Creek, S=California, C=US")

'####################### Start update for key handler
Dim cert As X509Certificate2 = New X509Certificate2(keystore, "CustomGRWorksServiceRetailer")
Dim token = New X509SecurityToken(cert)
'####################### End update for key handler

If token Is Nothing Then
Throw New SecurityFault("Message Requirements could not be satisfied.")
End If

' Add the security token to the WS-Security SOAP header.
security.Tokens.Add(token)

' Specify the security token to sign the message with.
Dim sig As New MessageSignature(token)
security.Elements.Add(sig)


'######################################### Start xml header definition
security.EncodedMustUnderstand = "0"

'creating the custom element in the outgoing message
Dim securityNode As XmlNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")

'creating the element
Dim binarySecurityTokenNode As XmlNode = envelope.CreateNode(XmlNodeType.Element, "wsse:BinarySecurityToken", "")
Dim binaryElement As XmlElement = binarySecurityTokenNode
binaryElement.SetAttribute("xmlns:wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")
binaryElement.SetAttribute("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3")
binaryElement.SetAttribute("wsu:Id", "CertId-3221922")
binaryElement.SetAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary")
binarySecurityTokenNode.InnerText = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

securityNode.AppendChild(binarySecurityTokenNode)

envelope.ImportNode(securityNode, True)
Dim node As XmlNode = envelope.Header
node.AppendChild(securityNode)
'######################################### End xml header definition


End Sub 'SecureMessage
End Class 'CustomSecurityClientOutputFilter
End Namespace

How to convert Java JKS keystore to Microsoft PFX certificate

I have some case need to create .NET WSE 3 Client to refer Axis2 Web Services with WSS4J OASIS Security( WS-Security specification 1.0), if you have only the Java format keystore, you have to convert it to MS format for signatures upon web service call.

Steps which were testified successfully follows here:
  1. Download J2SE package and setup JDK and JRE 6 on workstation. (There are many internet information about it, so omit here)
  2. Download KeyTool IUI version 2.4.1 (Match with JRE 6) from (http://yellowcat1.free.fr/index_ktl.html) and unzip to some folder and double click to run file run_ktl.bat to open visiable KeyTool IUI window
  3. Click menu [View] - [Select task] - [Export] - [Keystore's entry] - [Private key]
  4. Click browser icon to pick up the keystore JKS file and enter the keystore password in Source.
  5. Click to select PEM format on both Target Private key file and Certificates chain file:
  6. Click the save icon to designate some local folder to save the 2 PEM files.
  7. Manullay create a text file merged.pem and merge these 2 PEM files to one pem file. ( Use notepad to open the 2 PEM files and respectively copy to merge in, CERTIFICATE first and then RSA PRIVATE KEY.
  8. Download and setup OpenSSL (http://www.slproweb.com/products/Win32OpenSSL.html)
  9. Open command line and run follows: ( Assume the openssl in C root and you want the PFX file name: pfxname
    C:\OpenSSL\bin\openssl pkcs12 -export -out pfxname.pfx -in merged.pem
  10. You will find the pfx file in current folder then.