xref: /plugin/pureldap/vendor/freedsx/sasl/src/FreeDSx/Sasl/MechanismSelector.php (revision 0b3fd2d31e4d1997548a8fbc53fa771027c4a47f)
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