pax_global_header00006660000000000000000000000064124514570620014520gustar00rootroot0000000000000052 comment=cb49ffc81aa179420e1c7ed36dd06caa0ad3624b sasl-1.0.0/000077500000000000000000000000001245145706200124605ustar00rootroot00000000000000sasl-1.0.0/LICENSE.md000066400000000000000000000030501245145706200140620ustar00rootroot00000000000000Modified BSD License ==================== Copyright (c) 2002-2003 Richard Heyes, 2014 Jaussoin Timothée, 2014 Fabian Grutschus All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sasl-1.0.0/README.md000066400000000000000000000107231245145706200137420ustar00rootroot00000000000000# fabiang/sasl The PHP SASL Authentification Library. [![Latest Stable Version](https://poser.pugx.org/fabiang/sasl/v/stable.svg)](https://packagist.org/packages/fabiang/sasl) [![Total Downloads](https://poser.pugx.org/fabiang/sasl/downloads.svg)](https://packagist.org/packages/fabiang/sasl) [![Latest Unstable Version](https://poser.pugx.org/fabiang/sasl/v/unstable.svg)](https://packagist.org/packages/fabiang/sasl) [![License](https://poser.pugx.org/fabiang/sasl/license.svg)](https://packagist.org/packages/fabiang/sasl) [![HHVM Status](http://hhvm.h4cc.de/badge/fabiang/sasl.svg)](http://hhvm.h4cc.de/package/fabiang/sasl) [![Build Status](https://travis-ci.org/fabiang/sasl.svg?branch=master)](https://travis-ci.org/fabiang/sasl) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fabiang/sasl/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/fabiang/sasl/?branch=master) [![Code Climate](https://codeclimate.com/github/fabiang/sasl/badges/gpa.svg)](https://codeclimate.com/github/fabiang/sasl) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/e81e1e30-c545-420a-8a0c-59b60976f54b/mini.png)](https://insight.sensiolabs.com/projects/e81e1e30-c545-420a-8a0c-59b60976f54b) [![Coverage Status](https://img.shields.io/coveralls/fabiang/sasl.svg)](https://coveralls.io/r/fabiang/sasl) [![Dependency Status](https://gemnasium.com/fabiang/sasl.svg)](https://gemnasium.com/fabiang/sasl) Provides code to generate responses to common SASL mechanisms, including: * Digest-MD5 * Cram-MD5 * Plain * Anonymous * Login (Pseudo mechanism) * SCRAM Full refactored version of the the original [Auth_SASL2 Pear package](http://pear.php.net/package/Auth_SASL2/). ## Installation The easiest way to install fabiang/sasl is by using Composer: ``` curl -sS https://getcomposer.org/installer | php php composer.phar require fabiang/sasl='1.0.x-dev' ``` ## Usage Use the factory method to create a authentication mechanism object: ```php use Fabiang\Sasl\Sasl; $factory = new Sasl; $mechanism = $factory->factory('SCRAM-SHA-1', array( 'authcid' => 'username', 'secret' => 'password', 'authzid' => 'authzid', // optional. Username to proxy as 'service' => 'servicename', // optional. Name of the service 'hostname' => 'hostname', // optional. Hostname of the service )); $response = $mechanism->createResponse(); ``` Challenge-based authentication mechanisms implement the interface `Fabiang\Sasl\Authentication\ChallengeAuthenticationInterface`. For those mechanisms call the method again with the challenge: ```php $response = $mechanism->createResponse($challenge); ``` **Note**: The challenge must be Base64 decoded. ### SCRAM verification To verify the data returned by the server for SCRAM you can call: ```php $mechanism->verify($data); ``` If the method returns false you should disconnect. ### Required options List of options required by authentication mechanisms. For mechanisms that are challenge-based you'll need to call `createResponse()` again and send the returned value to the server. | Mechanism | Authcid | Secret | Authzid | Service | Hostname | Challenge | | --------- | ------- | ------ | -------- | ------- | -------- | --------- | | Anonymous | yes | no | no | no | no | no | | CramMD5 | yes | yes | no | no | no | yes | | DigestMD5 | yes | yes | optional | yes | yes | yes | | External | no | no | yes | no | no | no | | Login | yes | yes | no | no | no | no | | Plain | yes | yes | optional | no | no | no | | SCRAM-* | yes | yes | optional | no | no | yes | ## Developing If you like this library and you want to contribute, make sure the unit tests and integration tests are running. Composer will help you to install the right version of PHPUnit and Behat. ``` composer install --dev ``` After that run the unit tests: ``` ./vendor/bin/phpunit -c tests ``` The integration tests verify the authentication methods against an Ejabberd and Dovecot server. To launch the servers you can use the provided Vagrant box. Just [install Vagrant](https://www.vagrantup.com/downloads.html) and run: ``` vagrant up ``` After some minutes you'll have the runnig server instances inside of a virtual machine. Now you can run the integration tests: ``` ./vendor/bin/behat -c tests/behat.yml.dist ``` ## License BSD-3-Clause. See the [LICENSE.md](LICENSE.md). sasl-1.0.0/composer.json000066400000000000000000000023561245145706200152100ustar00rootroot00000000000000{ "name": "fabiang/sasl", "description": "Abstraction of various SASL mechanism responses.", "license": "BSD-3-Clause", "homepage": "https://github.com/fabiang/sasl", "keywords": ["sasl", "authentication", "scram", "auth"], "type": "library", "authors": [ { "name": "Fabian Grutschus", "email": "f.grutschus@lubyte.de" }, { "name": "Anish Mistry", "email": "amistry@am-productions.biz" }, { "name": "Richard Heyes", "email": "richard@php.net" }, { "name": "Michael Bretterklieber", "email": "michael@bretterklieber.com" } ], "autoload": { "psr-4": { "Fabiang\\Sasl\\": "src/" } }, "autoload-dev": { "psr-4": { "Fabiang\\Sasl\\Behat\\": "tests/features/bootstrap" } }, "require": { "php": ">=5.3.0" }, "require-dev": { "phpunit/phpunit": "~4.4", "satooshi/php-coveralls": "~0.6", "behat/behat": "~3.0", "codeclimate/php-test-reporter": "@dev" }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } } } sasl-1.0.0/src/000077500000000000000000000000001245145706200132475ustar00rootroot00000000000000sasl-1.0.0/src/Authentication/000077500000000000000000000000001245145706200162265ustar00rootroot00000000000000sasl-1.0.0/src/Authentication/AbstractAuthentication.php000066400000000000000000000055511245145706200234100ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; use Fabiang\Sasl\Options; /** * Common functionality to SASL mechanisms * * @author Richard Heyes */ abstract class AbstractAuthentication { /** * Use random devices. * * @var bool */ public static $useDevRandom = true; /** * Options object. * * @var Options */ protected $options; /** * * @param Options $options */ public function __construct(Options $options) { $this->options = $options; } /** * Get options object. * * @return Options */ public function getOptions() { return $this->options; } /** * Creates the client nonce for the response * * @return string The cnonce value */ protected function generateCnonce() { foreach (array('/dev/urandom', '/dev/random') as $file) { if (true === static::$useDevRandom && is_readable($file)) { return base64_encode(file_get_contents($file, false, null, -1, 32)); } } $cnonce = ''; for ($i = 0; $i < 32; $i++) { $cnonce .= chr(mt_rand(0, 255)); } return base64_encode($cnonce); } } sasl-1.0.0/src/Authentication/Anonymous.php000066400000000000000000000047171245145706200207400ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; /** * Implmentation of ANONYMOUS SASL mechanism * * @author Richard Heyes */ class Anonymous extends AbstractAuthentication implements AuthenticationInterface { /** * Not much to do here except return the token supplied. * No encoding, hashing or encryption takes place for this * mechanism, simply one of: * o An email address * o An opaque string not containing "@" that can be interpreted * by the sysadmin * o Nothing * * We could have some logic here for the second option, but this * would by no means create something interpretable. * @param string $challenge * @return string The unaltered input token */ public function createResponse($challenge = null) { return $this->options->getAuthcid(); } } sasl-1.0.0/src/Authentication/AuthenticationInterface.php000066400000000000000000000040031245145706200235340ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; /** * Interface for Sasl authentication classes. * * @author Fabian Grutschus */ interface AuthenticationInterface { /** * Create response. * * @param string $challenge Response challenge. Not every authentication method requires this value. */ public function createResponse($challenge = null); } sasl-1.0.0/src/Authentication/ChallengeAuthenticationInterface.php000066400000000000000000000035511245145706200253460ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; /** * Authentication mechaisms based on challenge responses. * * @author Fabian Grutschus */ interface ChallengeAuthenticationInterface extends AuthenticationInterface { } sasl-1.0.0/src/Authentication/CramMD5.php000066400000000000000000000050071245145706200201310ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; use Fabiang\Sasl\Authentication\AbstractAuthentication; /** * Implmentation of CRAM-MD5 SASL mechanism * * @author Richard Heyes */ class CramMD5 extends AbstractAuthentication implements ChallengeAuthenticationInterface { /** * Implements the CRAM-MD5 SASL mechanism * This DOES NOT base64 encode the return value, * you will need to do that yourself. * * @param string $challenge The challenge supplied by the server. * this should be already base64_decoded. * * @return string The string to pass back to the server, of the form * " ". This is NOT base64_encoded. */ public function createResponse($challenge = null) { return $this->options->getAuthcid() . ' ' . hash_hmac('md5', $challenge, $this->options->getSecret()); } } sasl-1.0.0/src/Authentication/DigestMD5.php000066400000000000000000000164071245145706200204740ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; use Fabiang\Sasl\Exception\InvalidArgumentException; use Fabiang\Sasl\Exception\RuntimeException; /** * Implmentation of DIGEST-MD5 SASL mechanism * * @author Richard Heyes */ class DigestMD5 extends AbstractAuthentication implements ChallengeAuthenticationInterface { /** * Provides the (main) client response for DIGEST-MD5 * requires a few extra parameters than the other * mechanisms, which are unavoidable. * * @param string $challenge The digest challenge sent by the server * @return string The digest response (NOT base64 encoded) */ public function createResponse($challenge = null) { $parsedChallenge = $this->parseChallenge($challenge); $authzidString = ''; $authcid = $this->options->getAuthcid(); $pass = $this->options->getSecret(); $authzid = $this->options->getAuthzid(); $service = $this->options->getService(); $hostname = $this->options->getHostname(); if (!empty($authzid)) { $authzidString = 'authzid="' . $authzid . '",'; } if (!empty($parsedChallenge)) { $cnonce = $this->generateCnonce(); $digestUri = sprintf('%s/%s', $service, $hostname); $responseValue = $this->getResponseValue( $authcid, $pass, $parsedChallenge['realm'], $parsedChallenge['nonce'], $cnonce, $digestUri, $authzid ); $realm = ''; if ($parsedChallenge['realm']) { $realm = 'realm="' . $parsedChallenge['realm'] . '",'; } return sprintf( 'username="%s",%s%snonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",' . 'response=%s,maxbuf=%d', $authcid, $realm, $authzidString, $parsedChallenge['nonce'], $cnonce, $digestUri, $responseValue, $parsedChallenge['maxbuf'] ); } throw new InvalidArgumentException('Invalid digest challenge'); } /** * Parses and verifies the digest challenge* * * @param string $challenge The digest challenge * @return array The parsed challenge as an assoc * array in the form "directive => value". * @access private */ private function parseChallenge($challenge) { /** * Defaults and required directives */ $tokens = array( 'realm' => '', 'maxbuf' => 65536, ); $matches = array(); while (preg_match('/^(?[a-z-]+)=(?"[^"]+(?checkToken($tokens, $key, $value); // Remove the just parsed directive from the challenge $challenge = substr($challenge, strlen($match) + 1); } // Required: nonce, algorithm if (empty($tokens['nonce']) || empty($tokens['algorithm'])) { return array(); } return $tokens; } /** * Check found token. * * @param array $tokens * @param string $key * @param string $value */ private function checkToken(array &$tokens, $key, $value) { // Ignore these as per rfc2831 if ($key !== 'opaque' && $key !== 'domain') { if (!empty($tokens[$key])) { // Allowed multiple "realm" and "auth-param" if ('realm' === $key || 'auth-param' === $key) { // we don't support multiple realms yet if ('realm' === $key) { throw new RuntimeException('Multiple realms are not supported'); } $tokens[$key] = (array) $tokens[$key]; $tokens[$key][] = $this->trim($value); // Any other multiple instance = failure } else { return array(); } } else { $tokens[$key] = $this->trim($value); } } } /** * * @param string $string * @return string */ private function trim($string) { return trim($string, '"'); } /** * Creates the response= part of the digest response * * @param string $authcid Authentication id (username) * @param string $pass Password * @param string $realm Realm as provided by the server * @param string $nonce Nonce as provided by the server * @param string $cnonce Client nonce * @param string $digest_uri The digest-uri= value part of the response * @param string $authzid Authorization id * @return string The response= part of the digest response * @access private */ private function getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '') { if ($authzid == '') { $A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce); } else { $A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid); } $A2 = 'AUTHENTICATE:' . $digest_uri; return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2))); } } sasl-1.0.0/src/Authentication/External.php000066400000000000000000000041121245145706200205170ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; /** * Implementation of EXTERNAL SASL mechanism * * @author Christoph Schulz */ class External extends AbstractAuthentication implements AuthenticationInterface { /** * Returns EXTERNAL response * * @param string $challenge * @return stringEXTERNAL Response */ public function createResponse($challenge = null) { return $this->options->getAuthcid(); } } sasl-1.0.0/src/Authentication/Login.php000066400000000000000000000043131245145706200200100ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; /** * This is technically not a SASL mechanism, however * it's used by Net_Sieve, Net_Cyrus and potentially * other protocols , so here is a good place to abstract * it. * * @author Richard Heyes */ class Login extends AbstractAuthentication implements AuthenticationInterface { /** * Pseudo SASL LOGIN mechanism * * @return string LOGIN string */ public function createResponse($challenge = null) { return sprintf('LOGIN %s %s', $this->options->getAuthcid(), $this->options->getSecret()); } } sasl-1.0.0/src/Authentication/Plain.php000066400000000000000000000041471245145706200200100ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; /** * Implmentation of PLAIN SASL mechanism * * @author Richard Heyes */ class Plain extends AbstractAuthentication implements AuthenticationInterface { /** * Returns PLAIN response * @return string PLAIN Response */ public function createResponse($challenge = null) { return $this->options->getAuthzid() . chr(0) . $this->options->getAuthcid() . chr(0) . $this->options->getSecret(); } } sasl-1.0.0/src/Authentication/SCRAM.php000066400000000000000000000237311245145706200176120ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; use Fabiang\Sasl\Authentication\AbstractAuthentication; use Fabiang\Sasl\Options; use Fabiang\Sasl\Exception\InvalidArgumentException; /** * Implementation of SCRAM-* SASL mechanisms. * SCRAM mechanisms have 3 main steps (initial response, response to the server challenge, then server signature * verification) which keep state-awareness. Therefore a single class instanciation must be done and reused for the whole * authentication process. * * @author Jehan */ class SCRAM extends AbstractAuthentication implements ChallengeAuthenticationInterface, VerificationInterface { private $hashAlgo; private $hash; private $hmac; private $gs2Header; private $cnonce; private $firstMessageBare; private $saltedPassword; private $authMessage; /** * Construct a SCRAM-H client where 'H' is a cryptographic hash function. * * @param Options $options * @param string $hash The name cryptographic hash function 'H' as registered by IANA in the "Hash Function Textual * Names" registry. * @link http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml "Hash Function Textual * Names" * format of core PHP hash function. * @throws InvalidArgumentException */ public function __construct(Options $options, $hash) { parent::__construct($options); // Though I could be strict, I will actually also accept the naming used in the PHP core hash framework. // For instance "sha1" is accepted, while the registered hash name should be "SHA-1". $normalizedHash = str_replace('-', '', strtolower($hash)); $hashAlgos = hash_algos(); if (!in_array($normalizedHash, $hashAlgos)) { throw new InvalidArgumentException("Invalid SASL mechanism type '$hash'"); } $this->hash = function ($data) use ($normalizedHash) { return hash($normalizedHash, $data, true); }; $this->hmac = function ($key, $str, $raw) use ($normalizedHash) { return hash_hmac($normalizedHash, $str, $key, $raw); }; $this->hashAlgo = $normalizedHash; } /** * Provides the (main) client response for SCRAM-H. * * @param string $challenge The challenge sent by the server. * If the challenge is null or an empty string, the result will be the "initial response". * @return string|false The response (binary, NOT base64 encoded) */ public function createResponse($challenge = null) { $authcid = $this->formatName($this->options->getAuthcid()); if (empty($authcid)) { return false; } $authzid = $this->options->getAuthzid(); if (!empty($authzid)) { $authzid = $this->formatName($authzid); } if (empty($challenge)) { return $this->generateInitialResponse($authcid, $authzid); } else { return $this->generateResponse($challenge, $this->options->getSecret()); } } /** * Prepare a name for inclusion in a SCRAM response. * * @param string $username a name to be prepared. * @return string the reformated name. */ private function formatName($username) { return str_replace(array('=', ','), array('=3D', '=2C'), $username); } /** * Generate the initial response which can be either sent directly in the first message or as a response to an empty * server challenge. * * @param string $authcid Prepared authentication identity. * @param string $authzid Prepared authorization identity. * @return string The SCRAM response to send. */ private function generateInitialResponse($authcid, $authzid) { $gs2CbindFlag = 'n,'; $this->gs2Header = $gs2CbindFlag . (!empty($authzid) ? 'a=' . $authzid : '') . ','; // I must generate a client nonce and "save" it for later comparison on second response. $this->cnonce = $this->generateCnonce(); $this->firstMessageBare = 'n=' . $authcid . ',r=' . $this->cnonce; return $this->gs2Header . $this->firstMessageBare; } /** * Parses and verifies a non-empty SCRAM challenge. * * @param string $challenge The SCRAM challenge * @return string|false The response to send; false in case of wrong challenge or if an initial response has not * been generated first. */ private function generateResponse($challenge, $password) { $matches = array(); $serverMessageRegexp = "#^r=([\x21-\x2B\x2D-\x7E/]+)" . ",s=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9/+]{3}=|[A-Za-z0-9/+]{2}==)?)" . ",i=([0-9]*)(,[A-Za-z]=[^,])*$#"; if (!isset($this->cnonce, $this->gs2Header) || !preg_match($serverMessageRegexp, $challenge, $matches)) { return false; } $nonce = $matches[1]; $salt = base64_decode($matches[2]); if (!$salt) { // Invalid Base64. return false; } $i = intval($matches[3]); $cnonce = substr($nonce, 0, strlen($this->cnonce)); if ($cnonce !== $this->cnonce) { // Invalid challenge! Are we under attack? return false; } $channelBinding = 'c=' . base64_encode($this->gs2Header); $finalMessage = $channelBinding . ',r=' . $nonce; $saltedPassword = $this->hi($password, $salt, $i); $this->saltedPassword = $saltedPassword; $clientKey = call_user_func($this->hmac, $saltedPassword, "Client Key", true); $storedKey = call_user_func($this->hash, $clientKey, true); $authMessage = $this->firstMessageBare . ',' . $challenge . ',' . $finalMessage; $this->authMessage = $authMessage; $clientSignature = call_user_func($this->hmac, $storedKey, $authMessage, true); $clientProof = $clientKey ^ $clientSignature; $proof = ',p=' . base64_encode($clientProof); return $finalMessage . $proof; } /** * Hi() call, which is essentially PBKDF2 (RFC-2898) with HMAC-H() as the pseudorandom function. * * @param string $str The string to hash. * @param string $salt The salt value. * @param int $i The iteration count. */ private function hi($str, $salt, $i) { $int1 = "\0\0\0\1"; $ui = call_user_func($this->hmac, $str, $salt . $int1, true); $result = $ui; for ($k = 1; $k < $i; $k++) { $ui = call_user_func($this->hmac, $str, $ui, true); $result = $result ^ $ui; } return $result; } /** * SCRAM has also a server verification step. On a successful outcome, it will send additional data which must * absolutely be checked against this function. If this fails, the entity which we are communicating with is probably * not the server as it has not access to your ServerKey. * * @param string $data The additional data sent along a successful outcome. * @return bool Whether the server has been authenticated. * If false, the client must close the connection and consider to be under a MITM attack. */ public function verify($data) { $verifierRegexp = '#^v=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9/+]{3}=|[A-Za-z0-9/+]{2}==)?)$#'; $matches = array(); if (!isset($this->saltedPassword, $this->authMessage) || !preg_match($verifierRegexp, $data, $matches)) { // This cannot be an outcome, you never sent the challenge's response. return false; } $verifier = $matches[1]; $proposedServerSignature = base64_decode($verifier); $serverKey = call_user_func($this->hmac, $this->saltedPassword, "Server Key", true); $serverSignature = call_user_func($this->hmac, $serverKey, $this->authMessage, true); return $proposedServerSignature === $serverSignature; } /** * @return string */ public function getCnonce() { return $this->cnonce; } public function getSaltedPassword() { return $this->saltedPassword; } public function getAuthMessage() { return $this->authMessage; } public function getHashAlgo() { return $this->hashAlgo; } } sasl-1.0.0/src/Authentication/VerificationInterface.php000066400000000000000000000035551245145706200232120ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Authentication; /** * * @author Fabian Grutschus */ interface VerificationInterface { /** * Varify connection. * * @param string $data */ public function verify($data); } sasl-1.0.0/src/Exception/000077500000000000000000000000001245145706200152055ustar00rootroot00000000000000sasl-1.0.0/src/Exception/ExceptionInterface.php000066400000000000000000000034241245145706200215000ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Exception; /** * Exception interface. * * @author Fabian Grutschus */ interface ExceptionInterface { } sasl-1.0.0/src/Exception/InvalidArgumentException.php000066400000000000000000000036121245145706200226700ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Exception; use Fabiang\Sasl\Exception\ExceptionInterface; /** * InvalidArgumentException * * @author Fabian Grutschus */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } sasl-1.0.0/src/Exception/RuntimeException.php000066400000000000000000000035621245145706200212260ustar00rootroot00000000000000 */ namespace Fabiang\Sasl\Exception; use Fabiang\Sasl\Exception\ExceptionInterface; /** * RuntimeException * * @author Fabian Grutschus */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } sasl-1.0.0/src/Options.php000066400000000000000000000064101245145706200154140ustar00rootroot00000000000000 */ namespace Fabiang\Sasl; /** * Options object for Sasl. * * @author Fabian Grutschus */ class Options { /** * Authentication identity (e.g. username). * * @var string */ protected $authcid; /** * Authentication secret (e.g. password) * * @var string */ protected $secret; /** * Authorization identity * * @var string */ protected $authzid; /** * Service name. * * @var string */ protected $service; /** * Service hostname. * * @var string */ protected $hostname; /** * Constructor. * * @param string $authcid authentication identity (e.g. username) * @param string $secret authentication secret (e.g. password) * @param string $authzid authorization identity (username to proxy as) * @param string $service service name * @param string $hostname service hostname */ public function __construct($authcid, $secret = null, $authzid = null, $service = null, $hostname = null) { $this->authcid = $authcid; $this->secret = $secret; $this->authzid = $authzid; $this->service = $service; $this->hostname = $hostname; } public function getAuthcid() { return $this->authcid; } public function getSecret() { return $this->secret; } public function getAuthzid() { return $this->authzid; } public function getService() { return $this->service; } public function getHostname() { return $this->hostname; } } sasl-1.0.0/src/Sasl.php000066400000000000000000000115211245145706200146620ustar00rootroot00000000000000 */ namespace Fabiang\Sasl; use Fabiang\Sasl\Exception\InvalidArgumentException; /** * Client implementation of various SASL mechanisms * * @author Richard Heyes */ class Sasl { /** * Known authentication mechanisms classes. * * @var array */ protected $mechanisms = array( 'anonymous' => 'Fabiang\\Sasl\Authentication\\Anonymous', 'login' => 'Fabiang\\Sasl\Authentication\\Login', 'plain' => 'Fabiang\\Sasl\Authentication\\Plain', 'external' => 'Fabiang\\Sasl\Authentication\\External', 'crammd5' => 'Fabiang\\Sasl\Authentication\\CramMD5', 'digestmd5' => 'Fabiang\\Sasl\Authentication\\DigestMD5', ); /** * Factory class. Returns an object of the request * type. * * @param string $type One of: Anonymous * Plain * CramMD5 * DigestMD5 * SCRAM-* (any mechanism of the SCRAM family) * Types are not case sensitive * @param Options|array Options for authentication * @return Authentication\AuthenticationInterface */ public function factory($type, $options = array()) { $className = null; $parameter = null; $matches = array(); $options = $this->createOptionsObject($options); $formatedType = strtolower(str_replace('-', '', $type)); if (isset($this->mechanisms[$formatedType])) { $className = $this->mechanisms[$formatedType]; } elseif (preg_match('/^scram(?.{1,9})$/i', $formatedType, $matches)) { $className = 'Fabiang\\Sasl\Authentication\\SCRAM'; $parameter = $matches['algo']; } if (null === $className) { throw new InvalidArgumentException("Invalid SASL mechanism type '$type'"); } $object = new $className($options, $parameter); return $object; } /** * * @param Options|array $options * @return \Fabiang\Sasl\Options * @throws InvalidArgumentException */ private function createOptionsObject($options) { $optionsObject = $options; if (is_array($options)) { $optionsObject = new Options( $this->checkEmpty($options, 'authcid'), $this->checkEmpty($options, 'secret'), $this->checkEmpty($options, 'authzid'), $this->checkEmpty($options, 'service'), $this->checkEmpty($options, 'hostname') ); } if (!($optionsObject instanceof Options)) { throw new InvalidArgumentException( 'Invalid options passed. Argument must be either of type "Fabiang\Sasl\Options" or "array", "' . (is_object($options) ? get_class($options) : gettype($options)) . '" given.' ); } return $optionsObject; } /** * * @param array $array * @param string $key * @return mixed */ private function checkEmpty(array $array, $key) { if (!empty($array[$key])) { return $array[$key]; } return null; } }