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