Archive

Posts Tagged ‘WebService’

Modifying SOAP Header WCF.

October 9, 2015 Leave a comment

While Working on a project , I had a requirement to communicate with a service hosted in non Microsoft environment, so the service did not like the way SOAP security header was being sent it.

SO I had to modify the SOAP security header , wow..

it was really pain in my neck , ha ha ha.. so modify it.

following steps helped me to modify it.

1) Change the binding to custom binding.

<customBinding>
<binding name="CustomSoapBinding">
<security includeTimestamp="false" authenticationMode="UserNameOverTransport" defaultAlgorithmSuite="Basic256" requireDerivedKeys="false" messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"/>
<textMessageEncoding messageVersion="Soap11"/>
<httpsTransport maxReceivedMessageSize="2000000000"/>
</binding>
</customBinding>

 

Things to note, Service did not like the timestamp in security header , so I had to exclude it, also note below joke..

messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"

ohh..what on the earth does that mean…ha ha ha.try to remember it.. Smile

2)  before calling the service , make the changes.

   1: ws.ChannelFactory.Endpoint.Behaviors.Remove<System.ServiceModel.Description.ClientCredentials>();

   2: ws.ChannelFactory.Endpoint.Behaviors.Add(new CustomCredentials());

   3: ws.ClientCredentials.UserName.UserName = ConfigReader.GetConstantValue("UserID");

   4: ws.ClientCredentials.UserName.Password = ConfigReader.GetConstantValue("Password");

   5:                

things to note

Line 1 :  removes the Client Credentials node from SOAP Security header.

Line 2 : Added a class[which we will see in a bit, which performs all the actions which are required to build custom security header node ].

Line 3, Line 4 : added some custom credentials.

3)  Custom Credential Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Description;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
using System.Security.Cryptography;
using System.IdentityModel.Tokens;

namespace MedImpactServicesClient
{
public class CustomCredentials : ClientCredentials
{
public CustomCredentials()
{ }

protected CustomCredentials(CustomCredentials cc)
: base(cc)
{ }

public override System.IdentityModel.Selectors.SecurityTokenManager CreateSecurityTokenManager()
{
return new CustomSecurityTokenManager(this);
}

protected override ClientCredentials CloneCore()
{
return new CustomCredentials(this);
}
}

public class CustomSecurityTokenManager : ClientCredentialsSecurityTokenManager
{
public CustomSecurityTokenManager(CustomCredentials cred)
: base(cred)
{ }

public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
{
return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity11);
}
}

public class CustomTokenSerializer : WSSecurityTokenSerializer
{
public CustomTokenSerializer(SecurityVersion sv)
: base(sv)
{ }

protected override void WriteTokenCore(System.Xml.XmlWriter writer,
System.IdentityModel.Tokens.SecurityToken token)
{


UserNameSecurityToken userToken = token as UserNameSecurityToken;


string tokennamespace = "o";

DateTime created = DateTime.UtcNow;
string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");

// unique Nonce value - encode with SHA-1 for 'randomness'
// in theory the nonce could just be the GUID by itself
//string phrase = Guid.NewGuid().ToString();
byte[] nonce = GetNonce();
string nonceStr = Convert.ToBase64String(nonce);

// in this case password is plain text
// for digest mode password needs to be encoded as:
//string PasswordAsDigest = GetSHA1String(userToken.Password);
// and profile needs to change to
string password = CreateHashedPassword(nonce, createdStr, userToken.Password);

//string password = userToken.Password;

writer.WriteRaw(string.Format(
"<{0}:UsernameToken u:Id=\"" + token.Id +
"\" xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
"<{0}:Username>" + "WSPAHUM" + "</{0}:Username>" +
"<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">" +
password + "</{0}:Password>" +
"<{0}:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" +
nonceStr + "</{0}:Nonce>" +
"<u:Created>" + createdStr + "</u:Created></{0}:UsernameToken>", tokennamespace));
}

private string CreateHashedPassword(byte[] nonce, string created, string password)
{
byte[] createdBytes = Encoding.UTF8.GetBytes(created);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
byte[] combined = new byte[createdBytes.Length + nonce.Length + passwordBytes.Length];
Buffer.BlockCopy(nonce, 0, combined, 0, nonce.Length);
Buffer.BlockCopy(createdBytes, 0, combined, nonce.Length, createdBytes.Length);
Buffer.BlockCopy(passwordBytes, 0, combined, nonce.Length + createdBytes.Length, passwordBytes.Length);
return Convert.ToBase64String(SHA1.Create().ComputeHash(combined));
}

internal static byte[] GetNonce()
{
byte[] nonce = new byte[0x10];
RandomNumberGenerator generator = new RNGCryptoServiceProvider();
generator.GetBytes(nonce);
return nonce;
}





}
}

 

Things to Note:

Most of the work is done in WriteTokenCore function and just copy everything related to Nounce, it took me around 1 week to figure out correct CreateHashedPassword algo.Smile

 

That’s it you are done.

let me know if you get stuck somewhere.

Categories: C#, WCF Tags: , ,