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