Click here to Skip to main content
15,885,309 members
Articles / Programming Languages / C#
Article

Single Sign-On with MSN Protocol 15

Rate me:
Please Sign up or sign in to vote.
3.29/5 (5 votes)
24 Mar 2008CPOL4 min read 155.4K   232   29   9
How to authenticate with your MSN account through SSO and MSNP15.

Introduction

Many people asked me if I couldn't show them an example of how to use Single Sign-On with the MSN Protocol 15. Of course, the hardest part for the most of them was, how to create the structure which we have to send back to the server and which needs a bit of knowledge about Cryptography.

But, of course, this article also addresses, to those who are interested, how MSN authentication works (at least with MSNP15), or want to have a general example of SSO.

Let's begin

First, of all, we will program the following classes:

  • MSNClient
  • TcpConnection
  • SOAPRequest
  • SSOticket

The primitive interface only consists of an input field for your MSN account, an input field for your password, and a log-in button.

If the user presses the button, we create a new MSN client with our account and our password:

C#
private void button1_Click(object sender, EventArgs e)
{
    Client = new MSNClient(textBox1.Text, textBox2.Text);

    ...

    //start an extra thread to connect to MSN
    backgroundWorker1.RunWorkerAsync();
}

As you can see, we start a thread on which we will connect to MSN.

C#
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    // connect to MSN
    Client.Connect();
} 

Now, let's have look at the Connect() function in the class MSNClient:

C#
public void Connect()
{
    // get the host of the notification server from a dispatch server
    string host = GetNSHostFromDispatchServer();

    // log into the NS
    LogIntoNS(host.Split(':')[0], Convert.ToInt32(host.Split(':')[1]));
}

First, we connect to a Dispatch Server where we have to get the IP and the port for the notification server where we have to log in. For those who haven't got an idea of at least MSN protocol 8 and want to understand what I'm writing here, please read the basic stuff first ;-) There are enough sites out there which will help you.

At the others: as usual, first, we send the VER command, then CVR, and finally USR. With MSNP15, the USR command has got a new parameter: SSO.

C#
// send a USR request to get the host, with MSNP15
// the new parameter SSO (single sign-on) has been introduced
recvbuf = SendReceiveMSNCommand("USR", "SSO", "I", Account);

When we successfully connect to the notification server, we will send again the VER, CVR, and USR commands. After the last request, we will receive a GCF response whose meaning I don't know and which you shouldn't care, too. And after that, the server will send a new response which contains an XML and the following USR command. The USR response is important for us:

C#
// save the location of the user command in the response...
int indexOfUSR = recvbuf.IndexOf("USR");

 //...and cut the USR command with
 //   its attributes out of the response
 recvbuf = recvbuf.Remove(0, indexOfUSR);
 

// save the policy 
string policy = recvbuf.Split(' ')[4];

// save the nonce 
string nonce = recvbuf.Split(' ')[5];

// get the ticket with the policy and nonce
string ticket = GetTicket(policy, nonce);

As you can see, the response contains a ticket and another string which can be something like "MBI_OLD" and which we call nonce. With this important information from the server, we will get the ticket. This was basic stuff until now. But now, it's getting interesting (at least for those who wrote me emails). Now, we have to get a ticket and a magic key from MSN to tinker us some nice new key. To do this, we have to create an XML which contains our username, password, and ticket. It's too long to display it here, so just have a look at my source code to see how it's built-on. Then, we have to make a SOAP request with this XML:

C#
// create a new SOAP request with the xml
SOAPRequest SOAPRequest = new SOAPRequest(xml, "https://login.live.com/RST.srf");

And in the class SOAPRequest:

C#
public SOAPRequest(string XmlCode, string Host)
{
    ServerRequest = (HttpWebRequest)WebRequest.Create(Host);
    ServerRequest.Method = "POST";

    Stream stream = ServerRequest.GetRequestStream();

    stream.Write(Encoding.UTF8.GetBytes(XmlCode), 0, 
                 Encoding.UTF8.GetBytes(XmlCode).Length);
    stream.Close();
}

SOAP uses HTTP to exchange XML. So, we have to do an HTTP request to https://login.live.com/RST.srf and send our XML to the server by writing it to the network stream. Don't forget to set the method to POST ;-) After we have sent the XML, we have to get the response of the server:

C#
// get the server response
public XmlDocument GetResponse()
{
    // get the server response
    HttpWebResponse ServerResponse = 
      (HttpWebResponse)ServerRequest.GetResponse();
    
    // get the stream of the response
    Stream stream = ServerResponse.GetResponseStream();

    // create a new xml document
    // and fill it with the server response
    XmlDocument xml = new XmlDocument();
    xml.Load(stream);

    // close the stream 
    stream.Close();

    // close the response object
    ServerResponse.Close();

    return xml;
}

The response should contain a binary secret and a security token, but attention! There are two binary secrets in this weird chaos of XML nodes ;-). If your policy reference URI contained something like MBI, MBI_SSL, or MBI_KEY_OLD, choose the one in <wsse:binarysecuritytoken id="Compactn"> where n is the same number as the RSTn request, else something like </wsse:binarysecuritytoken><wsse:BinarySecurityToken Id="PPTokenn"><wsse:binarysecuritytoken id="Compactn"><wsse:binarysecuritytoken id="PPTokenn"><wsse:binarysecuritytoken id="PPTokenn">. Now, when we finally get our binary secret and the security token, the hard part can begin on break down :-o. No, seriously, it isn't really hard. You even don't have to know what these algorithms are doing. You just have to know that you have to send a structure to Windows and that you have to calculate some elements of it. As I said, at last, we have to send the following structure to the server (I took this example from here):

C#
struct key
{
   unsigned long uStructHeaderSize; // 28. Does not count data
   unsigned long uCryptMode; // CRYPT_MODE_CBC (1)
   unsigned long uCipherType; // TripleDES (0x6603)
   unsigned long uHashType; // SHA1 (0x8004)
   unsigned long uIVLen;    // 8
   unsigned long uHashLen;  // 20
   unsigned long uCipherLen; // 72

   // Data
   unsigned char aIVBytes[8];
   unsigned char aHashBytes[20];
   unsigned char aCipherBytes[72];
}

At last, we have to send a string to the server, so we better make a structure in the form of an array. The only things we don't know in this structure are the last three ones. We can already program the beginning of the structure:

C#
// First of all, we need to create a structure
// of information, which elements' size is 4 bytes 
// To do that, we use an array which we can turn into a string, later...
Beginning = new byte[28];

//StructHeaderSize = 28
Beginning[0] = 0x1c;
Beginning[1] = 0x00;
Beginning[2] = 0x00;
Beginning[3] = 0x00;

//CryptMode = 1
Beginning[4] = 0x01;
Beginning[5] = 0x00;
Beginning[6] = 0x00;
Beginning[7] = 0x00;

//CipherType = 0x6603
Beginning[8] = 0x03;
Beginning[9] = 0x66;
Beginning[10] = 0x00;
Beginning[11] = 0x00;

//HashType = 0x8004
Beginning[12] = 0x04;
Beginning[13] = 0x80;
Beginning[14] = 0x00;
Beginning[15] = 0x00;

//IV length = 8
Beginning[16] = 0x08;
Beginning[17] = 0x00;
Beginning[18] = 0x00;
Beginning[19] = 0x00;

//hash length = 20
Beginning[20] = 0x14;
Beginning[21] = 0x00;
Beginning[22] = 0x00;
Beginning[23] = 0x00;

//cipher length = 72
Beginning[24] = 0x48;
Beginning[25] = 0x00;
Beginning[26] = 0x00;
Beginning[27] = 0x00;

Now, we want to create the "hash element" of the structure. We will create it with HMACSHA1. But first, we have to define a key:

C#
// now, we have to create a first, base64 decoded key,
// which we get from the input key
byte[] key1 = Convert.FromBase64String(key);

// then we calculate a second key through
// a specific algorithm (see function DeriveKey())
string key2 = DeriveKey(key1, "WS-SecureConversationSESSION KEY HASH");

// ...and a third key with the same algorithm...
string key3 = DeriveKey(key1, "WS-SecureConversationSESSION KEY ENCRYPTION");

And in the function, DeriveKey():

C#
// specific algorithm to calculate a key...
private string DeriveKey(byte[] key, string magic)
{
    HMACSHA1 sha = new HMACSHA1();
    sha.Key = key;
    byte[] Magic = Encoding.Default.GetBytes(magic);

    // compute 4 hashes with HMACSHA1
    byte[] hash1 = sha.ComputeHash(Magic);
    byte[] hash2 = sha.ComputeHash(Combine(hash1, Magic));
    byte[] hash3 = sha.ComputeHash(hash1);
    byte[] hash4 = sha.ComputeHash(Combine(hash3, Magic));

    // create an array with the 4 first bytes of the fourth hash
    byte[] o = { hash4[0], hash4[1], hash4[2], hash4[3] };

    // combine it with hash2 and return the key
    return Encoding.Default.GetString(Combine(hash2, o));
}

This is the way the keys are calculated. For our hash, we use key2. There isn't more you have to know ;-) So, now, let's create the hash with this key:

C#
// now we will use sha1 to create a hash from the nonce
HMACSHA1 sha = new HMACSHA1();

// the key for the algorithm is the second key we calculated above
sha.Key = Encoding.Default.GetBytes(key2);

// compute the hash
byte[] hash = sha.ComputeHash(Encoding.Default.GetBytes(nonce));

The aIVBytes which also has to be added to our structure is nothing else than an array of random data. I was too lazy, so my array doesn't really calculate random numbers, but this isn't important for this example here. The last element which we need is aCipherBytes, which we calculate by transforming the nonce with Triple DES and key3 as key:

C#
TripleDESCryptoServiceProvider DES3 = new TripleDESCryptoServiceProvider();
DES3.Key = Encoding.Default.GetBytes(key3);
DES3.Mode = CipherMode.CBC;
DES3.IV = iv;

ICryptoTransform Encryptor = DES3.CreateEncryptor();


// we have to fill the nonce with 8*8
byte[] RestOfNonce = { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 };

// this will be our output after the transforming 
byte[] output = new byte[72];

// now, transform the nonce 
Encryptor.TransformBlock(Combine(Encoding.Default.GetBytes(nonce), 
  RestOfNonce), 0, Combine(Encoding.Default.GetBytes(nonce), 
  RestOfNonce).Length, output, 0);

Now, we can merge everything to a structure:

C#
// the final key will be a base64 encoded structure,
// composed by the beginning of the structure, the initialization
// vector, the SHA1 - Hash and the transformed block
string struc = Encoding.Default.GetString(Beginning) + 
               Encoding.Default.GetString(iv) + Encoding.Default.GetString(hash) + 
               Encoding.Default.GetString(output);
value = Convert.ToBase64String(Encoding.Default.GetBytes(struc));

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhy I cannot login using your sample? Pin
benjamindai2-Nov-08 4:27
benjamindai2-Nov-08 4:27 
AnswerRe: Why I cannot login using your sample? Pin
Joson Jiang29-Apr-10 20:36
Joson Jiang29-Apr-10 20:36 
GeneralHelp on SOAP Action Pin
kkrisjoy13-Aug-08 5:35
kkrisjoy13-Aug-08 5:35 
GeneralProxy Pin
java possum14-Apr-08 17:57
java possum14-Apr-08 17:57 
QuestionComplete MSN Library (MSNP9 MSNP8) Pin
Derek Bartram24-Mar-08 13:06
Derek Bartram24-Mar-08 13:06 
GeneralRe: Complete MSN Library (MSNP9 MSNP8) Pin
Kuryn24-Mar-08 19:17
Kuryn24-Mar-08 19:17 
GeneralRe: Complete MSN Library (MSNP9 MSNP8) Pin
Derek Bartram24-Mar-08 22:22
Derek Bartram24-Mar-08 22:22 
GeneralRe: Complete MSN Library (MSNP9 MSNP8) Pin
dasd wdawdq25-Mar-08 5:10
dasd wdawdq25-Mar-08 5:10 
GeneralRe: Complete MSN Library (MSNP9 MSNP8) Pin
Derek Bartram25-Mar-08 9:24
Derek Bartram25-Mar-08 9:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.