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\Security; 13*0b3fd2d3SAndreas Gohr 14*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Exception\SaslException; 15*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\SaslContext; 16*0b3fd2d3SAndreas Gohr 17*0b3fd2d3SAndreas Gohr/** 18*0b3fd2d3SAndreas Gohr * The DIGEST-MD5 security layer. 19*0b3fd2d3SAndreas Gohr * 20*0b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 21*0b3fd2d3SAndreas Gohr */ 22*0b3fd2d3SAndreas Gohrclass DigestMD5SecurityLayer implements SecurityLayerInterface 23*0b3fd2d3SAndreas Gohr{ 24*0b3fd2d3SAndreas Gohr protected const MAXBUF = 65536; 25*0b3fd2d3SAndreas Gohr 26*0b3fd2d3SAndreas Gohr protected const KCC_MC = 'Digest H(A1) to client-to-server sealing key magic constant'; 27*0b3fd2d3SAndreas Gohr 28*0b3fd2d3SAndreas Gohr protected const KCS_MC = 'Digest H(A1) to server-to-client sealing key magic constant'; 29*0b3fd2d3SAndreas Gohr 30*0b3fd2d3SAndreas Gohr protected const KIC_MC = 'Digest session key to client-to-server signing key magic constant'; 31*0b3fd2d3SAndreas Gohr 32*0b3fd2d3SAndreas Gohr protected const KIS_MC = 'Digest session key to server-to-client signing key magic constant'; 33*0b3fd2d3SAndreas Gohr 34*0b3fd2d3SAndreas Gohr /** 35*0b3fd2d3SAndreas Gohr * RFC2831 Section 2.3 / 2.4 36*0b3fd2d3SAndreas Gohr */ 37*0b3fd2d3SAndreas Gohr protected const MESSAGE_TYPE = 1; 38*0b3fd2d3SAndreas Gohr 39*0b3fd2d3SAndreas Gohr /** 40*0b3fd2d3SAndreas Gohr * Cipher specific details related to the encryption / decryption process. 41*0b3fd2d3SAndreas Gohr */ 42*0b3fd2d3SAndreas Gohr protected const CIPHERS = [ 43*0b3fd2d3SAndreas Gohr '3des' => [ 44*0b3fd2d3SAndreas Gohr 'block_size' => 8, 45*0b3fd2d3SAndreas Gohr 'kcn' => 16, 46*0b3fd2d3SAndreas Gohr 'cipher' => 'des-ede3-cbc', 47*0b3fd2d3SAndreas Gohr ], 48*0b3fd2d3SAndreas Gohr 'des' => [ 49*0b3fd2d3SAndreas Gohr 'block_size' => 8, 50*0b3fd2d3SAndreas Gohr 'kcn' => 16, 51*0b3fd2d3SAndreas Gohr 'cipher' => 'des-ede-cbc' 52*0b3fd2d3SAndreas Gohr ], 53*0b3fd2d3SAndreas Gohr 'rc4' => [ 54*0b3fd2d3SAndreas Gohr 'block_size' => 1, 55*0b3fd2d3SAndreas Gohr 'kcn' => 16, 56*0b3fd2d3SAndreas Gohr 'cipher' => 'rc4', 57*0b3fd2d3SAndreas Gohr ], 58*0b3fd2d3SAndreas Gohr 'rc4-40' => [ 59*0b3fd2d3SAndreas Gohr 'block_size' => 1, 60*0b3fd2d3SAndreas Gohr 'kcn' => 5, 61*0b3fd2d3SAndreas Gohr 'cipher' => 'rc4-40', 62*0b3fd2d3SAndreas Gohr ], 63*0b3fd2d3SAndreas Gohr 'rc4-56' => [ 64*0b3fd2d3SAndreas Gohr 'block_size' => 1, 65*0b3fd2d3SAndreas Gohr 'kcn' => 7, 66*0b3fd2d3SAndreas Gohr 'cipher' => 'rc4-56', 67*0b3fd2d3SAndreas Gohr ], 68*0b3fd2d3SAndreas Gohr ]; 69*0b3fd2d3SAndreas Gohr 70*0b3fd2d3SAndreas Gohr /** 71*0b3fd2d3SAndreas Gohr * {@inheritDoc} 72*0b3fd2d3SAndreas Gohr */ 73*0b3fd2d3SAndreas Gohr public function wrap(string $data, SaslContext $context): string 74*0b3fd2d3SAndreas Gohr { 75*0b3fd2d3SAndreas Gohr $qop = $context->get('qop'); 76*0b3fd2d3SAndreas Gohr 77*0b3fd2d3SAndreas Gohr if ($qop === 'auth-conf') { 78*0b3fd2d3SAndreas Gohr $wrapped = $this->encrypt($data, $context); 79*0b3fd2d3SAndreas Gohr } elseif ($qop === 'auth-int') { 80*0b3fd2d3SAndreas Gohr $wrapped = $this->sign($data, $context); 81*0b3fd2d3SAndreas Gohr } else { 82*0b3fd2d3SAndreas Gohr throw new SaslException(sprintf('The qop option "%s" is not recognized as a security layer.', $qop)); 83*0b3fd2d3SAndreas Gohr } 84*0b3fd2d3SAndreas Gohr $this->validateBufferLength($wrapped, $context); 85*0b3fd2d3SAndreas Gohr $context->set('seqnumsnt', $context->get('seqnumsnt') + 1); 86*0b3fd2d3SAndreas Gohr 87*0b3fd2d3SAndreas Gohr return $wrapped; 88*0b3fd2d3SAndreas Gohr } 89*0b3fd2d3SAndreas Gohr 90*0b3fd2d3SAndreas Gohr /** 91*0b3fd2d3SAndreas Gohr * {@inheritDoc} 92*0b3fd2d3SAndreas Gohr */ 93*0b3fd2d3SAndreas Gohr public function unwrap(string $data, SaslContext $context): string 94*0b3fd2d3SAndreas Gohr { 95*0b3fd2d3SAndreas Gohr $qop = $context->get('qop'); 96*0b3fd2d3SAndreas Gohr $this->validateBufferLength($data, $context); 97*0b3fd2d3SAndreas Gohr 98*0b3fd2d3SAndreas Gohr if ($qop === 'auth-conf') { 99*0b3fd2d3SAndreas Gohr $unwrapped = $this->decrypt($data, $context); 100*0b3fd2d3SAndreas Gohr } elseif ($qop === 'auth-int') { 101*0b3fd2d3SAndreas Gohr $unwrapped = $this->verify($data, $context); 102*0b3fd2d3SAndreas Gohr } else { 103*0b3fd2d3SAndreas Gohr throw new SaslException(sprintf('The qop option "%s" is not recognized as a security layer.', $qop)); 104*0b3fd2d3SAndreas Gohr } 105*0b3fd2d3SAndreas Gohr $context->set('seqnumrcv', $context->get('seqnumrcv') + 1); 106*0b3fd2d3SAndreas Gohr 107*0b3fd2d3SAndreas Gohr return $unwrapped; 108*0b3fd2d3SAndreas Gohr } 109*0b3fd2d3SAndreas Gohr 110*0b3fd2d3SAndreas Gohr /** 111*0b3fd2d3SAndreas Gohr * @throws SaslException 112*0b3fd2d3SAndreas Gohr */ 113*0b3fd2d3SAndreas Gohr protected function decrypt(string $data, SaslContext $context): string 114*0b3fd2d3SAndreas Gohr { 115*0b3fd2d3SAndreas Gohr # At the very least we are expect 16 bytes. 10 for the actual MAC, 4 for the seqnum, 2 for the msgtype. 116*0b3fd2d3SAndreas Gohr if (strlen($data) < 16) { 117*0b3fd2d3SAndreas Gohr throw new SaslException('The data to decrypt must be at least 16 bytes.'); 118*0b3fd2d3SAndreas Gohr } 119*0b3fd2d3SAndreas Gohr $receivedMsgType = hexdec(bin2hex(substr($data, -6, 2))); 120*0b3fd2d3SAndreas Gohr $receivedSeqNum = hexdec(bin2hex(substr($data, -4))); 121*0b3fd2d3SAndreas Gohr if (self::MESSAGE_TYPE !== $receivedMsgType) { 122*0b3fd2d3SAndreas Gohr throw new SaslException(sprintf( 123*0b3fd2d3SAndreas Gohr 'The received message type of "%s" was unexpected.', 124*0b3fd2d3SAndreas Gohr $receivedMsgType 125*0b3fd2d3SAndreas Gohr )); 126*0b3fd2d3SAndreas Gohr } 127*0b3fd2d3SAndreas Gohr $seqnum = $context->get('seqnumrcv'); 128*0b3fd2d3SAndreas Gohr if (!is_int($seqnum) || $seqnum !== $receivedSeqNum) { 129*0b3fd2d3SAndreas Gohr throw new SaslException(sprintf( 130*0b3fd2d3SAndreas Gohr 'The received sequence number was unexpected. Expected %s, but got %s.', 131*0b3fd2d3SAndreas Gohr $seqnum, 132*0b3fd2d3SAndreas Gohr $receivedSeqNum 133*0b3fd2d3SAndreas Gohr )); 134*0b3fd2d3SAndreas Gohr } 135*0b3fd2d3SAndreas Gohr 136*0b3fd2d3SAndreas Gohr $cipher = $context->get('cipher'); 137*0b3fd2d3SAndreas Gohr $a1 = $context->get('a1'); 138*0b3fd2d3SAndreas Gohr $isServerMode = $context->isServerMode(); 139*0b3fd2d3SAndreas Gohr $this->validateCipher($cipher); 140*0b3fd2d3SAndreas Gohr $encrypted = substr($data, 0, -6); 141*0b3fd2d3SAndreas Gohr 142*0b3fd2d3SAndreas Gohr # Inverted selection of constants here and for $mcKi, as this would be the receiving end. 143*0b3fd2d3SAndreas Gohr $mcKc = $isServerMode ? self::KCC_MC : self::KCS_MC; 144*0b3fd2d3SAndreas Gohr $kc = $this->generateKeyKc($a1, $cipher, $mcKc); 145*0b3fd2d3SAndreas Gohr [$iv, $key] = $this->generateKeyAndIV($cipher, $kc); 146*0b3fd2d3SAndreas Gohr $data = openssl_decrypt($encrypted, self::CIPHERS[$cipher]['cipher'], $key, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA, $iv); 147*0b3fd2d3SAndreas Gohr if ($data === false) { 148*0b3fd2d3SAndreas Gohr throw new SaslException('Failed the decrypt the message.'); 149*0b3fd2d3SAndreas Gohr } 150*0b3fd2d3SAndreas Gohr $message = substr($data, 0, -10); 151*0b3fd2d3SAndreas Gohr if (self::CIPHERS[$cipher]['block_size'] > 1) { 152*0b3fd2d3SAndreas Gohr $message = $this->removePadding($message, self::CIPHERS[$cipher]['block_size']); 153*0b3fd2d3SAndreas Gohr } 154*0b3fd2d3SAndreas Gohr 155*0b3fd2d3SAndreas Gohr $receivedMac = substr($data, -10); 156*0b3fd2d3SAndreas Gohr $mcKi = $isServerMode ? self::KIC_MC : self::KIS_MC; 157*0b3fd2d3SAndreas Gohr $ki = $this->generateKeyKi($a1, $mcKi); 158*0b3fd2d3SAndreas Gohr $expectedMac = substr($this->generateMACBlock($ki, $message, $seqnum), 0, 10); 159*0b3fd2d3SAndreas Gohr 160*0b3fd2d3SAndreas Gohr if ($receivedMac !== $expectedMac) { 161*0b3fd2d3SAndreas Gohr throw new SaslException('The received MAC does not match the expected MAC.'); 162*0b3fd2d3SAndreas Gohr } 163*0b3fd2d3SAndreas Gohr 164*0b3fd2d3SAndreas Gohr return $message; 165*0b3fd2d3SAndreas Gohr } 166*0b3fd2d3SAndreas Gohr 167*0b3fd2d3SAndreas Gohr /** 168*0b3fd2d3SAndreas Gohr * SEAL(Ki, Kc, SeqNum, msg) = {CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg})[0..9])}), 0x0001, SeqNum} 169*0b3fd2d3SAndreas Gohr * 170*0b3fd2d3SAndreas Gohr * @throws SaslException 171*0b3fd2d3SAndreas Gohr */ 172*0b3fd2d3SAndreas Gohr protected function encrypt(string $data, SaslContext $context): string 173*0b3fd2d3SAndreas Gohr { 174*0b3fd2d3SAndreas Gohr $cipher = $context->get('cipher'); 175*0b3fd2d3SAndreas Gohr $a1 = $context->get('a1'); 176*0b3fd2d3SAndreas Gohr $isServerMode = $context->isServerMode(); 177*0b3fd2d3SAndreas Gohr $seqnum = $context->get('seqnumsnt'); 178*0b3fd2d3SAndreas Gohr $this->validateCipher($cipher); 179*0b3fd2d3SAndreas Gohr 180*0b3fd2d3SAndreas Gohr $mcKc = $isServerMode ? self::KCS_MC : self::KCC_MC; 181*0b3fd2d3SAndreas Gohr $kc = $this->generateKeyKc($a1, $cipher, $mcKc); 182*0b3fd2d3SAndreas Gohr 183*0b3fd2d3SAndreas Gohr $mcKi = $isServerMode ? self::KIS_MC : self::KIC_MC; 184*0b3fd2d3SAndreas Gohr $ki = $this->generateKeyKi($a1, $mcKi); 185*0b3fd2d3SAndreas Gohr 186*0b3fd2d3SAndreas Gohr # The first 10 bytes of the MAC block is used. Extract the last 6 bytes, as that gets tacked onto the end. 187*0b3fd2d3SAndreas Gohr $macBlock = $this->generateMACBlock($ki, $data, $seqnum); 188*0b3fd2d3SAndreas Gohr $ending = substr($macBlock, 10); 189*0b3fd2d3SAndreas Gohr $macBlock = substr($macBlock, 0, 10); 190*0b3fd2d3SAndreas Gohr 191*0b3fd2d3SAndreas Gohr $padding = $this->generatePadding($data, self::CIPHERS[$cipher]['block_size']); 192*0b3fd2d3SAndreas Gohr [$iv, $key] = $this->generateKeyAndIV($cipher, $kc); 193*0b3fd2d3SAndreas Gohr $encrypted = openssl_encrypt($data . $padding . $macBlock, self::CIPHERS[$cipher]['cipher'], $key, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA, $iv); 194*0b3fd2d3SAndreas Gohr 195*0b3fd2d3SAndreas Gohr return $encrypted . $ending; 196*0b3fd2d3SAndreas Gohr } 197*0b3fd2d3SAndreas Gohr 198*0b3fd2d3SAndreas Gohr /** 199*0b3fd2d3SAndreas Gohr * @throws SaslException 200*0b3fd2d3SAndreas Gohr */ 201*0b3fd2d3SAndreas Gohr protected function removePadding(string $message, int $blockSize): string 202*0b3fd2d3SAndreas Gohr { 203*0b3fd2d3SAndreas Gohr $padOrd = isset($message[-1]) ? ord($message[-1]) : 0; 204*0b3fd2d3SAndreas Gohr $padRaw = $message[-1] ?? ''; 205*0b3fd2d3SAndreas Gohr 206*0b3fd2d3SAndreas Gohr # The padding size should only ever be between these values... 207*0b3fd2d3SAndreas Gohr if ($padOrd < 1 || $padOrd > $blockSize) { 208*0b3fd2d3SAndreas Gohr throw new SaslException('The padding size is not correct.'); 209*0b3fd2d3SAndreas Gohr } 210*0b3fd2d3SAndreas Gohr 211*0b3fd2d3SAndreas Gohr $msgLength = strlen($message); 212*0b3fd2d3SAndreas Gohr for ($i = ($msgLength - $padOrd); $i < ($msgLength - 1); $i++) { 213*0b3fd2d3SAndreas Gohr if ($message[$i] !== $padRaw) { 214*0b3fd2d3SAndreas Gohr throw new SaslException('The padding does not match the expected value.'); 215*0b3fd2d3SAndreas Gohr } 216*0b3fd2d3SAndreas Gohr } 217*0b3fd2d3SAndreas Gohr 218*0b3fd2d3SAndreas Gohr return substr($message, 0, strlen($message) - $padOrd); 219*0b3fd2d3SAndreas Gohr } 220*0b3fd2d3SAndreas Gohr 221*0b3fd2d3SAndreas Gohr /** 222*0b3fd2d3SAndreas Gohr * @throws SaslException 223*0b3fd2d3SAndreas Gohr */ 224*0b3fd2d3SAndreas Gohr protected function validateCipher(string $cipher): void 225*0b3fd2d3SAndreas Gohr { 226*0b3fd2d3SAndreas Gohr if (!isset(self::CIPHERS[$cipher])) { 227*0b3fd2d3SAndreas Gohr throw new SaslException(sprintf( 228*0b3fd2d3SAndreas Gohr 'The cipher "%s" is not supported.', 229*0b3fd2d3SAndreas Gohr $cipher 230*0b3fd2d3SAndreas Gohr )); 231*0b3fd2d3SAndreas Gohr } 232*0b3fd2d3SAndreas Gohr } 233*0b3fd2d3SAndreas Gohr 234*0b3fd2d3SAndreas Gohr /** 235*0b3fd2d3SAndreas Gohr * Append a signed MAC to the message. 236*0b3fd2d3SAndreas Gohr */ 237*0b3fd2d3SAndreas Gohr protected function sign(string $message, SaslContext $context): string 238*0b3fd2d3SAndreas Gohr { 239*0b3fd2d3SAndreas Gohr $seqnum = $context->get('seqnumsnt'); 240*0b3fd2d3SAndreas Gohr $mc = $context->isServerMode() ? self::KIS_MC : self::KIC_MC; 241*0b3fd2d3SAndreas Gohr $ki = $this->generateKeyKi($context->get('a1'), $mc); 242*0b3fd2d3SAndreas Gohr $macBlock = $this->generateMACBlock($ki, $message, $seqnum); 243*0b3fd2d3SAndreas Gohr 244*0b3fd2d3SAndreas Gohr return $message . $macBlock; 245*0b3fd2d3SAndreas Gohr } 246*0b3fd2d3SAndreas Gohr 247*0b3fd2d3SAndreas Gohr /** 248*0b3fd2d3SAndreas Gohr * Verify a signed message. Return the unsigned message without the MAC. 249*0b3fd2d3SAndreas Gohr * 250*0b3fd2d3SAndreas Gohr * @throws SaslException 251*0b3fd2d3SAndreas Gohr */ 252*0b3fd2d3SAndreas Gohr protected function verify(string $data, SaslContext $context): string 253*0b3fd2d3SAndreas Gohr { 254*0b3fd2d3SAndreas Gohr $receivedMac = substr($data, -16); 255*0b3fd2d3SAndreas Gohr if (strlen($receivedMac) !== 16) { 256*0b3fd2d3SAndreas Gohr throw new SaslException('Expected at least 16 bytes of data for the MAC.'); 257*0b3fd2d3SAndreas Gohr } 258*0b3fd2d3SAndreas Gohr 259*0b3fd2d3SAndreas Gohr $seqnum = $context->get('seqnumrcv'); 260*0b3fd2d3SAndreas Gohr $message = substr($data, 0, -16); 261*0b3fd2d3SAndreas Gohr # Inverted selection of constant here, as this would be the receiving end. 262*0b3fd2d3SAndreas Gohr $mc = $context->isServerMode() ? self::KIC_MC : self::KIS_MC; 263*0b3fd2d3SAndreas Gohr $ki = $this->generateKeyKi($context->get('a1'), $mc); 264*0b3fd2d3SAndreas Gohr $expectedMac = $this->generateMACBlock($ki, $message, $seqnum); 265*0b3fd2d3SAndreas Gohr 266*0b3fd2d3SAndreas Gohr if ($receivedMac !== $expectedMac) { 267*0b3fd2d3SAndreas Gohr throw new SaslException('The received MAC is invalid.'); 268*0b3fd2d3SAndreas Gohr } 269*0b3fd2d3SAndreas Gohr 270*0b3fd2d3SAndreas Gohr return $message; 271*0b3fd2d3SAndreas Gohr } 272*0b3fd2d3SAndreas Gohr 273*0b3fd2d3SAndreas Gohr /** 274*0b3fd2d3SAndreas Gohr * Per the RFC: 275*0b3fd2d3SAndreas Gohr * 276*0b3fd2d3SAndreas Gohr * If the blocksize of the chosen cipher is not 1 byte, the padding prefix is one or more octets each containing the 277*0b3fd2d3SAndreas Gohr * number of padding bytes, such that total length of the encrypted part of the message is a multiple of the 278*0b3fd2d3SAndreas Gohr * blocksize. 279*0b3fd2d3SAndreas Gohr */ 280*0b3fd2d3SAndreas Gohr protected function generatePadding(string $data, int $blockSize): string 281*0b3fd2d3SAndreas Gohr { 282*0b3fd2d3SAndreas Gohr if ($blockSize === 1) { 283*0b3fd2d3SAndreas Gohr return ''; 284*0b3fd2d3SAndreas Gohr } 285*0b3fd2d3SAndreas Gohr $pad = $blockSize - (strlen($data) + 10) & ($blockSize - 1); 286*0b3fd2d3SAndreas Gohr 287*0b3fd2d3SAndreas Gohr return str_repeat(chr($pad), $pad); 288*0b3fd2d3SAndreas Gohr } 289*0b3fd2d3SAndreas Gohr 290*0b3fd2d3SAndreas Gohr /** 291*0b3fd2d3SAndreas Gohr * RFC2831 Section 2.3 292*0b3fd2d3SAndreas Gohr * 293*0b3fd2d3SAndreas Gohr * The MAC block is 16 bytes: the first 10 bytes of the HMAC-MD5 [RFC2104] of the message, a 2-byte message type 294*0b3fd2d3SAndreas Gohr * number in network byte order with value 1, and the 4-byte sequence number in network byte order. The message type 295*0b3fd2d3SAndreas Gohr * is to allow for future extensions such as rekeying. 296*0b3fd2d3SAndreas Gohr * 297*0b3fd2d3SAndreas Gohr * MAC(Ki, SeqNum, msg) = (HMAC(Ki, {SeqNum, msg})[0..9], 0x0001, SeqNum) 298*0b3fd2d3SAndreas Gohr */ 299*0b3fd2d3SAndreas Gohr protected function generateMACBlock(string $key, string $message, int $seqNum): string 300*0b3fd2d3SAndreas Gohr { 301*0b3fd2d3SAndreas Gohr /** 4-byte sequence number in network byte order. */ 302*0b3fd2d3SAndreas Gohr $seqNum = pack('N', $seqNum); 303*0b3fd2d3SAndreas Gohr $macBlock = substr(hash_hmac('md5', $seqNum . $message, $key, true), 0, 10); 304*0b3fd2d3SAndreas Gohr /** a 2-byte message type number in network byte order with value 1 */ 305*0b3fd2d3SAndreas Gohr $macBlock .= "\x00\x01"; 306*0b3fd2d3SAndreas Gohr $macBlock .= $seqNum; 307*0b3fd2d3SAndreas Gohr 308*0b3fd2d3SAndreas Gohr return $macBlock; 309*0b3fd2d3SAndreas Gohr } 310*0b3fd2d3SAndreas Gohr 311*0b3fd2d3SAndreas Gohr /** 312*0b3fd2d3SAndreas Gohr * The keys for integrity protecting messages from client to server / server to client: 313*0b3fd2d3SAndreas Gohr * 314*0b3fd2d3SAndreas Gohr * Kic = MD5({H(A1), "Digest session key to client-to-server signing key magic constant"}) 315*0b3fd2d3SAndreas Gohr * Kis = MD5({H(A1), "Digest session key to server-to-client signing key magic constant"}) 316*0b3fd2d3SAndreas Gohr * 317*0b3fd2d3SAndreas Gohr */ 318*0b3fd2d3SAndreas Gohr protected function generateKeyKi(string $a1, string $mc): string 319*0b3fd2d3SAndreas Gohr { 320*0b3fd2d3SAndreas Gohr return hash('md5', $a1 . $mc, true); 321*0b3fd2d3SAndreas Gohr } 322*0b3fd2d3SAndreas Gohr 323*0b3fd2d3SAndreas Gohr /** 324*0b3fd2d3SAndreas Gohr * The key for encrypting messages from client to server / server to client: 325*0b3fd2d3SAndreas Gohr * 326*0b3fd2d3SAndreas Gohr * Kcc = MD5({H(A1)[0..n], "Digest H(A1) to client-to-server sealing key magic constant"}) 327*0b3fd2d3SAndreas Gohr * Kcs = MD5({H(A1)[0..n], "Digest H(A1) to server-to-client sealing key magic constant"}) 328*0b3fd2d3SAndreas Gohr * 329*0b3fd2d3SAndreas Gohr * Where the key size is determined by "n" above. 330*0b3fd2d3SAndreas Gohr */ 331*0b3fd2d3SAndreas Gohr protected function generateKeyKc(string $a1, string $cipher, string $mc): string 332*0b3fd2d3SAndreas Gohr { 333*0b3fd2d3SAndreas Gohr return hash( 334*0b3fd2d3SAndreas Gohr 'md5', 335*0b3fd2d3SAndreas Gohr substr($a1, 0, self::CIPHERS[$cipher]['kcn']) . $mc, 336*0b3fd2d3SAndreas Gohr true 337*0b3fd2d3SAndreas Gohr ); 338*0b3fd2d3SAndreas Gohr } 339*0b3fd2d3SAndreas Gohr 340*0b3fd2d3SAndreas Gohr protected function generateKeyAndIV(string $cipher, string $kc): array 341*0b3fd2d3SAndreas Gohr { 342*0b3fd2d3SAndreas Gohr # No IV and all of the kc for the key with RC4 types 343*0b3fd2d3SAndreas Gohr if ($cipher === 'rc4' || $cipher === 'rc4-40' || $cipher === 'rc4-56') { 344*0b3fd2d3SAndreas Gohr return ['', $kc]; 345*0b3fd2d3SAndreas Gohr } 346*0b3fd2d3SAndreas Gohr 347*0b3fd2d3SAndreas Gohr $iv = substr($kc, 8, 8); 348*0b3fd2d3SAndreas Gohr if ($cipher === 'des') { 349*0b3fd2d3SAndreas Gohr $key = $this->expandDesKey(substr($kc, 0, 7)); 350*0b3fd2d3SAndreas Gohr } else { 351*0b3fd2d3SAndreas Gohr $key1 = substr($kc, 0, 7); 352*0b3fd2d3SAndreas Gohr $key2 = substr($kc, 7, 7); 353*0b3fd2d3SAndreas Gohr 354*0b3fd2d3SAndreas Gohr $key = ''; 355*0b3fd2d3SAndreas Gohr foreach ([$key1, $key2, $key1] as $desKey) { 356*0b3fd2d3SAndreas Gohr $key .= $this->expandDesKey($desKey); 357*0b3fd2d3SAndreas Gohr } 358*0b3fd2d3SAndreas Gohr } 359*0b3fd2d3SAndreas Gohr 360*0b3fd2d3SAndreas Gohr return [$iv, $key]; 361*0b3fd2d3SAndreas Gohr } 362*0b3fd2d3SAndreas Gohr 363*0b3fd2d3SAndreas Gohr /** 364*0b3fd2d3SAndreas Gohr * We need to manually expand the 7-byte DES keys to 8-bytes. This shifts the first 7 bytes into the high seven bits. 365*0b3fd2d3SAndreas Gohr * This also ignores parity, as it should not be strictly necessary and just adds additional complexity here. 366*0b3fd2d3SAndreas Gohr */ 367*0b3fd2d3SAndreas Gohr protected function expandDesKey(string $key): string 368*0b3fd2d3SAndreas Gohr { 369*0b3fd2d3SAndreas Gohr $bytes = []; 370*0b3fd2d3SAndreas Gohr 371*0b3fd2d3SAndreas Gohr for ($i = 0; $i < 7; $i++) { 372*0b3fd2d3SAndreas Gohr $bytes[$i] = ord($key[$i]); 373*0b3fd2d3SAndreas Gohr } 374*0b3fd2d3SAndreas Gohr 375*0b3fd2d3SAndreas Gohr return 376*0b3fd2d3SAndreas Gohr chr($bytes[0] & 0xfe) . 377*0b3fd2d3SAndreas Gohr chr(($bytes[0] << 7) | ($bytes[1] >> 1)) . 378*0b3fd2d3SAndreas Gohr chr(($bytes[1] << 6) | ($bytes[2] >> 2)) . 379*0b3fd2d3SAndreas Gohr chr(($bytes[2] << 5) | ($bytes[3] >> 3)) . 380*0b3fd2d3SAndreas Gohr chr(($bytes[3] << 4) | ($bytes[4] >> 4)) . 381*0b3fd2d3SAndreas Gohr chr(($bytes[4] << 3) | ($bytes[5] >> 5)) . 382*0b3fd2d3SAndreas Gohr chr(($bytes[5] << 2) | ($bytes[6] >> 6)) . 383*0b3fd2d3SAndreas Gohr chr($bytes[6] << 1); 384*0b3fd2d3SAndreas Gohr } 385*0b3fd2d3SAndreas Gohr 386*0b3fd2d3SAndreas Gohr /** 387*0b3fd2d3SAndreas Gohr * @throws SaslException 388*0b3fd2d3SAndreas Gohr */ 389*0b3fd2d3SAndreas Gohr protected function validateBufferLength(string $data, SaslContext $context): void 390*0b3fd2d3SAndreas Gohr { 391*0b3fd2d3SAndreas Gohr $maxbuf = $context->has('maxbuf') ? (int) $context->get('maxbuf') : self::MAXBUF; 392*0b3fd2d3SAndreas Gohr if (strlen($data) > $maxbuf) { 393*0b3fd2d3SAndreas Gohr throw new SaslException(sprintf( 394*0b3fd2d3SAndreas Gohr 'The wrapped buffer exceeds the maxbuf length of %s', 395*0b3fd2d3SAndreas Gohr $maxbuf 396*0b3fd2d3SAndreas Gohr )); 397*0b3fd2d3SAndreas Gohr } 398*0b3fd2d3SAndreas Gohr } 399*0b3fd2d3SAndreas Gohr} 400