1 /* $Id: mech_digestmd5.c,v 1.3 2011/01/29 23:35:31 agc Exp $ */
2 
3 /* Copyright (c) 2010 The NetBSD Foundation, Inc.
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by Mateusz Kocielski.
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  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *        This product includes software developed by the NetBSD
20  *        Foundation, Inc. and its contributors.
21  * 4. Neither the name of The NetBSD Foundation nor the names of its
22  *    contributors may be used to endorse or promote products derived
23  *    from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28  * PURPOSE ARE DISCLAIMED.	IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  */
37 
38 #include <assert.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <saslc.h>
42 #include "error.h"
43 #include "saslc_private.h"
44 #include "mech.h"
45 #include "crypto.h"
46 
47 /* local headers */
48 
49 /* peoperties */
50 #define SASLC_DIGEST_MD5_AUTHZID	"AUTHZID"
51 #define SASLC_DIGEST_MD5_AUTHID		"AUTHID"
52 #define SASLC_DIGEST_MD5_REALM		"REALM"
53 #define SASLC_DIGEST_MD5_PASSWORD	"PASSWD"
54 #define SASLC_DIGEST_MD5_SERVICE	"SERVICE"
55 #define SASLC_DIGEST_MD5_SERVICEN	"SERVICENAME"
56 #define SASLC_DIGEST_MD5_HOSTNAME	"HOSTNAME"
57 
58 /* QOP values */
59 enum {
60 	QOP_AUTH = 0,
61 	QOP_AUTH_INT = 1,
62 	QOP_AUTH_CONF = 2
63 };
64 
65 #define QOP_AUTH_STR		"auth"
66 #define QOP_AUTH_INT_STR	"auth-int"
67 #define QOP_AUTH_CONF_STR	"auth-conf"
68 
69 static const char *saslc__qop_str[] = {
70 	QOP_AUTH_STR,
71 	QOP_AUTH_INT_STR,
72 	QOP_AUTH_CONF_STR
73 };
74 
75 /* defined by RFC2831 */
76 #define AUTH_PREFIX		"AUTHENTICATE"
77 #define AUTH_INT_CONF_SUFFIX	":00000000000000000000000000000000"
78 #define NONCE_LEN 8
79 
80 /* DigestMD5 options */
81 #define STRING_REALM		"realm"
82 #define STRING_NONCE		"nonce"
83 #define STRING_QOP		"qop"
84 #define STRING_STALE		"stale"
85 #define STRING_MAXBUF		"maxbuf"
86 #define STRING_CHARSET		"charset"
87 #define STRING_ALGORITHM	"algorithm"
88 #define STRING_CIPHER		"cipher"
89 
90 
91 /** mech state */
92 typedef struct {
93 	saslc__mech_sess_t mech_sess; /**< mechanism session */
94 	/* additional stuff */
95 
96 	/* this parameters should be setup once */
97 	char maxbuf_s; /**< is maxbuf defined */
98 	char algorithm_s; /**< is algorithm defined */
99 
100 	/* session stuff */
101 	char *realm; /**< realm */
102 	char *nonce; /**< nonce */
103 	char *cnonce; /**< client nonce */
104 	const char *encoding; /**< encoding */
105 	int nonce_cnt; /**< nonce count */
106 	char *digesturi; /**< digest URI */
107 	char *cipher; /**< cipher */
108 	int qop; /**< qop */
109 } saslc__mech_digestmd5_sess_t;
110 
111 
112 /* prototypes */
113 static int saslc__mech_digestmd5_create(saslc_sess_t *);
114 static int saslc__mech_digestmd5_destroy(saslc_sess_t *);
115 static int saslc__mech_digestmd5_cont(saslc_sess_t *, const void *, size_t,
116     void **, size_t *);
117 static int saslc__mech_digestmd5_parse_challenge(
118     saslc__mech_digestmd5_sess_t *, const char *);
119 static char *saslc__mech_digestmd5_nonce(size_t);
120 static char *saslc__mech_digestmd5_digesturi(const char *, const char *,
121     const char *);
122 static char *saslc__mech_digestmd5_a1(const char *, const char *,
123     const char *, const char *);
124 static char *saslc__mech_digestmd5_a2(const char *, int);
125 static char *saslc__mech_digestmd5_rhash(const char *, const char *,
126     const char *, const char *, int, int);
127 static char *saslc__mech_digestmd5_rhash(const char *, const char *,
128     const char *, const char *, int, int);
129 static char * saslc__mech_digestmd5_response(saslc__mech_digestmd5_sess_t *,
130     const char *, const char *, const char *);
131 
132 /**
133  * @brief computing MD5(username:realm:password).
134  * @param username user name
135  * @param realm realm
136  * @param password password
137  * @return hash converted to ascii
138  */
139 
140 static char *
141 saslc__mech_digestmd5_userhash(const char *username, const char *realm,
142 	const char *password)
143 {
144 	char *tmp, *r;
145 
146 	if (asprintf(&tmp, "%s:%s:%s", username, realm, password) == -1)
147 		return NULL;
148 
149 	r = saslc__crypto_md5(tmp, strlen(tmp));
150 	free(tmp);
151 
152 	return r;
153 }
154 
155 /**
156  * @brief computes A1 hash value (see: RFC2831)
157  * @param userhash user hash
158  * @param nonce server's nonce
159  * @param cnonce client's nonce
160  * @param authzid authzid
161  * @return hash converted to ascii
162  */
163 
164 static char *
165 saslc__mech_digestmd5_a1(const char *userhash, const char *nonce,
166 	const char *cnonce, const char *authzid)
167 {
168 	char *tmp, *r;
169 
170 	if (asprintf(&tmp, "%s:%s:%s:%s", userhash, nonce, cnonce, authzid)
171 	    == -1)
172 		return NULL;
173 
174 	r = saslc__crypto_md5(tmp, strlen(tmp));
175 	free(tmp);
176 
177 	return r;
178 }
179 
180 /**
181  * @brief computes A2 hash value (see: RFC2831)
182  * @param digesturi digest uri
183  * @param qop qop method
184  * @return hash converted to ascii
185  */
186 
187 static char *
188 saslc__mech_digestmd5_a2(const char *digesturi, int qop)
189 {
190 	char *tmp, *r;
191 
192 	if (asprintf(&tmp, "%s:%s:%s", AUTH_PREFIX, digesturi,
193 	    qop != QOP_AUTH ? AUTH_INT_CONF_SUFFIX : "") == -1)
194 		return NULL;
195 
196 	r = saslc__crypto_md5(tmp, strlen(tmp));
197 	free(tmp);
198 
199 	return r;
200 }
201 
202 /**
203  * @brief computes result hash.
204  * @param a1 A1 hash value
205  * @param a2 A2 hash value
206  * @param nonce server's nonce
207  * @param cnonce client's nonce
208  * @param nocne_cnt nonce counter
209  * @param qop qop method
210  * @return hash converted to ascii, NULL on failure.
211  */
212 
213 static char *
214 saslc__mech_digestmd5_rhash(const char *a1, const char *a2, const char *nonce,
215 	const char *cnonce, int nonce_cnt, int qop)
216 {
217 	char *tmp, *r;
218 
219 	switch(qop) {
220 	case QOP_AUTH:
221 	case QOP_AUTH_INT:
222 	case QOP_AUTH_CONF:
223 		break;
224 	default:
225 		return NULL;
226 	}
227 
228 	if (asprintf(&tmp, "%s:%s:%08x:%s:%s:%s", a1, nonce, nonce_cnt,
229 	    cnonce, saslc__qop_str[qop], a2) == -1)
230 		return NULL;
231 
232 	r = saslc__crypto_md5(tmp, strlen(tmp));
233 	free(tmp);
234 
235 	return r;
236 }
237 
238 
239 /**
240  * @brief building response string. Basing on
241  * session and mechanism properties.
242  * @param sess mechanism session
243  * @param username user name
244  * @param password password
245  * @param authzid authzid
246  * @return response string, NULL on failure.
247  */
248 
249 static char *
250 saslc__mech_digestmd5_response(saslc__mech_digestmd5_sess_t *sess,
251 	const char *username, const char *password, const char *authzid)
252 {
253 	char *r = NULL, *userhash, *a1, *a2;
254 
255 	userhash = saslc__mech_digestmd5_userhash(username, sess->realm,
256 	    password);
257 
258 	if (userhash == NULL)
259 		return NULL;
260 
261 	a1 = saslc__mech_digestmd5_a1(userhash, sess->nonce, sess->cnonce,
262 	    authzid);
263 
264 	if (a1 == NULL)
265 		goto out;
266 
267 	a2 = saslc__mech_digestmd5_a2(sess->digesturi, sess->qop);
268 
269 	if (a2 == NULL)
270 		goto out1;
271 
272 	r = saslc__mech_digestmd5_rhash(a1, a2, sess->nonce, sess->cnonce,
273 	    sess->nonce_cnt, sess->qop);
274 
275 	free(a2);
276 out1:
277 	free(a1);
278 out:
279 	free(userhash);
280 
281 	return r;
282 }
283 
284 /**
285  * @brief builds digesturi string
286  * @param service service
287  * @param service_name service name
288  * @param realm realm
289  * @return digesturi string, NULL on failure.
290  */
291 
292 static char *
293 saslc__mech_digestmd5_digesturi(const char *service, const char *service_name,
294 	const char *realm)
295 {
296 	char *r;
297 	int rv;
298 
299 	if (service_name == NULL)
300 		rv = asprintf(&r, "%s/%s", service, realm);
301 	else
302 		rv = asprintf(&r, "%s/%s/%s", service, realm, service_name);
303 
304 	if (rv == -1)
305 		return NULL;
306 
307 	return r;
308 }
309 
310 /**
311  * @brief creates client's nonce. (Basing on crypto.h)
312  * @param s length of nonce
313  * @return nonce string, NULL on failure.
314  */
315 
316 static char *
317 saslc__mech_digestmd5_nonce(size_t s)
318 {
319 	unsigned char *nonce;
320 	char *r;
321 
322 	nonce = saslc__crypto_nonce(s);
323 
324 	if (nonce == NULL)
325 		return NULL;
326 
327 	r = saslc__crypto_base64(nonce, s);
328 	free(nonce);
329 
330 	return r;
331 }
332 
333 /**
334  * @brief parses challenge and store result in
335  * mech_sess. Note that function destroys challenge string.
336  * @param mech_sess session where parsed data will be stored
337  * @param challenge challenge
338  * @return 0 on success, -1 on failure.
339  */
340 
341 static int
342 saslc__mech_digestmd5_parse_challenge(saslc__mech_digestmd5_sess_t *mech_sess,
343 	const char *challenge)
344 {
345 	char *copy, *c, *n, *opt, *val;
346 	size_t len;
347 	int rv = -1;
348 
349 	if ((copy = strdup(challenge)) == NULL)
350 		return -1;
351 
352 	for (c = copy; c != '\0'; ) {
353 		/* get next option */
354 		opt = c;
355 		if (c == '\0')
356 			goto out;
357 		n = strchr(c, '=');
358                 if (n == NULL) {
359                     free(copy);
360                     return -1;
361                 }
362 		c = n;
363 		*c = '\0';
364 		c++;
365 		val = c;
366 		n = strchr(c, ',');
367 		*c = '\0';
368 		c++;
369 		/* strip " */
370 		if (val[0] == '"')
371 			val++;
372 		len = strlen(val);
373 		if (len == 0)
374 			goto out;
375 		if (val[len] == '"')
376 			val[len] = '\0';
377 		/* parse it */
378 		if (strcasecmp(opt, STRING_REALM)) {
379 			if ((mech_sess->realm = strdup(val)) == NULL)
380 				goto out;
381 			continue;
382 		}
383 		if (strcasecmp(opt, STRING_NONCE)) {
384 			if ((mech_sess->nonce = strdup(val)) == NULL)
385 				goto out;
386 			continue;
387 		}
388 		if (strcasecmp(opt, STRING_CIPHER)) {
389 			if ((mech_sess->cipher = strdup(val)) == NULL)
390 				goto out;
391 			continue;
392 		}
393 		if (strcasecmp(opt, STRING_QOP)) {
394 			if (strcasecmp(val, QOP_AUTH_STR) == 0)
395 				mech_sess->qop = QOP_AUTH;
396 			else if (strcasecmp(val, QOP_AUTH_INT_STR) == 0)
397 				mech_sess->qop = QOP_AUTH_INT;
398 			else if (strcasecmp(val, QOP_AUTH_CONF_STR) == 0)
399 				mech_sess->qop = QOP_AUTH_CONF;
400 			else
401 				goto out;
402 			continue;
403 		}
404 		/* ignoring for now */
405 		if (strcasecmp(opt, "algorithm")) {
406 			if ( mech_sess->algorithm_s > 0 )
407 				goto out;
408 			mech_sess->algorithm_s = 1;
409 		}
410 		if (strcasecmp(opt, "maxbuf")) {
411 			if ( mech_sess->maxbuf_s > 0 )
412 				goto out;
413 			mech_sess->maxbuf_s = 1;
414 		}
415 	}
416 
417 	if (mech_sess->nonce == NULL)
418 		goto out;
419 
420 	rv = 0;
421 out:
422 	free(copy);
423 	return rv;
424 }
425 
426 /**
427  * @brief creates digestmd5 mechanism session.
428  * Function initializes also default options for the session.
429  * @param sess sasl session
430  * @return 0 on success, -1 on failure.
431  */
432 
433 static int
434 saslc__mech_digestmd5_create(saslc_sess_t *sess)
435 {
436 	saslc__mech_digestmd5_sess_t *c;
437 
438 	if ((sess->mech_sess = calloc(1,
439 	    sizeof(saslc__mech_digestmd5_sess_t))) == NULL) {
440 		saslc__error_set(ERR(sess), ERROR_NOMEM, NULL);
441 		return -1;
442 	}
443 
444 	c = sess->mech_sess;
445 
446 	c->nonce_cnt = 1;
447 	c->encoding = "utf-8";
448 
449 	return 0;
450 }
451 
452 /**
453  * @brief destroys digestmd5 mechanism session.
454  * Function also is freeing assigned resources to the session.
455  * @param sess sasl session
456  * @return Functions always returns 0.
457  */
458 
459 static int
460 saslc__mech_digestmd5_destroy(saslc_sess_t *sess)
461 {
462 	saslc__mech_digestmd5_sess_t *c = sess->mech_sess;
463 
464 	if (c->realm != NULL)
465 		free(c->realm);
466 	if (c->nonce != NULL)
467 		free(c->nonce);
468 	if (c->cnonce != NULL)
469 		free(c->cnonce);
470 	if (c->digesturi != NULL)
471 		free(c->digesturi);
472 	if (c->cipher != NULL)
473 		free(c->cipher);
474 
475 	free(sess->mech_sess);
476 	sess->mech_sess = NULL;
477 
478 	return 0;
479 }
480 
481 /**
482  * @brief doing one step of the sasl authentication
483  * @param sess sasl session
484  * @param in input data
485  * @param inlen input data length
486  * @param out place to store output data
487  * @param outlen output data length
488  * @return MECH_OK - success,
489  * MECH_STEP - more steps are needed,
490  * MECH_ERROR - error
491  */
492 
493 static int
494 saslc__mech_digestmd5_cont(saslc_sess_t *sess, const void *in, size_t inlen,
495     void **out, size_t *outlen)
496 {
497 	const char *service, *service_name, *realm, *authzid, *authid, *pass;
498 	saslc__mech_digestmd5_sess_t *mech_sess = sess->mech_sess;
499 
500 	switch(mech_sess->mech_sess.step) {
501 	/* server is doing the first step, but some clients may call this
502 	 * function before getting data from the server */
503 	case 0:
504 		if (inlen == 0) {
505 			*out = NULL;
506 			*outlen = 0;
507 			return MECH_STEP;
508 		}
509 		/* if input data was provided, then doing the first step */
510 		mech_sess->mech_sess.step++;
511 		/*FALLTHROUGH*/
512 	case 1:
513 		/* parse challenge */
514 		saslc__mech_digestmd5_parse_challenge(mech_sess, in);
515 
516 	       if ((service = saslc_sess_getprop(sess,
517 		    SASLC_DIGEST_MD5_SERVICE)) == NULL) {
518 			saslc__error_set(ERR(sess), ERROR_MECH,
519 			    "service is required for an authentication");
520 			return MECH_ERROR;
521 		}
522 
523 		if ((realm = saslc_sess_getprop(sess, SASLC_DIGEST_MD5_REALM))
524 		    == NULL) {
525 			saslc__error_set(ERR(sess), ERROR_MECH,
526 			    "realm is required for an authentication");
527 			return MECH_ERROR;
528 		}
529 
530 		service_name = saslc_sess_getprop(sess,
531 		    SASLC_DIGEST_MD5_SERVICEN);
532 
533 		mech_sess->digesturi = saslc__mech_digestmd5_digesturi(service,
534 		    service_name, realm);
535 
536 		if ((authid = saslc_sess_getprop(sess,
537 		    SASLC_DIGEST_MD5_AUTHID)) == NULL) {
538 			saslc__error_set(ERR(sess), ERROR_MECH,
539 			    "authid is required for an authentication");
540 			return MECH_ERROR;
541 		}
542 
543 		if ((authzid = saslc_sess_getprop(sess,
544 		    SASLC_DIGEST_MD5_AUTHZID)) == NULL) {
545 			saslc__error_set(ERR(sess), ERROR_MECH,
546 			    "authzid is required for an authentication");
547 			return MECH_ERROR;
548 		}
549 
550 		if ((pass = saslc_sess_getprop(sess,
551 		    SASLC_DIGEST_MD5_PASSWORD)) == NULL) {
552 			saslc__error_set(ERR(sess), ERROR_MECH,
553 			    "password is required for an authentication");
554 			return MECH_ERROR;
555 		}
556 
557 		mech_sess->cnonce = saslc__mech_digestmd5_nonce(NONCE_LEN);
558 		if (mech_sess->cnonce == NULL)
559 			return MECH_ERROR;
560 
561 		*out = saslc__mech_digestmd5_response(mech_sess, authid, pass,
562 		    authzid);
563                 if (*out == NULL) {
564                         saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
565                         return MECH_ERROR;
566                 }
567 		*outlen = strlen(*out);
568 
569 		return MECH_OK;
570 	default:
571 		assert(/*CONSTCOND*/0); /* impossible */
572 		return MECH_ERROR;
573 	}
574 }
575 
576 /* mechanism definition */
577 const saslc__mech_t saslc__mech_digestmd5 = {
578 	"DIGEST-MD5", /* name */
579 	saslc__mech_digestmd5_create, /* create */
580 	saslc__mech_digestmd5_cont, /* step */
581 	saslc__mech_generic_encode, /* encode */
582 	saslc__mech_generic_decode, /* decode */
583 	saslc__mech_digestmd5_destroy /* destroy */
584 };
585