1<?php
2/*
3Copyright (c) 2002-2010, Michael Bretterklieber <michael@bretterklieber.com>
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions
8are met:
9
101. Redistributions of source code must retain the above copyright
11   notice, this list of conditions and the following disclaimer.
122. Redistributions in binary form must reproduce the above copyright
13   notice, this list of conditions and the following disclaimer in the
14   documentation and/or other materials provided with the distribution.
153. The names of the authors may not be used to endorse or promote products
16   derived from this software without specific prior written permission.
17
18THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29This code cannot simply be copied and put under the GNU Public License or
30any other GPL-like (LGPL, GPL2) License.
31
32    $Id: CHAP.php 302857 2010-08-28 21:12:59Z mbretter $
33*/
34
35require_once 'PEAR.php';
36
37/**
38* Classes for generating packets for various CHAP Protocols:
39* CHAP-MD5: RFC1994
40* MS-CHAPv1: RFC2433
41* MS-CHAPv2: RFC2759
42*
43* @package Crypt_CHAP
44* @author  Michael Bretterklieber <michael@bretterklieber.com>
45* @access  public
46* @version $Revision: 302857 $
47*/
48
49/**
50 * class Crypt_CHAP
51 *
52 * Abstract base class for CHAP
53 *
54 * @package Crypt_CHAP
55 */
56class Crypt_CHAP extends PEAR
57{
58    /**
59     * Random binary challenge
60     * @var  string
61     */
62    var $challenge = null;
63
64    /**
65     * Binary response
66     * @var  string
67     */
68    var $response = null;
69
70    /**
71     * User password
72     * @var  string
73     */
74    var $password = null;
75
76    /**
77     * Id of the authentication request. Should incremented after every request.
78     * @var  integer
79     */
80    var $chapid = 1;
81
82    /**
83     * Constructor
84     *
85     * Generates a random challenge
86     * @return void
87     */
88    function __construct()
89    {
90        //$this->PEAR();
91        $this->generateChallenge();
92    }
93
94    /**
95     * Generates a random binary challenge
96     *
97     * @param  string  $varname  Name of the property
98     * @param  integer $size     Size of the challenge in Bytes
99     * @return void
100     */
101    function generateChallenge($varname = 'challenge', $size = 8)
102    {
103        $this->$varname = '';
104        for ($i = 0; $i < $size; $i++) {
105            $this->$varname .= pack('C', 1 + mt_rand() % 255);
106        }
107        return $this->$varname;
108    }
109
110    /**
111     * Generates the response. Overwrite this.
112     *
113     * @return void
114     */
115    function challengeResponse()
116    {
117    }
118
119}
120
121/**
122 * class Crypt_CHAP_MD5
123 *
124 * Generate CHAP-MD5 Packets
125 *
126 * @package Crypt_CHAP
127 */
128class Crypt_CHAP_MD5 extends Crypt_CHAP
129{
130
131    /**
132     * Generates the response.
133     *
134     * CHAP-MD5 uses MD5-Hash for generating the response. The Hash consists
135     * of the chapid, the plaintext password and the challenge.
136     *
137     * @return string
138     */
139    function challengeResponse()
140    {
141        return pack('H*', md5(pack('C', $this->chapid) . $this->password . $this->challenge));
142    }
143}
144
145/**
146 * class Crypt_CHAP_MSv1
147 *
148 * Generate MS-CHAPv1 Packets. MS-CHAP doesen't use the plaintext password, it uses the
149 * NT-HASH wich is stored in the SAM-Database or in the smbpasswd, if you are using samba.
150 * The NT-HASH is MD4(str2unicode(plaintextpass)).
151 * You need the hash extension for this class.
152 *
153 * @package Crypt_CHAP
154 */
155class Crypt_CHAP_MSv1 extends Crypt_CHAP
156{
157    /**
158     * Wether using deprecated LM-Responses or not.
159     * 0 = use LM-Response, 1 = use NT-Response
160     * @var  bool
161     */
162    var $flags = 1;
163
164    /**
165     * Constructor
166     *
167     * Loads the hash extension
168     * @return void
169     */
170    function __construct()
171    {
172        //$this->Crypt_CHAP();
173        $this->loadExtension('hash');
174    }
175
176    /**
177     * Generates the NT-HASH from the given plaintext password.
178     *
179     * @access public
180     * @return string
181     */
182    function ntPasswordHash($password = null)
183    {
184        //if (isset($password)) {
185        if (!is_null($password)) {
186            return pack('H*',hash('md4', $this->str2unicode($password)));
187        } else {
188            return pack('H*',hash('md4', $this->str2unicode($this->password)));
189        }
190    }
191
192    /**
193     * Converts ascii to unicode.
194     *
195     * @access public
196     * @return string
197     */
198    function str2unicode($str)
199    {
200        $uni = '';
201        $str = (string) $str;
202        for ($i = 0; $i < strlen($str); $i++) {
203            $a = ord($str{$i}) << 8;
204            $uni .= sprintf("%X", $a);
205        }
206        return pack('H*', $uni);
207    }
208
209    /**
210     * Generates the NT-Response.
211     *
212     * @access public
213     * @return string
214     */
215    function challengeResponse()
216    {
217        return $this->_challengeResponse();
218    }
219
220    /**
221     * Generates the NT-Response.
222     *
223     * @access public
224     * @return string
225     */
226    function ntChallengeResponse()
227    {
228        return $this->_challengeResponse(false);
229    }
230
231    /**
232     * Generates the LAN-Manager-Response.
233     *
234     * @access public
235     * @return string
236     */
237    function lmChallengeResponse()
238    {
239        return $this->_challengeResponse(true);
240    }
241
242    /**
243     * Generates the response.
244     *
245     * Generates the response using DES.
246     *
247     * @param  bool  $lm  wether generating LAN-Manager-Response
248     * @access private
249     * @return string
250     */
251    function _challengeResponse($lm = false)
252    {
253        if ($lm) {
254            $hash = $this->lmPasswordHash();
255        } else {
256            $hash = $this->ntPasswordHash();
257        }
258
259        $hash = str_pad($hash, 21, "\0");
260
261        if (!extension_loaded('mcrypt') && extension_loaded('openssl')) {
262            // added openssl routines for dapphp/radius
263            $key   = $this->_desAddParity(substr($hash, 0, 7));
264            $resp1 = openssl_encrypt($this->challenge, 'des-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
265
266            $key   = $this->_desAddParity(substr($hash, 7, 7));
267            $resp2 = openssl_encrypt($this->challenge, 'des-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
268
269            $key   = $this->_desAddParity(substr($hash, 14, 7));
270            $resp3 = openssl_encrypt($this->challenge, 'des-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
271        } else {
272            $td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, '');
273            $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
274            $key = $this->_desAddParity(substr($hash, 0, 7));
275            mcrypt_generic_init($td, $key, $iv);
276            $resp1 = mcrypt_generic($td, $this->challenge);
277            mcrypt_generic_deinit($td);
278
279            $key = $this->_desAddParity(substr($hash, 7, 7));
280            mcrypt_generic_init($td, $key, $iv);
281            $resp2 = mcrypt_generic($td, $this->challenge);
282            mcrypt_generic_deinit($td);
283
284            $key = $this->_desAddParity(substr($hash, 14, 7));
285            mcrypt_generic_init($td, $key, $iv);
286            $resp3 = mcrypt_generic($td, $this->challenge);
287            mcrypt_generic_deinit($td);
288            mcrypt_module_close($td);
289        }
290
291        return $resp1 . $resp2 . $resp3;
292    }
293
294    /**
295     * Generates the LAN-Manager-HASH from the given plaintext password.
296     *
297     * @access public
298     * @return string
299     */
300    function lmPasswordHash($password = null)
301    {
302        $plain = isset($password) ? $password : $this->password;
303
304        $plain = substr(strtoupper($plain), 0, 14);
305        while (strlen($plain) < 14) {
306             $plain .= "\0";
307        }
308
309        return $this->_desHash(substr($plain, 0, 7)) . $this->_desHash(substr($plain, 7, 7));
310    }
311
312    /**
313     * Generates an irreversible HASH.
314     *
315     * @access private
316     * @return string
317     */
318    function _desHash($plain)
319    {
320        if (!extension_loaded('mcrypt') && extension_loaded('openssl')) {
321            // added openssl routines for dapphp/radius
322            $key = $this->_desAddParity($plain);
323            $hash = openssl_encrypt('KGS!@#$%', 'des-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
324
325            return $hash;
326        } else {
327            $key = $this->_desAddParity($plain);
328            $td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, '');
329            $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
330            mcrypt_generic_init($td, $key, $iv);
331            $hash = mcrypt_generic($td, 'KGS!@#$%');
332            mcrypt_generic_deinit($td);
333            mcrypt_module_close($td);
334
335            return $hash;
336        }
337    }
338
339    /**
340     * Adds the parity bit to the given DES key.
341     *
342     * @access private
343     * @param  string  $key 7-Bytes Key without parity
344     * @return string
345     */
346    function _desAddParity($key)
347    {
348        static $odd_parity = array(
349                1,  1,  2,  2,  4,  4,  7,  7,  8,  8, 11, 11, 13, 13, 14, 14,
350                16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31,
351                32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47,
352                49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62,
353                64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79,
354                81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94,
355                97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110,
356                112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127,
357                128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143,
358                145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158,
359                161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174,
360                176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191,
361                193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206,
362                208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223,
363                224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239,
364                241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254);
365
366        $bin = '';
367        for ($i = 0; $i < strlen($key); $i++) {
368            $bin .= sprintf('%08s', decbin(ord($key{$i})));
369        }
370
371        $str1 = explode('-', substr(chunk_split($bin, 7, '-'), 0, -1));
372        $x = '';
373        foreach($str1 as $s) {
374            $x .= sprintf('%02s', dechex($odd_parity[bindec($s . '0')]));
375        }
376
377        return pack('H*', $x);
378
379    }
380
381    /**
382     * Generates the response-packet.
383     *
384     * @param  bool  $lm  wether including LAN-Manager-Response
385     * @access private
386     * @return string
387     */
388    function response($lm = false)
389    {
390        $ntresp = $this->ntChallengeResponse();
391        if ($lm) {
392            $lmresp = $this->lmChallengeResponse();
393        } else {
394            $lmresp = str_repeat ("\0", 24);
395        }
396
397        // Response: LM Response, NT Response, flags (0 = use LM Response, 1 = use NT Response)
398        return $lmresp . $ntresp . pack('C', !$lm);
399    }
400}
401
402/**
403 * class Crypt_CHAP_MSv2
404 *
405 * Generate MS-CHAPv2 Packets. This version of MS-CHAP uses a 16 Bytes authenticator
406 * challenge and a 16 Bytes peer Challenge. LAN-Manager responses no longer exists
407 * in this version. The challenge is already a SHA1 challenge hash of both challenges
408 * and of the username.
409 *
410 * @package Crypt_CHAP
411 */
412class Crypt_CHAP_MSv2 extends Crypt_CHAP_MSv1
413{
414    /**
415     * The username
416     * @var  string
417     */
418    var $username = null;
419
420    /**
421     * The 16 Bytes random binary peer challenge
422     * @var  string
423     */
424    var $peerChallenge = null;
425
426    /**
427     * The 16 Bytes random binary authenticator challenge
428     * @var  string
429     */
430    var $authChallenge = null;
431
432    /**
433     * Constructor
434     *
435     * Generates the 16 Bytes peer and authentication challenge
436     * @return void
437     */
438    function __construct()
439    {
440        //$this->Crypt_CHAP_MSv1();
441        $this->generateChallenge('peerChallenge', 16);
442        $this->generateChallenge('authChallenge', 16);
443    }
444
445    /**
446     * Generates a hash from the NT-HASH.
447     *
448     * @access public
449     * @param  string  $nthash The NT-HASH
450     * @return string
451     */
452    function ntPasswordHashHash($nthash)
453    {
454        return pack('H*',hash('md4', $nthash));
455    }
456
457    /**
458     * Generates the challenge hash from the peer and the authenticator challenge and
459     * the username. SHA1 is used for this, but only the first 8 Bytes are used.
460     *
461     * @access public
462     * @return string
463     */
464    function challengeHash()
465    {
466        return substr(pack('H*',hash('sha1', $this->peerChallenge . $this->authChallenge . $this->username)), 0, 8);
467    }
468
469    /**
470     * Generates the response.
471     *
472     * @access public
473     * @return string
474     */
475    function challengeResponse()
476    {
477        $this->challenge = $this->challengeHash();
478        return $this->_challengeResponse();
479    }
480}
481
482