xref: /plugin/pureldap/vendor/freedsx/sasl/src/FreeDSx/Sasl/Encoder/CramMD5Encoder.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\Encoder;
13*0b3fd2d3SAndreas Gohr
14*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Exception\SaslEncodingException;
15*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\Message;
16*0b3fd2d3SAndreas Gohruse FreeDSx\Sasl\SaslContext;
17*0b3fd2d3SAndreas Gohr
18*0b3fd2d3SAndreas Gohr/**
19*0b3fd2d3SAndreas Gohr * Responsible for encoding / decoding CRAM-MD5 messages.
20*0b3fd2d3SAndreas Gohr *
21*0b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com>
22*0b3fd2d3SAndreas Gohr */
23*0b3fd2d3SAndreas Gohrclass CramMD5Encoder implements EncoderInterface
24*0b3fd2d3SAndreas Gohr{
25*0b3fd2d3SAndreas Gohr
26*0b3fd2d3SAndreas Gohr    /**
27*0b3fd2d3SAndreas Gohr     * {@inheritDoc}
28*0b3fd2d3SAndreas Gohr     */
29*0b3fd2d3SAndreas Gohr    public function encode(Message $message, SaslContext $context): string
30*0b3fd2d3SAndreas Gohr    {
31*0b3fd2d3SAndreas Gohr        if ($context->isServerMode()) {
32*0b3fd2d3SAndreas Gohr            return $this->encodeServerChallenge($message);
33*0b3fd2d3SAndreas Gohr        } else {
34*0b3fd2d3SAndreas Gohr            return $this->encodeClientResponse($message);
35*0b3fd2d3SAndreas Gohr        }
36*0b3fd2d3SAndreas Gohr    }
37*0b3fd2d3SAndreas Gohr
38*0b3fd2d3SAndreas Gohr    /**
39*0b3fd2d3SAndreas Gohr     * {@inheritDoc}
40*0b3fd2d3SAndreas Gohr     */
41*0b3fd2d3SAndreas Gohr    public function decode(string $data, SaslContext $context): Message
42*0b3fd2d3SAndreas Gohr    {
43*0b3fd2d3SAndreas Gohr        if ($context->isServerMode()) {
44*0b3fd2d3SAndreas Gohr            return $this->decodeClientResponse($data);
45*0b3fd2d3SAndreas Gohr        } else {
46*0b3fd2d3SAndreas Gohr            return $this->decodeServerChallenge($data);
47*0b3fd2d3SAndreas Gohr        }
48*0b3fd2d3SAndreas Gohr    }
49*0b3fd2d3SAndreas Gohr
50*0b3fd2d3SAndreas Gohr    /**
51*0b3fd2d3SAndreas Gohr     * @throws SaslEncodingException
52*0b3fd2d3SAndreas Gohr     */
53*0b3fd2d3SAndreas Gohr    protected function encodeServerChallenge(Message $message): string
54*0b3fd2d3SAndreas Gohr    {
55*0b3fd2d3SAndreas Gohr        if (!$message->has('challenge')) {
56*0b3fd2d3SAndreas Gohr            throw new SaslEncodingException('The server challenge message must contain a "challenge".');
57*0b3fd2d3SAndreas Gohr        }
58*0b3fd2d3SAndreas Gohr        $challenge = $message->get('challenge');
59*0b3fd2d3SAndreas Gohr
60*0b3fd2d3SAndreas Gohr        return '<' . $challenge . '>';
61*0b3fd2d3SAndreas Gohr    }
62*0b3fd2d3SAndreas Gohr
63*0b3fd2d3SAndreas Gohr    /**
64*0b3fd2d3SAndreas Gohr     * @throws SaslEncodingException
65*0b3fd2d3SAndreas Gohr     */
66*0b3fd2d3SAndreas Gohr    protected function encodeClientResponse(Message $message): string
67*0b3fd2d3SAndreas Gohr    {
68*0b3fd2d3SAndreas Gohr        if (!$message->has('username')) {
69*0b3fd2d3SAndreas Gohr            throw new SaslEncodingException('The client response must contain a username.');
70*0b3fd2d3SAndreas Gohr        }
71*0b3fd2d3SAndreas Gohr        if (!$message->has('digest')) {
72*0b3fd2d3SAndreas Gohr            throw new SaslEncodingException('The client response must contain a digest.');
73*0b3fd2d3SAndreas Gohr        }
74*0b3fd2d3SAndreas Gohr        $username = $message->get('username');
75*0b3fd2d3SAndreas Gohr        $digest = $message->get('digest');
76*0b3fd2d3SAndreas Gohr
77*0b3fd2d3SAndreas Gohr        if (!preg_match('/^[0-9a-f]{32}$/', $digest)) {
78*0b3fd2d3SAndreas Gohr            throw new SaslEncodingException('The client digest must be a 16 octet, lower-case, hexadecimal value');
79*0b3fd2d3SAndreas Gohr        }
80*0b3fd2d3SAndreas Gohr
81*0b3fd2d3SAndreas Gohr        return $username . ' ' . $digest;
82*0b3fd2d3SAndreas Gohr    }
83*0b3fd2d3SAndreas Gohr
84*0b3fd2d3SAndreas Gohr    /**
85*0b3fd2d3SAndreas Gohr     * @throws SaslEncodingException
86*0b3fd2d3SAndreas Gohr     */
87*0b3fd2d3SAndreas Gohr    protected function decodeServerChallenge(string $challenge): Message
88*0b3fd2d3SAndreas Gohr    {
89*0b3fd2d3SAndreas Gohr        if (!preg_match('/^<.*>$/', $challenge)) {
90*0b3fd2d3SAndreas Gohr            throw new SaslEncodingException('The server challenge is malformed.');
91*0b3fd2d3SAndreas Gohr        }
92*0b3fd2d3SAndreas Gohr
93*0b3fd2d3SAndreas Gohr        return new Message(['challenge' => $challenge]);
94*0b3fd2d3SAndreas Gohr    }
95*0b3fd2d3SAndreas Gohr
96*0b3fd2d3SAndreas Gohr    /**
97*0b3fd2d3SAndreas Gohr     * @throws SaslEncodingException
98*0b3fd2d3SAndreas Gohr     */
99*0b3fd2d3SAndreas Gohr    protected function decodeClientResponse(string $response): Message
100*0b3fd2d3SAndreas Gohr    {
101*0b3fd2d3SAndreas Gohr        if (!preg_match('/(.*) ([0-9a-f]{32})$/', $response, $matches)) {
102*0b3fd2d3SAndreas Gohr            throw new SaslEncodingException('The client response is malformed.');
103*0b3fd2d3SAndreas Gohr        }
104*0b3fd2d3SAndreas Gohr
105*0b3fd2d3SAndreas Gohr        return new Message([
106*0b3fd2d3SAndreas Gohr            'username' => $matches[1],
107*0b3fd2d3SAndreas Gohr            'digest' => $matches[2],
108*0b3fd2d3SAndreas Gohr        ]);
109*0b3fd2d3SAndreas Gohr    }
110*0b3fd2d3SAndreas Gohr}
111