xref: /freebsd/lib/libiscsiutil/chap.c (revision e32fecd0c2c3ee37c47ee100f169e7eb0282a873)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 
40 static void
41 chap_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 
56 static int
57 chap_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 
103 static int
104 chap_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  */
129 static int
130 chap_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
186 static char *
187 chap_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
204 static char *
205 chap_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 
227 struct chap *
228 chap_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 
245 char *
246 chap_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 
258 char *
259 chap_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 
269 static int
270 chap_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 
284 int
285 chap_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 
304 int
305 chap_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 
321 void
322 chap_delete(struct chap *chap)
323 {
324 
325 	free(chap);
326 }
327 
328 struct rchap *
329 rchap_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 
342 static void
343 rchap_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 
355 int
356 rchap_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 
379 static void
380 rchap_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 
398 char *
399 rchap_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 
412 void
413 rchap_delete(struct rchap *rchap)
414 {
415 
416 	free(rchap->rchap_secret);
417 	free(rchap->rchap_challenge);
418 	free(rchap);
419 }
420