#native_company# #native_desc#
#native_cta#

MSN SSO Authentication

By Adam Malcontenti-Wilson
on January 6, 2009

Version: 0.1 beta

Type: Function

Category: Networking

License: Other

Description: This script contains functions to generate tickets and return values for Windows Live Messenger SSO Authentication. See in-script comments for more info.

<?php
/*
	Windows Live (MSN) Messenger Protocol 15+
	SSO Authentication Snippet version 0.1 beta
	
	Developed for PHP by: Adam Malcontenti-Wilson
					<[email protected]>
	(this is my first public snippet - please be nice)
	Derived from:	* C# SSO MSNP15 <http://www.codeproject.com/KB/IP/SSOwithMSNP15.aspx>
				* <http://msnpiki.msnfanatic.com/index.php/MSNP15:SSO>
				* Others I can't remember... (sorry)
	Licence: Public Domain (Free for any use), Attribution would be liked but is not needed.
	
	Please ensure that you have the OPENSSL, MHASH and MCRYPT extentions enabled and installed in your PHP library,
	and fsockopen enabled.
	
	Code to retrieve the policy and token (nonce) is not included. 
	Hopefully, everything is self explainitory, but if not, basically all you need to do is call the GetTicket function 
	with the $username, $password, $policy and $nonce, and you'll get back a ticket and base64 encoded struct to add onto 
	the end of your response. If there is an error, it should return false.
	
*/

/* 
	The following code is a simple demo on how to use the functions below
	"1" is constantly used in place of the transaction id
	This demo is non-functional in it's current state, as the lack of a real username, password, policy and nonce make it impossible to generate a return value
*/
// This is the response we have got from the "USR 1 SSO I <username>" command
$USR_response = 'USR 1 SSO S MBI_KEY_OLD AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';

// Parse that response
$response_array = explode(' ',$USR_response);
$policy = $response_array[4];
$nonce = $response_array[5];

// Generate a ticket and response
$ticket = GetTicket($username,$password,$policy,$nonce);
if ($ticket == false) { die('An error has occured'); } // Some simple error trapping.
$response = 'USR 1 SSO S '.$ticket;

// Send off that response and hopefully you'll get back a "USR 1 OK ..." response meaning you were successfully authenticated with the new SSO authentication

///// That was the end of the example/demo /////
///// ALL FUNCTIONS ARE BELOW /////

function GetTicket ($username,$password,$policy,$nonce) {
	// Prepare XML for SOAP request
	$xml = "<?xml version="1.0" encoding="UTF-8"?>";
	$xml .= "<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wssc="http://schemas.xmlsoap.org/ws/2004/04/sc" xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust"><Header>";
	$xml .= "<ps:AuthInfo xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="PPAuthInfo">";
	$xml .= "<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>";
	$xml .= "<ps:BinaryVersion>4</ps:BinaryVersion>";
	$xml .= "<ps:UIVersion>1</ps:UIVersion>";
	$xml .= "<ps:Cookies></ps:Cookies>";
	$xml .= "<ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>";
	$xml .= "</ps:AuthInfo>";
	$xml .= "<wsse:Security><wsse:UsernameToken Id="user">";
	$xml .= "<wsse:Username>" . $username . "</wsse:Username>";
	$xml .= "<wsse:Password>" . $password . "</wsse:Password>";
	$xml .= "</wsse:UsernameToken></wsse:Security></Header><Body>";
	$xml .= "<ps:RequestMultipleSecurityTokens xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="RSTS">";
	$xml .= "<wst:RequestSecurityToken Id="RST0">";
	$xml .= "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>";
	$xml .= "<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>http://Passport.NET/tb";
	$xml .= "</wsa:Address></wsa:EndpointReference></wsp:AppliesTo></wst:RequestSecurityToken>";
	$xml .= "<wst:RequestSecurityToken Id="RST1">";
	$xml .= "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType><wsp:AppliesTo><wsa:EndpointReference>";
	$xml .= "<wsa:Address>messengerclear.live.com</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>";
	$xml .= "<wsse:PolicyReference URI="" . $policy . ""></wsse:PolicyReference></wst:RequestSecurityToken>";
	$xml .= "<wst:RequestSecurityToken Id="RST2">";
	$xml .= "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>";
	$xml .= "<wsp:AppliesTo>";
	$xml .= "<wsa:EndpointReference>";
	$xml .= "<wsa:Address>contacts.msn.com</wsa:Address>";
	$xml .= "</wsa:EndpointReference>";
	$xml .= "</wsp:AppliesTo>";
	$xml .= "<wsse:PolicyReference URI="MBI">";
	$xml .= "</wsse:PolicyReference>";
	$xml .= "</wst:RequestSecurityToken>";
	$xml .= "</ps:RequestMultipleSecurityTokens></Body></Envelope>";
	
	// Make a SOAP request
	$response = makeSoapRequest('/RST.srf','ssl://','login.live.com',443,$xml); // note: you may need to change this url for @msn.com addresses
	
	// Parse the response for the BinarySecurityToken
	if ($policy == 'MBI' || $policy == 'MBI_SSL' || $policy == 'MBI_KEY_OLD') {
		$id = 'Compact1';
	} else {
		$id = 'PPToken1';
	}
	if (!preg_match('/<wsse:BinarySecurityToken Id="'.$id.'">(.+?)</wsse:BinarySecurityToken>/',$response,$matches)) {
		//BinarySecurityToken not found
		return false;
	} else { $ticket = str_replace('&amp;','&',$matches[1]); }
	if (!preg_match('/'.$id.'.+?<wst:BinarySecret>(.+?)</wst:BinarySecret>/s',$response,$matches)) {
		//BinarySecret not found
		return false;
	} else { $secret = $matches[1]; }
	
	// Generate Keys
	$key1 = base64_decode($secret);
	$key2 = deriveKey($key1, "WS-SecureConversationSESSION KEY HASH");
	$key3 = deriveKey($key1, "WS-SecureConversationSESSION KEY ENCRYPTION");
	
	// Generate IV
	$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_TRIPLEDES, MCRYPT_MODE_CBC), MCRYPT_RAND);
	
	// Generate Hashes
	$hash1 = mhash(MHASH_SHA1,$nonce,$key2);
	$hash2 = mcrypt_encrypt(MCRYPT_TRIPLEDES,$key3,$nonce."x08x08x08x08x08x08x08x08",'cbc',$iv);
	
	// Use hashes to create final ticket
	$structdata = "x1cx00x00x00x01x00x00x00x03x66x00x00x04x80x00x00x08x00x00x00x14x00x00x00x48x00x00x00";
	return $ticket . ' ' . base64_encode($structdata.$iv.$hash1.$hash2); //returns both the ticket and the base64 encoded response structure with a space seperating them
}

function deriveKey($key, $magic) {
	// This function does some funky s$*t to generate a key
	$hash1 = mhash(MHASH_SHA1,$magic,$key);
	$hash2 = mhash(MHASH_SHA1,$hash1 . $magic,$key);
	$hash3 = mhash(MHASH_SHA1,$hash1,$key);
	$hash4 = mhash(MHASH_SHA1,$hash3 . $magic,$key);
	return $hash2 . substr($hash4,0,4);
}

function makeSoapRequest($scripturl,$proto_wrapper,$host,$port,$post) {
	// Simple and dodgy way to send a SOAP request
	$fp = fsockopen($proto_wrapper.$host,$port, $errno, $errstr, 30);
	if (!$fp) {
		echo "$errstr ($errno)<br />n";
		return false;
	} else {
		$out = "POST $scripturl HTTP/1.1rn";
		$out .= "Host: $hostrn";
		$out .= "Content-Type: application/soap+xml; charset=utf-8rn";
		$out .= 'Content-Length: '.strlen($post)."rn";
		$out .= "Connection: Closernrn";
		$out .= $post;
		fwrite($fp, $out);
		while (!feof($fp)) {
			$response .= fgets($fp, 128);
		}
		fclose($fp);
		$response = explode("rnrn",$response,2);
		return $response[1];
	}
}