Pull fix for bug #436 from branch_1_1
[freeradius.git] / src / modules / rlm_digest / rlm_digest.c
1 /*
2  * rlm_chap.c
3  *
4  * Version:  $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program 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
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2002,2006  The FreeRADIUS server project
21  * Copyright 2002  Alan DeKok <aland@ox.org>
22  */
23
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29
30 static int digest_authorize(void *instance, REQUEST *request)
31 {
32         VALUE_PAIR *vp;
33
34         /* quiet the compiler */
35         instance = instance;
36
37         /*
38          *      We need both of these attributes to do the authentication.
39          */
40         vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
41         if (vp == NULL) {
42                 return RLM_MODULE_NOOP;
43         }
44
45         /*
46          *      Check the sanity of the attribute.
47          */
48         if (vp->length != 32) {
49                 DEBUG("ERROR: Received invalid Digest-Response attribute (length %d should be 32)", vp->length);
50                 return RLM_MODULE_INVALID;
51         }
52
53         /*
54          *      We need these, too.
55          */
56         vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
57         if (vp == NULL) {
58                 DEBUG("ERROR: Received Digest-Response without Digest-Attributes");
59                 return RLM_MODULE_INVALID;
60         }
61
62         /*
63          *      Everything's OK, add a digest authentication type.
64          */
65         if (pairfind(request->config_items, PW_AUTHTYPE) == NULL) {
66                 DEBUG("rlm_digest: Adding Auth-Type = DIGEST");
67                 pairadd(&request->config_items,
68                         pairmake("Auth-Type", "DIGEST", T_OP_EQ));
69         }
70
71         return RLM_MODULE_OK;
72 }
73
74 /*
75  *      Perform all of the wondrous variants of digest authentication.
76  */
77 static int digest_authenticate(void *instance, REQUEST *request)
78 {
79         int i;
80         size_t a1_len, a2_len, kd_len;
81         uint8_t a1[(MAX_STRING_LEN + 1) * 5]; /* can be 5 attributes */
82         uint8_t a2[(MAX_STRING_LEN + 1) * 3]; /* can be 3 attributes */
83         uint8_t kd[(MAX_STRING_LEN + 1) * 5];
84         uint8_t hash[16];       /* MD5 output */
85         VALUE_PAIR *vp, *passwd, *algo;
86         VALUE_PAIR *qop, *nonce;
87
88         instance = instance;    /* -Wunused */
89
90         /*
91          *      We require access to the plain-text password.
92          */
93         passwd = pairfind(request->config_items, PW_DIGEST_HA1);
94         if (passwd) {
95                 if (passwd->length != 32) {
96                         radlog(L_AUTH, "rlm_digest: Digest-HA1 has invalid length, authentication failed.");
97                         return RLM_MODULE_INVALID;
98                 }
99         } else {
100                 passwd = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
101         }
102         if (!passwd) {
103                 radlog(L_AUTH, "rlm_digest: Cleartext-Password or Digest-HA1 is required for authentication.");
104                 return RLM_MODULE_INVALID;
105         }
106
107         /*
108          *      We need these, too.
109          */
110         vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
111         if (vp == NULL) {
112                 DEBUG("ERROR: You set 'Auth-Type = Digest' for a request that did not contain any digest attributes!");
113                 return RLM_MODULE_INVALID;
114         }
115
116         /*
117          *      Loop through the Digest-Attributes, sanity checking them.
118          */
119         DEBUG("    rlm_digest: Converting Digest-Attributes to something sane...");
120         while (vp) {
121                 int length = vp->length;
122                 int attrlen;
123                 uint8_t *p = &vp->vp_octets[0];
124                 VALUE_PAIR *sub;
125
126                 /*
127                  *      Until this stupidly encoded attribure is exhausted.
128                  */
129                 while (length > 0) {
130                         /*
131                          *      The attribute type must be valid
132                          */
133                         if ((p[0] == 0) || (p[0] > 10)) {
134                                 DEBUG("ERROR: Received Digest-Attributes with invalid sub-attribute %d", p[0]);
135                                 return RLM_MODULE_INVALID;
136                         }
137
138                         attrlen = p[1]; /* stupid VSA format */
139
140                         /*
141                          *      Too short.
142                          */
143                         if (attrlen < 3) {
144                                 DEBUG("ERROR: Received Digest-Attributes with short sub-attribute %d, of length %d", p[0], attrlen);
145                                 return RLM_MODULE_INVALID;
146                         }
147
148                         /*
149                          *      Too long.
150                          */
151                         if (attrlen > length) {
152                                 DEBUG("ERROR: Received Digest-Attributes with long sub-attribute %d, of length %d", p[0], attrlen);
153                                 return RLM_MODULE_INVALID;
154                         }
155
156                         /*
157                          *      Create a new attribute, broken out of
158                          *      the stupid sub-attribute crap.
159                          *
160                          *      Didn't they know that VSA's exist?
161                          */
162                         sub = paircreate(PW_DIGEST_REALM - 1 + p[0],
163                                          PW_TYPE_STRING);
164                         if (!sub) {
165                                 return RLM_MODULE_FAIL; /* out of memory */
166                         }
167                         memcpy(&sub->vp_octets[0], &p[2], attrlen - 2);
168                         sub->vp_octets[attrlen - 2] = '\0';
169                         sub->length = attrlen - 2;
170
171                         if (debug_flag) {
172                           putchar('\t');
173                           vp_print(stdout, sub);
174                           putchar('\n');
175                         }
176
177                         /*
178                          *      And add it to the request pairs.
179                          */
180                         pairadd(&request->packet->vps, sub);
181
182                         /*
183                          *      FIXME: Check for the existence
184                          *      of the necessary attributes!
185                          */
186
187                         length -= attrlen;
188                         p += attrlen;
189                 } /* loop over this one attribute */
190
191                 /*
192                  *      Find the next one, if it exists.
193                  */
194                 vp = pairfind(vp->next, PW_DIGEST_ATTRIBUTES);
195         }
196
197         /*
198          *      We require access to the Digest-Nonce-Value
199          */
200         nonce = pairfind(request->packet->vps, PW_DIGEST_NONCE);
201         if (!nonce) {
202                 DEBUG("ERROR: No Digest-Nonce: Cannot perform Digest authentication");
203                 return RLM_MODULE_INVALID;
204         }
205
206         /*
207          *      A1 = Digest-User-Name ":" Realm ":" Password
208          */
209         vp = pairfind(request->packet->vps, PW_DIGEST_USER_NAME);
210         if (!vp) {
211                 DEBUG("ERROR: No Digest-User-Name: Cannot perform Digest authentication");
212                 return RLM_MODULE_INVALID;
213         }
214         memcpy(&a1[0], &vp->vp_octets[0], vp->length);
215         a1_len = vp->length;
216
217         a1[a1_len] = ':';
218         a1_len++;
219
220         vp = pairfind(request->packet->vps, PW_DIGEST_REALM);
221         if (!vp) {
222                 DEBUG("ERROR: No Digest-Realm: Cannot perform Digest authentication");
223                 return RLM_MODULE_INVALID;
224         }
225         memcpy(&a1[a1_len], &vp->vp_octets[0], vp->length);
226         a1_len += vp->length;
227
228         a1[a1_len] = ':';
229         a1_len++;
230
231         if (passwd->attribute == PW_CLEARTEXT_PASSWORD) {
232                 memcpy(&a1[a1_len], &passwd->vp_octets[0], passwd->length);
233                 a1_len += passwd->length;
234                 a1[a1_len] = '\0';
235                 DEBUG2("A1 = %s", a1);
236         } else {
237                 a1[a1_len] = '\0';
238                 DEBUG2("A1 = %s (using Digest-HA1)", a1);
239                 a1_len = 16;
240         }
241
242         /*
243          *      See which variant we calculate.
244          *      Assume MD5 if no Digest-Algorithm attribute received
245          */
246         algo = pairfind(request->packet->vps, PW_DIGEST_ALGORITHM);
247         if ((algo == NULL) || 
248             (strcasecmp(algo->vp_strvalue, "MD5") == 0)) {
249                 /*
250                  *      Set A1 to Digest-HA1 if no User-Password found
251                  */
252                 if (passwd->attribute == PW_DIGEST_HA1) {
253                         if (lrad_hex2bin(passwd->vp_strvalue, &a1[0], 16) != 16) {
254                                 DEBUG2("rlm_digest: Invalid text in Digest-HA1");
255                                 return RLM_MODULE_INVALID;
256                         }
257                 }
258
259         } else if (strcasecmp(algo->vp_strvalue, "MD5-sess") == 0) {
260                 /*
261                  *      K1 = H(A1) : Digest-Nonce ... : H(A2)
262                  *
263                  *      If we find Digest-HA1, we assume it contains
264                  *      H(A1).
265                  */
266                 if (passwd->attribute == PW_CLEARTEXT_PASSWORD) {
267                         librad_md5_calc(hash, &a1[0], a1_len);
268                         lrad_bin2hex(hash, (char *) &a1[0], 16);
269                 } else {        /* MUST be Digest-HA1 */
270                         memcpy(&a1[0], passwd->vp_strvalue, 32);
271                 }
272                 a1_len = 32;
273
274                 a1[a1_len] = ':';
275                 a1_len++;
276
277                 /*
278                  *      Tack on the Digest-Nonce. Length must be even
279                  */
280                 if ((nonce->length & 1) != 0) {
281                         DEBUG("ERROR: Received Digest-Nonce hex string with invalid length: Cannot perform Digest authentication");
282                         return RLM_MODULE_INVALID;
283                 }
284                 memcpy(&a1[a1_len], &nonce->vp_octets[0], nonce->length);
285                 a1_len += nonce->length;
286
287                 a1[a1_len] = ':';
288                 a1_len++;
289
290                 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
291                 if (!vp) {
292                         DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
293                         return RLM_MODULE_INVALID;
294                 }
295
296                 /*
297                  *      Digest-CNonce length must be even
298                  */
299                 if ((vp->length & 1) != 0) {
300                         DEBUG("ERROR: Received Digest-CNonce hex string with invalid length: Cannot perform Digest authentication");
301                         return RLM_MODULE_INVALID;
302                 }
303                 memcpy(&a1[a1_len], &vp->vp_octets[0], vp->length);
304                 a1_len += vp->length;
305
306         } else if ((algo != NULL) &&
307                    (strcasecmp(algo->vp_strvalue, "MD5") != 0)) {
308                 /*
309                  *      We check for "MD5-sess" and "MD5".
310                  *      Anything else is an error.
311                  */
312                 DEBUG("ERROR: Unknown Digest-Algorithm \"%s\": Cannot perform Digest authentication", vp->vp_strvalue);
313                 return RLM_MODULE_INVALID;
314         }
315
316         /*
317          *      A2 = Digest-Method ":" Digest-URI
318          */
319         vp = pairfind(request->packet->vps, PW_DIGEST_METHOD);
320         if (!vp) {
321                 DEBUG("ERROR: No Digest-Method: Cannot perform Digest authentication");
322                 return RLM_MODULE_INVALID;
323         }
324         memcpy(&a2[0], &vp->vp_octets[0], vp->length);
325         a2_len = vp->length;
326
327         a2[a2_len] = ':';
328         a2_len++;
329
330         vp = pairfind(request->packet->vps, PW_DIGEST_URI);
331         if (!vp) {
332                 DEBUG("ERROR: No Digest-URI: Cannot perform Digest authentication");
333                 return RLM_MODULE_INVALID;
334         }
335         memcpy(&a2[a2_len], &vp->vp_octets[0], vp->length);
336         a2_len += vp->length;
337
338         /*
339          *  QOP is "auth-int", tack on ": Digest-Body-Digest"
340          */
341         qop = pairfind(request->packet->vps, PW_DIGEST_QOP);
342         if ((qop != NULL) &&
343             (strcasecmp(qop->vp_strvalue, "auth-int") == 0)) {
344                 VALUE_PAIR *body;
345
346                 /*
347                  *      Add in Digest-Body-Digest
348                  */
349                 a2[a2_len] = ':';
350                 a2_len++;
351
352                 /*
353                  *  Must be a hex representation of an MD5 digest.
354                  */
355                 body = pairfind(request->packet->vps, PW_DIGEST_BODY_DIGEST);
356                 if (!body) {
357                         DEBUG("ERROR: No Digest-Body-Digest: Cannot perform Digest authentication");
358                         return RLM_MODULE_INVALID;
359                 }
360
361                 if ((a2_len + body->length) > sizeof(a2)) {
362                         DEBUG("ERROR: Digest-Body-Digest is too long");
363                         return RLM_MODULE_INVALID;
364                 }
365
366                 memcpy(a2 + a2_len, body->vp_octets, body->length);
367                 a2_len += body->length;
368
369         } else if ((qop != NULL) &&
370                    (strcasecmp(qop->vp_strvalue, "auth") != 0)) {
371                 DEBUG("ERROR: Unknown Digest-QOP \"%s\": Cannot perform Digest authentication", qop->vp_strvalue);
372                 return RLM_MODULE_INVALID;
373         }
374
375         a2[a2_len] = '\0';
376         DEBUG2("A2 = %s", a2);
377
378         /*
379          *     KD = H(A1) : Digest-Nonce ... : H(A2).
380          *     Compute MD5 if Digest-Algorithm == "MD5-Sess",
381          *     or if we found a User-Password.
382          */
383         if (((algo != NULL) && 
384              (strcasecmp(algo->vp_strvalue, "MD5-Sess") == 0)) ||
385             (passwd->attribute == PW_CLEARTEXT_PASSWORD)) {
386                 a1[a1_len] = '\0';
387                 librad_md5_calc(&hash[0], &a1[0], a1_len);
388         } else {
389                 memcpy(&hash[0], &a1[0], a1_len);
390         }
391         lrad_bin2hex(hash, (char *) kd, sizeof(hash));
392
393 #ifndef NDEBUG
394         if (debug_flag) {
395                 printf("H(A1) = ");
396                 for (i = 0; i < 16; i++) {
397                         printf("%02x", hash[i]);
398                 }
399                 printf("\n");
400         }
401 #endif
402         kd_len = 32;
403
404         kd[kd_len] = ':';
405         kd_len++;
406
407         memcpy(&kd[kd_len], nonce->vp_octets, nonce->length);
408         kd_len += nonce->length;
409
410         /*
411          *      No QOP defined.  Do RFC 2069 compatibility.
412          */
413         if (!qop) {
414                 /*
415                  *      Do nothing here.
416                  */
417
418         } else {                /* Digest-QOP MUST be "auth" or "auth-int" */
419                 /*
420                  *      Tack on ":" Digest-Nonce-Count ":" Digest-CNonce
421                  *             ":" Digest-QOP
422                  */
423                 kd[kd_len] = ':';
424                 kd_len++;
425
426                 vp = pairfind(request->packet->vps, PW_DIGEST_NONCE_COUNT);
427                 if (!vp) {
428                         DEBUG("ERROR: No Digest-Nonce-Count: Cannot perform Digest authentication");
429                         return RLM_MODULE_INVALID;
430                 }
431                 memcpy(&kd[kd_len], &vp->vp_octets[0], vp->length);
432                 kd_len += vp->length;
433
434                 kd[kd_len] = ':';
435                 kd_len++;
436
437                 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
438                 if (!vp) {
439                         DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
440                         return RLM_MODULE_INVALID;
441                 }
442                 memcpy(&kd[kd_len], &vp->vp_octets[0], vp->length);
443                 kd_len += vp->length;
444
445                 kd[kd_len] = ':';
446                 kd_len++;
447
448                 memcpy(&kd[kd_len], &qop->vp_octets[0], qop->length);
449                 kd_len += qop->length;
450         }
451
452         /*
453          *      Tack on ":" H(A2)
454          */
455         kd[kd_len] = ':';
456         kd_len++;
457
458         librad_md5_calc(&hash[0], &a2[0], a2_len);
459
460         lrad_bin2hex(hash, (char *) kd + kd_len, sizeof(hash));
461
462 #ifndef NDEBUG
463         if (debug_flag) {
464                 printf("H(A2) = ");
465                 for (i = 0; i < 16; i++) {
466                         printf("%02x", hash[i]);
467                 }
468                 printf("\n");
469         }
470 #endif
471         kd_len += 32;
472
473         kd[kd_len] = 0;
474
475         DEBUG2("KD = %s\n", &kd[0]);
476
477         /*
478          *      Take the hash of KD.
479          */
480         librad_md5_calc(&hash[0], &kd[0], kd_len);
481         memcpy(&kd[0], &hash[0], 16);
482
483         /*
484          *      Get the binary value of Digest-Response
485          */
486         vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
487         if (!vp) {
488                 DEBUG("ERROR: No Digest-Response attribute in the request.  Cannot perform digest authentication");
489                 return RLM_MODULE_INVALID;
490         }
491
492         if (lrad_hex2bin(&vp->vp_strvalue[0], &hash[0], vp->length >> 1) != (vp->length >> 1)) {
493                 DEBUG2("rlm_digest: Invalid text in Digest-Response");
494                 return RLM_MODULE_INVALID;
495         }
496
497 #ifndef NDEBUG
498         if (debug_flag) {
499                 printf("EXPECTED ");
500                 for (i = 0; i < 16; i++) {
501                         printf("%02x", kd[i]);
502                 }
503                 printf("\n");
504
505                 printf("RECEIVED ");
506                 for (i = 0; i < 16; i++) {
507                         printf("%02x", hash[i]);
508                 }
509                 printf("\n");
510         }
511 #endif
512
513         /*
514          *  And finally, compare the digest in the packet with KD.
515          */
516         if (memcmp(&kd[0], &hash[0], 16) == 0) {
517                 return RLM_MODULE_OK;
518         }
519
520         DEBUG("rlm_digest: FAILED authentication");
521         return RLM_MODULE_REJECT;
522 }
523
524 /*
525  *      The module name should be the only globally exported symbol.
526  *      That is, everything else should be 'static'.
527  *
528  *      If the module needs to temporarily modify it's instantiation
529  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
530  *      The server will then take care of ensuring that the module
531  *      is single-threaded.
532  */
533 module_t rlm_digest = {
534         RLM_MODULE_INIT,
535         "digest",
536         0,                              /* type */
537         NULL,                           /* instantiation */
538         NULL,                           /* detach */
539         {
540                 digest_authenticate,    /* authentication */
541                 digest_authorize,       /* authorization */
542                 NULL,                   /* preaccounting */
543                 NULL,                   /* accounting */
544                 NULL,                   /* checksimul */
545                 NULL,                   /* pre-proxy */
546                 NULL,                   /* post-proxy */
547                 NULL                    /* post-auth */
548         },
549 };