xref: /plugin/pureldap/vendor/freedsx/sasl/src/FreeDSx/Sasl/Mechanism/DigestMD5Mechanism.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\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