1 /* $NetBSD: mech_digestmd5.c,v 1.11 2013/06/28 15:04:35 joerg 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 #include <sys/cdefs.h>
38 __RCSID("$NetBSD: mech_digestmd5.c,v 1.11 2013/06/28 15:04:35 joerg Exp $");
39
40 #include <sys/param.h>
41
42 #include <assert.h>
43 #include <ctype.h>
44 #include <md5.h>
45 #include <saslc.h>
46 #include <stdbool.h>
47 #include <stdio.h>
48 #include <string.h>
49
50 #include <openssl/evp.h>
51
52 #include "buffer.h"
53 #include "crypto.h"
54 #include "error.h"
55 #include "list.h"
56 #include "mech.h"
57 #include "msg.h"
58 #include "saslc_private.h"
59
60 /* See RFC 2831. */
61
62 /*
63 * TODO:
64 *
65 * 1) Add support for Subsequent Authentication (see RFC 2831 section 2.2).
66 */
67
68 /* properties */
69 #define SASLC_DIGESTMD5_AUTHCID SASLC_PROP_AUTHCID
70 #define SASLC_DIGESTMD5_AUTHZID SASLC_PROP_AUTHZID
71 #define SASLC_DIGESTMD5_CIPHERMASK SASLC_PROP_CIPHERMASK
72 #define SASLC_DIGESTMD5_HOSTNAME SASLC_PROP_HOSTNAME
73 #define SASLC_DIGESTMD5_MAXBUF SASLC_PROP_MAXBUF
74 #define SASLC_DIGESTMD5_PASSWD SASLC_PROP_PASSWD
75 #define SASLC_DIGESTMD5_QOPMASK SASLC_PROP_QOPMASK
76 #define SASLC_DIGESTMD5_REALM SASLC_PROP_REALM
77 #define SASLC_DIGESTMD5_SERVICE SASLC_PROP_SERVICE
78 #define SASLC_DIGESTMD5_SERVNAME SASLC_PROP_SERVNAME
79 /*
80 * XXX: define this if you want to be able to set a fixed cnonce for
81 * debugging purposes.
82 */
83 #define SASLC_DIGESTMD5_CNONCE "CNONCE"
84 /*
85 * XXX: define this if you want to test the saslc_sess_encode() and
86 * saslc_sess_decode() routines against themselves, i.e., have them
87 * use the same key.
88 */
89 #define SASLC_DIGESTMD5_SELFTEST "SELFTEST"
90
91 #define DEFAULT_QOP_MASK (F_QOP_NONE | F_QOP_INT | F_QOP_CONF)
92 #define DEFAULT_CIPHER_MASK (F_CIPHER_DES | F_CIPHER_3DES | \
93 F_CIPHER_RC4 | F_CIPHER_RC4_40 | \
94 F_CIPHER_RC4_56 | F_CIPHER_AES)
95
96 #define DEFAULT_MAXBUF 0x10000
97 #define MAX_MAXBUF 0xffffff
98 #define INVALID_MAXBUF(m) ((m) <= sizeof(md5hash_t) && (m) > MAX_MAXBUF)
99
100 #define NONCE_LEN 33 /* Minimum recommended length is 64bits (rfc2831).
101 cyrus-sasl uses 33 bytes. */
102
103 typedef enum {
104 CHALLENGE_IGNORE = -1, /* must be -1 */
105 CHALLENGE_REALM = 0,
106 CHALLENGE_NONCE = 1,
107 CHALLENGE_QOP = 2,
108 CHALLENGE_STALE = 3,
109 CHALLENGE_MAXBUF = 4,
110 CHALLENGE_CHARSET = 5,
111 CHALLENGE_ALGORITHM = 6,
112 CHALLENGE_CIPHER = 7
113 } challenge_t;
114
115 typedef enum {
116 /*
117 * NB: Values used to index cipher_tbl[] and cipher_ctx_tbl[]
118 * in cipher_context_create().
119 */
120 CIPHER_DES = 0,
121 CIPHER_3DES = 1,
122 CIPHER_RC4 = 2,
123 CIPHER_RC4_40 = 3,
124 CIPHER_RC4_56 = 4,
125 CIPHER_AES = 5
126 } cipher_t;
127
128 #define F_CIPHER_DES (1 << CIPHER_DES)
129 #define F_CIPHER_3DES (1 << CIPHER_3DES)
130 #define F_CIPHER_RC4 (1 << CIPHER_RC4)
131 #define F_CIPHER_RC4_40 (1 << CIPHER_RC4_40)
132 #define F_CIPHER_RC4_56 (1 << CIPHER_RC4_56)
133 #define F_CIPHER_AES (1 << CIPHER_AES)
134
135 static const named_flag_t cipher_tbl[] = {
136 /* NB: to be indexed by cipher_t values */
137 { "des", F_CIPHER_DES },
138 { "3des", F_CIPHER_3DES },
139 { "rc4", F_CIPHER_RC4 },
140 { "rc4-40", F_CIPHER_RC4_40 },
141 { "rc4-56", F_CIPHER_RC4_56 },
142 { "aes", F_CIPHER_AES },
143 { NULL, 0 }
144 };
145
146 static inline const char *
cipher_name(cipher_t cipher)147 cipher_name(cipher_t cipher)
148 {
149
150 assert(cipher < __arraycount(cipher_tbl) - 1); /* NULL terminated */
151 if (cipher < __arraycount(cipher_tbl) - 1)
152 return cipher_tbl[cipher].name;
153 return NULL;
154 }
155
156 static inline unsigned int
cipher_list_flags(list_t * list)157 cipher_list_flags(list_t *list)
158 {
159
160 return saslc__list_flags(list, cipher_tbl);
161 }
162
163 typedef struct { /* data parsed from challenge */
164 bool utf8;
165 bool algorithm;
166 bool stale;
167 char * nonce;
168 list_t * realm;
169 uint32_t cipher_flags;
170 uint32_t qop_flags;
171 size_t maxbuf;
172 } cdata_t;
173
174 typedef struct { /* response data */
175 /* NB: the qop is in saslc__mech_sess_t */
176 char *authcid;
177 char *authzid;
178 char *cnonce;
179 char *digesturi;
180 char *passwd;
181 char *realm;
182 cipher_t cipher;
183 int nonce_cnt;
184 size_t maxbuf;
185 } rdata_t;
186
187 typedef uint8_t md5hash_t[MD5_DIGEST_LENGTH];
188
189 typedef struct {
190 md5hash_t kic; /* client->server integrity key */
191 md5hash_t kis; /* server->client integrity key */
192 md5hash_t kcc; /* client->server confidentiality key */
193 md5hash_t kcs; /* server->client confidentiality key */
194 } keys_t;
195
196 typedef struct cipher_context_t {
197 size_t blksize; /* block size for cipher */
198 EVP_CIPHER_CTX *evp_ctx; /* openssl EVP context */
199 } cipher_context_t;
200
201 typedef struct coder_context_t {
202 uint8_t *key; /* key for coding */
203 uint32_t seqnum; /* 4 byte sequence number */
204
205 void *buf_ctx; /* buffer context */
206 cipher_context_t *cph_ctx; /* cipher context */
207 saslc_sess_t *sess; /* session: for error setting */
208 } coder_context_t;
209
210 /* mech state */
211 typedef struct {
212 saslc__mech_sess_t mech_sess; /* must be first */
213 cdata_t cdata; /* data parsed from challenge string */
214 rdata_t rdata; /* data used for response string */
215 keys_t keys; /* keys */
216 coder_context_t dec_ctx; /* decode context */
217 coder_context_t enc_ctx; /* encode context */
218 } saslc__mech_digestmd5_sess_t;
219
220 /**
221 * @brief if possible convert a UTF-8 string to a ISO8859-1 string.
222 * @param utf8 original UTF-8 string.
223 * @param iso8859 pointer to pointer to the malloced ISO8859-1 string.
224 * @return -1 if the string cannot be translated.
225 *
226 * NOTE: this allocates memory for its output and the caller is
227 * responsible for freeing it.
228 */
229 static int
utf8_to_8859_1(char * utf8,char ** iso8859)230 utf8_to_8859_1(char *utf8, char **iso8859)
231 {
232 unsigned char *s, *d, *end, *src;
233 size_t cnt;
234
235 src = (unsigned char *)utf8;
236 cnt = 0;
237 end = src + strlen(utf8);
238 for (s = src; s < end; ++s) {
239 if (*s > 0xC3) /* abort if outside 8859-1 range */
240 return -1;
241 /*
242 * Look for valid 2 byte UTF-8 encoding with, 8 bits
243 * of info. Quit if invalid pair found.
244 */
245 if (*s >= 0xC0 && *s <= 0xC3) { /* 2 bytes, 8 bits */
246 if (++s == end || *s < 0x80 || *s > 0xBF)
247 return -1; /* broken utf-8 encoding */
248 }
249 cnt++;
250 }
251
252 /* Allocate adequate space. */
253 d = malloc(cnt + 1);
254 if (d == NULL)
255 return -1;
256
257 *iso8859 = (char *)d;
258
259 /* convert to 8859-1 */
260 do {
261 for (s = src; s < end && *s < 0xC0; ++s)
262 *d++ = *s;
263 if (s + 1 >= end)
264 break;
265 *d++ = ((s[0] & 0x3) << 6) | (s[1] & 0x3f);
266 src = s + 2;
267 } while (src < end);
268
269 *d = '\0';
270 return 0;
271 }
272
273 /**
274 * @brief unquote a string by removing escapes.
275 * @param str string to unquote.
276 * @return NULL on failure
277 *
278 * NOTE: this allocates memory for its output and the caller is
279 * responsible for freeing it.
280 */
281 static char *
unq(const char * str)282 unq(const char *str)
283 {
284 const char *s;
285 char *unq_str, *d;
286 int escaped;
287
288 unq_str = malloc(strlen(str) + 1);
289 if (unq_str == NULL)
290 return NULL;
291
292 escaped = 0;
293 d = unq_str;
294 for (s = str; *s != '\0'; s++) {
295 switch (*s) {
296 case '\\':
297 if (escaped)
298 *d++ = *s;
299 escaped = !escaped;
300 break;
301 default:
302 *d++ = *s;
303 escaped = 0;
304 }
305 }
306 *d = '\0';
307
308 return unq_str;
309 }
310
311 /**
312 * @brief computing MD5(username:realm:password).
313 * @param ms mechanism session
314 * @param buf buffer for hash
315 * @return 0 on success, -1 on failure
316 */
317 static int
saslc__mech_digestmd5_userhash(saslc__mech_digestmd5_sess_t * ms,uint8_t * buf)318 saslc__mech_digestmd5_userhash(saslc__mech_digestmd5_sess_t *ms, uint8_t *buf)
319 {
320 char *tmp;
321 char *unq_username, *unq_realm;
322 ssize_t len;
323
324 if ((unq_username = unq(ms->rdata.authcid)) == NULL)
325 return -1;
326
327 /********************************************************/
328 /* RFC 2831 section 2.1.2 */
329 /* ... If the directive is missing, "realm-value" will */
330 /* set to the empty string when computing A1. */
331 /********************************************************/
332 if (ms->rdata.realm == NULL)
333 unq_realm = strdup("");
334 else
335 unq_realm = unq(ms->rdata.realm);
336
337 if (unq_realm == NULL) {
338 free(unq_username);
339 return -1;
340 }
341 len = asprintf(&tmp, "%s:%s:%s",
342 unq_username, unq_realm, ms->rdata.passwd);
343 free(unq_realm);
344 free(unq_username);
345
346 if (len == -1)
347 return -1;
348
349 saslc__crypto_md5_hash(tmp, (size_t)len, buf);
350 memset(tmp, 0, (size_t)len);
351 free(tmp);
352 return 0;
353 }
354
355 /**
356 * @brief setup the appropriate QOP keys as determined by the chosen
357 * QOP type (see RFC2831 sections 2.3 and 2.4).
358 * @param ms mechanism session
359 * @param a1hash MD5(a1)
360 * @return 0 on success, -1 on failure
361 */
362 static int
setup_qop_keys(saslc__mech_digestmd5_sess_t * ms,md5hash_t a1hash)363 setup_qop_keys(saslc__mech_digestmd5_sess_t *ms, md5hash_t a1hash)
364 {
365 #define KIC_MAGIC "Digest session key to client-to-server signing key magic constant"
366 #define KIS_MAGIC "Digest session key to server-to-client signing key magic constant"
367 #define KCC_MAGIC "Digest H(A1) to client-to-server sealing key magic constant"
368 #define KCS_MAGIC "Digest H(A1) to server-to-client sealing key magic constant"
369 #define KIC_MAGIC_LEN (sizeof(KIC_MAGIC) - 1)
370 #define KIS_MAGIC_LEN (sizeof(KIS_MAGIC) - 1)
371 #define KCC_MAGIC_LEN (sizeof(KCC_MAGIC) - 1)
372 #define KCS_MAGIC_LEN (sizeof(KCS_MAGIC) - 1)
373 #define MAX_MAGIC_LEN KIC_MAGIC_LEN
374
375 char buf[MD5_DIGEST_LENGTH + MAX_MAGIC_LEN];
376 size_t buflen;
377 size_t n;
378
379 switch (ms->mech_sess.qop) {
380 case QOP_NONE:
381 /* nothing to do */
382 break;
383
384 case QOP_CONF:
385 /*************************************************************************/
386 /* See RFC2831 section 2.4 (Confidentiality Protection) */
387 /* */
388 /* The key for confidentiality protecting messages from client to server */
389 /* is: */
390 /* */
391 /* Kcc = MD5({H(A1)[0..n], */
392 /* "Digest H(A1) to client-to-server sealing key magic constant"}) */
393 /* */
394 /* The key for confidentiality protecting messages from server to client */
395 /* is: */
396 /* */
397 /* Kcs = MD5({H(A1)[0..n], */
398 /* "Digest H(A1) to server-to-client sealing key magic constant"}) */
399 /* */
400 /* where MD5 is as specified in [RFC 1321]. For cipher "rc4-40" n is 5; */
401 /* for "rc4-56" n is 7; for the rest n is 16. */
402 /*************************************************************************/
403
404 switch (ms->rdata.cipher) {
405 case CIPHER_RC4_40: n = 5; break;
406 case CIPHER_RC4_56: n = 7; break;
407 default: n = MD5_DIGEST_LENGTH; break;
408 }
409 memcpy(buf, a1hash, n);
410
411 memcpy(buf + n, KCC_MAGIC, KCC_MAGIC_LEN);
412 buflen = n + KCC_MAGIC_LEN;
413 saslc__crypto_md5_hash(buf, buflen, ms->keys.kcc);
414
415 memcpy(buf + n, KCS_MAGIC, KCS_MAGIC_LEN);
416 buflen = n + KCS_MAGIC_LEN;
417 saslc__crypto_md5_hash(buf, buflen, ms->keys.kcs);
418
419 /*FALLTHROUGH*/
420
421 case QOP_INT:
422 /*************************************************************************/
423 /* See RFC2831 section 2.3 (Integrity Protection) */
424 /* The key for integrity protecting messages from client to server is: */
425 /* */
426 /* Kic = MD5({H(A1), */
427 /* "Digest session key to client-to-server signing key magic constant"}) */
428 /* */
429 /* The key for integrity protecting messages from server to client is: */
430 /* */
431 /* Kis = MD5({H(A1), */
432 /* "Digest session key to server-to-client signing key magic constant"}) */
433 /*************************************************************************/
434 memcpy(buf, a1hash, MD5_DIGEST_LENGTH);
435
436 memcpy(buf + MD5_DIGEST_LENGTH, KIC_MAGIC, KIC_MAGIC_LEN);
437 buflen = MD5_DIGEST_LENGTH + KIC_MAGIC_LEN;
438 saslc__crypto_md5_hash(buf, buflen, ms->keys.kic);
439
440 memcpy(buf + MD5_DIGEST_LENGTH, KIS_MAGIC, KIS_MAGIC_LEN);
441 buflen = MD5_DIGEST_LENGTH + KIS_MAGIC_LEN;
442 saslc__crypto_md5_hash(buf, buflen, ms->keys.kis);
443 break;
444 }
445 return 0;
446
447 #undef KIC_MAGIC
448 #undef KIS_MAGIC
449 #undef KCC_MAGIC
450 #undef KCS_MAGIC
451 #undef KIC_MAGIC_LEN
452 #undef KIS_MAGIC_LEN
453 #undef KCC_MAGIC_LEN
454 #undef KCS_MAGIC_LEN
455 #undef MAX_MAGIC_LEN
456 }
457
458 /**
459 * @brief computes A1 hash value (see: RFC2831)
460 * @param ms mechanism session
461 * @return hash in hex form
462 */
463 static char *
saslc__mech_digestmd5_a1(saslc__mech_digestmd5_sess_t * ms)464 saslc__mech_digestmd5_a1(saslc__mech_digestmd5_sess_t *ms)
465 {
466 char *tmp1, *tmp2, *r;
467 char *unq_authzid;
468 md5hash_t a1hash, userhash;
469 int plen;
470 size_t len;
471 /*****************************************************************************/
472 /* If authzid is specified, then A1 is */
473 /* */
474 /* A1 = { H({ unq(username-value), ":", unq(realm-value), ":", passwd }), */
475 /* ":", nonce-value, ":", cnonce-value, ":", unq(authzid-value) } */
476 /* */
477 /* If authzid is not specified, then A1 is */
478 /* */
479 /* A1 = { H({ unq(username-value), ":", unq(realm-value), ":", passwd }), */
480 /* ":", nonce-value, ":", cnonce-value } */
481 /*****************************************************************************/
482
483 if (saslc__mech_digestmd5_userhash(ms, userhash) == -1)
484 return NULL;
485
486 if (ms->rdata.authzid == NULL)
487 plen = asprintf(&tmp1, ":%s:%s",
488 ms->cdata.nonce, ms->rdata.cnonce);
489 else {
490 if ((unq_authzid = unq(ms->rdata.authzid)) == NULL)
491 return NULL;
492
493 plen = asprintf(&tmp1, ":%s:%s:%s",
494 ms->cdata.nonce, ms->rdata.cnonce, unq_authzid);
495 free(unq_authzid);
496 }
497 if (plen == -1)
498 return NULL;
499 len = plen;
500
501 tmp2 = malloc(MD5_DIGEST_LENGTH + len);
502 if (tmp2 == NULL) {
503 free(tmp1);
504 return NULL;
505 }
506 memcpy(tmp2, userhash, MD5_DIGEST_LENGTH);
507 memcpy(tmp2 + MD5_DIGEST_LENGTH, tmp1, len);
508 free(tmp1);
509
510 saslc__crypto_md5_hash(tmp2, MD5_DIGEST_LENGTH + len, a1hash);
511 free(tmp2);
512
513 r = saslc__crypto_hash_to_hex(a1hash);
514 setup_qop_keys(ms, a1hash);
515 return r;
516 }
517
518 /**
519 * @brief computes A2 hash value (see: RFC2831)
520 * @param ms mechanism session
521 * @param method string indicating method "AUTHENTICATE" or ""
522 * @return hash converted to ascii
523 */
524 static char *
saslc__mech_digestmd5_a2(saslc__mech_digestmd5_sess_t * ms,const char * method)525 saslc__mech_digestmd5_a2(saslc__mech_digestmd5_sess_t *ms,
526 const char *method)
527 {
528 char *tmp, *r;
529 int rval;
530 /*****************************************************************/
531 /* If the "qop" directive's value is "auth", then A2 is: */
532 /* */
533 /* A2 = { "AUTHENTICATE:", digest-uri-value } */
534 /* */
535 /* If the "qop" value is "auth-int" or "auth-conf" then A2 is: */
536 /* */
537 /* A2 = { "AUTHENTICATE:", digest-uri-value, */
538 /* ":00000000000000000000000000000000" } */
539 /*****************************************************************/
540
541 rval = -1;
542 switch(ms->mech_sess.qop) {
543 case QOP_NONE:
544 rval = asprintf(&tmp, "%s:%s", method,
545 ms->rdata.digesturi);
546 break;
547 case QOP_INT:
548 case QOP_CONF:
549 rval = asprintf(&tmp,
550 "%s:%s:00000000000000000000000000000000",
551 method, ms->rdata.digesturi);
552 break;
553 }
554 if (rval == -1)
555 return NULL;
556
557 r = saslc__crypto_md5_hex(tmp, strlen(tmp));
558 free(tmp);
559 return r;
560 }
561
562 /**
563 * @brief computes result hash.
564 * @param ms mechanism session
565 * @param a1 A1 hash value
566 * @param a2 A2 hash value
567 * @return hash converted to ascii, NULL on failure.
568 */
569 static char *
saslc__mech_digestmd5_rhash(saslc__mech_digestmd5_sess_t * ms,const char * a1,const char * a2)570 saslc__mech_digestmd5_rhash(saslc__mech_digestmd5_sess_t *ms,
571 const char *a1, const char *a2)
572 {
573 char *tmp, *r;
574 /******************************************************************/
575 /* response-value = */
576 /* HEX( KD ( HEX(H(A1)), */
577 /* { nonce-value, ":" nc-value, ":", */
578 /* cnonce-value, ":", qop-value, ":", HEX(H(A2)) })) */
579 /******************************************************************/
580
581 if (asprintf(&tmp, "%s:%s:%08x:%s:%s:%s", a1, ms->cdata.nonce,
582 ms->rdata.nonce_cnt, ms->rdata.cnonce,
583 saslc__mech_qop_name(ms->mech_sess.qop), a2)
584 == -1)
585 return NULL;
586
587 r = saslc__crypto_md5_hex(tmp, strlen(tmp));
588 free(tmp);
589 return r;
590 }
591
592 /**
593 * @brief building response string. Basing on
594 * session and mechanism properties.
595 * @param ms mechanism session
596 * @param method string indicating method: "AUTHENTICATE" or ""
597 * @return response string, NULL on failure.
598 */
599 static char *
saslc__mech_digestmd5_response(saslc__mech_digestmd5_sess_t * ms,const char * method)600 saslc__mech_digestmd5_response(saslc__mech_digestmd5_sess_t *ms,
601 const char *method)
602 {
603 char *r, *a1, *a2;
604
605 /******************************************************************/
606 /* charset = "charset" "=" "utf-8" */
607 /* */
608 /* This directive, if present, specifies that the client has used */
609 /* UTF-8 [UTF-8] encoding for the username, realm and */
610 /* password. If present, the username, realm and password are in */
611 /* Unicode, prepared using the "SASLPrep" profile [SASLPrep] of */
612 /* the "stringprep" algorithm [StringPrep] and than encoded as */
613 /* UTF-8 [UTF-8]. If not present, the username and password must */
614 /* be encoded in ISO 8859-1 [ISO-8859] (of which US-ASCII */
615 /* [USASCII] is a subset). The client should send this directive */
616 /* only if the server has indicated it supports UTF-8 */
617 /* [UTF-8]. The directive is needed for backwards compatibility */
618 /* with HTTP Digest, which only supports ISO 8859-1. */
619 /******************************************************************/
620 /*
621 * NOTE: We don't set charset in the response, so this is not
622 * an issue here. However, see the note in stringprep_realms()
623 * which is called when processing the challenge.
624 */
625 /******************************************************************/
626 /* response-value = */
627 /* HEX( KD ( HEX(H(A1)), */
628 /* { nonce-value, ":" nc-value, ":", */
629 /* cnonce-value, ":", qop-value, ":", HEX(H(A2)) })) */
630 /******************************************************************/
631
632 r = NULL;
633
634 a1 = saslc__mech_digestmd5_a1(ms);
635 if (a1 == NULL)
636 return NULL;
637
638 a2 = saslc__mech_digestmd5_a2(ms, method);
639 if (a2 != NULL) {
640 r = saslc__mech_digestmd5_rhash(ms, a1, a2);
641 free(a2);
642 }
643 free(a1);
644 return r;
645 }
646
647 /**
648 * @brief Choose a string from a user provided host qualified list,
649 * i.e., a comma delimited list with possible hostname qualifiers on
650 * the elements.
651 * @param hqlist a comma delimited list with entries of the form
652 * "[hostname:]string".
653 * @param hostname the hostname to use in the selection.
654 * @param rval pointer to location for returned string. Set to NULL
655 * if none found, otherwise set to strdup(3) of the string found.
656 * @return 0 on success, -1 on failure (no memory).
657 *
658 * NOTE: hqlist and rval must not be NULL.
659 * NOTE: this allocates memory for its output and the caller is
660 * responsible for freeing it.
661 */
662 static int
choose_from_hqlist(const char * hqlist,const char * hostname,char ** rval)663 choose_from_hqlist(const char *hqlist, const char *hostname, char **rval)
664 {
665 list_t *l, *list;
666 size_t len;
667 char *p;
668
669 if (saslc__list_parse(&list, hqlist) == -1)
670 return -1; /* no memory */
671
672 /*
673 * If the user provided a list and the caller provided a
674 * hostname, pick the first string from the list that
675 * corresponds to the hostname.
676 */
677 if (hostname != NULL) {
678 len = strlen(hostname);
679 for (l = list; l != NULL; l = l->next) {
680 p = l->value + len;
681 if (*p != ':' ||
682 strncasecmp(l->value, hostname, len) != 0)
683 continue;
684
685 if (*(++p) != '\0' && isalnum((unsigned char)*p)) {
686 if ((p = strdup(p)) == NULL)
687 goto nomem;
688 goto done;
689 }
690 }
691 }
692 /*
693 * If one couldn't be found, look for first string in the list
694 * without a hostname specifier.
695 */
696 p = NULL;
697 for (l = list; l != NULL; l = l->next) {
698 if (strchr(l->value, ':') == NULL) {
699 if ((p = strdup(l->value)) == NULL)
700 goto nomem;
701 goto done;
702 }
703 }
704 done:
705 saslc__list_free(list);
706 *rval = p;
707 return 0;
708 nomem:
709 saslc__list_free(list);
710 return -1;
711 }
712
713 /**
714 * @brief builds digesturi string
715 * @param serv_type type of service to use, e.g., "smtp"
716 * @param host fully-qualified canonical DNS name of host
717 * @param serv_name service name if it is replicated via DNS records; may
718 * be NULL.
719 * @return digesturi string, NULL on failure.
720 */
721 static char *
saslc__mech_digestmd5_digesturi(saslc_sess_t * sess,const char * serv_host)722 saslc__mech_digestmd5_digesturi(saslc_sess_t *sess, const char *serv_host)
723 {
724 const char *serv_list;
725 char *serv_name;
726 const char *serv_type;
727 char *r;
728 int rv;
729
730 serv_type = saslc_sess_getprop(sess, SASLC_DIGESTMD5_SERVICE);
731 if (serv_type == NULL) {
732 saslc__error_set(ERR(sess), ERROR_MECH,
733 "service is required for an authentication");
734 return NULL;
735 }
736 serv_list = saslc_sess_getprop(sess, SASLC_DIGESTMD5_SERVNAME);
737 if (serv_list == NULL)
738 serv_name = NULL;
739 else if (choose_from_hqlist(serv_list, serv_host, &serv_name) == -1)
740 goto nomem;
741
742 saslc__msg_dbg("%s: serv_name='%s'", __func__,
743 serv_name ? serv_name : "<null>");
744
745 /****************************************************************/
746 /* digest-uri = "digest-uri" "=" <"> digest-uri-value <"> */
747 /* digest-uri-value = serv-type "/" host [ "/" serv-name ] */
748 /* */
749 /* If the service is not replicated, or the serv-name is */
750 /* identical to the host, then the serv-name component MUST be */
751 /* omitted. The service is considered to be replicated if the */
752 /* client's service-location process involves resolution using */
753 /* standard DNS lookup operations, and if these operations */
754 /* involve DNS records (such as SRV, or MX) which resolve one */
755 /* DNS name into a set of other DNS names. */
756 /****************************************************************/
757
758 rv = serv_name == NULL || strcmp(serv_host, serv_name) == 0
759 ? asprintf(&r, "%s/%s", serv_type, serv_host)
760 : asprintf(&r, "%s/%s/%s", serv_type, serv_host, serv_name);
761 if (serv_name != NULL)
762 free(serv_name);
763 if (rv == -1)
764 goto nomem;
765
766 saslc__msg_dbg("%s: digest-uri='%s'", __func__, r);
767 return r;
768 nomem:
769 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
770 return NULL;
771 }
772
773 /**
774 * @brief creates client's nonce. (Basing on crypto.h)
775 * @param s length of nonce
776 * @return nonce string, NULL on failure.
777 */
778 static char *
saslc__mech_digestmd5_nonce(size_t s)779 saslc__mech_digestmd5_nonce(size_t s)
780 {
781 char *nonce;
782 char *r;
783
784 nonce = saslc__crypto_nonce(s);
785 if (nonce == NULL)
786 return NULL;
787
788 if (saslc__crypto_encode_base64(nonce, s, &r, NULL) == -1)
789 return NULL;
790 free(nonce);
791
792 return r;
793 }
794
795 /**
796 * @brief strip quotes from a string (modifies the string)
797 * @param str the string
798 * @return string without quotes.
799 */
800 static char *
strip_quotes(char * str)801 strip_quotes(char *str)
802 {
803 char *p;
804 size_t len;
805
806 if (*str != '"')
807 return str;
808
809 len = strlen(str);
810 p = str + len;
811 if (len < 2 || p[-1] != '"')
812 return str;
813
814 p[-1] = '\0';
815 return ++str;
816 }
817
818 /**
819 * @brief convert a list of realms from utf-8 to iso8859-q if necessary.
820 * @param is_utf8 the characterset of the realms (true if utf8)
821 * @param realms the realm list
822 */
823 static int
stringprep_realms(bool is_utf8,list_t * realms)824 stringprep_realms(bool is_utf8, list_t *realms)
825 {
826 list_t *l;
827 char *utf8, *iso8859;
828
829 /******************************************************************/
830 /* If at least one realm is present and the charset directive is */
831 /* also specified (which means that realm(s) are encoded as */
832 /* UTF-8), the client should prepare each instance of realm using */
833 /* the "SASLPrep" profile [SASLPrep] of the "stringprep" */
834 /* algorithm [StringPrep]. If preparation of a realm instance */
835 /* fails or results in an empty string, the client should abort */
836 /* the authentication exchange. */
837 /******************************************************************/
838 if (!is_utf8)
839 return 0;
840
841 for (l = realms; l != NULL; l = l->next) {
842 utf8 = l->value;
843 if (utf8_to_8859_1(utf8, &iso8859) == -1)
844 return -1;
845 free(utf8);
846 l->value = iso8859;
847 }
848 return 0;
849 }
850
851 /**
852 * @brief choose a realm from a list of possible realms provided by the server
853 * @param sess the session context
854 * @param realms the list of realms
855 * @return our choice of realm or NULL on failure. It is the user's
856 * responsibility to free the memory allocated for the return string.
857 */
858 static char *
choose_realm(saslc_sess_t * sess,const char * hostname,list_t * realms)859 choose_realm(saslc_sess_t *sess, const char *hostname, list_t *realms)
860 {
861 const char *user_realms;
862 list_t *l;
863 char *p;
864
865 /*****************************************************************/
866 /* The realm containing the user's account. This directive is */
867 /* required if the server provided any realms in the */
868 /* "digest-challenge", in which case it may appear exactly once */
869 /* and its value SHOULD be one of those realms. If the directive */
870 /* is missing, "realm-value" will set to the empty string when */
871 /* computing A1 (see below for details). */
872 /*****************************************************************/
873
874 user_realms = saslc_sess_getprop(sess, SASLC_DIGESTMD5_REALM);
875
876 /*
877 * If the challenge provided no realms, try to pick one from a
878 * user specified list, which may be keyed by the hostname.
879 * If one can't be found, return NULL;
880 */
881 if (realms == NULL) {
882 /*
883 * No realm was supplied in challenge. Figure out a
884 * plausable default.
885 */
886 if (user_realms == NULL) {
887 saslc__error_set(ERR(sess), ERROR_MECH,
888 "cannot determine the realm");
889 return NULL;
890 }
891 if (choose_from_hqlist(user_realms, hostname, &p) == -1)
892 goto nomem;
893
894 if (p == NULL)
895 saslc__error_set(ERR(sess), ERROR_MECH,
896 "cannot choose a realm");
897 return p;
898 }
899
900 /************************************************************/
901 /* Multiple realm directives are allowed, in which case the */
902 /* user or client must choose one as the realm for which to */
903 /* supply to username and password. */
904 /************************************************************/
905 /*
906 * If the user hasn't specified any realms, or we can't find
907 * one from the user provided list, just take the first realm
908 * from the challenge.
909 */
910 if (user_realms == NULL)
911 goto use_1st_realm;
912
913 if (choose_from_hqlist(user_realms, hostname, &p) == -1)
914 goto nomem;
915
916 if (p == NULL)
917 goto use_1st_realm;
918
919 /*
920 * If we found a matching user provide realm, make sure it is
921 * on the list of realms. If it isn't, just take the first
922 * realm in the challenge.
923 */
924 for (l = realms; l != NULL; l = l->next) {
925 if (strcasecmp(p, l->value) == 0)
926 return p;
927 }
928 use_1st_realm:
929 if ((p = strdup(realms->value)) == NULL)
930 goto nomem;
931 return p;
932 nomem:
933 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
934 return NULL;
935 }
936
937 /**
938 * @brief destroy a cipher context
939 * @param ctx cipher context
940 * @return nothing
941 */
942 static void
cipher_context_destroy(cipher_context_t * ctx)943 cipher_context_destroy(cipher_context_t *ctx)
944 {
945
946 if (ctx != NULL) {
947 if (ctx->evp_ctx != NULL)
948 EVP_CIPHER_CTX_free(ctx->evp_ctx);
949 free(ctx);
950 }
951 }
952
953 /**
954 * @brief slide the bits from 7 bytes into the high 7 bits of 8 bites
955 * @param ikey input key
956 * @param okey output key
957 *
958 * This matches cyrus-sasl 2.1.23
959 */
960 static inline void
slidebits(uint8_t * ikey,uint8_t * okey)961 slidebits(uint8_t *ikey, uint8_t *okey)
962 {
963
964 okey[0] = ikey[0] << 0;
965 okey[1] = ikey[0] << 7 | (unsigned)ikey[1] >> 1;
966 okey[2] = ikey[1] << 6 | (unsigned)ikey[2] >> 2;
967 okey[3] = ikey[2] << 5 | (unsigned)ikey[3] >> 3;
968 okey[4] = ikey[3] << 4 | (unsigned)ikey[4] >> 4;
969 okey[5] = ikey[4] << 3 | (unsigned)ikey[5] >> 5;
970 okey[6] = ikey[5] << 2 | (unsigned)ikey[6] >> 6;
971 okey[7] = ikey[6] << 1;
972 }
973
974 /**
975 * @brief convert our key to a DES key
976 * @param key our key
977 * @param keylen our key length
978 * @param deskey the key in DES format
979 *
980 * NOTE: The openssl implementations of "des" and "3des" expect their
981 * keys to be in the high 7 bits of 8 bytes and 16 bytes,
982 * respectively. Thus, our key length will be 7 and 14 bytes,
983 * respectively.
984 */
985 static void
make_deskey(uint8_t * key,size_t keylen,uint8_t * deskey)986 make_deskey(uint8_t *key, size_t keylen, uint8_t *deskey)
987 {
988
989 assert(keylen == 7 || keylen == 14);
990
991 slidebits(deskey + 0, key + 0);
992 if (keylen == 14)
993 slidebits(deskey + 7, key + 7);
994 }
995
996 /**
997 * @brief create a cipher context, including EVP cipher initialization.
998 * @param sess session context
999 * @param cipher cipher to use
1000 * @param do_enc encode context if set, decode context if 0
1001 * @param key crypt key to use
1002 * @return cipher context, or NULL on error
1003 */
1004 static cipher_context_t *
cipher_context_create(saslc_sess_t * sess,cipher_t cipher,int do_enc,uint8_t * key)1005 cipher_context_create(saslc_sess_t *sess, cipher_t cipher, int do_enc, uint8_t *key)
1006 {
1007 #define AES_IV_MAGIC "aes-128"
1008 #define AES_IV_MAGIC_LEN (sizeof(AES_IV_MAGIC) - 1)
1009 static const struct cipher_ctx_tbl_s {
1010 cipher_t eval; /* for error checking */
1011 const EVP_CIPHER *(*evp_type)(void);/* type of cipher */
1012 size_t keylen; /* key length */
1013 ssize_t blksize; /* block size for cipher */
1014 size_t ivlen; /* initial value length */
1015 } cipher_ctx_tbl[] = {
1016 /* NB: table indexed by cipher_t */
1017 /* eval evp_type keylen blksize ivlen */
1018 { CIPHER_DES, EVP_des_cbc, 7, 8, 8 },
1019 { CIPHER_3DES, EVP_des_ede_cbc, 14, 8, 8 },
1020 { CIPHER_RC4, EVP_rc4, 16, 1, 0 },
1021 { CIPHER_RC4_40, EVP_rc4, 5, 1, 0 },
1022 { CIPHER_RC4_56, EVP_rc4, 7, 1, 0 },
1023 { CIPHER_AES, EVP_aes_128_cbc, 16, 16, 16 }
1024 };
1025 const struct cipher_ctx_tbl_s *ctp;
1026 char buf[sizeof(md5hash_t) + AES_IV_MAGIC_LEN];
1027 uint8_t deskey[16];
1028 md5hash_t aes_iv; /* initial value buffer for aes */
1029 cipher_context_t *ctx; /* cipher context */
1030 uint8_t *ivp;
1031 const char *errmsg;
1032 int rv;
1033
1034 /*************************************************************************/
1035 /* See draft-ietf-sasl-rfc2831bis-02.txt section 2.4 (mentions "aes") */
1036 /* The key for the "rc4" and "aes" ciphers is all 16 bytes of Kcc or Kcs.*/
1037 /* The key for the "rc4-40" cipher is the first 5 bytes of Kcc or Kcs. */
1038 /* The key for the "rc4-56" is the first 7 bytes of Kcc or Kcs. */
1039 /* The key for "des" is the first 7 bytes of Kcc or Kcs. */
1040 /* The key for "3des" is the first 14 bytes of Kcc or Kcs. */
1041 /* */
1042 /* The IV used to send/receive the initial buffer of security encoded */
1043 /* data for "des" and "3des" is the last 8 bytes of Kcc or Kcs. For all */
1044 /* subsequent buffers the last 8 bytes of the ciphertext of the buffer */
1045 /* NNN is used as the IV for the buffer (NNN + 1). */
1046 /* */
1047 /* The IV for the "aes" cipher in CBC mode for messages going from the */
1048 /* client to the server (IVc) consists of 16 bytes calculated as */
1049 /* follows: IVc = MD5({Kcc, "aes-128"}) */
1050 /* */
1051 /* The IV for the "aes" cipher in CBC mode for messages going from the */
1052 /* server to the client (IVs) consists of 16 bytes calculated as */
1053 /* follows: IVs = MD5({Kcs, "aes-128"}) */
1054 /*************************************************************************/
1055
1056 assert(cipher < __arraycount(cipher_ctx_tbl));
1057 if (cipher >= __arraycount(cipher_ctx_tbl)) {
1058 saslc__error_set_errno(ERR(sess), ERROR_BADARG);
1059 return NULL;
1060 }
1061
1062 ctx = malloc(sizeof(*ctx));
1063 if (ctx == NULL) {
1064 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
1065 return NULL;
1066 }
1067
1068 ctp = &cipher_ctx_tbl[cipher];
1069 assert(ctp->eval == cipher);
1070
1071 ctx->blksize = ctp->blksize;
1072
1073 ctx->evp_ctx = EVP_CIPHER_CTX_new();
1074 if (ctx->evp_ctx == NULL) {
1075 errmsg = "EVP_CIPHER_CTX_new failed";
1076 goto err;
1077 }
1078 if (EVP_CipherInit_ex(ctx->evp_ctx, ctp->evp_type(), NULL, NULL, NULL,
1079 do_enc) == 0) {
1080 errmsg = "EVP_CipherInit_ex failed";
1081 goto err;
1082 }
1083 if (EVP_CIPHER_CTX_set_padding(ctx->evp_ctx, 0) == 0) {
1084 errmsg = "EVP_CIPHER_CTX_set_padding failed";
1085 goto err;
1086 }
1087 ivp = NULL;
1088 switch (cipher) { /* prepare key and IV */
1089 case CIPHER_RC4:
1090 case CIPHER_RC4_40:
1091 case CIPHER_RC4_56:
1092 assert(ctp->ivlen == 0); /* no IV */
1093 rv = EVP_CIPHER_CTX_set_key_length(ctx->evp_ctx,
1094 (int)ctp->keylen);
1095 if (rv == 0) {
1096 errmsg = "EVP_CIPHER_CTX_set_key_length failed";
1097 goto err;
1098 }
1099 break;
1100 case CIPHER_DES:
1101 case CIPHER_3DES:
1102 assert(ctp->ivlen == 8);
1103 ivp = key + 8;
1104 make_deskey(key, ctp->keylen, deskey);
1105 key = deskey;
1106 break;
1107 case CIPHER_AES:
1108 assert(ctp->ivlen == 16);
1109 /* IVs = MD5({Kcs, "aes-128"}) */
1110 memcpy(buf, key, sizeof(md5hash_t));
1111 memcpy(buf + sizeof(md5hash_t), AES_IV_MAGIC, AES_IV_MAGIC_LEN);
1112 saslc__crypto_md5_hash(buf, sizeof(buf), aes_iv);
1113 ivp = aes_iv;
1114 break;
1115 }
1116 if (EVP_CipherInit_ex(ctx->evp_ctx, NULL, NULL, key, ivp, do_enc) == 0) {
1117 errmsg = "EVP_CipherInit_ex 2 failed";
1118 goto err;
1119 }
1120 return ctx;
1121 err:
1122 cipher_context_destroy(ctx);
1123 saslc__error_set(ERR(sess), ERROR_MECH, errmsg);
1124 return NULL;
1125
1126 #undef AES_IV_MAGIC_LEN
1127 #undef AES_IV_MAGIC
1128 }
1129
1130 /**
1131 * @brief compute the necessary padding length
1132 * @param ctx the cipher context
1133 * @param inlen the data length to put in the packet
1134 * @return the length of padding needed (zero if none needed)
1135 */
1136 static size_t
get_padlen(cipher_context_t * ctx,size_t inlen)1137 get_padlen(cipher_context_t *ctx, size_t inlen)
1138 {
1139 size_t blksize;
1140
1141 if (ctx == NULL)
1142 return 0;
1143
1144 blksize = ctx->blksize;
1145 if (blksize == 1)
1146 return 0;
1147
1148 return blksize - ((inlen + 10) % blksize);
1149 }
1150
1151 /**
1152 * @brief compute the packet integrity including the version and
1153 * sequence number
1154 * @param key the hmac_md5 hash key
1155 * @param seqnum the sequence number
1156 * @param in the input buffer
1157 * @param inlen the input buffer length
1158 * @return 0 on success, -1 on failure
1159 */
1160 static int
packet_integrity(md5hash_t key,uint32_t seqnum,void * in,size_t inlen,md5hash_t mac)1161 packet_integrity(md5hash_t key, uint32_t seqnum, void *in, size_t inlen,
1162 md5hash_t mac)
1163 {
1164
1165 be32enc(in, seqnum);
1166 if (saslc__crypto_hmac_md5_hash(key, MD5_DIGEST_LENGTH, in, inlen, mac)
1167 == -1)
1168 return -1;
1169
1170 /* we keep only the first 10 bytes of the hash */
1171 be16enc(mac + 10, 0x0001); /* add 2 byte version number */
1172 be32enc(mac + 12, seqnum); /* add 4 byte sequence number */
1173 return 0;
1174 }
1175
1176 /**
1177 * @brief encode or decode a buffer (in place)
1178 * @param ctx the cipher context
1179 * @param in the input buffer
1180 * @param inlen the buffer length
1181 * @return the length of the result left in the input buffer after
1182 * processing, or -1 on failure.
1183 */
1184 static ssize_t
cipher_update(cipher_context_t * ctx,void * in,size_t inlen)1185 cipher_update(cipher_context_t *ctx, void *in, size_t inlen)
1186 {
1187 int outl, rv;
1188 void *out;
1189
1190 out = in; /* XXX: this assumes we can encoded and decode in place */
1191 rv = EVP_CipherUpdate(ctx->evp_ctx, out, &outl, in, (int)inlen);
1192 if (rv == 0)
1193 return -1;
1194
1195 return outl;
1196 }
1197
1198 /**
1199 * @brief incapsulate a message with confidentiality (sign and encrypt)
1200 * @param ctx coder context
1201 * @param in pointer to message to encode
1202 * @param inlen length of message
1203 * @param out encoded output packet (including prefixed 4 byte length field)
1204 * @param outlen decoded output packet length
1205 * @returns 0 on success, -1 on failure
1206 *
1207 * NOTE: this allocates memory for its output and the caller is
1208 * responsible for freeing it.
1209 *
1210 * integrity (auth-int):
1211 * len, HMAC(ki, {SeqNum, msg})[0..9], x0001, SeqNum
1212 *
1213 * confidentiality (auth-conf):
1214 * len, CIPHER(Kc, {msg, pag, HMAC(ki, {SeqNum, msg})[0..9]}), x0001, SeqNum
1215 */
1216 static ssize_t
encode_buffer(coder_context_t * ctx,const void * in,size_t inlen,void ** out,size_t * outlen)1217 encode_buffer(coder_context_t *ctx, const void *in, size_t inlen,
1218 void **out, size_t *outlen)
1219 {
1220 void *buf;
1221 uint8_t *mac, *p;
1222 ssize_t tmplen;
1223 size_t buflen;
1224 size_t padlen;
1225
1226 padlen = get_padlen(ctx->cph_ctx, inlen);
1227 buflen = 4 + inlen + padlen + sizeof(md5hash_t);
1228 buf = malloc(buflen);
1229 if (buf == NULL) {
1230 saslc__error_set_errno(ERR(ctx->sess), ERROR_NOMEM);
1231 return -1;
1232 }
1233 p = buf;
1234 memcpy(p + 4, in, inlen);
1235 mac = p + 4 + inlen + padlen;
1236 if (packet_integrity(ctx->key, ctx->seqnum, buf, 4 + inlen, mac)
1237 == -1) {
1238 saslc__error_set(ERR(ctx->sess), ERROR_MECH, "HMAC failed");
1239 free(buf);
1240 return -1;
1241 }
1242
1243 if (padlen)
1244 memset(p + 4 + inlen, (int)padlen, padlen);
1245
1246 if (ctx->cph_ctx != NULL) {
1247 if ((tmplen = cipher_update(ctx->cph_ctx, p + 4,
1248 inlen + padlen + 10)) == -1) {
1249 saslc__error_set(ERR(ctx->sess), ERROR_MECH,
1250 "cipher error");
1251 free(buf);
1252 return -1;
1253 }
1254 assert((size_t)tmplen == inlen + padlen + 10);
1255 if ((size_t)tmplen != inlen + padlen + 10)
1256 return -1;
1257 }
1258
1259 be32enc(buf, (uint32_t)(buflen - 4));
1260
1261 *out = buf;
1262 *outlen = buflen;
1263 ctx->seqnum++; /* wraps at 2^32 */
1264 return 0;
1265 }
1266
1267 /**
1268 * @brief decode one complete confidentiality encoded packet
1269 * @param ctx coder context
1270 * @param in pointer to packet, including the beginning 4 byte length field.
1271 * @param inlen length of packet
1272 * @param out decoded output
1273 * @param outlen decoded output length
1274 * @returns 0 on success, -1 on failure
1275 *
1276 * NOTE: this modifies the intput buffer!
1277 * NOTE: this allocates memory for its output and the caller is
1278 * responsible for freeing it.
1279 *
1280 * integrity (auth-int):
1281 * len, HMAC(ki, {SeqNum, msg})[0..9], x0001, SeqNum
1282 *
1283 * confidentiality (auth-conf):
1284 * len, CIPHER(Kc, {msg, pag, HMAC(ki, {SeqNum, msg})[0..9]}), x0001, SeqNum
1285 */
1286 static ssize_t
decode_buffer(coder_context_t * ctx,void * in,size_t inlen,void ** out,size_t * outlen)1287 decode_buffer(coder_context_t *ctx, void *in, size_t inlen,
1288 void **out, size_t *outlen)
1289 {
1290 md5hash_t mac;
1291 void *buf;
1292 uint8_t *p;
1293 size_t blksize, buflen, padlen;
1294 ssize_t tmplen;
1295 uint32_t len;
1296
1297 padlen = get_padlen(ctx->cph_ctx, 1);
1298 if (inlen < 4 + 1 + padlen + MD5_DIGEST_LENGTH) {
1299 saslc__error_set(ERR(ctx->sess), ERROR_MECH,
1300 "zero payload packet");
1301 return -1;
1302 }
1303 len = be32dec(in);
1304 if (len + 4 != inlen) {
1305 saslc__error_set(ERR(ctx->sess), ERROR_MECH,
1306 "bad packet length");
1307 return -1;
1308 }
1309
1310 if (ctx->cph_ctx != NULL) {
1311 p = in;
1312 if ((tmplen = cipher_update(ctx->cph_ctx, p + 4, len - 6)) == -1) {
1313 saslc__error_set(ERR(ctx->sess), ERROR_MECH,
1314 "cipher error");
1315 return -1;
1316 }
1317 assert(tmplen == (ssize_t)len - 6);
1318 if (tmplen != (ssize_t)len - 6)
1319 return -1;
1320 }
1321
1322 blksize = ctx->cph_ctx ? ctx->cph_ctx->blksize : 0;
1323 if (blksize <= 1)
1324 padlen = 0;
1325 else{
1326 p = in;
1327 padlen = p[inlen - sizeof(md5hash_t) - 1];
1328 if (padlen > blksize || padlen == 0) {
1329 saslc__error_set(ERR(ctx->sess), ERROR_MECH,
1330 "invalid padding length after decode");
1331 return -1;
1332 }
1333 }
1334 if (packet_integrity(ctx->key, ctx->seqnum, in,
1335 inlen - padlen - sizeof(mac), mac) == -1) {
1336 saslc__error_set(ERR(ctx->sess), ERROR_MECH, "HMAC failed");
1337 return -1;
1338 }
1339
1340 p = in;
1341 p += 4 + len - MD5_DIGEST_LENGTH;
1342 if (memcmp(p, mac, MD5_DIGEST_LENGTH) != 0) {
1343 uint32_t seqnum;
1344
1345 p = in;
1346 seqnum = be32dec(p + inlen - 4);
1347 saslc__error_set(ERR(ctx->sess), ERROR_MECH,
1348 seqnum != ctx->seqnum ? "invalid MAC (bad seqnum)" :
1349 "invalid MAC");
1350 return -1;
1351 }
1352
1353 buflen = len - padlen - MD5_DIGEST_LENGTH;
1354 buf = malloc(buflen);
1355 if (buf == NULL) {
1356 saslc__error_set_errno(ERR(ctx->sess), ERROR_NOMEM);
1357 return -1;
1358 }
1359 p = in;
1360 p += 4;
1361 memcpy(buf, p, buflen);
1362
1363 *out = buf;
1364 *outlen = buflen;
1365 ctx->seqnum++;
1366 return 0;
1367 }
1368
1369 /**
1370 * @brief add integrity or confidentiality layer
1371 * @param sess session handle
1372 * @param in input buffer
1373 * @param inlen input buffer length
1374 * @param out pointer to output buffer
1375 * @param out pointer to output buffer length
1376 * @return number of bytes consumed on success, 0 if insufficient data
1377 * to process, -1 on failure
1378 */
1379 static ssize_t
saslc__mech_digestmd5_encode(saslc_sess_t * sess,const void * in,size_t inlen,void ** out,size_t * outlen)1380 saslc__mech_digestmd5_encode(saslc_sess_t *sess, const void *in, size_t inlen,
1381 void **out, size_t *outlen)
1382 {
1383 saslc__mech_digestmd5_sess_t *ms;
1384 uint8_t *buf;
1385 size_t buflen;
1386 ssize_t rval;
1387
1388 ms = sess->mech_sess;
1389 assert(ms->mech_sess.qop != QOP_NONE);
1390 if (ms->mech_sess.qop == QOP_NONE)
1391 return -1;
1392
1393 rval = saslc__buffer_fetch(ms->enc_ctx.buf_ctx, in, inlen, &buf, &buflen);
1394 if (rval == -1)
1395 return -1;
1396 if (buflen == 0) {
1397 *out = NULL;
1398 *outlen = 0;
1399 return rval;
1400 }
1401 if (encode_buffer(&ms->enc_ctx, buf, buflen, out, outlen) == -1)
1402 return -1;
1403
1404 return rval;
1405 }
1406
1407 /**
1408 * @brief remove integrity or confidentiality layer
1409 * @param sess session handle
1410 * @param in input buffer
1411 * @param inlen input buffer length
1412 * @param out pointer to output buffer
1413 * @param out pointer to output buffer length
1414 * @return number of bytes consumed on success, 0 if insufficient data
1415 * to process, -1 on failure
1416 *
1417 * integrity (auth-int):
1418 * len, HMAC(ki, {SeqNum, msg})[0..9], x0001, SeqNum
1419 *
1420 * confidentiality (auth-conf):
1421 * len, CIPHER(Kc, {msg, pag, HMAC(ki, {SeqNum, msg})[0..9]}), x0001, SeqNum
1422 */
1423 static ssize_t
saslc__mech_digestmd5_decode(saslc_sess_t * sess,const void * in,size_t inlen,void ** out,size_t * outlen)1424 saslc__mech_digestmd5_decode(saslc_sess_t *sess, const void *in, size_t inlen,
1425 void **out, size_t *outlen)
1426 {
1427 saslc__mech_digestmd5_sess_t *ms;
1428 uint8_t *buf;
1429 size_t buflen;
1430 ssize_t rval;
1431
1432 ms = sess->mech_sess;
1433 assert(ms->mech_sess.qop != QOP_NONE);
1434 if (ms->mech_sess.qop == QOP_NONE)
1435 return -1;
1436
1437 rval = saslc__buffer32_fetch(ms->dec_ctx.buf_ctx, in, inlen, &buf, &buflen);
1438 if (rval == -1)
1439 return -1;
1440
1441 if (buflen == 0) {
1442 *out = NULL;
1443 *outlen = 0;
1444 return rval;
1445 }
1446 if (decode_buffer(&ms->dec_ctx, buf, buflen, out, outlen) == -1)
1447 return -1;
1448
1449 return rval;
1450 }
1451
1452 /************************************************************************
1453 * XXX: Share with mech_gssapi.c? They are almost identical.
1454 */
1455 /**
1456 * @brief choose the best qop based on what was provided by the
1457 * challenge and a possible user mask.
1458 * @param sess the session context
1459 * @param qop_flags the qop flags parsed from the challenge string
1460 * @return the selected saslc__mech_sess_qop_t or -1 if no match
1461 */
1462 static int
choose_qop(saslc_sess_t * sess,uint32_t qop_flags)1463 choose_qop(saslc_sess_t *sess, uint32_t qop_flags)
1464 {
1465 list_t *list;
1466 const char *user_qop;
1467
1468 if (qop_flags == 0) /* no qop spec in challenge (it's optional) */
1469 return QOP_NONE;
1470
1471 qop_flags &= DEFAULT_QOP_MASK;
1472 user_qop = saslc_sess_getprop(sess, SASLC_DIGESTMD5_QOPMASK);
1473 if (user_qop != NULL) {
1474 if (saslc__list_parse(&list, user_qop) == -1) {
1475 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
1476 return -1;
1477 }
1478 qop_flags &= saslc__mech_qop_list_flags(list);
1479 saslc__list_free(list);
1480 }
1481
1482 /*
1483 * Select the most secure supported qop.
1484 */
1485 if ((qop_flags & F_QOP_CONF) != 0)
1486 return QOP_CONF;
1487 if ((qop_flags & F_QOP_INT) != 0)
1488 return QOP_INT;
1489 if ((qop_flags & F_QOP_NONE) != 0)
1490 return QOP_NONE;
1491
1492 saslc__error_set(ERR(sess), ERROR_MECH,
1493 "cannot choose an acceptable qop");
1494 return -1;
1495 }
1496 /************************************************************************/
1497
1498 /**
1499 * @brief choose the best cipher based on what was provided by the
1500 * challenge and a possible user mask.
1501 * @param sess the session context
1502 * @param cipher_flags the cipher flags parsed from the challenge
1503 * string
1504 * @return the selected cipher_t
1505 */
1506 static int
choose_cipher(saslc_sess_t * sess,unsigned int cipher_flags)1507 choose_cipher(saslc_sess_t *sess, unsigned int cipher_flags)
1508 {
1509 list_t *list;
1510 unsigned int cipher_mask;
1511 const char *user_cipher;
1512
1513 if (cipher_flags == 0) {
1514 saslc__error_set(ERR(sess), ERROR_MECH,
1515 "no cipher spec in challenge");
1516 return -1;
1517 }
1518 cipher_mask = DEFAULT_CIPHER_MASK;
1519 user_cipher = saslc_sess_getprop(sess, SASLC_DIGESTMD5_CIPHERMASK);
1520 if (user_cipher != NULL) {
1521 if (saslc__list_parse(&list, user_cipher) == -1) {
1522 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
1523 return -1;
1524 }
1525 cipher_mask = cipher_list_flags(list);
1526 saslc__list_free(list);
1527 }
1528 cipher_flags &= cipher_mask;
1529
1530 /*
1531 * Select the most secure cipher supported.
1532 * XXX: Is the order here right?
1533 */
1534 if ((cipher_flags & F_CIPHER_AES) != 0)
1535 return CIPHER_AES;
1536 if ((cipher_flags & F_CIPHER_3DES) != 0)
1537 return CIPHER_3DES;
1538 if ((cipher_flags & F_CIPHER_DES) != 0)
1539 return CIPHER_DES;
1540 if ((cipher_flags & F_CIPHER_RC4) != 0)
1541 return CIPHER_RC4;
1542 if ((cipher_flags & F_CIPHER_RC4_56) != 0)
1543 return CIPHER_RC4_56;
1544 if ((cipher_flags & F_CIPHER_RC4_40) != 0)
1545 return CIPHER_RC4_40;
1546
1547 saslc__error_set(ERR(sess), ERROR_MECH,
1548 "qop \"auth-conf\" requires a cipher");
1549 return -1;
1550 }
1551
1552 /**
1553 * @brief get the challenge_t value corresponding to a challenge key
1554 * string.
1555 * @param key challenge key string
1556 * @return the challenge_t value including CHALLENGE_IGNORE (-1) if
1557 * the key is not recognized
1558 */
1559 static challenge_t
get_challenge_t(const char * key)1560 get_challenge_t(const char *key)
1561 {
1562 static const struct {
1563 const char *key;
1564 challenge_t value;
1565 } challenge_keys[] = {
1566 { "realm", CHALLENGE_REALM },
1567 { "nonce", CHALLENGE_NONCE },
1568 { "qop", CHALLENGE_QOP },
1569 { "stale", CHALLENGE_STALE },
1570 { "maxbuf", CHALLENGE_MAXBUF },
1571 { "charset", CHALLENGE_CHARSET },
1572 { "algorithm", CHALLENGE_ALGORITHM },
1573 { "cipher", CHALLENGE_CIPHER }
1574 };
1575 size_t i;
1576
1577 for (i = 0; i < __arraycount(challenge_keys); i++) {
1578 if (strcasecmp(key, challenge_keys[i].key) == 0)
1579 return challenge_keys[i].value;
1580 }
1581 return CHALLENGE_IGNORE;
1582 }
1583
1584 /**
1585 * @brief parses challenge and store result in mech_sess.
1586 * @param mech_sess session where parsed data will be stored
1587 * @param challenge challenge
1588 * @return 0 on success, -1 on failure.
1589 */
1590 static int
saslc__mech_digestmd5_parse_challenge(saslc_sess_t * sess,const char * challenge)1591 saslc__mech_digestmd5_parse_challenge(saslc_sess_t *sess, const char *challenge)
1592 {
1593 saslc__mech_digestmd5_sess_t *ms;
1594 list_t *list, *n;
1595 list_t *tmp_list;
1596 cdata_t *cdata;
1597 size_t maxbuf;
1598 uint32_t tmp_flags;
1599 int rv;
1600
1601 /******************************************************************/
1602 /* digest-challenge = */
1603 /* 1#( realm | nonce | qop-options | stale | server_maxbuf | */
1604 /* charset | algorithm | cipher-opts | auth-param ) */
1605 /******************************************************************/
1606
1607 saslc__msg_dbg("challenge: '%s'\n", challenge);
1608
1609 ms = sess->mech_sess;
1610 cdata = &ms->cdata;
1611
1612 rv = -1;
1613 memset(cdata, 0, sizeof(*cdata));
1614 if (saslc__list_parse(&list, challenge) == -1) {
1615 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
1616 return -1;
1617 }
1618 saslc__list_log(list, "parse list:\n");
1619 for (n = list; n != NULL; n = n->next) {
1620 char *key;
1621 char *val;
1622
1623 /* Split string into key and val */
1624 key = n->value;
1625 val = strchr(key, '=');
1626 if (val == NULL)
1627 goto no_mem;
1628 *val = '\0';
1629 val = strip_quotes(val + 1);
1630
1631 saslc__msg_dbg("key='%s' val='%s'\n", key, val);
1632 switch (get_challenge_t(key)) {
1633 case CHALLENGE_REALM:
1634 /**************************************************/
1635 /* realm = "realm" "=" <"> realm-value <"> */
1636 /* realm-value = qdstr-val */
1637 /* */
1638 /* This directive is optional; if not present, */
1639 /* the client SHOULD solicit it from the user or */
1640 /* be able to compute a default; a plausible */
1641 /* default might be the realm supplied by the */
1642 /* user when they logged in to the client system. */
1643 /* Multiple realm directives are allowed, in */
1644 /* which case the user or client must choose one */
1645 /* as the realm for which to supply to username */
1646 /* and password. */
1647 /**************************************************/
1648 if (saslc__list_append(&cdata->realm, val) == -1)
1649 goto no_mem;
1650 break;
1651 case CHALLENGE_NONCE:
1652 /**************************************************/
1653 /* nonce = "nonce" "=" <"> nonce-value <"> */
1654 /* nonce-value = *qdtext */
1655 /* */
1656 /* This directive is required and MUST appear */
1657 /* exactly once; if not present, or if multiple */
1658 /* instances are present, the client should abort */
1659 /* the authentication exchange. */
1660 /**************************************************/
1661 if (cdata->nonce != NULL) {
1662 saslc__error_set(ERR(sess), ERROR_MECH,
1663 "multiple nonce in challenge");
1664 goto out;
1665 }
1666 cdata->nonce = strdup(val);
1667 if (cdata->nonce == NULL)
1668 goto no_mem;
1669 break;
1670 case CHALLENGE_QOP:
1671 /**************************************************/
1672 /* qop-options = "qop" "=" <"> qop-list <"> */
1673 /* qop-list = 1#qop-value */
1674 /* qop-value = "auth" | "auth-int" | */
1675 /* "auth-conf" | token */
1676 /* */
1677 /* This directive is optional; if not present it */
1678 /* defaults to "auth". The client MUST ignore */
1679 /* unrecognized options; if the client recognizes */
1680 /* no option, it should abort the authentication */
1681 /* exchange. */
1682 /**************************************************/
1683 if (saslc__list_parse(&tmp_list, val) == -1)
1684 goto no_mem;
1685 saslc__list_log(tmp_list, "qop list:\n");
1686 tmp_flags = saslc__mech_qop_list_flags(tmp_list);
1687 saslc__list_free(tmp_list);
1688 if (tmp_flags == 0) {
1689 saslc__error_set(ERR(sess), ERROR_MECH,
1690 "qop required in challenge");
1691 goto out;
1692 }
1693 cdata->qop_flags |= tmp_flags;
1694 break;
1695 case CHALLENGE_STALE:
1696 /**************************************************/
1697 /* stale = "stale" "=" "true" */
1698 /* */
1699 /* This directive may appear at most once; if */
1700 /* multiple instances are present, the client */
1701 /* should abort the authentication exchange. */
1702 /**************************************************/
1703 if (cdata->stale) {
1704 saslc__error_set(ERR(sess), ERROR_MECH,
1705 "multiple stale in challenge");
1706 goto out;
1707 }
1708 if (strcasecmp(val, "true") != 0) {
1709 saslc__error_set(ERR(sess), ERROR_MECH,
1710 "stale must be true");
1711 goto out;
1712 }
1713 cdata->stale = true;
1714 break;
1715 case CHALLENGE_MAXBUF:
1716 /**************************************************/
1717 /* maxbuf-value = 1*DIGIT */
1718 /* */
1719 /* The value MUST be bigger than 16 and smaller */
1720 /* or equal to 16777215 (i.e. 2**24-1). If this */
1721 /* directive is missing, the default value is */
1722 /* 65536. This directive may appear at most once; */
1723 /* if multiple instances are present, the client */
1724 /* MUST abort the authentication exchange. */
1725 /**************************************************/
1726 if (cdata->maxbuf != 0) {
1727 saslc__error_set(ERR(sess), ERROR_MECH,
1728 "multiple maxbuf in challenge");
1729 goto out;
1730 }
1731 maxbuf = (size_t)strtoul(val, NULL, 10);
1732 if (INVALID_MAXBUF(maxbuf)) {
1733 saslc__error_set(ERR(sess), ERROR_MECH,
1734 "invalid maxbuf in challenge");
1735 goto out;
1736 }
1737 cdata->maxbuf = maxbuf;
1738 break;
1739 case CHALLENGE_CHARSET:
1740 /**************************************************/
1741 /* charset = "charset" "=" "utf-8" */
1742 /* */
1743 /* This directive may appear at most once; if */
1744 /* multiple instances are present, the client */
1745 /* should abort the authentication exchange. */
1746 /**************************************************/
1747 if (cdata->utf8) {
1748 saslc__error_set(ERR(sess), ERROR_MECH,
1749 "multiple charset in challenge");
1750 goto out;
1751 }
1752 if (strcasecmp(val, "utf-8") != 0) {
1753 saslc__error_set(ERR(sess), ERROR_MECH,
1754 "charset != \"utf-8\" in challenge");
1755 goto out;
1756 }
1757 cdata->utf8 = true;
1758 break;
1759 case CHALLENGE_ALGORITHM:
1760 /**************************************************/
1761 /* algorithm = "algorithm" "=" "md5-sess" */
1762 /* */
1763 /* This directive is required and MUST appear */
1764 /* exactly once; if not present, or if multiple */
1765 /* instances are present, the client should abort */
1766 /* the authentication exchange. */
1767 /**************************************************/
1768 if (cdata->algorithm) {
1769 saslc__error_set(ERR(sess), ERROR_MECH,
1770 "multiple algorithm in challenge");
1771 goto out;
1772 }
1773 if (strcasecmp(val, "md5-sess") != 0) {
1774 saslc__error_set(ERR(sess), ERROR_MECH,
1775 "algorithm != \"md5-sess\" in challenge");
1776 goto out;
1777 }
1778 cdata->algorithm = true;
1779 break;
1780 case CHALLENGE_CIPHER:
1781 /**************************************************/
1782 /* cipher-opts = "cipher" "=" <"> 1#cipher-val <">*/
1783 /* cipher-val = "3des" | "des" | "rc4-40" | */
1784 /* "rc4" |"rc4-56" | "aes" | */
1785 /* token */
1786 /* */
1787 /* This directive must be present exactly once if */
1788 /* "auth-conf" is offered in the "qop-options" */
1789 /* directive, in which case the "3des" cipher is */
1790 /* mandatory-to-implement. The client MUST ignore */
1791 /* unrecognized options; if the client recognizes */
1792 /* no option, it should abort the authentication */
1793 /* exchange. */
1794 /**************************************************/
1795 if (saslc__list_parse(&tmp_list, val) == -1)
1796 goto no_mem;
1797 saslc__list_log(tmp_list, "cipher list:\n");
1798 tmp_flags = cipher_list_flags(tmp_list);
1799 saslc__list_free(tmp_list);
1800 if (tmp_flags == 0) {
1801 saslc__error_set(ERR(sess), ERROR_MECH,
1802 "unknown cipher");
1803 goto out;
1804 }
1805 cdata->cipher_flags |= tmp_flags;
1806 break;
1807 case CHALLENGE_IGNORE:
1808 /**************************************************/
1809 /* auth-param = token "=" ( token | */
1810 /* quoted-string ) */
1811 /* */
1812 /* The client MUST ignore any unrecognized */
1813 /* directives. */
1814 /**************************************************/
1815 break;
1816 }
1817 }
1818
1819 /*
1820 * make sure realms are in iso8859-1
1821 */
1822 if (stringprep_realms(cdata->utf8, cdata->realm) == -1) {
1823 saslc__error_set(ERR(sess), ERROR_MECH,
1824 "unable to convert realms in challenge from "
1825 "\"utf-8\" to iso8859-1");
1826 goto out;
1827 }
1828
1829 /*
1830 * test for required options
1831 */
1832 if (cdata->nonce == NULL) {
1833 saslc__error_set(ERR(sess), ERROR_MECH,
1834 "nonce required in challenge");
1835 goto out;
1836 }
1837
1838 if (!cdata->algorithm) {
1839 saslc__error_set(ERR(sess), ERROR_MECH,
1840 "algorithm required in challenge");
1841 goto out;
1842 }
1843
1844 /*
1845 * set the default maxbuf value if it was missing from the
1846 * challenge.
1847 */
1848 if (cdata->maxbuf == 0)
1849 cdata->maxbuf = DEFAULT_MAXBUF;
1850
1851 saslc__msg_dbg("qop_flags=0x%04x\n", cdata->qop_flags);
1852 saslc__msg_dbg("cipher_flags=0x%04x\n", cdata->cipher_flags);
1853
1854 rv = 0;
1855 out:
1856 saslc__list_free(list);
1857 return rv;
1858 no_mem:
1859 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
1860 goto out;
1861 }
1862
1863 /**
1864 * @brief creates digestmd5 mechanism session.
1865 * Function initializes also default options for the session.
1866 * @param sess sasl session
1867 * @return 0 on success, -1 on failure.
1868 */
1869 static int
saslc__mech_digestmd5_create(saslc_sess_t * sess)1870 saslc__mech_digestmd5_create(saslc_sess_t *sess)
1871 {
1872 saslc__mech_digestmd5_sess_t *c;
1873
1874 if ((c = calloc(1, sizeof(*c))) == NULL) {
1875 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
1876 return -1;
1877 }
1878 c->rdata.nonce_cnt = 1;
1879 sess->mech_sess = c;
1880
1881 return 0;
1882 }
1883
1884 static void
free_cdata(cdata_t * cdata)1885 free_cdata(cdata_t *cdata)
1886 {
1887
1888 free(cdata->nonce);
1889 saslc__list_free(cdata->realm);
1890 }
1891
1892 static void
free_rdata(rdata_t * rdata)1893 free_rdata(rdata_t *rdata)
1894 {
1895
1896 free(rdata->authcid);
1897 free(rdata->authzid);
1898 free(rdata->cnonce);
1899 free(rdata->digesturi);
1900 if (rdata->passwd != NULL) {
1901 memset(rdata->passwd, 0, strlen(rdata->passwd));
1902 free(rdata->passwd);
1903 }
1904 free(rdata->realm);
1905 }
1906
1907 /**
1908 * @brief destroys digestmd5 mechanism session.
1909 * Function also is freeing assigned resources to the session.
1910 * @param sess sasl session
1911 * @return Functions always returns 0.
1912 */
1913 static int
saslc__mech_digestmd5_destroy(saslc_sess_t * sess)1914 saslc__mech_digestmd5_destroy(saslc_sess_t *sess)
1915 {
1916 saslc__mech_digestmd5_sess_t *ms;
1917
1918 ms = sess->mech_sess;
1919
1920 free_cdata(&ms->cdata);
1921 free_rdata(&ms->rdata);
1922
1923 saslc__buffer32_destroy(ms->dec_ctx.buf_ctx);
1924 saslc__buffer_destroy(ms->enc_ctx.buf_ctx);
1925
1926 cipher_context_destroy(ms->dec_ctx.cph_ctx);
1927 cipher_context_destroy(ms->enc_ctx.cph_ctx);
1928
1929 free(sess->mech_sess);
1930 sess->mech_sess = NULL;
1931
1932 return 0;
1933 }
1934
1935 /**
1936 * @brief collect the response data necessary to build the reply.
1937 * @param sess the session context
1938 * @return 0 on success, -1 on failure
1939 *
1940 * NOTE:
1941 * The input info is from the challenge (previously saved in cdata of
1942 * saslc__mech_digestmd5_sess_t) or from the property dictionaries.
1943 *
1944 * The output info is saved in (mostly) in rdata of the
1945 * saslc__mech_digestmd5_sess_t structure. The qop is special in that
1946 * it is exposed to the saslc__mech_sess_t layer.
1947 */
1948 static int
saslc__mech_digestmd5_response_data(saslc_sess_t * sess)1949 saslc__mech_digestmd5_response_data(saslc_sess_t *sess)
1950 {
1951 saslc__mech_digestmd5_sess_t *ms;
1952 cdata_t *cdata;
1953 rdata_t *rdata;
1954 const char *authcid;
1955 const char *authzid;
1956 const char *hostname;
1957 const char *maxbuf;
1958 const char *passwd;
1959 int rv;
1960
1961 ms = sess->mech_sess;
1962 cdata = &ms->cdata;
1963 rdata = &ms->rdata;
1964
1965 if ((rv = choose_qop(sess, cdata->qop_flags)) == -1)
1966 return -1; /* error message already set */
1967 ms->mech_sess.qop = rv;
1968
1969 if (ms->mech_sess.qop == QOP_CONF) {
1970 if ((rv = choose_cipher(sess, cdata->cipher_flags)) == -1)
1971 return -1; /* error message already set */
1972 rdata->cipher = rv;
1973 }
1974
1975 hostname = saslc_sess_getprop(sess, SASLC_DIGESTMD5_HOSTNAME);
1976 if (hostname == NULL) {
1977 saslc__error_set(ERR(sess), ERROR_MECH,
1978 "hostname is required for authentication");
1979 return -1;
1980 }
1981
1982 rdata->realm = choose_realm(sess, hostname, cdata->realm);
1983 if (rdata->realm == NULL)
1984 return -1; /* error message already set */
1985
1986 rdata->digesturi = saslc__mech_digestmd5_digesturi(sess, hostname);
1987 if (rdata->digesturi == NULL)
1988 return -1; /* error message already set */
1989
1990 authcid = saslc_sess_getprop(sess, SASLC_DIGESTMD5_AUTHCID);
1991 if (authcid == NULL) {
1992 saslc__error_set(ERR(sess), ERROR_MECH,
1993 "authcid is required for an authentication");
1994 return -1;
1995 }
1996 rdata->authcid = strdup(authcid);
1997 if (rdata->authcid == NULL)
1998 goto no_mem;
1999
2000 authzid = saslc_sess_getprop(sess, SASLC_DIGESTMD5_AUTHZID);
2001 if (authzid != NULL) {
2002 rdata->authzid = strdup(authzid);
2003 if (rdata->authzid == NULL)
2004 goto no_mem;
2005 }
2006
2007 passwd = saslc_sess_getprop(sess, SASLC_DIGESTMD5_PASSWD);
2008 if (passwd == NULL) {
2009 saslc__error_set(ERR(sess), ERROR_MECH,
2010 "password is required for an authentication");
2011 return -1;
2012 }
2013 rdata->passwd = strdup(passwd);
2014 if (rdata->passwd == NULL)
2015 goto no_mem;
2016
2017 rdata->cnonce = saslc__mech_digestmd5_nonce(NONCE_LEN);
2018 if (rdata->cnonce == NULL) {
2019 saslc__error_set(ERR(sess), ERROR_MECH,
2020 "failed to create cnonce");
2021 return -1;
2022 }
2023 #ifdef SASLC_DIGESTMD5_CNONCE /* XXX: for debugging! */
2024 {
2025 const char *cnonce;
2026 cnonce = saslc_sess_getprop(sess, SASLC_DIGESTMD5_CNONCE);
2027 if (cnonce != NULL) {
2028 rdata->cnonce = strdup(cnonce);
2029 if (rdata->cnonce == NULL)
2030 goto no_mem;
2031 }
2032 }
2033 #endif
2034 if (ms->mech_sess.qop != QOP_NONE) {
2035 maxbuf = saslc_sess_getprop(sess, SASLC_DIGESTMD5_MAXBUF);
2036 if (maxbuf != NULL)
2037 rdata->maxbuf = (size_t)strtoul(maxbuf, NULL, 10);
2038 if (rdata->maxbuf == 0)
2039 rdata->maxbuf = cdata->maxbuf;
2040 if (INVALID_MAXBUF(rdata->maxbuf)) {
2041 saslc__error_set(ERR(sess), ERROR_MECH,
2042 "maxbuf out of range");
2043 return -1;
2044 }
2045 }
2046 return 0;
2047
2048 no_mem:
2049 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
2050 return -1;
2051 }
2052
2053 /**
2054 * @brief compute the maximum payload that can go into an integrity or
2055 * confidentiality packet.
2056 * @param maxbuf the server's maxbuf size.
2057 * @param blksize the ciphers block size. 0 or 1 if there is no blocking.
2058 * @return the payload size
2059 *
2060 * The packet (not including the leading uint32_t packet length field)
2061 * has this structure:
2062 *
2063 * struct {
2064 * uint8_t payload[]; // packet payload
2065 * uint8_t padding[]; // padding to block size
2066 * uint8_t hmac_0_9[10]; // the first 10 bytes of the hmac
2067 * uint8_t version[2]; // version number (1) in BE format
2068 * uint8_t seqnum[4]; // sequence number in BE format
2069 * } __packed
2070 *
2071 * NOTE: if the block size is > 1, then padding is required to make
2072 * the {payload[], padding[], and hmac_0_9[]} a multiple of the block
2073 * size. Furthermore there must be at least one byte of padding! The
2074 * padding bytes are all set to the padding length and one byte of
2075 * padding is necessary to recover the padding length.
2076 */
2077 static size_t
maxpayload(size_t maxbuf,size_t blksize)2078 maxpayload(size_t maxbuf, size_t blksize)
2079 {
2080 size_t l;
2081
2082 if (blksize <= 1) { /* no padding used */
2083 if (maxbuf <= sizeof(md5hash_t))
2084 return 0;
2085
2086 return maxbuf - sizeof(md5hash_t);
2087 }
2088 if (maxbuf < 2 * blksize + 6)
2089 return 0;
2090
2091 l = rounddown(maxbuf - 6, blksize);
2092 if (l <= 10 + 1) /* we need at least one byte of padding */
2093 return 0;
2094
2095 return l - 10 - 1;
2096 }
2097
2098 /**
2099 * @brief initialize the encode and decode coder contexts for the session
2100 * @param sess the current session
2101 * @return 0 on success, -1 on failure.
2102 */
2103 static int
init_coder_context(saslc_sess_t * sess)2104 init_coder_context(saslc_sess_t *sess)
2105 {
2106 saslc__mech_digestmd5_sess_t *ms;
2107 size_t blksize;
2108 #ifdef SASLC_DIGESTMD5_SELFTEST
2109 int selftest; /* XXX: allow for testing against ourselves */
2110 #endif
2111
2112 ms = sess->mech_sess;
2113 #ifdef SASLC_DIGESTMD5_SELFTEST
2114 selftest = saslc_sess_getprop(sess, SASLC_DIGESTMD5_SELFTEST) != NULL;
2115 #endif
2116 blksize = 0;
2117 switch (ms->mech_sess.qop) {
2118 case QOP_NONE:
2119 return 0;
2120 case QOP_INT:
2121 #ifdef SASLC_DIGESTMD5_SELFTEST
2122 ms->dec_ctx.key = selftest ? ms->keys.kic : ms->keys.kis;
2123 #else
2124 ms->dec_ctx.key = ms->keys.kis;
2125 #endif
2126 ms->enc_ctx.key = ms->keys.kic;
2127 ms->dec_ctx.cph_ctx = NULL;
2128 ms->enc_ctx.cph_ctx = NULL;
2129 break;
2130 case QOP_CONF:
2131 #ifdef SASLC_DIGESTMD5_SELFTEST
2132 ms->dec_ctx.key = selftest ? ms->keys.kcc : ms->keys.kcs;
2133 #else
2134 ms->dec_ctx.key = ms->keys.kcs;
2135 #endif
2136 ms->enc_ctx.key = ms->keys.kcc;
2137 ms->dec_ctx.cph_ctx = cipher_context_create(sess,
2138 ms->rdata.cipher, 0, ms->dec_ctx.key);
2139 if (ms->dec_ctx.cph_ctx == NULL)
2140 return -1;
2141
2142 ms->enc_ctx.cph_ctx = cipher_context_create(sess,
2143 ms->rdata.cipher, 1, ms->enc_ctx.key);
2144 if (ms->enc_ctx.cph_ctx == NULL)
2145 return -1;
2146
2147 blksize = ms->enc_ctx.cph_ctx->blksize;
2148 break;
2149 }
2150 ms->dec_ctx.sess = sess;
2151 ms->enc_ctx.sess = sess;
2152 ms->dec_ctx.buf_ctx = saslc__buffer32_create(sess, ms->rdata.maxbuf);
2153 if (ms->cdata.maxbuf < 2 * blksize + 6) {
2154 saslc__error_set(ERR(sess), ERROR_MECH,
2155 "server buffer too small for packet");
2156 return -1;
2157 }
2158 ms->enc_ctx.buf_ctx = saslc__buffer_create(sess,
2159 maxpayload(ms->cdata.maxbuf, blksize));
2160
2161 if (ms->dec_ctx.buf_ctx == NULL || ms->enc_ctx.buf_ctx == NULL) {
2162 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
2163 return -1;
2164 }
2165 return 0;
2166 }
2167
2168 /**
2169 * @brief construct the reply string.
2170 * @param sess session context
2171 * @param response string
2172 * @return reply string or NULL on failure.
2173 */
2174 static char *
saslc__mech_digestmd5_reply(saslc_sess_t * sess,char * response)2175 saslc__mech_digestmd5_reply(saslc_sess_t *sess, char *response)
2176 {
2177 saslc__mech_digestmd5_sess_t *ms;
2178 char *out;
2179 char *cipher, *maxbuf, *realm;
2180
2181 ms = sess->mech_sess;
2182
2183 out = NULL;
2184 cipher = __UNCONST("");
2185 maxbuf = __UNCONST("");
2186 realm = __UNCONST("");
2187
2188 switch (ms->mech_sess.qop) {
2189 case QOP_CONF:
2190 if (asprintf(&cipher, "cipher=\"%s\",",
2191 cipher_name(ms->rdata.cipher)) == -1) {
2192 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
2193 goto done;
2194 }
2195 /*FALLTHROUGH*/
2196 case QOP_INT:
2197 if (asprintf(&maxbuf, "maxbuf=%zu,", ms->rdata.maxbuf) == -1) {
2198 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
2199 goto done;
2200 }
2201 break;
2202 case QOP_NONE:
2203 break;
2204 default:
2205 assert(/*CONSTCOND*/0);
2206 return NULL;
2207 }
2208 if (ms->rdata.realm != NULL &&
2209 asprintf(&realm, "realm=\"%s\",", ms->rdata.realm) == -1) {
2210 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
2211 goto done;
2212 }
2213
2214 if (asprintf(&out,
2215 "username=\"%s\","
2216 "%s" /* realm= */
2217 "nonce=\"%s\","
2218 "cnonce=\"%s\","
2219 "nc=%08d,"
2220 "qop=%s,"
2221 "%s" /* cipher= */
2222 "%s" /* maxbuf= */
2223 "digest-uri=\"%s\","
2224 "response=%s",
2225 ms->rdata.authcid,
2226 realm,
2227 ms->cdata.nonce,
2228 ms->rdata.cnonce,
2229 ms->rdata.nonce_cnt,
2230 saslc__mech_qop_name(ms->mech_sess.qop),
2231 cipher,
2232 maxbuf,
2233 ms->rdata.digesturi,
2234 response
2235 ) == -1) {
2236 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
2237 out = NULL;
2238 }
2239 done:
2240 if (realm[0] != '\0')
2241 free(realm);
2242 if (maxbuf[0] != '\0')
2243 free(maxbuf);
2244 if (cipher[0] != '\0')
2245 free(cipher);
2246
2247 return out;
2248 }
2249
2250 /**
2251 * @brief do one step of the sasl authentication
2252 * @param sess sasl session
2253 * @param in input data
2254 * @param inlen input data length
2255 * @param out place to store output data
2256 * @param outlen output data length
2257 * @return MECH_OK - authentication successful,
2258 * MECH_STEP - more steps are needed,
2259 * MECH_ERROR - error
2260 */
2261 static int
saslc__mech_digestmd5_cont(saslc_sess_t * sess,const void * in,size_t inlen,void ** out,size_t * outlen)2262 saslc__mech_digestmd5_cont(saslc_sess_t *sess, const void *in, size_t inlen,
2263 void **out, size_t *outlen)
2264 {
2265 saslc__mech_digestmd5_sess_t *ms;
2266 char *response;
2267 const char *p;
2268
2269 ms = sess->mech_sess;
2270
2271 switch(ms->mech_sess.step) {
2272 case 0:
2273 /* in case we are called before getting data from server */
2274 if (inlen == 0) {
2275 *out = NULL;
2276 *outlen = 0;
2277 return MECH_STEP;
2278 }
2279 /* if input data was provided, then doing the first step */
2280 ms->mech_sess.step++;
2281 /*FALLTHROUGH*/
2282 case 1:
2283 if (saslc__mech_digestmd5_parse_challenge(sess, in) == -1)
2284 return MECH_ERROR;
2285
2286 if (saslc__mech_digestmd5_response_data(sess) == -1)
2287 return MECH_ERROR;
2288
2289 if ((response = saslc__mech_digestmd5_response(ms,
2290 "AUTHENTICATE")) == NULL) {
2291 saslc__error_set(ERR(sess), ERROR_MECH,
2292 "unable to construct response");
2293 return MECH_ERROR;
2294 }
2295 *out = saslc__mech_digestmd5_reply(sess, response);
2296 free(response);
2297 if (out == NULL)
2298 return MECH_ERROR;
2299
2300 *outlen = strlen(*out);
2301 return MECH_STEP;
2302 case 2:
2303 if ((response = saslc__mech_digestmd5_response(ms, ""))
2304 == NULL) {
2305 saslc__error_set(ERR(sess), ERROR_MECH,
2306 "unable to construct rspauth");
2307 return MECH_ERROR;
2308 }
2309 p = in;
2310 if (strncmp(p, "rspauth=", 8) != 0 ||
2311 strcmp(response, p + 8) != 0) {
2312 saslc__msg_dbg("rspauth='%s'\n", response);
2313 saslc__error_set(ERR(sess), ERROR_MECH,
2314 "failed to validate rspauth response");
2315 free(response);
2316 return MECH_ERROR;
2317 }
2318 free(response);
2319 if (init_coder_context(sess) == -1)
2320 return MECH_ERROR;
2321 *out = NULL;
2322 *outlen = 0;
2323 return MECH_OK;
2324 default:
2325 assert(/*CONSTCOND*/0); /* impossible */
2326 return MECH_ERROR;
2327 }
2328 }
2329
2330 /* mechanism definition */
2331 const saslc__mech_t saslc__mech_digestmd5 = {
2332 .name = "DIGEST-MD5",
2333 .flags = FLAG_MUTUAL | FLAG_DICTIONARY,
2334 .create = saslc__mech_digestmd5_create,
2335 .cont = saslc__mech_digestmd5_cont,
2336 .encode = saslc__mech_digestmd5_encode,
2337 .decode = saslc__mech_digestmd5_decode,
2338 .destroy = saslc__mech_digestmd5_destroy
2339 };
2340