1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2014 The FreeBSD Foundation
5 *
6 * This software was developed by Edward Tomasz Napierala under sponsorship
7 * from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <assert.h>
32#include <stdlib.h>
33#include <string.h>
34#include <netinet/in.h>
35#include <resolv.h>
36#include <md5.h>
37
38#include "libiscsiutil.h"
39
40static void
41chap_compute_md5(const char id, const char *secret,
42    const void *challenge, size_t challenge_len, void *response,
43    size_t response_len)
44{
45	MD5_CTX ctx;
46
47	assert(response_len == CHAP_DIGEST_LEN);
48
49	MD5Init(&ctx);
50	MD5Update(&ctx, &id, sizeof(id));
51	MD5Update(&ctx, secret, strlen(secret));
52	MD5Update(&ctx, challenge, challenge_len);
53	MD5Final(response, &ctx);
54}
55
56static int
57chap_hex2int(const char hex)
58{
59	switch (hex) {
60	case '0':
61		return (0x00);
62	case '1':
63		return (0x01);
64	case '2':
65		return (0x02);
66	case '3':
67		return (0x03);
68	case '4':
69		return (0x04);
70	case '5':
71		return (0x05);
72	case '6':
73		return (0x06);
74	case '7':
75		return (0x07);
76	case '8':
77		return (0x08);
78	case '9':
79		return (0x09);
80	case 'a':
81	case 'A':
82		return (0x0a);
83	case 'b':
84	case 'B':
85		return (0x0b);
86	case 'c':
87	case 'C':
88		return (0x0c);
89	case 'd':
90	case 'D':
91		return (0x0d);
92	case 'e':
93	case 'E':
94		return (0x0e);
95	case 'f':
96	case 'F':
97		return (0x0f);
98	default:
99		return (-1);
100	}
101}
102
103static int
104chap_b642bin(const char *b64, void **binp, size_t *bin_lenp)
105{
106	char *bin;
107	int b64_len, bin_len;
108
109	b64_len = strlen(b64);
110	bin_len = (b64_len + 3) / 4 * 3;
111	bin = calloc(bin_len, 1);
112	if (bin == NULL)
113		log_err(1, "calloc");
114
115	bin_len = b64_pton(b64, bin, bin_len);
116	if (bin_len < 0) {
117		log_warnx("malformed base64 variable");
118		free(bin);
119		return (-1);
120	}
121	*binp = bin;
122	*bin_lenp = bin_len;
123	return (0);
124}
125
126/*
127 * XXX: Review this _carefully_.
128 */
129static int
130chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp)
131{
132	int i, hex_len, nibble;
133	bool lo = true; /* As opposed to 'hi'. */
134	char *bin;
135	size_t bin_off, bin_len;
136
137	if (strncasecmp(hex, "0b", strlen("0b")) == 0)
138		return (chap_b642bin(hex + 2, binp, bin_lenp));
139
140	if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
141		log_warnx("malformed variable, should start with \"0x\""
142		    " or \"0b\"");
143		return (-1);
144	}
145
146	hex += strlen("0x");
147	hex_len = strlen(hex);
148	if (hex_len < 1) {
149		log_warnx("malformed variable; doesn't contain anything "
150		    "but \"0x\"");
151		return (-1);
152	}
153
154	bin_len = hex_len / 2 + hex_len % 2;
155	bin = calloc(bin_len, 1);
156	if (bin == NULL)
157		log_err(1, "calloc");
158
159	bin_off = bin_len - 1;
160	for (i = hex_len - 1; i >= 0; i--) {
161		nibble = chap_hex2int(hex[i]);
162		if (nibble < 0) {
163			log_warnx("malformed variable, invalid char \"%c\"",
164			    hex[i]);
165			free(bin);
166			return (-1);
167		}
168
169		assert(bin_off < bin_len);
170		if (lo) {
171			bin[bin_off] = nibble;
172			lo = false;
173		} else {
174			bin[bin_off] |= nibble << 4;
175			bin_off--;
176			lo = true;
177		}
178	}
179
180	*binp = bin;
181	*bin_lenp = bin_len;
182	return (0);
183}
184
185#ifdef USE_BASE64
186static char *
187chap_bin2hex(const char *bin, size_t bin_len)
188{
189	unsigned char *b64, *tmp;
190	size_t b64_len;
191
192	b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */
193	b64 = malloc(b64_len);
194	if (b64 == NULL)
195		log_err(1, "malloc");
196
197	tmp = b64;
198	tmp += sprintf(tmp, "0b");
199	b64_ntop(bin, bin_len, tmp, b64_len - 2);
200
201	return (b64);
202}
203#else
204static char *
205chap_bin2hex(const char *bin, size_t bin_len)
206{
207	unsigned char *hex, *tmp, ch;
208	size_t hex_len;
209	size_t i;
210
211	hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
212	hex = malloc(hex_len);
213	if (hex == NULL)
214		log_err(1, "malloc");
215
216	tmp = hex;
217	tmp += sprintf(tmp, "0x");
218	for (i = 0; i < bin_len; i++) {
219		ch = bin[i];
220		tmp += sprintf(tmp, "%02x", ch);
221	}
222
223	return (hex);
224}
225#endif /* !USE_BASE64 */
226
227struct chap *
228chap_new(void)
229{
230	struct chap *chap;
231
232	chap = calloc(1, sizeof(*chap));
233	if (chap == NULL)
234		log_err(1, "calloc");
235
236	/*
237	 * Generate the challenge.
238	 */
239	arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge));
240	arc4random_buf(&chap->chap_id, sizeof(chap->chap_id));
241
242	return (chap);
243}
244
245char *
246chap_get_id(const struct chap *chap)
247{
248	char *chap_i;
249	int ret;
250
251	ret = asprintf(&chap_i, "%d", chap->chap_id);
252	if (ret < 0)
253		log_err(1, "asprintf");
254
255	return (chap_i);
256}
257
258char *
259chap_get_challenge(const struct chap *chap)
260{
261	char *chap_c;
262
263	chap_c = chap_bin2hex(chap->chap_challenge,
264	    sizeof(chap->chap_challenge));
265
266	return (chap_c);
267}
268
269static int
270chap_receive_bin(struct chap *chap, void *response, size_t response_len)
271{
272
273	if (response_len != sizeof(chap->chap_response)) {
274		log_debugx("got CHAP response with invalid length; "
275		    "got %zd, should be %zd",
276		    response_len, sizeof(chap->chap_response));
277		return (1);
278	}
279
280	memcpy(chap->chap_response, response, response_len);
281	return (0);
282}
283
284int
285chap_receive(struct chap *chap, const char *response)
286{
287	void *response_bin;
288	size_t response_bin_len;
289	int error;
290
291	error = chap_hex2bin(response, &response_bin, &response_bin_len);
292	if (error != 0) {
293		log_debugx("got incorrectly encoded CHAP response \"%s\"",
294		    response);
295		return (1);
296	}
297
298	error = chap_receive_bin(chap, response_bin, response_bin_len);
299	free(response_bin);
300
301	return (error);
302}
303
304int
305chap_authenticate(struct chap *chap, const char *secret)
306{
307	char expected_response[CHAP_DIGEST_LEN];
308
309	chap_compute_md5(chap->chap_id, secret,
310	    chap->chap_challenge, sizeof(chap->chap_challenge),
311	    expected_response, sizeof(expected_response));
312
313	if (memcmp(chap->chap_response,
314	    expected_response, sizeof(expected_response)) != 0) {
315		return (-1);
316	}
317
318	return (0);
319}
320
321void
322chap_delete(struct chap *chap)
323{
324
325	free(chap);
326}
327
328struct rchap *
329rchap_new(const char *secret)
330{
331	struct rchap *rchap;
332
333	rchap = calloc(1, sizeof(*rchap));
334	if (rchap == NULL)
335		log_err(1, "calloc");
336
337	rchap->rchap_secret = checked_strdup(secret);
338
339	return (rchap);
340}
341
342static void
343rchap_receive_bin(struct rchap *rchap, const unsigned char id,
344    const void *challenge, size_t challenge_len)
345{
346
347	rchap->rchap_id = id;
348	rchap->rchap_challenge = calloc(challenge_len, 1);
349	if (rchap->rchap_challenge == NULL)
350		log_err(1, "calloc");
351	memcpy(rchap->rchap_challenge, challenge, challenge_len);
352	rchap->rchap_challenge_len = challenge_len;
353}
354
355int
356rchap_receive(struct rchap *rchap, const char *id, const char *challenge)
357{
358	unsigned char id_bin;
359	void *challenge_bin;
360	size_t challenge_bin_len;
361
362	int error;
363
364	id_bin = strtoul(id, NULL, 10);
365
366	error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len);
367	if (error != 0) {
368		log_debugx("got incorrectly encoded CHAP challenge \"%s\"",
369		    challenge);
370		return (1);
371	}
372
373	rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len);
374	free(challenge_bin);
375
376	return (0);
377}
378
379static void
380rchap_get_response_bin(struct rchap *rchap,
381    void **responsep, size_t *response_lenp)
382{
383	void *response_bin;
384	size_t response_bin_len = CHAP_DIGEST_LEN;
385
386	response_bin = calloc(response_bin_len, 1);
387	if (response_bin == NULL)
388		log_err(1, "calloc");
389
390	chap_compute_md5(rchap->rchap_id, rchap->rchap_secret,
391	    rchap->rchap_challenge, rchap->rchap_challenge_len,
392	    response_bin, response_bin_len);
393
394	*responsep = response_bin;
395	*response_lenp = response_bin_len;
396}
397
398char *
399rchap_get_response(struct rchap *rchap)
400{
401	void *response;
402	size_t response_len;
403	char *chap_r;
404
405	rchap_get_response_bin(rchap, &response, &response_len);
406	chap_r = chap_bin2hex(response, response_len);
407	free(response);
408
409	return (chap_r);
410}
411
412void
413rchap_delete(struct rchap *rchap)
414{
415
416	free(rchap->rchap_secret);
417	free(rchap->rchap_challenge);
418	free(rchap);
419}
420