1273459Strasz/*-
2273459Strasz * Copyright (c) 2014 The FreeBSD Foundation
3273459Strasz * All rights reserved.
4273459Strasz *
5273459Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
6273459Strasz * from the FreeBSD Foundation.
7273459Strasz *
8273459Strasz * Redistribution and use in source and binary forms, with or without
9273459Strasz * modification, are permitted provided that the following conditions
10273459Strasz * are met:
11273459Strasz * 1. Redistributions of source code must retain the above copyright
12273459Strasz *    notice, this list of conditions and the following disclaimer.
13273459Strasz * 2. Redistributions in binary form must reproduce the above copyright
14273459Strasz *    notice, this list of conditions and the following disclaimer in the
15273459Strasz *    documentation and/or other materials provided with the distribution.
16273459Strasz *
17273459Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18273459Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19273459Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20273459Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21273459Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22273459Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23273459Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24273459Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25273459Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26273459Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27273459Strasz * SUCH DAMAGE.
28273459Strasz *
29273459Strasz */
30273459Strasz
31273459Strasz#include <sys/cdefs.h>
32273459Strasz__FBSDID("$FreeBSD: releng/10.3/usr.sbin/ctld/chap.c 286219 2015-08-03 07:20:33Z trasz $");
33273459Strasz
34273459Strasz#include <assert.h>
35286219Strasz#include <stdlib.h>
36273459Strasz#include <string.h>
37274909Smav#include <netinet/in.h>
38274909Smav#include <resolv.h>
39286219Strasz#include <md5.h>
40273459Strasz
41273459Strasz#include "ctld.h"
42273459Strasz
43273459Straszstatic void
44273459Straszchap_compute_md5(const char id, const char *secret,
45273459Strasz    const void *challenge, size_t challenge_len, void *response,
46273459Strasz    size_t response_len)
47273459Strasz{
48273459Strasz	MD5_CTX ctx;
49273459Strasz
50286219Strasz	assert(response_len == CHAP_DIGEST_LEN);
51273459Strasz
52286219Strasz	MD5Init(&ctx);
53286219Strasz	MD5Update(&ctx, &id, sizeof(id));
54286219Strasz	MD5Update(&ctx, secret, strlen(secret));
55286219Strasz	MD5Update(&ctx, challenge, challenge_len);
56286219Strasz	MD5Final(response, &ctx);
57273459Strasz}
58273459Strasz
59273459Straszstatic int
60273459Straszchap_hex2int(const char hex)
61273459Strasz{
62273459Strasz	switch (hex) {
63273459Strasz	case '0':
64273459Strasz		return (0x00);
65273459Strasz	case '1':
66273459Strasz		return (0x01);
67273459Strasz	case '2':
68273459Strasz		return (0x02);
69273459Strasz	case '3':
70273459Strasz		return (0x03);
71273459Strasz	case '4':
72273459Strasz		return (0x04);
73273459Strasz	case '5':
74273459Strasz		return (0x05);
75273459Strasz	case '6':
76273459Strasz		return (0x06);
77273459Strasz	case '7':
78273459Strasz		return (0x07);
79273459Strasz	case '8':
80273459Strasz		return (0x08);
81273459Strasz	case '9':
82273459Strasz		return (0x09);
83273459Strasz	case 'a':
84273459Strasz	case 'A':
85273459Strasz		return (0x0a);
86273459Strasz	case 'b':
87273459Strasz	case 'B':
88273459Strasz		return (0x0b);
89273459Strasz	case 'c':
90273459Strasz	case 'C':
91273459Strasz		return (0x0c);
92273459Strasz	case 'd':
93273459Strasz	case 'D':
94273459Strasz		return (0x0d);
95273459Strasz	case 'e':
96273459Strasz	case 'E':
97273459Strasz		return (0x0e);
98273459Strasz	case 'f':
99273459Strasz	case 'F':
100273459Strasz		return (0x0f);
101273459Strasz	default:
102273459Strasz		return (-1);
103273459Strasz	}
104273459Strasz}
105273459Strasz
106274909Smavstatic int
107274909Smavchap_b642bin(const char *b64, void **binp, size_t *bin_lenp)
108274909Smav{
109274909Smav	char *bin;
110274909Smav	int b64_len, bin_len;
111274909Smav
112274909Smav	b64_len = strlen(b64);
113274909Smav	bin_len = (b64_len + 3) / 4 * 3;
114274909Smav	bin = calloc(bin_len, 1);
115274909Smav	if (bin == NULL)
116274909Smav		log_err(1, "calloc");
117274909Smav
118274909Smav	bin_len = b64_pton(b64, bin, bin_len);
119274909Smav	if (bin_len < 0) {
120274909Smav		log_warnx("malformed base64 variable");
121274909Smav		free(bin);
122274909Smav		return (-1);
123274909Smav	}
124274909Smav	*binp = bin;
125274909Smav	*bin_lenp = bin_len;
126274909Smav	return (0);
127274909Smav}
128274909Smav
129273459Strasz/*
130273459Strasz * XXX: Review this _carefully_.
131273459Strasz */
132273459Straszstatic int
133273459Straszchap_hex2bin(const char *hex, void **binp, size_t *bin_lenp)
134273459Strasz{
135273459Strasz	int i, hex_len, nibble;
136273459Strasz	bool lo = true; /* As opposed to 'hi'. */
137273459Strasz	char *bin;
138273459Strasz	size_t bin_off, bin_len;
139273459Strasz
140274909Smav	if (strncasecmp(hex, "0b", strlen("0b")) == 0)
141274909Smav		return (chap_b642bin(hex + 2, binp, bin_lenp));
142274909Smav
143273459Strasz	if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
144274909Smav		log_warnx("malformed variable, should start with \"0x\""
145274909Smav		    " or \"0b\"");
146273459Strasz		return (-1);
147273459Strasz	}
148273459Strasz
149273459Strasz	hex += strlen("0x");
150273459Strasz	hex_len = strlen(hex);
151273459Strasz	if (hex_len < 1) {
152273459Strasz		log_warnx("malformed variable; doesn't contain anything "
153273459Strasz		    "but \"0x\"");
154273459Strasz		return (-1);
155273459Strasz	}
156273459Strasz
157273459Strasz	bin_len = hex_len / 2 + hex_len % 2;
158273459Strasz	bin = calloc(bin_len, 1);
159273459Strasz	if (bin == NULL)
160273459Strasz		log_err(1, "calloc");
161273459Strasz
162273459Strasz	bin_off = bin_len - 1;
163273459Strasz	for (i = hex_len - 1; i >= 0; i--) {
164273459Strasz		nibble = chap_hex2int(hex[i]);
165273459Strasz		if (nibble < 0) {
166273459Strasz			log_warnx("malformed variable, invalid char \"%c\"",
167273459Strasz			    hex[i]);
168273459Strasz			free(bin);
169273459Strasz			return (-1);
170273459Strasz		}
171273459Strasz
172273459Strasz		assert(bin_off < bin_len);
173273459Strasz		if (lo) {
174273459Strasz			bin[bin_off] = nibble;
175273459Strasz			lo = false;
176273459Strasz		} else {
177273459Strasz			bin[bin_off] |= nibble << 4;
178273459Strasz			bin_off--;
179273459Strasz			lo = true;
180273459Strasz		}
181273459Strasz	}
182273459Strasz
183273459Strasz	*binp = bin;
184273459Strasz	*bin_lenp = bin_len;
185273459Strasz	return (0);
186273459Strasz}
187273459Strasz
188274909Smav#ifdef USE_BASE64
189273459Straszstatic char *
190273459Straszchap_bin2hex(const char *bin, size_t bin_len)
191273459Strasz{
192274909Smav	unsigned char *b64, *tmp;
193274909Smav	size_t b64_len;
194274909Smav
195274909Smav	b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */
196274909Smav	b64 = malloc(b64_len);
197274909Smav	if (b64 == NULL)
198274909Smav		log_err(1, "malloc");
199274909Smav
200274909Smav	tmp = b64;
201274909Smav	tmp += sprintf(tmp, "0b");
202274909Smav	b64_ntop(bin, bin_len, tmp, b64_len - 2);
203274909Smav
204274909Smav	return (b64);
205274909Smav}
206274909Smav#else
207274909Smavstatic char *
208274909Smavchap_bin2hex(const char *bin, size_t bin_len)
209274909Smav{
210273459Strasz	unsigned char *hex, *tmp, ch;
211273459Strasz	size_t hex_len;
212273459Strasz	size_t i;
213273459Strasz
214273459Strasz	hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
215273459Strasz	hex = malloc(hex_len);
216273459Strasz	if (hex == NULL)
217273459Strasz		log_err(1, "malloc");
218273459Strasz
219273459Strasz	tmp = hex;
220273459Strasz	tmp += sprintf(tmp, "0x");
221273459Strasz	for (i = 0; i < bin_len; i++) {
222273459Strasz		ch = bin[i];
223273459Strasz		tmp += sprintf(tmp, "%02x", ch);
224273459Strasz	}
225273459Strasz
226273459Strasz	return (hex);
227273459Strasz}
228274909Smav#endif /* !USE_BASE64 */
229273459Strasz
230273459Straszstruct chap *
231273459Straszchap_new(void)
232273459Strasz{
233273459Strasz	struct chap *chap;
234273459Strasz
235273459Strasz	chap = calloc(sizeof(*chap), 1);
236273459Strasz	if (chap == NULL)
237273459Strasz		log_err(1, "calloc");
238273459Strasz
239273459Strasz	/*
240273459Strasz	 * Generate the challenge.
241273459Strasz	 */
242286219Strasz	arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge));
243286219Strasz	arc4random_buf(&chap->chap_id, sizeof(chap->chap_id));
244273459Strasz
245273459Strasz	return (chap);
246273459Strasz}
247273459Strasz
248273459Straszchar *
249273459Straszchap_get_id(const struct chap *chap)
250273459Strasz{
251273459Strasz	char *chap_i;
252273459Strasz	int ret;
253273459Strasz
254273459Strasz	ret = asprintf(&chap_i, "%d", chap->chap_id);
255273459Strasz	if (ret < 0)
256273459Strasz		log_err(1, "asprintf");
257273459Strasz
258273459Strasz	return (chap_i);
259273459Strasz}
260273459Strasz
261273459Straszchar *
262273459Straszchap_get_challenge(const struct chap *chap)
263273459Strasz{
264273459Strasz	char *chap_c;
265273459Strasz
266273459Strasz	chap_c = chap_bin2hex(chap->chap_challenge,
267273459Strasz	    sizeof(chap->chap_challenge));
268273459Strasz
269273459Strasz	return (chap_c);
270273459Strasz}
271273459Strasz
272273459Straszstatic int
273273459Straszchap_receive_bin(struct chap *chap, void *response, size_t response_len)
274273459Strasz{
275273459Strasz
276273459Strasz	if (response_len != sizeof(chap->chap_response)) {
277273459Strasz		log_debugx("got CHAP response with invalid length; "
278273459Strasz		    "got %zd, should be %zd",
279273459Strasz		    response_len, sizeof(chap->chap_response));
280273459Strasz		return (1);
281273459Strasz	}
282273459Strasz
283273459Strasz	memcpy(chap->chap_response, response, response_len);
284273459Strasz	return (0);
285273459Strasz}
286273459Strasz
287273459Straszint
288273459Straszchap_receive(struct chap *chap, const char *response)
289273459Strasz{
290273459Strasz	void *response_bin;
291273459Strasz	size_t response_bin_len;
292273459Strasz	int error;
293273459Strasz
294273459Strasz	error = chap_hex2bin(response, &response_bin, &response_bin_len);
295273459Strasz	if (error != 0) {
296273459Strasz		log_debugx("got incorrectly encoded CHAP response \"%s\"",
297273459Strasz		    response);
298273459Strasz		return (1);
299273459Strasz	}
300273459Strasz
301273459Strasz	error = chap_receive_bin(chap, response_bin, response_bin_len);
302273459Strasz	free(response_bin);
303273459Strasz
304273459Strasz	return (error);
305273459Strasz}
306273459Strasz
307273459Straszint
308273459Straszchap_authenticate(struct chap *chap, const char *secret)
309273459Strasz{
310286219Strasz	char expected_response[CHAP_DIGEST_LEN];
311273459Strasz
312273459Strasz	chap_compute_md5(chap->chap_id, secret,
313273459Strasz	    chap->chap_challenge, sizeof(chap->chap_challenge),
314273459Strasz	    expected_response, sizeof(expected_response));
315273459Strasz
316273459Strasz	if (memcmp(chap->chap_response,
317273459Strasz	    expected_response, sizeof(expected_response)) != 0) {
318273459Strasz		return (-1);
319273459Strasz	}
320273459Strasz
321273459Strasz	return (0);
322273459Strasz}
323273459Strasz
324273459Straszvoid
325273459Straszchap_delete(struct chap *chap)
326273459Strasz{
327273459Strasz
328273459Strasz	free(chap);
329273459Strasz}
330273459Strasz
331273459Straszstruct rchap *
332273459Straszrchap_new(const char *secret)
333273459Strasz{
334273459Strasz	struct rchap *rchap;
335273459Strasz
336273459Strasz	rchap = calloc(sizeof(*rchap), 1);
337273459Strasz	if (rchap == NULL)
338273459Strasz		log_err(1, "calloc");
339273459Strasz
340273459Strasz	rchap->rchap_secret = checked_strdup(secret);
341273459Strasz
342273459Strasz	return (rchap);
343273459Strasz}
344273459Strasz
345273459Straszstatic void
346273459Straszrchap_receive_bin(struct rchap *rchap, const unsigned char id,
347273459Strasz    const void *challenge, size_t challenge_len)
348273459Strasz{
349273459Strasz
350273459Strasz	rchap->rchap_id = id;
351273459Strasz	rchap->rchap_challenge = calloc(challenge_len, 1);
352273459Strasz	if (rchap->rchap_challenge == NULL)
353273459Strasz		log_err(1, "calloc");
354273459Strasz	memcpy(rchap->rchap_challenge, challenge, challenge_len);
355273459Strasz	rchap->rchap_challenge_len = challenge_len;
356273459Strasz}
357273459Strasz
358273459Straszint
359273459Straszrchap_receive(struct rchap *rchap, const char *id, const char *challenge)
360273459Strasz{
361273459Strasz	unsigned char id_bin;
362273459Strasz	void *challenge_bin;
363273459Strasz	size_t challenge_bin_len;
364273459Strasz
365273459Strasz	int error;
366273459Strasz
367273459Strasz	id_bin = strtoul(id, NULL, 10);
368273459Strasz
369273459Strasz	error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len);
370273459Strasz	if (error != 0) {
371273459Strasz		log_debugx("got incorrectly encoded CHAP challenge \"%s\"",
372273459Strasz		    challenge);
373273459Strasz		return (1);
374273459Strasz	}
375273459Strasz
376273459Strasz	rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len);
377273459Strasz	free(challenge_bin);
378273459Strasz
379273459Strasz	return (0);
380273459Strasz}
381273459Strasz
382273459Straszstatic void
383273459Straszrchap_get_response_bin(struct rchap *rchap,
384273459Strasz    void **responsep, size_t *response_lenp)
385273459Strasz{
386273459Strasz	void *response_bin;
387286219Strasz	size_t response_bin_len = CHAP_DIGEST_LEN;
388273459Strasz
389273459Strasz	response_bin = calloc(response_bin_len, 1);
390273459Strasz	if (response_bin == NULL)
391273459Strasz		log_err(1, "calloc");
392273459Strasz
393273459Strasz	chap_compute_md5(rchap->rchap_id, rchap->rchap_secret,
394273459Strasz	    rchap->rchap_challenge, rchap->rchap_challenge_len,
395273459Strasz	    response_bin, response_bin_len);
396273459Strasz
397273459Strasz	*responsep = response_bin;
398273459Strasz	*response_lenp = response_bin_len;
399273459Strasz}
400273459Strasz
401273459Straszchar *
402273459Straszrchap_get_response(struct rchap *rchap)
403273459Strasz{
404273459Strasz	void *response;
405273459Strasz	size_t response_len;
406273459Strasz	char *chap_r;
407273459Strasz
408273459Strasz	rchap_get_response_bin(rchap, &response, &response_len);
409273459Strasz	chap_r = chap_bin2hex(response, response_len);
410273459Strasz	free(response);
411273459Strasz
412273459Strasz	return (chap_r);
413273459Strasz}
414273459Strasz
415273459Straszvoid
416273459Straszrchap_delete(struct rchap *rchap)
417273459Strasz{
418273459Strasz
419273459Strasz	free(rchap->rchap_secret);
420273459Strasz	free(rchap->rchap_challenge);
421273459Strasz	free(rchap);
422273459Strasz}
423