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\Mechanism; 13*0b3fd2d3SAndreas Gohr 14*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Challenge\ChallengeInterface; 15*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Challenge\DigestMD5Challenge; 16*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Exception\SaslException; 17*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Message; 18*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Security\DigestMD5SecurityLayer; 19*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Security\SecurityLayerInterface; 20*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\SecurityStrength; 21*0b3fd2d3SAndreas Gohr 22*0b3fd2d3SAndreas Gohr/** 23*0b3fd2d3SAndreas Gohr * The Digest-MD5 mechanism. 24*0b3fd2d3SAndreas Gohr * 25*0b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 26*0b3fd2d3SAndreas Gohr */ 27*0b3fd2d3SAndreas Gohrclass DigestMD5Mechanism implements MechanismInterface 28*0b3fd2d3SAndreas Gohr{ 29*0b3fd2d3SAndreas Gohr public const NAME = 'DIGEST-MD5'; 30*0b3fd2d3SAndreas Gohr 31*0b3fd2d3SAndreas Gohr protected const A2_SERVER = ':'; 32*0b3fd2d3SAndreas Gohr 33*0b3fd2d3SAndreas Gohr protected const A2_CLIENT = 'AUTHENTICATE:'; 34*0b3fd2d3SAndreas Gohr 35*0b3fd2d3SAndreas Gohr /** 36*0b3fd2d3SAndreas Gohr * {@inheritDoc} 37*0b3fd2d3SAndreas Gohr */ 38*0b3fd2d3SAndreas Gohr public function getName(): string 39*0b3fd2d3SAndreas Gohr { 40*0b3fd2d3SAndreas Gohr return self::NAME; 41*0b3fd2d3SAndreas Gohr } 42*0b3fd2d3SAndreas Gohr 43*0b3fd2d3SAndreas Gohr /** 44*0b3fd2d3SAndreas Gohr * {@inheritDoc} 45*0b3fd2d3SAndreas Gohr */ 46*0b3fd2d3SAndreas Gohr public function challenge(): ChallengeInterface 47*0b3fd2d3SAndreas Gohr { 48*0b3fd2d3SAndreas Gohr $challenge = new DigestMD5Challenge(); 49*0b3fd2d3SAndreas Gohr 50*0b3fd2d3SAndreas Gohr return $challenge; 51*0b3fd2d3SAndreas Gohr } 52*0b3fd2d3SAndreas Gohr 53*0b3fd2d3SAndreas Gohr /** 54*0b3fd2d3SAndreas Gohr * {@inheritDoc} 55*0b3fd2d3SAndreas Gohr */ 56*0b3fd2d3SAndreas Gohr public function securityStrength(): SecurityStrength 57*0b3fd2d3SAndreas Gohr { 58*0b3fd2d3SAndreas Gohr return new SecurityStrength( 59*0b3fd2d3SAndreas Gohr true, 60*0b3fd2d3SAndreas Gohr true, 61*0b3fd2d3SAndreas Gohr true, 62*0b3fd2d3SAndreas Gohr false, 63*0b3fd2d3SAndreas Gohr 128 64*0b3fd2d3SAndreas Gohr ); 65*0b3fd2d3SAndreas Gohr } 66*0b3fd2d3SAndreas Gohr 67*0b3fd2d3SAndreas Gohr /** 68*0b3fd2d3SAndreas Gohr * {@inheritDoc} 69*0b3fd2d3SAndreas Gohr */ 70*0b3fd2d3SAndreas Gohr public function securityLayer(): SecurityLayerInterface 71*0b3fd2d3SAndreas Gohr { 72*0b3fd2d3SAndreas Gohr return new DigestMD5SecurityLayer(); 73*0b3fd2d3SAndreas Gohr } 74*0b3fd2d3SAndreas Gohr 75*0b3fd2d3SAndreas Gohr public function __toString() 76*0b3fd2d3SAndreas Gohr { 77*0b3fd2d3SAndreas Gohr return self::NAME; 78*0b3fd2d3SAndreas Gohr } 79*0b3fd2d3SAndreas Gohr 80*0b3fd2d3SAndreas Gohr /** 81*0b3fd2d3SAndreas Gohr * Generates the computed response value. RFC2831 2.1.2.1 82*0b3fd2d3SAndreas Gohr * 83*0b3fd2d3SAndreas Gohr * HEX( KD ( HEX(H(A1)), 84*0b3fd2d3SAndreas Gohr * { nonce-value, ":" nc-value, ":", 85*0b3fd2d3SAndreas Gohr * cnonce-value, ":", qop-value, ":", HEX(H(A2)) })) 86*0b3fd2d3SAndreas Gohr * 87*0b3fd2d3SAndreas Gohr * If the "qop" directive's value is "auth", then A2 is: 88*0b3fd2d3SAndreas Gohr * 89*0b3fd2d3SAndreas Gohr * A2 = { "AUTHENTICATE:", digest-uri-value } 90*0b3fd2d3SAndreas Gohr * 91*0b3fd2d3SAndreas Gohr * If the "qop" value is "auth-int" or "auth-conf" then A2 is: 92*0b3fd2d3SAndreas Gohr * 93*0b3fd2d3SAndreas Gohr * A2 = { "AUTHENTICATE:", digest-uri-value, 94*0b3fd2d3SAndreas Gohr * ":00000000000000000000000000000000" } 95*0b3fd2d3SAndreas Gohr * 96*0b3fd2d3SAndreas Gohr * If this is the server context, then the beginning of A2 is just a semi-colon. 97*0b3fd2d3SAndreas Gohr * 98*0b3fd2d3SAndreas Gohr * @throws SaslException 99*0b3fd2d3SAndreas Gohr */ 100*0b3fd2d3SAndreas Gohr public static function computeResponse(string $password, Message $challenge, Message $response, bool $useServerMode = false): string 101*0b3fd2d3SAndreas Gohr { 102*0b3fd2d3SAndreas Gohr $a1 = self::computeA1($password, $challenge, $response); 103*0b3fd2d3SAndreas Gohr 104*0b3fd2d3SAndreas Gohr $qop = $response->get('qop'); 105*0b3fd2d3SAndreas Gohr $digestUri = $response->get('digest-uri'); 106*0b3fd2d3SAndreas Gohr $a2 = $useServerMode ? self::A2_SERVER : self::A2_CLIENT; 107*0b3fd2d3SAndreas Gohr 108*0b3fd2d3SAndreas Gohr if ($qop === 'auth') { 109*0b3fd2d3SAndreas Gohr $a2 .= $digestUri; 110*0b3fd2d3SAndreas Gohr } elseif ($qop === 'auth-int' || $qop === 'auth-conf') { 111*0b3fd2d3SAndreas Gohr $a2 .= $digestUri . ':00000000000000000000000000000000'; 112*0b3fd2d3SAndreas Gohr } else { 113*0b3fd2d3SAndreas Gohr throw new SaslException('The qop directive must be one of: auth, auth-conf, auth-int.'); 114*0b3fd2d3SAndreas Gohr } 115*0b3fd2d3SAndreas Gohr $a2 = hash('md5', $a2); 116*0b3fd2d3SAndreas Gohr 117*0b3fd2d3SAndreas Gohr return hash('md5', sprintf( 118*0b3fd2d3SAndreas Gohr '%s:%s:%s:%s:%s:%s', 119*0b3fd2d3SAndreas Gohr $a1, 120*0b3fd2d3SAndreas Gohr $challenge->get('nonce'), 121*0b3fd2d3SAndreas Gohr str_pad(dechex($response->get('nc')), 8, '0', STR_PAD_LEFT), 122*0b3fd2d3SAndreas Gohr $response->get('cnonce'), 123*0b3fd2d3SAndreas Gohr $response->get('qop'), 124*0b3fd2d3SAndreas Gohr $a2 125*0b3fd2d3SAndreas Gohr )); 126*0b3fd2d3SAndreas Gohr } 127*0b3fd2d3SAndreas Gohr 128*0b3fd2d3SAndreas Gohr /** 129*0b3fd2d3SAndreas Gohr * If authzid is specified, then A1 is 130*0b3fd2d3SAndreas Gohr * 131*0b3fd2d3SAndreas Gohr * A1 = { H( { username-value, ":", realm-value, ":", passwd } ), 132*0b3fd2d3SAndreas Gohr * ":", nonce-value, ":", cnonce-value, ":", authzid-value } 133*0b3fd2d3SAndreas Gohr * 134*0b3fd2d3SAndreas Gohr * If authzid is not specified, then A1 is 135*0b3fd2d3SAndreas Gohr * 136*0b3fd2d3SAndreas Gohr * A1 = { H( { username-value, ":", realm-value, ":", passwd } ), 137*0b3fd2d3SAndreas Gohr * ":", nonce-value, ":", cnonce-value } 138*0b3fd2d3SAndreas Gohr * 139*0b3fd2d3SAndreas Gohr */ 140*0b3fd2d3SAndreas Gohr public static function computeA1(string $password, Message $challenge, Message $response): string 141*0b3fd2d3SAndreas Gohr { 142*0b3fd2d3SAndreas Gohr $a1 = hash('md5', sprintf( 143*0b3fd2d3SAndreas Gohr '%s:%s:%s', 144*0b3fd2d3SAndreas Gohr $response->get('username'), 145*0b3fd2d3SAndreas Gohr $response->get('realm'), 146*0b3fd2d3SAndreas Gohr $password 147*0b3fd2d3SAndreas Gohr ), true); 148*0b3fd2d3SAndreas Gohr $a1 = sprintf( 149*0b3fd2d3SAndreas Gohr '%s:%s:%s', 150*0b3fd2d3SAndreas Gohr $a1, 151*0b3fd2d3SAndreas Gohr $challenge->get('nonce'), 152*0b3fd2d3SAndreas Gohr $response->get('cnonce') 153*0b3fd2d3SAndreas Gohr ); 154*0b3fd2d3SAndreas Gohr if ($response->has('authzid')) { 155*0b3fd2d3SAndreas Gohr $a1 .= ':' . $response->get('authzid'); 156*0b3fd2d3SAndreas Gohr } 157*0b3fd2d3SAndreas Gohr 158*0b3fd2d3SAndreas Gohr return hash('md5', $a1); 159*0b3fd2d3SAndreas Gohr } 160*0b3fd2d3SAndreas Gohr} 161