Branch data Line data Source code
1 : : /* parser.c --- DIGEST-MD5 parser.
2 : : * Copyright (C) 2002-2012 Simon Josefsson
3 : : *
4 : : * This file is part of GNU SASL Library.
5 : : *
6 : : * GNU SASL Library is free software; you can redistribute it and/or
7 : : * modify it under the terms of the GNU Lesser General Public License
8 : : * as published by the Free Software Foundation; either version 2.1 of
9 : : * the License, or (at your option) any later version.
10 : : *
11 : : * GNU SASL Library is distributed in the hope that it will be useful,
12 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : : * Lesser General Public License for more details.
15 : : *
16 : : * You should have received a copy of the GNU Lesser General Public
17 : : * License along with GNU SASL Library; if not, write to the Free
18 : : * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 : : * Boston, MA 02110-1301, USA.
20 : : *
21 : : */
22 : :
23 : : #ifdef HAVE_CONFIG_H
24 : : #include "config.h"
25 : : #endif
26 : :
27 : : /* Get prototypes. */
28 : : #include "parser.h"
29 : :
30 : : /* Get malloc, free. */
31 : : #include <stdlib.h>
32 : :
33 : : /* Get memcpy, strlen. */
34 : : #include <string.h>
35 : :
36 : : /* Get validator. */
37 : : #include "validate.h"
38 : :
39 : : #define DEFAULT_CHARSET "utf-8"
40 : : #define DEFAULT_ALGORITHM "md5-sess"
41 : :
42 : : enum
43 : : {
44 : : /* the order must match the following struct */
45 : : CHALLENGE_REALM = 0,
46 : : CHALLENGE_NONCE,
47 : : CHALLENGE_QOP,
48 : : CHALLENGE_STALE,
49 : : CHALLENGE_MAXBUF,
50 : : CHALLENGE_CHARSET,
51 : : CHALLENGE_ALGORITHM,
52 : : CHALLENGE_CIPHER
53 : : };
54 : :
55 : : static const char *const digest_challenge_opts[] = {
56 : : /* the order must match the previous enum */
57 : : "realm",
58 : : "nonce",
59 : : "qop",
60 : : "stale",
61 : : "maxbuf",
62 : : "charset",
63 : : "algorithm",
64 : : "cipher",
65 : : NULL
66 : : };
67 : :
68 : : /* qop-value = "auth" | "auth-int" | "auth-conf" | qop-token */
69 : : enum
70 : : {
71 : : /* the order must match the following struct */
72 : : QOP_AUTH = 0,
73 : : QOP_AUTH_INT,
74 : : QOP_AUTH_CONF
75 : : };
76 : :
77 : : static const char *const qop_opts[] = {
78 : : /* the order must match the previous enum */
79 : : "auth",
80 : : "auth-int",
81 : : "auth-conf",
82 : : NULL
83 : : };
84 : :
85 : : /* cipher-value = "3des" | "des" | "rc4-40" | "rc4" |
86 : : * "rc4-56" | "aes-cbc" | cipher-token
87 : : * ;; "des" and "3des" ciphers are obsolete.
88 : : */
89 : : enum
90 : : {
91 : : /* the order must match the following struct */
92 : : CIPHER_DES = 0,
93 : : CIPHER_3DES,
94 : : CIPHER_RC4,
95 : : CIPHER_RC4_40,
96 : : CIPHER_RC4_56,
97 : : CIPHER_AES_CBC
98 : : };
99 : :
100 : : static const char *const cipher_opts[] = {
101 : : /* the order must match the previous enum */
102 : : "des",
103 : : "3des",
104 : : "rc4",
105 : : "rc4-40",
106 : : "rc4-56",
107 : : "aes-cbc",
108 : : NULL
109 : : };
110 : :
111 : : static int
112 : 25 : parse_challenge (char *challenge, digest_md5_challenge * out)
113 : : {
114 : 25 : int done_algorithm = 0;
115 : 25 : int disable_qop_auth_conf = 0;
116 : : char *value;
117 : :
118 : 25 : memset (out, 0, sizeof (*out));
119 : :
120 : : /* The size of a digest-challenge MUST be less than 2048 bytes. */
121 [ - + ]: 25 : if (strlen (challenge) >= 2048)
122 : 0 : return -1;
123 : :
124 [ + + ]: 114 : while (*challenge != '\0')
125 [ + + + - : 89 : switch (digest_md5_getsubopt (&challenge, digest_challenge_opts, &value))
- + + +
+ ]
126 : : {
127 : : case CHALLENGE_REALM:
128 : : {
129 : : char **tmp;
130 : 8 : out->nrealms++;
131 : 8 : tmp = realloc (out->realms, out->nrealms * sizeof (*out->realms));
132 [ - + ]: 8 : if (!tmp)
133 : 0 : return -1;
134 : 8 : out->realms = tmp;
135 : 8 : out->realms[out->nrealms - 1] = strdup (value);
136 [ - + ]: 8 : if (!out->realms[out->nrealms - 1])
137 : 0 : return -1;
138 : : }
139 : 8 : break;
140 : :
141 : : case CHALLENGE_NONCE:
142 : : /* This directive is required and MUST appear exactly once; if
143 : : not present, or if multiple instances are present, the
144 : : client should abort the authentication exchange. */
145 [ - + ]: 20 : if (out->nonce)
146 : 0 : return -1;
147 : 20 : out->nonce = strdup (value);
148 [ - + ]: 20 : if (!out->nonce)
149 : 0 : return -1;
150 : 20 : break;
151 : :
152 : : case CHALLENGE_QOP:
153 : : /* <<What if this directive is present multiple times? Error,
154 : : or take the union of all values?>> */
155 [ - + ]: 17 : if (out->qops)
156 : 0 : return -1;
157 : : {
158 : : char *subsubopts;
159 : : char *val;
160 : :
161 : 17 : subsubopts = value;
162 [ + + ]: 37 : while (*subsubopts != '\0')
163 [ + + + - ]: 20 : switch (digest_md5_getsubopt (&subsubopts, qop_opts, &val))
164 : : {
165 : : case QOP_AUTH:
166 : 15 : out->qops |= DIGEST_MD5_QOP_AUTH;
167 : 15 : break;
168 : :
169 : : case QOP_AUTH_INT:
170 : 3 : out->qops |= DIGEST_MD5_QOP_AUTH_INT;
171 : 3 : break;
172 : :
173 : : case QOP_AUTH_CONF:
174 : 2 : out->qops |= DIGEST_MD5_QOP_AUTH_CONF;
175 : 2 : break;
176 : :
177 : : default:
178 : : /* The client MUST ignore unrecognized options */
179 : 0 : break;
180 : : }
181 : : }
182 : : /* if the client recognizes no cipher, it MUST behave as if
183 : : "auth-conf" qop option wasn't provided by the server. */
184 [ - + ]: 17 : if (disable_qop_auth_conf)
185 : 0 : out->qops &= ~DIGEST_MD5_QOP_AUTH_CONF;
186 : : /* if the client recognizes no option, it MUST abort the
187 : : authentication exchange. */
188 [ - + ]: 17 : if (!out->qops)
189 : 0 : return -1;
190 : 17 : break;
191 : :
192 : : case CHALLENGE_STALE:
193 : : /* This directive may appear at most once; if multiple
194 : : instances are present, the client MUST abort the
195 : : authentication exchange. */
196 [ # # ]: 0 : if (out->stale)
197 : 0 : return -1;
198 : 0 : out->stale = 1;
199 : 0 : break;
200 : :
201 : : case CHALLENGE_MAXBUF:
202 : : /* This directive may appear at most once; if multiple
203 : : instances are present, or the value is out of range the
204 : : client MUST abort the authentication exchange. */
205 [ # # ]: 0 : if (out->servermaxbuf)
206 : 0 : return -1;
207 : 0 : out->servermaxbuf = strtoul (value, NULL, 10);
208 : : /* FIXME: error handling. */
209 : : /* The value MUST be bigger than 16 (32 for Confidentiality
210 : : protection with the "aes-cbc" cipher) and smaller or equal
211 : : to 16777215 (i.e. 2**24-1). */
212 [ # # ][ # # ]: 0 : if (out->servermaxbuf <= 16 || out->servermaxbuf > 16777215)
213 : 0 : return -1;
214 : 0 : break;
215 : :
216 : : case CHALLENGE_CHARSET:
217 : : /* This directive may appear at most once; if multiple
218 : : instances are present, the client MUST abort the
219 : : authentication exchange. */
220 [ - + ]: 15 : if (out->utf8)
221 : 0 : return -1;
222 [ - + ]: 15 : if (strcmp (DEFAULT_CHARSET, value) != 0)
223 : 0 : return -1;
224 : 15 : out->utf8 = 1;
225 : 15 : break;
226 : :
227 : : case CHALLENGE_ALGORITHM:
228 : : /* This directive is required and MUST appear exactly once; if
229 : : not present, or if multiple instances are present, the
230 : : client SHOULD abort the authentication exchange. */
231 [ - + ]: 20 : if (done_algorithm)
232 : 0 : return -1;
233 [ - + ]: 20 : if (strcmp (DEFAULT_ALGORITHM, value) != 0)
234 : 0 : return -1;
235 : 20 : done_algorithm = 1;
236 : 20 : break;
237 : :
238 : :
239 : : case CHALLENGE_CIPHER:
240 : : /* This directive must be present exactly once if "auth-conf"
241 : : is offered in the "qop-options" directive */
242 [ - + ]: 2 : if (out->ciphers)
243 : 0 : return -1;
244 : : {
245 : : char *subsubopts;
246 : : char *val;
247 : :
248 : 2 : subsubopts = value;
249 [ + + ]: 4 : while (*subsubopts != '\0')
250 [ + - - - : 2 : switch (digest_md5_getsubopt (&subsubopts, cipher_opts, &val))
- - - ]
251 : : {
252 : : case CIPHER_DES:
253 : 2 : out->ciphers |= DIGEST_MD5_CIPHER_DES;
254 : 2 : break;
255 : :
256 : : case CIPHER_3DES:
257 : 0 : out->ciphers |= DIGEST_MD5_CIPHER_3DES;
258 : 0 : break;
259 : :
260 : : case CIPHER_RC4:
261 : 0 : out->ciphers |= DIGEST_MD5_CIPHER_RC4;
262 : 0 : break;
263 : :
264 : : case CIPHER_RC4_40:
265 : 0 : out->ciphers |= DIGEST_MD5_CIPHER_RC4_40;
266 : 0 : break;
267 : :
268 : : case CIPHER_RC4_56:
269 : 0 : out->ciphers |= DIGEST_MD5_CIPHER_RC4_56;
270 : 0 : break;
271 : :
272 : : case CIPHER_AES_CBC:
273 : 0 : out->ciphers |= DIGEST_MD5_CIPHER_AES_CBC;
274 : 0 : break;
275 : :
276 : : default:
277 : : /* The client MUST ignore unrecognized ciphers */
278 : 0 : break;
279 : : }
280 : : }
281 : : /* if the client recognizes no cipher, it MUST behave as if
282 : : "auth-conf" qop option wasn't provided by the server. */
283 [ - + ]: 2 : if (!out->ciphers)
284 : : {
285 : 0 : disable_qop_auth_conf = 1;
286 [ # # ]: 0 : if (out->qops)
287 : : {
288 : : /* if the client recognizes no option, it MUST abort the
289 : : authentication exchange. */
290 : 0 : out->qops &= ~DIGEST_MD5_QOP_AUTH_CONF;
291 [ # # ]: 0 : if (!out->qops)
292 : 0 : return -1;
293 : : }
294 : : }
295 : 2 : break;
296 : :
297 : : default:
298 : : /* The client MUST ignore any unrecognized directives. */
299 : 7 : break;
300 : : }
301 : :
302 : : /* This directive is required and MUST appear exactly once; if
303 : : not present, or if multiple instances are present, the
304 : : client SHOULD abort the authentication exchange. */
305 [ + + ]: 25 : if (!done_algorithm)
306 : 5 : return -1;
307 : :
308 : : /* Validate that we have the mandatory fields. */
309 [ + + ]: 20 : if (digest_md5_validate_challenge (out) != 0)
310 : 2 : return -1;
311 : :
312 : 25 : return 0;
313 : : }
314 : :
315 : : enum
316 : : {
317 : : /* the order must match the following struct */
318 : : RESPONSE_USERNAME = 0,
319 : : RESPONSE_REALM,
320 : : RESPONSE_NONCE,
321 : : RESPONSE_CNONCE,
322 : : RESPONSE_NC,
323 : : RESPONSE_QOP,
324 : : RESPONSE_DIGEST_URI,
325 : : RESPONSE_RESPONSE,
326 : : RESPONSE_MAXBUF,
327 : : RESPONSE_CHARSET,
328 : : RESPONSE_CIPHER,
329 : : RESPONSE_AUTHZID
330 : : };
331 : :
332 : : static const char *const digest_response_opts[] = {
333 : : /* the order must match the previous enum */
334 : : "username",
335 : : "realm",
336 : : "nonce",
337 : : "cnonce",
338 : : "nc",
339 : : "qop",
340 : : "digest-uri",
341 : : "response",
342 : : "maxbuf",
343 : : "charset",
344 : : "cipher",
345 : : "authzid",
346 : : NULL
347 : : };
348 : :
349 : : static int
350 : 17 : parse_response (char *response, digest_md5_response * out)
351 : : {
352 : : char *value;
353 : :
354 : 17 : memset (out, 0, sizeof (*out));
355 : :
356 : : /* The size of a digest-response MUST be less than 4096 bytes. */
357 [ - + ]: 17 : if (strlen (response) >= 4096)
358 : 0 : return -1;
359 : :
360 [ + + ]: 157 : while (*response != '\0')
361 [ + + + + : 140 : switch (digest_md5_getsubopt (&response, digest_response_opts, &value))
+ + + + -
+ - + + ]
362 : : {
363 : : case RESPONSE_USERNAME:
364 : : /* This directive is required and MUST be present exactly
365 : : once; otherwise, authentication fails. */
366 [ - + ]: 16 : if (out->username)
367 : 0 : return -1;
368 : 16 : out->username = strdup (value);
369 [ - + ]: 16 : if (!out->username)
370 : 0 : return -1;
371 : 16 : break;
372 : :
373 : : case RESPONSE_REALM:
374 : : /* This directive is required if the server provided any
375 : : realms in the "digest-challenge", in which case it may
376 : : appear exactly once and its value SHOULD be one of those
377 : : realms. */
378 [ - + ]: 7 : if (out->realm)
379 : 0 : return -1;
380 : 7 : out->realm = strdup (value);
381 [ - + ]: 7 : if (!out->realm)
382 : 0 : return -1;
383 : 7 : break;
384 : :
385 : : case RESPONSE_NONCE:
386 : : /* This directive is required and MUST be present exactly
387 : : once; otherwise, authentication fails. */
388 [ - + ]: 16 : if (out->nonce)
389 : 0 : return -1;
390 : 16 : out->nonce = strdup (value);
391 [ - + ]: 16 : if (!out->nonce)
392 : 0 : return -1;
393 : 16 : break;
394 : :
395 : : case RESPONSE_CNONCE:
396 : : /* This directive is required and MUST be present exactly once;
397 : : otherwise, authentication fails. */
398 [ - + ]: 16 : if (out->cnonce)
399 : 0 : return -1;
400 : 16 : out->cnonce = strdup (value);
401 [ - + ]: 16 : if (!out->cnonce)
402 : 0 : return -1;
403 : 16 : break;
404 : :
405 : : case RESPONSE_NC:
406 : : /* This directive is required and MUST be present exactly
407 : : once; otherwise, authentication fails. */
408 [ - + ]: 16 : if (out->nc)
409 : 0 : return -1;
410 : : /* nc-value = 8LHEX */
411 [ - + ]: 16 : if (strlen (value) != 8)
412 : 0 : return -1;
413 : 16 : out->nc = strtoul (value, NULL, 16);
414 : : /* FIXME: error handling. */
415 : 16 : break;
416 : :
417 : : case RESPONSE_QOP:
418 : : /* If present, it may appear exactly once and its value MUST
419 : : be one of the alternatives in qop-options. */
420 [ - + ]: 15 : if (out->qop)
421 : 0 : return -1;
422 [ + + ]: 15 : if (strcmp (value, "auth") == 0)
423 : 13 : out->qop = DIGEST_MD5_QOP_AUTH;
424 [ + - ]: 2 : else if (strcmp (value, "auth-int") == 0)
425 : 2 : out->qop = DIGEST_MD5_QOP_AUTH_INT;
426 [ # # ]: 0 : else if (strcmp (value, "auth-conf") == 0)
427 : 0 : out->qop = DIGEST_MD5_QOP_AUTH_CONF;
428 : : else
429 : 0 : return -1;
430 : 15 : break;
431 : :
432 : : case RESPONSE_DIGEST_URI:
433 : : /* This directive is required and MUST be present exactly
434 : : once; if multiple instances are present, the client MUST
435 : : abort the authentication exchange. */
436 [ - + ]: 16 : if (out->digesturi)
437 : 0 : return -1;
438 : : /* FIXME: sub-parse. */
439 : 16 : out->digesturi = strdup (value);
440 [ - + ]: 16 : if (!out->digesturi)
441 : 0 : return -1;
442 : 16 : break;
443 : :
444 : : case RESPONSE_RESPONSE:
445 : : /* This directive is required and MUST be present exactly
446 : : once; otherwise, authentication fails. */
447 [ - + ]: 16 : if (*out->response)
448 : 0 : return -1;
449 : : /* A string of 32 hex digits */
450 [ - + ]: 16 : if (strlen (value) != DIGEST_MD5_RESPONSE_LENGTH)
451 : 0 : return -1;
452 : 16 : strcpy (out->response, value);
453 : 16 : break;
454 : :
455 : : case RESPONSE_MAXBUF:
456 : : /* This directive may appear at most once; if multiple
457 : : instances are present, the server MUST abort the
458 : : authentication exchange. */
459 [ # # ]: 0 : if (out->clientmaxbuf)
460 : 0 : return -1;
461 : 0 : out->clientmaxbuf = strtoul (value, NULL, 10);
462 : : /* FIXME: error handling. */
463 : : /* If the value is less or equal to 16 (<<32 for aes-cbc>>) or
464 : : bigger than 16777215 (i.e. 2**24-1), the server MUST abort
465 : : the authentication exchange. */
466 [ # # ][ # # ]: 0 : if (out->clientmaxbuf <= 16 || out->clientmaxbuf > 16777215)
467 : 0 : return -1;
468 : 0 : break;
469 : :
470 : : case RESPONSE_CHARSET:
471 [ - + ]: 15 : if (strcmp (DEFAULT_CHARSET, value) != 0)
472 : 0 : return -1;
473 : 15 : out->utf8 = 1;
474 : 15 : break;
475 : :
476 : : case RESPONSE_CIPHER:
477 [ # # ]: 0 : if (out->cipher)
478 : 0 : return -1;
479 [ # # ]: 0 : if (strcmp (value, "3des") == 0)
480 : 0 : out->cipher = DIGEST_MD5_CIPHER_3DES;
481 [ # # ]: 0 : else if (strcmp (value, "des") == 0)
482 : 0 : out->cipher = DIGEST_MD5_CIPHER_DES;
483 [ # # ]: 0 : else if (strcmp (value, "rc4-40") == 0)
484 : 0 : out->cipher = DIGEST_MD5_CIPHER_RC4_40;
485 [ # # ]: 0 : else if (strcmp (value, "rc4") == 0)
486 : 0 : out->cipher = DIGEST_MD5_CIPHER_RC4;
487 [ # # ]: 0 : else if (strcmp (value, "rc4-56") == 0)
488 : 0 : out->cipher = DIGEST_MD5_CIPHER_RC4_56;
489 [ # # ]: 0 : else if (strcmp (value, "aes-cbc") == 0)
490 : 0 : out->cipher = DIGEST_MD5_CIPHER_AES_CBC;
491 : : else
492 : 0 : return -1;
493 : 0 : break;
494 : :
495 : : case RESPONSE_AUTHZID:
496 : : /* This directive may appear at most once; if multiple
497 : : instances are present, the server MUST abort the
498 : : authentication exchange. <<FIXME NOT IN DRAFT>> */
499 [ - + ]: 5 : if (out->authzid)
500 : 0 : return -1;
501 : : /* The authzid MUST NOT be an empty string. */
502 [ - + ]: 5 : if (*value == '\0')
503 : 0 : return -1;
504 : 5 : out->authzid = strdup (value);
505 [ - + ]: 5 : if (!out->authzid)
506 : 0 : return -1;
507 : 5 : break;
508 : :
509 : : default:
510 : : /* The client MUST ignore any unrecognized directives. */
511 : 2 : break;
512 : : }
513 : :
514 : : /* Validate that we have the mandatory fields. */
515 [ + + ]: 17 : if (digest_md5_validate_response (out) != 0)
516 : 1 : return -1;
517 : :
518 : 17 : return 0;
519 : : }
520 : :
521 : : enum
522 : : {
523 : : /* the order must match the following struct */
524 : : RESPONSEAUTH_RSPAUTH = 0
525 : : };
526 : :
527 : : static const char *const digest_responseauth_opts[] = {
528 : : /* the order must match the previous enum */
529 : : "rspauth",
530 : : NULL
531 : : };
532 : :
533 : : static int
534 : 17 : parse_finish (char *finish, digest_md5_finish * out)
535 : : {
536 : : char *value;
537 : :
538 : 17 : memset (out, 0, sizeof (*out));
539 : :
540 : : /* The size of a response-auth MUST be less than 2048 bytes. */
541 [ - + ]: 17 : if (strlen (finish) >= 2048)
542 : 0 : return -1;
543 : :
544 [ + + ]: 35 : while (*finish != '\0')
545 [ + + ]: 18 : switch (digest_md5_getsubopt (&finish, digest_responseauth_opts, &value))
546 : : {
547 : : case RESPONSEAUTH_RSPAUTH:
548 [ - + ]: 16 : if (*out->rspauth)
549 : 0 : return -1;
550 : : /* A string of 32 hex digits */
551 [ - + ]: 16 : if (strlen (value) != DIGEST_MD5_RESPONSE_LENGTH)
552 : 0 : return -1;
553 : 16 : strcpy (out->rspauth, value);
554 : 16 : break;
555 : :
556 : : default:
557 : : /* The client MUST ignore any unrecognized directives. */
558 : 2 : break;
559 : : }
560 : :
561 : : /* Validate that we have the mandatory fields. */
562 [ + + ]: 17 : if (digest_md5_validate_finish (out) != 0)
563 : 1 : return -1;
564 : :
565 : 17 : return 0;
566 : : }
567 : :
568 : : int
569 : 25 : digest_md5_parse_challenge (const char *challenge, size_t len,
570 : : digest_md5_challenge * out)
571 : : {
572 [ + + ]: 25 : char *subopts = len ? strndup (challenge, len) : strdup (challenge);
573 : : int rc;
574 : :
575 [ - + ]: 25 : if (!subopts)
576 : 0 : return -1;
577 : :
578 : 25 : rc = parse_challenge (subopts, out);
579 : :
580 : 25 : free (subopts);
581 : :
582 : 25 : return rc;
583 : : }
584 : :
585 : : int
586 : 17 : digest_md5_parse_response (const char *response, size_t len,
587 : : digest_md5_response * out)
588 : : {
589 [ + + ]: 17 : char *subopts = len ? strndup (response, len) : strdup (response);
590 : : int rc;
591 : :
592 [ - + ]: 17 : if (!subopts)
593 : 0 : return -1;
594 : :
595 : 17 : rc = parse_response (subopts, out);
596 : :
597 : 17 : free (subopts);
598 : :
599 : 17 : return rc;
600 : : }
601 : :
602 : : int
603 : 17 : digest_md5_parse_finish (const char *finish, size_t len,
604 : : digest_md5_finish * out)
605 : : {
606 [ + + ]: 17 : char *subopts = len ? strndup (finish, len) : strdup (finish);
607 : : int rc;
608 : :
609 [ - + ]: 17 : if (!subopts)
610 : 0 : return -1;
611 : :
612 : 17 : rc = parse_finish (subopts, out);
613 : :
614 : 17 : free (subopts);
615 : :
616 : 17 : return rc;
617 : : }
|