1*0b3fd2d3SAndreas Gohr<?php 2*0b3fd2d3SAndreas Gohr 3*0b3fd2d3SAndreas Gohr/** 4*0b3fd2d3SAndreas Gohr * This file is part of the FreeDSx SASL package. 5*0b3fd2d3SAndreas Gohr * 6*0b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 7*0b3fd2d3SAndreas Gohr * 8*0b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE 9*0b3fd2d3SAndreas Gohr * file that was distributed with this source code. 10*0b3fd2d3SAndreas Gohr */ 11*0b3fd2d3SAndreas Gohr 12*0b3fd2d3SAndreas Gohrnamespace FreeDSx\Sasl\Encoder; 13*0b3fd2d3SAndreas Gohr 14*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Exception\SaslEncodingException; 15*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Message; 16*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\SaslContext; 17*0b3fd2d3SAndreas Gohr 18*0b3fd2d3SAndreas Gohr/** 19*0b3fd2d3SAndreas Gohr * Responsible for encoding / decoding CRAM-MD5 messages. 20*0b3fd2d3SAndreas Gohr * 21*0b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 22*0b3fd2d3SAndreas Gohr */ 23*0b3fd2d3SAndreas Gohrclass CramMD5Encoder implements EncoderInterface 24*0b3fd2d3SAndreas Gohr{ 25*0b3fd2d3SAndreas Gohr 26*0b3fd2d3SAndreas Gohr /** 27*0b3fd2d3SAndreas Gohr * {@inheritDoc} 28*0b3fd2d3SAndreas Gohr */ 29*0b3fd2d3SAndreas Gohr public function encode(Message $message, SaslContext $context): string 30*0b3fd2d3SAndreas Gohr { 31*0b3fd2d3SAndreas Gohr if ($context->isServerMode()) { 32*0b3fd2d3SAndreas Gohr return $this->encodeServerChallenge($message); 33*0b3fd2d3SAndreas Gohr } else { 34*0b3fd2d3SAndreas Gohr return $this->encodeClientResponse($message); 35*0b3fd2d3SAndreas Gohr } 36*0b3fd2d3SAndreas Gohr } 37*0b3fd2d3SAndreas Gohr 38*0b3fd2d3SAndreas Gohr /** 39*0b3fd2d3SAndreas Gohr * {@inheritDoc} 40*0b3fd2d3SAndreas Gohr */ 41*0b3fd2d3SAndreas Gohr public function decode(string $data, SaslContext $context): Message 42*0b3fd2d3SAndreas Gohr { 43*0b3fd2d3SAndreas Gohr if ($context->isServerMode()) { 44*0b3fd2d3SAndreas Gohr return $this->decodeClientResponse($data); 45*0b3fd2d3SAndreas Gohr } else { 46*0b3fd2d3SAndreas Gohr return $this->decodeServerChallenge($data); 47*0b3fd2d3SAndreas Gohr } 48*0b3fd2d3SAndreas Gohr } 49*0b3fd2d3SAndreas Gohr 50*0b3fd2d3SAndreas Gohr /** 51*0b3fd2d3SAndreas Gohr * @throws SaslEncodingException 52*0b3fd2d3SAndreas Gohr */ 53*0b3fd2d3SAndreas Gohr protected function encodeServerChallenge(Message $message): string 54*0b3fd2d3SAndreas Gohr { 55*0b3fd2d3SAndreas Gohr if (!$message->has('challenge')) { 56*0b3fd2d3SAndreas Gohr throw new SaslEncodingException('The server challenge message must contain a "challenge".'); 57*0b3fd2d3SAndreas Gohr } 58*0b3fd2d3SAndreas Gohr $challenge = $message->get('challenge'); 59*0b3fd2d3SAndreas Gohr 60*0b3fd2d3SAndreas Gohr return '<' . $challenge . '>'; 61*0b3fd2d3SAndreas Gohr } 62*0b3fd2d3SAndreas Gohr 63*0b3fd2d3SAndreas Gohr /** 64*0b3fd2d3SAndreas Gohr * @throws SaslEncodingException 65*0b3fd2d3SAndreas Gohr */ 66*0b3fd2d3SAndreas Gohr protected function encodeClientResponse(Message $message): string 67*0b3fd2d3SAndreas Gohr { 68*0b3fd2d3SAndreas Gohr if (!$message->has('username')) { 69*0b3fd2d3SAndreas Gohr throw new SaslEncodingException('The client response must contain a username.'); 70*0b3fd2d3SAndreas Gohr } 71*0b3fd2d3SAndreas Gohr if (!$message->has('digest')) { 72*0b3fd2d3SAndreas Gohr throw new SaslEncodingException('The client response must contain a digest.'); 73*0b3fd2d3SAndreas Gohr } 74*0b3fd2d3SAndreas Gohr $username = $message->get('username'); 75*0b3fd2d3SAndreas Gohr $digest = $message->get('digest'); 76*0b3fd2d3SAndreas Gohr 77*0b3fd2d3SAndreas Gohr if (!preg_match('/^[0-9a-f]{32}$/', $digest)) { 78*0b3fd2d3SAndreas Gohr throw new SaslEncodingException('The client digest must be a 16 octet, lower-case, hexadecimal value'); 79*0b3fd2d3SAndreas Gohr } 80*0b3fd2d3SAndreas Gohr 81*0b3fd2d3SAndreas Gohr return $username . ' ' . $digest; 82*0b3fd2d3SAndreas Gohr } 83*0b3fd2d3SAndreas Gohr 84*0b3fd2d3SAndreas Gohr /** 85*0b3fd2d3SAndreas Gohr * @throws SaslEncodingException 86*0b3fd2d3SAndreas Gohr */ 87*0b3fd2d3SAndreas Gohr protected function decodeServerChallenge(string $challenge): Message 88*0b3fd2d3SAndreas Gohr { 89*0b3fd2d3SAndreas Gohr if (!preg_match('/^<.*>$/', $challenge)) { 90*0b3fd2d3SAndreas Gohr throw new SaslEncodingException('The server challenge is malformed.'); 91*0b3fd2d3SAndreas Gohr } 92*0b3fd2d3SAndreas Gohr 93*0b3fd2d3SAndreas Gohr return new Message(['challenge' => $challenge]); 94*0b3fd2d3SAndreas Gohr } 95*0b3fd2d3SAndreas Gohr 96*0b3fd2d3SAndreas Gohr /** 97*0b3fd2d3SAndreas Gohr * @throws SaslEncodingException 98*0b3fd2d3SAndreas Gohr */ 99*0b3fd2d3SAndreas Gohr protected function decodeClientResponse(string $response): Message 100*0b3fd2d3SAndreas Gohr { 101*0b3fd2d3SAndreas Gohr if (!preg_match('/(.*) ([0-9a-f]{32})$/', $response, $matches)) { 102*0b3fd2d3SAndreas Gohr throw new SaslEncodingException('The client response is malformed.'); 103*0b3fd2d3SAndreas Gohr } 104*0b3fd2d3SAndreas Gohr 105*0b3fd2d3SAndreas Gohr return new Message([ 106*0b3fd2d3SAndreas Gohr 'username' => $matches[1], 107*0b3fd2d3SAndreas Gohr 'digest' => $matches[2], 108*0b3fd2d3SAndreas Gohr ]); 109*0b3fd2d3SAndreas Gohr } 110*0b3fd2d3SAndreas Gohr} 111