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