1273459Strasz/*-
2332595Strasz * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3332595Strasz *
4273459Strasz * Copyright (c) 2014 The FreeBSD Foundation
5273459Strasz * All rights reserved.
6273459Strasz *
7273459Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
8273459Strasz * from the FreeBSD Foundation.
9273459Strasz *
10273459Strasz * Redistribution and use in source and binary forms, with or without
11273459Strasz * modification, are permitted provided that the following conditions
12273459Strasz * are met:
13273459Strasz * 1. Redistributions of source code must retain the above copyright
14273459Strasz *    notice, this list of conditions and the following disclaimer.
15273459Strasz * 2. Redistributions in binary form must reproduce the above copyright
16273459Strasz *    notice, this list of conditions and the following disclaimer in the
17273459Strasz *    documentation and/or other materials provided with the distribution.
18273459Strasz *
19273459Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20273459Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21273459Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22273459Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23273459Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24273459Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25273459Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26273459Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27273459Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28273459Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29273459Strasz * SUCH DAMAGE.
30273459Strasz *
31273459Strasz */
32273459Strasz
33273459Strasz#include <sys/cdefs.h>
34273459Strasz__FBSDID("$FreeBSD: stable/11/usr.sbin/ctld/chap.c 332595 2018-04-16 16:14:05Z trasz $");
35273459Strasz
36273459Strasz#include <assert.h>
37285086Strasz#include <stdlib.h>
38273459Strasz#include <string.h>
39274328Smav#include <netinet/in.h>
40274328Smav#include <resolv.h>
41285086Strasz#include <md5.h>
42273459Strasz
43273459Strasz#include "ctld.h"
44273459Strasz
45273459Straszstatic void
46273459Straszchap_compute_md5(const char id, const char *secret,
47273459Strasz    const void *challenge, size_t challenge_len, void *response,
48273459Strasz    size_t response_len)
49273459Strasz{
50273459Strasz	MD5_CTX ctx;
51273459Strasz
52285086Strasz	assert(response_len == CHAP_DIGEST_LEN);
53273459Strasz
54285086Strasz	MD5Init(&ctx);
55285086Strasz	MD5Update(&ctx, &id, sizeof(id));
56285086Strasz	MD5Update(&ctx, secret, strlen(secret));
57285086Strasz	MD5Update(&ctx, challenge, challenge_len);
58285086Strasz	MD5Final(response, &ctx);
59273459Strasz}
60273459Strasz
61273459Straszstatic int
62273459Straszchap_hex2int(const char hex)
63273459Strasz{
64273459Strasz	switch (hex) {
65273459Strasz	case '0':
66273459Strasz		return (0x00);
67273459Strasz	case '1':
68273459Strasz		return (0x01);
69273459Strasz	case '2':
70273459Strasz		return (0x02);
71273459Strasz	case '3':
72273459Strasz		return (0x03);
73273459Strasz	case '4':
74273459Strasz		return (0x04);
75273459Strasz	case '5':
76273459Strasz		return (0x05);
77273459Strasz	case '6':
78273459Strasz		return (0x06);
79273459Strasz	case '7':
80273459Strasz		return (0x07);
81273459Strasz	case '8':
82273459Strasz		return (0x08);
83273459Strasz	case '9':
84273459Strasz		return (0x09);
85273459Strasz	case 'a':
86273459Strasz	case 'A':
87273459Strasz		return (0x0a);
88273459Strasz	case 'b':
89273459Strasz	case 'B':
90273459Strasz		return (0x0b);
91273459Strasz	case 'c':
92273459Strasz	case 'C':
93273459Strasz		return (0x0c);
94273459Strasz	case 'd':
95273459Strasz	case 'D':
96273459Strasz		return (0x0d);
97273459Strasz	case 'e':
98273459Strasz	case 'E':
99273459Strasz		return (0x0e);
100273459Strasz	case 'f':
101273459Strasz	case 'F':
102273459Strasz		return (0x0f);
103273459Strasz	default:
104273459Strasz		return (-1);
105273459Strasz	}
106273459Strasz}
107273459Strasz
108274328Smavstatic int
109274328Smavchap_b642bin(const char *b64, void **binp, size_t *bin_lenp)
110274328Smav{
111274328Smav	char *bin;
112274328Smav	int b64_len, bin_len;
113274328Smav
114274328Smav	b64_len = strlen(b64);
115274328Smav	bin_len = (b64_len + 3) / 4 * 3;
116274328Smav	bin = calloc(bin_len, 1);
117274328Smav	if (bin == NULL)
118274328Smav		log_err(1, "calloc");
119274328Smav
120274328Smav	bin_len = b64_pton(b64, bin, bin_len);
121274328Smav	if (bin_len < 0) {
122274328Smav		log_warnx("malformed base64 variable");
123274328Smav		free(bin);
124274328Smav		return (-1);
125274328Smav	}
126274328Smav	*binp = bin;
127274328Smav	*bin_lenp = bin_len;
128274328Smav	return (0);
129274328Smav}
130274328Smav
131273459Strasz/*
132273459Strasz * XXX: Review this _carefully_.
133273459Strasz */
134273459Straszstatic int
135273459Straszchap_hex2bin(const char *hex, void **binp, size_t *bin_lenp)
136273459Strasz{
137273459Strasz	int i, hex_len, nibble;
138273459Strasz	bool lo = true; /* As opposed to 'hi'. */
139273459Strasz	char *bin;
140273459Strasz	size_t bin_off, bin_len;
141273459Strasz
142274328Smav	if (strncasecmp(hex, "0b", strlen("0b")) == 0)
143274328Smav		return (chap_b642bin(hex + 2, binp, bin_lenp));
144274328Smav
145273459Strasz	if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
146274328Smav		log_warnx("malformed variable, should start with \"0x\""
147274328Smav		    " or \"0b\"");
148273459Strasz		return (-1);
149273459Strasz	}
150273459Strasz
151273459Strasz	hex += strlen("0x");
152273459Strasz	hex_len = strlen(hex);
153273459Strasz	if (hex_len < 1) {
154273459Strasz		log_warnx("malformed variable; doesn't contain anything "
155273459Strasz		    "but \"0x\"");
156273459Strasz		return (-1);
157273459Strasz	}
158273459Strasz
159273459Strasz	bin_len = hex_len / 2 + hex_len % 2;
160273459Strasz	bin = calloc(bin_len, 1);
161273459Strasz	if (bin == NULL)
162273459Strasz		log_err(1, "calloc");
163273459Strasz
164273459Strasz	bin_off = bin_len - 1;
165273459Strasz	for (i = hex_len - 1; i >= 0; i--) {
166273459Strasz		nibble = chap_hex2int(hex[i]);
167273459Strasz		if (nibble < 0) {
168273459Strasz			log_warnx("malformed variable, invalid char \"%c\"",
169273459Strasz			    hex[i]);
170273459Strasz			free(bin);
171273459Strasz			return (-1);
172273459Strasz		}
173273459Strasz
174273459Strasz		assert(bin_off < bin_len);
175273459Strasz		if (lo) {
176273459Strasz			bin[bin_off] = nibble;
177273459Strasz			lo = false;
178273459Strasz		} else {
179273459Strasz			bin[bin_off] |= nibble << 4;
180273459Strasz			bin_off--;
181273459Strasz			lo = true;
182273459Strasz		}
183273459Strasz	}
184273459Strasz
185273459Strasz	*binp = bin;
186273459Strasz	*bin_lenp = bin_len;
187273459Strasz	return (0);
188273459Strasz}
189273459Strasz
190274328Smav#ifdef USE_BASE64
191273459Straszstatic char *
192273459Straszchap_bin2hex(const char *bin, size_t bin_len)
193273459Strasz{
194274328Smav	unsigned char *b64, *tmp;
195274328Smav	size_t b64_len;
196274328Smav
197274328Smav	b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */
198274328Smav	b64 = malloc(b64_len);
199274328Smav	if (b64 == NULL)
200274328Smav		log_err(1, "malloc");
201274328Smav
202274328Smav	tmp = b64;
203274328Smav	tmp += sprintf(tmp, "0b");
204274328Smav	b64_ntop(bin, bin_len, tmp, b64_len - 2);
205274328Smav
206274328Smav	return (b64);
207274328Smav}
208274328Smav#else
209274328Smavstatic char *
210274328Smavchap_bin2hex(const char *bin, size_t bin_len)
211274328Smav{
212273459Strasz	unsigned char *hex, *tmp, ch;
213273459Strasz	size_t hex_len;
214273459Strasz	size_t i;
215273459Strasz
216273459Strasz	hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
217273459Strasz	hex = malloc(hex_len);
218273459Strasz	if (hex == NULL)
219273459Strasz		log_err(1, "malloc");
220273459Strasz
221273459Strasz	tmp = hex;
222273459Strasz	tmp += sprintf(tmp, "0x");
223273459Strasz	for (i = 0; i < bin_len; i++) {
224273459Strasz		ch = bin[i];
225273459Strasz		tmp += sprintf(tmp, "%02x", ch);
226273459Strasz	}
227273459Strasz
228273459Strasz	return (hex);
229273459Strasz}
230274328Smav#endif /* !USE_BASE64 */
231273459Strasz
232273459Straszstruct chap *
233273459Straszchap_new(void)
234273459Strasz{
235273459Strasz	struct chap *chap;
236273459Strasz
237307697Saraujo	chap = calloc(1, sizeof(*chap));
238273459Strasz	if (chap == NULL)
239273459Strasz		log_err(1, "calloc");
240273459Strasz
241273459Strasz	/*
242273459Strasz	 * Generate the challenge.
243273459Strasz	 */
244285086Strasz	arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge));
245285086Strasz	arc4random_buf(&chap->chap_id, sizeof(chap->chap_id));
246273459Strasz
247273459Strasz	return (chap);
248273459Strasz}
249273459Strasz
250273459Straszchar *
251273459Straszchap_get_id(const struct chap *chap)
252273459Strasz{
253273459Strasz	char *chap_i;
254273459Strasz	int ret;
255273459Strasz
256273459Strasz	ret = asprintf(&chap_i, "%d", chap->chap_id);
257273459Strasz	if (ret < 0)
258273459Strasz		log_err(1, "asprintf");
259273459Strasz
260273459Strasz	return (chap_i);
261273459Strasz}
262273459Strasz
263273459Straszchar *
264273459Straszchap_get_challenge(const struct chap *chap)
265273459Strasz{
266273459Strasz	char *chap_c;
267273459Strasz
268273459Strasz	chap_c = chap_bin2hex(chap->chap_challenge,
269273459Strasz	    sizeof(chap->chap_challenge));
270273459Strasz
271273459Strasz	return (chap_c);
272273459Strasz}
273273459Strasz
274273459Straszstatic int
275273459Straszchap_receive_bin(struct chap *chap, void *response, size_t response_len)
276273459Strasz{
277273459Strasz
278273459Strasz	if (response_len != sizeof(chap->chap_response)) {
279273459Strasz		log_debugx("got CHAP response with invalid length; "
280273459Strasz		    "got %zd, should be %zd",
281273459Strasz		    response_len, sizeof(chap->chap_response));
282273459Strasz		return (1);
283273459Strasz	}
284273459Strasz
285273459Strasz	memcpy(chap->chap_response, response, response_len);
286273459Strasz	return (0);
287273459Strasz}
288273459Strasz
289273459Straszint
290273459Straszchap_receive(struct chap *chap, const char *response)
291273459Strasz{
292273459Strasz	void *response_bin;
293273459Strasz	size_t response_bin_len;
294273459Strasz	int error;
295273459Strasz
296273459Strasz	error = chap_hex2bin(response, &response_bin, &response_bin_len);
297273459Strasz	if (error != 0) {
298273459Strasz		log_debugx("got incorrectly encoded CHAP response \"%s\"",
299273459Strasz		    response);
300273459Strasz		return (1);
301273459Strasz	}
302273459Strasz
303273459Strasz	error = chap_receive_bin(chap, response_bin, response_bin_len);
304273459Strasz	free(response_bin);
305273459Strasz
306273459Strasz	return (error);
307273459Strasz}
308273459Strasz
309273459Straszint
310273459Straszchap_authenticate(struct chap *chap, const char *secret)
311273459Strasz{
312285086Strasz	char expected_response[CHAP_DIGEST_LEN];
313273459Strasz
314273459Strasz	chap_compute_md5(chap->chap_id, secret,
315273459Strasz	    chap->chap_challenge, sizeof(chap->chap_challenge),
316273459Strasz	    expected_response, sizeof(expected_response));
317273459Strasz
318273459Strasz	if (memcmp(chap->chap_response,
319273459Strasz	    expected_response, sizeof(expected_response)) != 0) {
320273459Strasz		return (-1);
321273459Strasz	}
322273459Strasz
323273459Strasz	return (0);
324273459Strasz}
325273459Strasz
326273459Straszvoid
327273459Straszchap_delete(struct chap *chap)
328273459Strasz{
329273459Strasz
330273459Strasz	free(chap);
331273459Strasz}
332273459Strasz
333273459Straszstruct rchap *
334273459Straszrchap_new(const char *secret)
335273459Strasz{
336273459Strasz	struct rchap *rchap;
337273459Strasz
338307697Saraujo	rchap = calloc(1, sizeof(*rchap));
339273459Strasz	if (rchap == NULL)
340273459Strasz		log_err(1, "calloc");
341273459Strasz
342273459Strasz	rchap->rchap_secret = checked_strdup(secret);
343273459Strasz
344273459Strasz	return (rchap);
345273459Strasz}
346273459Strasz
347273459Straszstatic void
348273459Straszrchap_receive_bin(struct rchap *rchap, const unsigned char id,
349273459Strasz    const void *challenge, size_t challenge_len)
350273459Strasz{
351273459Strasz
352273459Strasz	rchap->rchap_id = id;
353273459Strasz	rchap->rchap_challenge = calloc(challenge_len, 1);
354273459Strasz	if (rchap->rchap_challenge == NULL)
355273459Strasz		log_err(1, "calloc");
356273459Strasz	memcpy(rchap->rchap_challenge, challenge, challenge_len);
357273459Strasz	rchap->rchap_challenge_len = challenge_len;
358273459Strasz}
359273459Strasz
360273459Straszint
361273459Straszrchap_receive(struct rchap *rchap, const char *id, const char *challenge)
362273459Strasz{
363273459Strasz	unsigned char id_bin;
364273459Strasz	void *challenge_bin;
365273459Strasz	size_t challenge_bin_len;
366273459Strasz
367273459Strasz	int error;
368273459Strasz
369273459Strasz	id_bin = strtoul(id, NULL, 10);
370273459Strasz
371273459Strasz	error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len);
372273459Strasz	if (error != 0) {
373273459Strasz		log_debugx("got incorrectly encoded CHAP challenge \"%s\"",
374273459Strasz		    challenge);
375273459Strasz		return (1);
376273459Strasz	}
377273459Strasz
378273459Strasz	rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len);
379273459Strasz	free(challenge_bin);
380273459Strasz
381273459Strasz	return (0);
382273459Strasz}
383273459Strasz
384273459Straszstatic void
385273459Straszrchap_get_response_bin(struct rchap *rchap,
386273459Strasz    void **responsep, size_t *response_lenp)
387273459Strasz{
388273459Strasz	void *response_bin;
389285086Strasz	size_t response_bin_len = CHAP_DIGEST_LEN;
390273459Strasz
391273459Strasz	response_bin = calloc(response_bin_len, 1);
392273459Strasz	if (response_bin == NULL)
393273459Strasz		log_err(1, "calloc");
394273459Strasz
395273459Strasz	chap_compute_md5(rchap->rchap_id, rchap->rchap_secret,
396273459Strasz	    rchap->rchap_challenge, rchap->rchap_challenge_len,
397273459Strasz	    response_bin, response_bin_len);
398273459Strasz
399273459Strasz	*responsep = response_bin;
400273459Strasz	*response_lenp = response_bin_len;
401273459Strasz}
402273459Strasz
403273459Straszchar *
404273459Straszrchap_get_response(struct rchap *rchap)
405273459Strasz{
406273459Strasz	void *response;
407273459Strasz	size_t response_len;
408273459Strasz	char *chap_r;
409273459Strasz
410273459Strasz	rchap_get_response_bin(rchap, &response, &response_len);
411273459Strasz	chap_r = chap_bin2hex(response, response_len);
412273459Strasz	free(response);
413273459Strasz
414273459Strasz	return (chap_r);
415273459Strasz}
416273459Strasz
417273459Straszvoid
418273459Straszrchap_delete(struct rchap *rchap)
419273459Strasz{
420273459Strasz
421273459Strasz	free(rchap->rchap_secret);
422273459Strasz	free(rchap->rchap_challenge);
423273459Strasz	free(rchap);
424273459Strasz}
425