Accessing WCF services using SharePoint Secured Store Services


With the invent of Secure Stored Services so often we land in a situation to access third party WCF services using the credentials stored in the SSS providers. Sometimes third-party service providers add a clause to use host headers to the all the subsequent requests except first.

A possible way to implement this is to use cache objects to store credentials. If these credentials are sent through wire, it breaks the whole purpose of SSS. Hence its important to encrypt these requests before passing on.

This blog will cover this scenario.

Algorithm goes like this:

1) Retrieve Credentials for SSS

2) Encrypt the credentials

3) Store the credentials in HTTP Cache object

4) First time access to web service with above credentials and cache the response

5) Subsequent request to the Web-service using the cached HTTP header object

1) Retrieve Credentials for SSS:

This is pretty staright forward. Use the Class SecureStoreCredentialCollection from Microsoft.BusinessData.Infrastructure.SecureStore to retrieve the credentials.

private SecureStoreCredentialCollection GetCredentials(string targetApplicationID)
{
SecureStoreCredentialCollection credentials = null;
SPServiceContext context = SPServiceContext.GetContext(SPServiceApplicationProxyGroup.Default, SPSiteSubscriptionIdentifier.Default);
SecureStoreServiceProxy ssp = new SecureStoreServiceProxy();
ISecureStore iss = ssp.GetSecureStore(context);
credentials = iss.GetCredentials(targetApplicationID);

return credentials;
}

2) Encrypt the credentials:

Cryptography is another big concept. I am not going deep in to it. Idea is to use some Microsoft provided cryptographic algorithms which will encode the data using the passphrase along with modes and padding attributes we select.

public string EncryptString(string message, string passphrase)
{
byte[] Results;
System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();

// Step 1. We hash the passphrase using MD5
// We use the MD5 hash generator as the result is a 128 bit byte array
// which is a valid length for the TripleDES encoder we use below

MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(passphrase));

// Step 2. Create a new TripleDESCryptoServiceProvider object
TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();

// Step 3. Setup the encoder
TDESAlgorithm.Key = TDESKey;
TDESAlgorithm.Mode = CipherMode.ECB;
TDESAlgorithm.Padding = PaddingMode.PKCS7;

// Step 4. Convert the input string to a byte[]
byte[] DataToEncrypt = UTF8.GetBytes(message);

// Step 5. Attempt to encrypt the string
try
{
ICryptoTransform Encryptor = TDESAlgorithm.CreateEncryptor();
Results = Encryptor.TransformFinalBlock(DataToEncrypt, 0, DataToEncrypt.Length);
}
finally
{
// Clear the TripleDes and Hashprovider services of any sensitive information
TDESAlgorithm.Clear();
HashProvider.Clear();
}

// Step 6. Return the encrypted string as a base64 encoded string
return Convert.ToBase64String(Results);
}

 

Reverse engineer the algorithm to decrypt:

 public string DecryptString(string message, string passphrase)
{
byte[] Results;
System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();

// Step 1. We hash the passphrase using MD5
// We use the MD5 hash generator as the result is a 128 bit byte array
// which is a valid length for the TripleDES encoder we use below

MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(passphrase));

// Step 2. Create a new TripleDESCryptoServiceProvider object
TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();

// Step 3. Setup the decoder
TDESAlgorithm.Key = TDESKey;
TDESAlgorithm.Mode = CipherMode.ECB;
TDESAlgorithm.Padding = PaddingMode.PKCS7;

// Step 4. Convert the input string to a byte[]
byte[] DataToDecrypt = Convert.FromBase64String(message);

// Step 5. Attempt to decrypt the string
try
{
ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
}
finally
{
// Clear the TripleDes and Hashprovider services of any sensitive information
TDESAlgorithm.Clear();
HashProvider.Clear();
}

// Step 6. Return the decrypted string in UTF8 format
return UTF8.GetString(Results);
}

}

Convert Secured string to string:

To convert secured string to string use the interop API’s and dispose (Marshal) the object

 private string ReadSecureString(SecureString sstrIn)
{
if (sstrIn == null)
{
return null;
}

IntPtr ptr = Marshal.SecureStringToBSTR(sstrIn);
string str = Marshal.PtrToStringBSTR(ptr);
Marshal.ZeroFreeBSTR(ptr);
return str;
}

3) Store the credentials in HTTP Cache object

If the credentials are not secured, secure it using crypography method described above and cache the object for 30 mins.

{
string cacheValue = isSecured ? crypto.EncryptString(returnValue, PASSPHRASE) :returnValue;
//Cache credentials for 30 mins
HttpContext.Current.Cache.Insert(strCacheName,cacheValue,null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(30),
System.Web.Caching.CacheItemPriority.Normal, null);
}

return returnvalue;

Decrypt the cache object and return the credentials on subsequent requests:

Since the cache is secured, even if the packets are trapped, hackers cannot retreive the credentials (Unless they are aware of our encrypt algorithms :)).

Now coming back the WCF service, for the first request to the service, credentials would be passed and this response will be stored in the header and for subsequent requests, requests with this header will be passed.

Define the XML format:

 XmlDocument xDoc = new XmlDocument();
xDoc.PreserveWhitespace = true; //keep all line breaks
xDoc.XmlResolver = new HtmlResolver(); //will resolve entities
XmlNamespaceManager ns = new XmlNamespaceManager(xDoc.NameTable);
ns.AddNamespace("html", "http://www.w3.org/1999/xhtml");

4) First time access to web service with above credentials and cache the response

Now get the secured credentials form the methods described before and use them to download the data from WCF service and transform it to XML:

client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(strUsername, strPwd);
StringBuilder strBuilder = new StringBuilder(client.DownloadString(new Uri(URI)));
xDoc.LoadXml(strBuilder.ToString());

Please note, this initial response will be used as cookie header for subsequent requests.And this URI will be same for all the requests. This initial response will have response which is defined by expression “SessionClass” (Eg:”//html:span[@id=’session_key’]”) and ‘selectsinglenode’ will retrieve the first element which matches the response and ‘sessioncookieformat’ defines the format of session object (eg: “Session={0}”) and store the cache until the WCF session expires.

if (xDoc != null)
{
XmlNode node = xDoc.SelectSingleNode(SESSIONCLASS, ns);
string strSessionId = string.Format(SESSIONCOOKIEFORMAT, node.InnerText);
//Cache session id for 23 hrs
HttpContext.Current.Cache.Insert(SESSIONCACHENAME,strSessionId,
null,
System.DateTime.UtcNow.AddHours(23),
System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Normal, null);
return strSessionId;
}

5) Subsequent request to the Web-service using the cached HTTP header object

Now build the “RequestURL” which will required URL for WCF service to download the data and start the request with the client header which contains the SessionID from the cache using GetSessionCookie method.

using (WebClient client = new WebClient())
{
client.UseDefaultCredentials = false;
string strToken = GetSessionCookie();
client.Headers.Add(HttpRequestHeader.Cookie, strToken);
StringBuilder strBuilder = new StringBuilder(client.DownloadString(new Uri(url)));
XmlDocument xDoc = new XmlDocument();
xDoc.PreserveWhitespace = true; //keep all line breaks
xDoc.XmlResolver = new HtmlResolver(); //will resolve entities
xDoc.LoadXml(strBuilder.ToString());
return xDoc;
}

XDoc data can be used in the presentation layers further. Thus to implement the WCF interfaces on SharePoint its crucial to understand the structure and security features implemented by WCF service providers.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s