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; 13*0b3fd2d3SAndreas Gohr 14*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Exception\SaslException; 15*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Mechanism\MechanismInterface; 16*0b3fd2d3SAndreas Gohr 17*0b3fd2d3SAndreas Gohr/** 18*0b3fd2d3SAndreas Gohr * Given an array of mechanism names, choose the best one available. 19*0b3fd2d3SAndreas Gohr * 20*0b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 21*0b3fd2d3SAndreas Gohr */ 22*0b3fd2d3SAndreas Gohrclass MechanismSelector 23*0b3fd2d3SAndreas Gohr{ 24*0b3fd2d3SAndreas Gohr /** 25*0b3fd2d3SAndreas Gohr * @var MechanismInterface[] 26*0b3fd2d3SAndreas Gohr */ 27*0b3fd2d3SAndreas Gohr protected $mechanisms; 28*0b3fd2d3SAndreas Gohr 29*0b3fd2d3SAndreas Gohr /** 30*0b3fd2d3SAndreas Gohr * @param MechanismInterface[] $mechanisms 31*0b3fd2d3SAndreas Gohr */ 32*0b3fd2d3SAndreas Gohr public function __construct(array $mechanisms) 33*0b3fd2d3SAndreas Gohr { 34*0b3fd2d3SAndreas Gohr $this->mechanisms = $mechanisms; 35*0b3fd2d3SAndreas Gohr } 36*0b3fd2d3SAndreas Gohr 37*0b3fd2d3SAndreas Gohr /** 38*0b3fd2d3SAndreas Gohr * @throws SaslException 39*0b3fd2d3SAndreas Gohr */ 40*0b3fd2d3SAndreas Gohr public function select(array $choices = [], array $options = []): MechanismInterface 41*0b3fd2d3SAndreas Gohr { 42*0b3fd2d3SAndreas Gohr $mechs = $this->getAvailableMechsFromChoices($choices, $options); 43*0b3fd2d3SAndreas Gohr 44*0b3fd2d3SAndreas Gohr return $this->selectMech($mechs); 45*0b3fd2d3SAndreas Gohr } 46*0b3fd2d3SAndreas Gohr 47*0b3fd2d3SAndreas Gohr /** 48*0b3fd2d3SAndreas Gohr * From RFC 4422: 49*0b3fd2d3SAndreas Gohr * 50*0b3fd2d3SAndreas Gohr * Mechanism negotiation is protocol specific. 51*0b3fd2d3SAndreas Gohr * 52*0b3fd2d3SAndreas Gohr * Commonly, a protocol will specify that the server advertises 53*0b3fd2d3SAndreas Gohr * supported and available mechanisms to the client via some facility 54*0b3fd2d3SAndreas Gohr * provided by the protocol, and the client will then select the "best" 55*0b3fd2d3SAndreas Gohr * mechanism from this list that it supports and finds suitable. 56*0b3fd2d3SAndreas Gohr * 57*0b3fd2d3SAndreas Gohr * So basically we are on our own in determining the best available mechanism from a list. This really seems like 58*0b3fd2d3SAndreas Gohr * something that should have been included in the RFC. There is SSF, but that was never formalized into an RFC and 59*0b3fd2d3SAndreas Gohr * some vendors have different ways of calculating it, making it somewhat less meaningful. 60*0b3fd2d3SAndreas Gohr * 61*0b3fd2d3SAndreas Gohr * @param MechanismInterface[] $available 62*0b3fd2d3SAndreas Gohr * @return MechanismInterface 63*0b3fd2d3SAndreas Gohr * @throws SaslException 64*0b3fd2d3SAndreas Gohr */ 65*0b3fd2d3SAndreas Gohr protected function selectMech(array $available): MechanismInterface 66*0b3fd2d3SAndreas Gohr { 67*0b3fd2d3SAndreas Gohr # We sort the mechanisms by: 68*0b3fd2d3SAndreas Gohr # 1. Key size first. 69*0b3fd2d3SAndreas Gohr # 2. Privacy / encryption support. 70*0b3fd2d3SAndreas Gohr # 3. Integrity / signing support. 71*0b3fd2d3SAndreas Gohr # 4. Authentication support (anonymous should be at the bottom...) 72*0b3fd2d3SAndreas Gohr # 5. Authentication that is not plain text. 73*0b3fd2d3SAndreas Gohr usort($available, function (MechanismInterface $mechA, MechanismInterface $mechB) { 74*0b3fd2d3SAndreas Gohr $strengthA = $mechA->securityStrength(); 75*0b3fd2d3SAndreas Gohr $strengthB = $mechB->securityStrength(); 76*0b3fd2d3SAndreas Gohr 77*0b3fd2d3SAndreas Gohr # We need to invert the boolean checks, expect for plain text (logic is already inverted). 78*0b3fd2d3SAndreas Gohr return (int)!$strengthA->supportsPrivacy() <=> (int)!$strengthB->supportsPrivacy() 79*0b3fd2d3SAndreas Gohr ?: (int)!$strengthA->supportsIntegrity() <=> (int)!$strengthB->supportsIntegrity() 80*0b3fd2d3SAndreas Gohr ?: (int)$strengthA->maxKeySize() <=> (int)$strengthB->maxKeySize() 81*0b3fd2d3SAndreas Gohr ?: (int)!$strengthA->supportsAuth() <=> (int)!$strengthB->supportsAuth() 82*0b3fd2d3SAndreas Gohr ?: (int)$strengthA->isPlainTextAuth() <=> (int)$strengthB->isPlainTextAuth(); 83*0b3fd2d3SAndreas Gohr }); 84*0b3fd2d3SAndreas Gohr $first = array_shift($available); 85*0b3fd2d3SAndreas Gohr 86*0b3fd2d3SAndreas Gohr if (!$first instanceof MechanismInterface) { 87*0b3fd2d3SAndreas Gohr throw new SaslException('No supported SASL mechanisms could be found.'); 88*0b3fd2d3SAndreas Gohr } 89*0b3fd2d3SAndreas Gohr 90*0b3fd2d3SAndreas Gohr return $first; 91*0b3fd2d3SAndreas Gohr } 92*0b3fd2d3SAndreas Gohr 93*0b3fd2d3SAndreas Gohr /** 94*0b3fd2d3SAndreas Gohr * @param string[] $choices 95*0b3fd2d3SAndreas Gohr * @param array $options 96*0b3fd2d3SAndreas Gohr * @return MechanismInterface[] 97*0b3fd2d3SAndreas Gohr * @throws SaslException 98*0b3fd2d3SAndreas Gohr */ 99*0b3fd2d3SAndreas Gohr protected function getAvailableMechsFromChoices(array $choices, array $options): array 100*0b3fd2d3SAndreas Gohr { 101*0b3fd2d3SAndreas Gohr $available = $this->filterFromChoices($choices); 102*0b3fd2d3SAndreas Gohr if (count($available) === 0) { 103*0b3fd2d3SAndreas Gohr $this->throwException($choices); 104*0b3fd2d3SAndreas Gohr } 105*0b3fd2d3SAndreas Gohr 106*0b3fd2d3SAndreas Gohr $available = $this->filterOptions($available, $options); 107*0b3fd2d3SAndreas Gohr if (count($available) === 0) { 108*0b3fd2d3SAndreas Gohr $this->throwException($choices); 109*0b3fd2d3SAndreas Gohr } 110*0b3fd2d3SAndreas Gohr 111*0b3fd2d3SAndreas Gohr return $available; 112*0b3fd2d3SAndreas Gohr } 113*0b3fd2d3SAndreas Gohr 114*0b3fd2d3SAndreas Gohr /** 115*0b3fd2d3SAndreas Gohr * @param string[] $choices 116*0b3fd2d3SAndreas Gohr * @return MechanismInterface[] 117*0b3fd2d3SAndreas Gohr */ 118*0b3fd2d3SAndreas Gohr protected function filterFromChoices(array $choices): array 119*0b3fd2d3SAndreas Gohr { 120*0b3fd2d3SAndreas Gohr if (count($choices) === 0) { 121*0b3fd2d3SAndreas Gohr return $this->mechanisms; 122*0b3fd2d3SAndreas Gohr } 123*0b3fd2d3SAndreas Gohr $filtered = []; 124*0b3fd2d3SAndreas Gohr 125*0b3fd2d3SAndreas Gohr foreach ($this->mechanisms as $choice) { 126*0b3fd2d3SAndreas Gohr if (in_array($choice->getName(), $choices, true)) { 127*0b3fd2d3SAndreas Gohr $filtered[] = $choice; 128*0b3fd2d3SAndreas Gohr } 129*0b3fd2d3SAndreas Gohr } 130*0b3fd2d3SAndreas Gohr 131*0b3fd2d3SAndreas Gohr return $filtered; 132*0b3fd2d3SAndreas Gohr } 133*0b3fd2d3SAndreas Gohr 134*0b3fd2d3SAndreas Gohr /** 135*0b3fd2d3SAndreas Gohr * @param MechanismInterface[] $available 136*0b3fd2d3SAndreas Gohr * @param array $options 137*0b3fd2d3SAndreas Gohr * @return MechanismInterface[] 138*0b3fd2d3SAndreas Gohr */ 139*0b3fd2d3SAndreas Gohr protected function filterOptions(array $available, array $options): array 140*0b3fd2d3SAndreas Gohr { 141*0b3fd2d3SAndreas Gohr $useIntegrity = $options['use_integrity'] ?? false; 142*0b3fd2d3SAndreas Gohr $usePrivacy = $options['use_privacy'] ?? false; 143*0b3fd2d3SAndreas Gohr 144*0b3fd2d3SAndreas Gohr # Don't need to worry whether it supports integrity or privacy... 145*0b3fd2d3SAndreas Gohr if ($usePrivacy === false && $useIntegrity === false) { 146*0b3fd2d3SAndreas Gohr return $available; 147*0b3fd2d3SAndreas Gohr } 148*0b3fd2d3SAndreas Gohr $supportsInt = []; 149*0b3fd2d3SAndreas Gohr $supportsPriv = []; 150*0b3fd2d3SAndreas Gohr 151*0b3fd2d3SAndreas Gohr # Filter to those only those supporting integrity... 152*0b3fd2d3SAndreas Gohr if ($useIntegrity === true) { 153*0b3fd2d3SAndreas Gohr $supportsInt = array_filter($available, function (MechanismInterface $mech) use ($useIntegrity) { 154*0b3fd2d3SAndreas Gohr return $mech->securityStrength()->supportsIntegrity() === $useIntegrity; 155*0b3fd2d3SAndreas Gohr }); 156*0b3fd2d3SAndreas Gohr } 157*0b3fd2d3SAndreas Gohr # Filter to those only supporting privacy... 158*0b3fd2d3SAndreas Gohr if ($usePrivacy === true) { 159*0b3fd2d3SAndreas Gohr $supportsPriv = array_filter($available, function (MechanismInterface $mech) use ($usePrivacy) { 160*0b3fd2d3SAndreas Gohr return $mech->securityStrength()->supportsPrivacy() === $usePrivacy; 161*0b3fd2d3SAndreas Gohr }); 162*0b3fd2d3SAndreas Gohr } 163*0b3fd2d3SAndreas Gohr 164*0b3fd2d3SAndreas Gohr return array_unique(array_merge($supportsInt, $supportsPriv), SORT_REGULAR); 165*0b3fd2d3SAndreas Gohr } 166*0b3fd2d3SAndreas Gohr 167*0b3fd2d3SAndreas Gohr /** 168*0b3fd2d3SAndreas Gohr * @throws SaslException 169*0b3fd2d3SAndreas Gohr */ 170*0b3fd2d3SAndreas Gohr protected function throwException(array $choices = []): void 171*0b3fd2d3SAndreas Gohr { 172*0b3fd2d3SAndreas Gohr throw new SaslException(sprintf( 173*0b3fd2d3SAndreas Gohr 'No supported SASL mechanisms could be found from the provided choices: %s', 174*0b3fd2d3SAndreas Gohr implode($choices, ', ') 175*0b3fd2d3SAndreas Gohr )); 176*0b3fd2d3SAndreas Gohr } 177*0b3fd2d3SAndreas Gohr} 178