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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2002 The FreeRADIUS server project
21 * Copyright 2002 Alan DeKok <aland@ox.org>
24 #include <freeradius-devel/autoconf.h>
30 #include <freeradius-devel/radiusd.h>
31 #include <freeradius-devel/modules.h>
32 #include <freeradius-devel/conffile.h>
33 #include <freeradius-devel/rad_assert.h>
35 static const char rcsid[] = "$Id$";
37 static int digest_authorize(void *instance, REQUEST *request)
41 /* quiet the compiler */
45 * We need both of these attributes to do the authentication.
47 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
49 return RLM_MODULE_NOOP;
53 * Check the sanity of the attribute.
55 if (vp->length != 32) {
56 DEBUG("ERROR: Received invalid Digest-Response attribute (length %d should be 32)", vp->length);
57 return RLM_MODULE_INVALID;
63 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
65 DEBUG("ERROR: Received Digest-Response without Digest-Attributes");
66 return RLM_MODULE_INVALID;
70 * Everything's OK, add a digest authentication type.
72 if (pairfind(request->config_items, PW_AUTHTYPE) == NULL) {
73 DEBUG("rlm_digest: Adding Auth-Type = DIGEST");
74 pairadd(&request->config_items,
75 pairmake("Auth-Type", "DIGEST", T_OP_EQ));
82 * Perform all of the wondrous variants of digest authentication.
84 static int digest_authenticate(void *instance, REQUEST *request)
87 int a1_len, a2_len, kd_len;
88 uint8_t a1[(MAX_STRING_LEN + 1) * 5]; /* can be 5 attributes */
89 uint8_t a2[(MAX_STRING_LEN + 1) * 3]; /* can be 3 attributes */
90 uint8_t kd[(MAX_STRING_LEN + 1) * 5];
91 uint8_t hash[16]; /* MD5 output */
92 VALUE_PAIR *vp, *passwd;
93 VALUE_PAIR *qop, *nonce;
95 instance = instance; /* -Wunused */
98 * We require access to the plain-text password.
100 passwd = pairfind(request->config_items, PW_PASSWORD);
101 if (!passwd) passwd = pairfind(request->config_items, PW_MD5_PASSWORD);
103 radlog(L_AUTH, "rlm_digest: Configuration item \"User-Password\" or MD5-Password is required for authentication.");
104 return RLM_MODULE_INVALID;
108 * We need these, too.
110 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
112 DEBUG("ERROR: You set 'Auth-Type = Digest' for a request that did not contain any digest attributes!");
113 return RLM_MODULE_INVALID;
117 * Loop through the Digest-Attributes, sanity checking them.
119 DEBUG(" rlm_digest: Converting Digest-Attributes to something sane...");
121 int length = vp->length;
123 uint8_t *p = &vp->vp_octets[0];
127 * Until this stupidly encoded attribure is exhausted.
131 * The attribute type must be valid
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;
138 attrlen = p[1]; /* stupid VSA format */
144 DEBUG("ERROR: Received Digest-Attributes with short sub-attribute %d, of length %d", p[0], attrlen);
145 return RLM_MODULE_INVALID;
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;
157 * Create a new attribute, broken out of
158 * the stupid sub-attribute crap.
160 * Didn't they know that VSA's exist?
162 sub = paircreate(PW_DIGEST_REALM - 1 + p[0],
165 return RLM_MODULE_FAIL; /* out of memory */
167 memcpy(&sub->vp_octets[0], &p[2], attrlen - 2);
168 sub->vp_octets[attrlen - 2] = '\0';
169 sub->length = attrlen - 2;
173 vp_print(stdout, sub);
178 * And add it to the request pairs.
180 pairadd(&request->packet->vps, sub);
183 * FIXME: Check for the existence
184 * of the necessary attributes!
189 } /* loop over this one attribute */
192 * Find the next one, if it exists.
194 vp = pairfind(vp->next, PW_DIGEST_ATTRIBUTES);
198 * We require access to the Digest-Nonce-Value
200 nonce = pairfind(request->packet->vps, PW_DIGEST_NONCE);
202 DEBUG("ERROR: No Digest-Nonce: Cannot perform Digest authentication");
203 return RLM_MODULE_INVALID;
207 * A1 = Digest-User-Name ":" Realm ":" Password
209 vp = pairfind(request->packet->vps, PW_DIGEST_USER_NAME);
211 DEBUG("ERROR: No Digest-User-Name: Cannot perform Digest authentication");
212 return RLM_MODULE_INVALID;
214 memcpy(&a1[0], &vp->vp_octets[0], vp->length);
220 vp = pairfind(request->packet->vps, PW_DIGEST_REALM);
222 DEBUG("ERROR: No Digest-Realm: Cannot perform Digest authentication");
223 return RLM_MODULE_INVALID;
225 memcpy(&a1[a1_len], &vp->vp_octets[0], vp->length);
226 a1_len += vp->length;
231 if (passwd->attribute == PW_USER_PASSWORD) {
232 memcpy(&a1[a1_len], &passwd->vp_octets[0], passwd->length);
233 a1_len += passwd->length;
235 DEBUG2("A1 = %s", a1);
238 DEBUG2("A1 = %s (using MD5-Password)", a1);
242 * See which variant we calculate.
244 vp = pairfind(request->packet->vps, PW_DIGEST_ALGORITHM);
246 (strcasecmp(vp->vp_strvalue, "MD5-sess") == 0)) {
248 * K1 = H(A1) : Digest-Nonce ... : H(A2)
250 * If we find MD5-Password, we assume it contains
253 if (passwd->attribute == PW_USER_PASSWORD) {
254 librad_md5_calc(hash, &a1[0], a1_len);
255 memcpy(&a1[0], hash, 16);
257 memcpy(&a1[0], passwd->vp_octets, 16);
265 * Tack on the Digest-Nonce
267 lrad_hex2bin(&nonce->vp_octets[0], &a1[a1_len], nonce->length >> 1);
268 a1_len += (nonce->length >> 1); /* FIXME: CHECK LENGTH */
273 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
275 DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
276 return RLM_MODULE_INVALID;
279 lrad_hex2bin(&vp->vp_octets[0], &a1[a1_len], vp->length >> 1);
280 a1_len += (vp->length >> 1); /* FIXME: CHECK LENGTH */
282 } else if ((vp != NULL) &&
283 (strcasecmp(vp->vp_strvalue, "MD5") != 0)) {
285 * We check for "MD5-sess" and "MD5".
286 * Anything else is an error.
288 DEBUG("ERROR: Unknown Digest-Algorithm \"%s\": Cannot perform Digest authentication", vp->vp_strvalue);
289 return RLM_MODULE_INVALID;
293 * A2 = Digest-Method ":" Digest-URI
295 vp = pairfind(request->packet->vps, PW_DIGEST_METHOD);
297 DEBUG("ERROR: No Digest-Method: Cannot perform Digest authentication");
298 return RLM_MODULE_INVALID;
300 memcpy(&a2[0], &vp->vp_octets[0], vp->length);
306 vp = pairfind(request->packet->vps, PW_DIGEST_URI);
308 DEBUG("ERROR: No Digest-URI: Cannot perform Digest authentication");
309 return RLM_MODULE_INVALID;
311 memcpy(&a2[a2_len], &vp->vp_octets[0], vp->length);
312 a2_len += vp->length;
315 * QOP is "auth-int", tack on ": Digest-Body-Digest"
317 qop = pairfind(request->packet->vps, PW_DIGEST_QOP);
319 (strcasecmp(qop->vp_strvalue, "auth-int") == 0)) {
323 * Add in Digest-Body-Digest
329 * Must be a hex representation of an MD5 digest.
331 body = pairfind(request->packet->vps, PW_DIGEST_BODY_DIGEST);
333 DEBUG("ERROR: No Digest-Body-Digest: Cannot perform Digest authentication");
334 return RLM_MODULE_INVALID;
337 rad_assert(body->length == 32); /* FIXME: check in 'auth' */
338 lrad_hex2bin(&body->vp_octets[0], &a2[a2_len], body->length >> 1);
339 a2_len += (body->length >> 1);
341 } else if ((qop != NULL) &&
342 (strcasecmp(qop->vp_strvalue, "auth") != 0)) {
343 DEBUG("ERROR: Unknown Digest-QOP \"%s\": Cannot perform Digest authentication", qop->vp_strvalue);
344 return RLM_MODULE_INVALID;
348 DEBUG2("A2 = %s", a2);
351 * KD = H(A1) : Digest-Nonce ... : H(A2)
353 librad_md5_calc(&hash[0], &a1[0], a1_len);
355 for (i = 0; i < 16; i++) {
356 sprintf(&kd[i * 2], "%02x", hash[i]);
362 for (i = 0; i < 16; i++) {
363 printf("%02x", hash[i]);
373 memcpy(&kd[kd_len], nonce->vp_octets, nonce->length);
374 kd_len += nonce->length;
377 * No QOP defined. Do RFC 2069 compatibility.
384 } else { /* Digest-QOP MUST be "auth" or "auth-int" */
386 * Tack on ":" Digest-Nonce-Count ":" Digest-CNonce
392 vp = pairfind(request->packet->vps, PW_DIGEST_NONCE_COUNT);
394 DEBUG("ERROR: No Digest-Nonce-Count: Cannot perform Digest authentication");
395 return RLM_MODULE_INVALID;
397 memcpy(&kd[kd_len], &vp->vp_octets[0], vp->length);
398 kd_len += vp->length;
403 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
405 DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
406 return RLM_MODULE_INVALID;
408 memcpy(&kd[kd_len], &vp->vp_octets[0], vp->length);
409 kd_len += vp->length;
414 memcpy(&kd[kd_len], &qop->vp_octets[0], qop->length);
415 kd_len += qop->length;
424 librad_md5_calc(&hash[0], &a2[0], a2_len);
426 for (i = 0; i < 16; i++) {
427 sprintf(&kd[kd_len + (i * 2)], "%02x", hash[i]);
433 for (i = 0; i < 16; i++) {
434 printf("%02x", hash[i]);
443 DEBUG2("KD = %s\n", &kd[0]);
446 * Take the hash of KD.
448 librad_md5_calc(&hash[0], &kd[0], kd_len);
449 memcpy(&kd[0], &hash[0], 16);
452 * Get the binary value of Digest-Response
454 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
455 rad_assert(vp != NULL);
457 lrad_hex2bin(&vp->vp_octets[0], &hash[0], vp->length >> 1);
462 for (i = 0; i < 16; i++) {
463 printf("%02x", kd[i]);
468 for (i = 0; i < 16; i++) {
469 printf("%02x", hash[i]);
476 * And finally, compare the digest in the packet with KD.
478 if (memcmp(&kd[0], &hash[0], 16) == 0) {
479 return RLM_MODULE_OK;
482 DEBUG("rlm_digest: FAILED authentication");
483 return RLM_MODULE_REJECT;
487 * The module name should be the only globally exported symbol.
488 * That is, everything else should be 'static'.
490 * If the module needs to temporarily modify it's instantiation
491 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
492 * The server will then take care of ensuring that the module
493 * is single-threaded.
495 module_t rlm_digest = {
499 NULL, /* instantiation */
502 digest_authenticate, /* authentication */
503 digest_authorize, /* authorization */
504 NULL, /* preaccounting */
505 NULL, /* accounting */
506 NULL, /* checksimul */
507 NULL, /* pre-proxy */
508 NULL, /* post-proxy */