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