Skip to content
This repository has been archived by the owner on Jan 10, 2022. It is now read-only.

Commit

Permalink
Added Digist-MD5 support
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiang committed Jan 27, 2014
1 parent 6304eb4 commit 181241c
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 6 deletions.
100 changes: 98 additions & 2 deletions src/Fabiang/Xmpp/EventListener/Stream/Authentication/DigestMd5.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

use Fabiang\Xmpp\EventListener\AbstractEventListener;
use Fabiang\Xmpp\Event\XMLEvent;
use Fabiang\Xmpp\Util\XML;

/**
* Handler for "digest md5" authentication mechanism.
Expand All @@ -54,13 +55,26 @@ class DigestMd5 extends AbstractEventListener implements AuthenticationInterface
*/
protected $blocking = false;

/**
*
* @var string
*/
protected $username;

/**
*
* @var string
*/
protected $password;

/**
* {@inheritDoc}
*/
public function attachEvents()
{
$input = $this->getConnection()->getInputStream()->getEventManager();
$input->attach('{urn:ietf:params:xml:ns:xmpp-sasl}challenge', array($this, 'challenge'));
$input->attach('{urn:ietf:params:xml:ns:xmpp-sasl}success', array($this, 'success'));

$output = $this->getConnection()->getOutputStream()->getEventManager();
$output->attach('{urn:ietf:params:xml:ns:xmpp-sasl}auth', array($this, 'auth'));
Expand All @@ -70,7 +84,8 @@ public function attachEvents()
* {@inheritDoc}
*/
public function authenticate($username, $password)
{
{
$this->setUsername($username)->setPassword($password);
$auth = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="DIGEST-MD5"/>';
$this->getConnection()->send($auth);
}
Expand All @@ -95,11 +110,70 @@ public function challenge(XMLEvent $event)
{
if (false === $event->isStartTag()) {
list($element) = $event->getParameters();
$challenge = $element->nodeValue;

$challenge = XML::base64Decode($element->nodeValue);

if ($challenge) {
$matches = array();

preg_match_all('#(\w+)\=(?:"([^"]+)"|([^,]+))#', $challenge, $matches);
list(, $variables, $quoted, $unquoted) = $matches;
// filter empty strings; preserve keys
$quoted = array_filter($quoted);
$unquoted = array_filter($unquoted);
// replace "unquoted" values into "quoted" array and combine variables array with it
$values = array_combine($variables, array_replace($quoted, $unquoted));

$values['cnonce'] = uniqid(mt_rand(), false);
$values['nc'] = '00000001';
$values['qop'] = 'auth';

if (!isset($values['digest-uri'])) {
$values['digest-uri'] = 'xmpp/' . $this->getOptions()->getTo();
}

$a1 = sprintf('%s:%s:%s', $this->getUsername(), $values['realm'], $this->getPassword());

if ('md5-sess' === $values['algorithm']) {
$a1 = pack('H32', md5($a1)) . ':' . $values['nonce'] . ':' . $values['cnonce'];
}

$a2 = "AUTHENTICATE:" . $values['digest-uri'];

$password = md5($a1) . ':' . $values['nonce'] . ':' . $values['nc'] . ':'
. $values['cnonce'] . ':' . $values['qop'] . ':' . md5($a2);
$password = md5($password);

$response = sprintf(
'username="%s",realm="%s",nonce="%s",cnonce="%s",nc=%s,qop=%s,digest-uri="%s",response=%s,charset=utf-8',
$this->getUsername(),
$values['realm'],
$values['nonce'],
$values['cnonce'],
$values['nc'],
$values['qop'],
$values['digest-uri'],
$password
);

$this->getConnection()->send(
'<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">' . XML::base64Encode($response) . '</response>'
);
}
$this->blocking = false;
}
}

/**
* Handle success event.
*
* @return void
*/
public function success()
{
$this->blocking = false;
}

/**
* {@inheritDoc}
*/
Expand All @@ -108,4 +182,26 @@ public function isBlocking()
return $this->blocking;
}

public function getUsername()
{
return $this->username;
}

public function setUsername($username)
{
$this->username = $username;
return $this;
}

public function getPassword()
{
return $this->password;
}

public function setPassword($password)
{
$this->password = $password;
return $this;
}

}
2 changes: 1 addition & 1 deletion src/Fabiang/Xmpp/EventListener/Stream/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function attachEvents()
{
$input = $this->getConnection()->getInputStream()->getEventManager();
$input->attach('{urn:ietf:params:xml:ns:xmpp-session}session', array($this, 'session'));
$input->attach('{http://etherx.jabber.org/streams}iq', array($this, 'iq'));
$input->attach('{jabber:client}iq', array($this, 'iq'));
}

/**
Expand Down
23 changes: 23 additions & 0 deletions src/Fabiang/Xmpp/Util/XML.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,27 @@ public static function generateId()
return static::quote('fabiang_xmpp_' . uniqid());
}

/**
* Encode a string with Base64 and quote it.
*
* @param string $data
* @param string $encoding
* @return string
*/
public static function base64Encode($data, $encoding = 'UTF-8')
{
return static::quote(base64_encode($data), $encoding);
}

/**
* Decode a Base64 encoded string.
*
* @param string $data
* @return string
*/
public static function base64Decode($data)
{
return base64_decode($data);
}

}
14 changes: 13 additions & 1 deletion tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@
* @link http://github.com/fabiang/dependency-composer
*/

$autoloaderFile = __DIR__ . '/../vendor/autoload.php';

if (!file_exists($autoloaderFile)) {
die(
'You need to set up the project dependencies using the following commands:' . PHP_EOL .
'wget http://getcomposer.org/composer.phar' . PHP_EOL .
'php composer.phar install' . PHP_EOL
);
}

/* @var $autoloader \Composer\Autoload\ClassLoader */
$autoloader = require __DIR__ . '/../vendor/autoload.php';
$autoloader = require $autoloaderFile;
$autoloader->add('Fabiang\\Xmpp\\', __DIR__ . '/src/');

unset($autoloaderFile, $autoloader);
6 changes: 6 additions & 0 deletions tests/features/authentication.feature
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ Feature: Authentication
And exceptions are catched when connecting
When connecting
Then a authorization exception should be catched

Scenario: digest-md5 authentication
Given Test connection adapter
And Test response data for digest-md5 auth
When connecting
Then digest-md5 authentication element should be send
38 changes: 38 additions & 0 deletions tests/features/bootstrap/AuthenticationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

use Behat\Behat\Context\BehatContext;
use Behat\Behat\Exception\PendingException;
use Fabiang\Xmpp\Util\XML;

require_once 'PHPUnit/Framework/Assert/Functions.php';

Expand Down Expand Up @@ -73,6 +74,31 @@ public function testResponseDataForAuthenticationFailure()
"<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"
));
}

/**
* @Given /^Test response data for digest-md5 auth$/
*/
public function testResponseDataForDigestMdAuth()
{
$this->getConnection()->setData(array(
"<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' "
. "id='1234567890' from='localhost' version='1.0' xml:lang='en'><stream:features>"
. "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism></mechanisms>"
. "</stream:features>",
'<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl">'
. XML::base64Encode(
'realm="localhost",nonce="abcdefghijklmnopqrstuvw",'
. 'qop="auth",charset=utf-8,algorithm=md5-sess'
)
. '</challenge>',
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl">'
. XML::base64Encode('rspauth=7fb0ac7ac1ff501a330a76e89a0f1633')
. '</success>',
"<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' "
. "id='1234567890' from='localhost' version='1.0' xml:lang='en'><stream:features></stream:features>"
));
}


/**
* @Then /^plain authentication element should be send$/
Expand All @@ -84,6 +110,18 @@ public function plainAuthenticationElementShouldBeSend()
$this->getConnection()->getBuffer()
);
}

/**
* @Then /^digest-md(\d+) authentication element should be send$/
*/
public function digestMdAuthenticationElementShouldBeSend($arg1)
{
assertContains(
'<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="DIGIST-MD5"/>',
$this->getConnection()->getBuffer()
);
}


/**
* @Given /^should be authenticated$/
Expand Down
2 changes: 1 addition & 1 deletion tests/features/bootstrap/SessionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function testResponseDataForEmptySession()
public function manipulatingId()
{
$listeners = $this->getConnection()->getInputStream()->getEventManager()->getEventList();
$listener = array_filter($listeners['{http://etherx.jabber.org/streams}iq'], function ($listener) {
$listener = array_filter($listeners['{jabber:client}iq'], function ($listener) {
return ($listener[0] instanceof Session);
});

Expand Down
Loading

0 comments on commit 181241c

Please sign in to comment.