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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2002,2006 The FreeRADIUS server project
21 * Copyright 2002 Alan DeKok <aland@ox.org>
24 #include <freeradius-devel/ident.h>
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
30 static int digest_fix(REQUEST *request)
35 * We need both of these attributes to do the authentication.
37 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE, 0);
39 return RLM_MODULE_NOOP;
43 * Check the sanity of the attribute.
45 if (vp->length != 32) {
46 return RLM_MODULE_NOOP;
52 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES, 0);
54 return RLM_MODULE_NOOP;
58 * Check for proper format of the Digest-Attributes
60 RDEBUG("Checking for correctly formatted Digest-Attributes");
62 int length = vp->length;
64 uint8_t *p = &vp->vp_octets[0];
67 * Until this stupidly encoded attribute is exhausted.
71 * The attribute type must be valid
73 if ((p[0] == 0) || (p[0] > 10)) {
74 RDEBUG("Not formatted as Digest-Attributes");
75 return RLM_MODULE_NOOP;
78 attrlen = p[1]; /* stupid VSA format */
84 RDEBUG("Not formatted as Digest-Attributes");
85 return RLM_MODULE_NOOP;
91 if (attrlen > length) {
92 RDEBUG("Not formatted as Digest-Attributes");
93 return RLM_MODULE_NOOP;
98 } /* loop over this one attribute */
101 * Find the next one, if it exists.
103 vp = pairfind(vp->next, PW_DIGEST_ATTRIBUTES, 0);
107 * Convert them to something sane.
109 RDEBUG("Digest-Attributes look OK. Converting them to something more usful.");
110 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES, 0);
112 int length = vp->length;
114 uint8_t *p = &vp->vp_octets[0];
118 * Until this stupidly encoded attribute is exhausted.
122 * The attribute type must be valid
124 if ((p[0] == 0) || (p[0] > 10)) {
125 RDEBUG("ERROR: Received Digest-Attributes with invalid sub-attribute %d", p[0]);
126 return RLM_MODULE_INVALID;
129 attrlen = p[1]; /* stupid VSA format */
135 RDEBUG("ERROR: Received Digest-Attributes with short sub-attribute %d, of length %d", p[0], attrlen);
136 return RLM_MODULE_INVALID;
142 if (attrlen > length) {
143 RDEBUG("ERROR: Received Digest-Attributes with long sub-attribute %d, of length %d", p[0], attrlen);
144 return RLM_MODULE_INVALID;
148 * Create a new attribute, broken out of
149 * the stupid sub-attribute crap.
151 * Didn't they know that VSA's exist?
153 sub = radius_paircreate(request, &request->packet->vps,
154 PW_DIGEST_REALM - 1 + p[0], 0,
156 memcpy(&sub->vp_octets[0], &p[2], attrlen - 2);
157 sub->vp_octets[attrlen - 2] = '\0';
158 sub->length = attrlen - 2;
160 if ((debug_flag > 1) && fr_log_fp) {
161 fputc('\t', fr_log_fp);
162 vp_print(fr_log_fp, sub);
163 fputc('\n', fr_log_fp);
167 * FIXME: Check for the existence
168 * of the necessary attributes!
173 } /* loop over this one attribute */
176 * Find the next one, if it exists.
178 vp = pairfind(vp->next, PW_DIGEST_ATTRIBUTES, 0);
181 return RLM_MODULE_OK;
184 static int digest_authorize(void *instance, REQUEST *request)
188 /* quiet the compiler */
192 * Double-check and fix the attributes.
194 rcode = digest_fix(request);
195 if (rcode != RLM_MODULE_OK) return rcode;
198 * Everything's OK, add a digest authentication type.
200 if (pairfind(request->config_items, PW_AUTHTYPE, 0) == NULL) {
201 RDEBUG("Adding Auth-Type = DIGEST");
202 pairadd(&request->config_items,
203 pairmake("Auth-Type", "DIGEST", T_OP_EQ));
206 return RLM_MODULE_OK;
210 * Perform all of the wondrous variants of digest authentication.
212 static int digest_authenticate(void *instance, REQUEST *request)
215 size_t a1_len, a2_len, kd_len;
216 uint8_t a1[(MAX_STRING_LEN + 1) * 5]; /* can be 5 attributes */
217 uint8_t a2[(MAX_STRING_LEN + 1) * 3]; /* can be 3 attributes */
218 uint8_t kd[(MAX_STRING_LEN + 1) * 5];
219 uint8_t hash[16]; /* MD5 output */
220 VALUE_PAIR *vp, *passwd, *algo;
221 VALUE_PAIR *qop, *nonce;
223 instance = instance; /* -Wunused */
226 * We require access to the plain-text password, or to the
227 * Digest-HA1 parameter.
229 passwd = pairfind(request->config_items, PW_DIGEST_HA1, 0);
231 if (passwd->length != 32) {
232 radlog_request(L_AUTH, 0, request, "Digest-HA1 has invalid length, authentication failed.");
233 return RLM_MODULE_INVALID;
236 passwd = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0);
239 radlog_request(L_AUTH, 0, request, "Cleartext-Password or Digest-HA1 is required for authentication.");
240 return RLM_MODULE_INVALID;
244 * We need these, too.
246 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES, 0);
249 RDEBUG("ERROR: You set 'Auth-Type = Digest' for a request that did not contain any digest attributes!w");
250 return RLM_MODULE_INVALID;
254 * Look for the "internal" FreeRADIUS Digest attributes.
255 * If they don't exist, it means that someone forced
256 * Auth-Type = digest, without putting "digest" into the
257 * "authorize" section. In that case, try to decode the
260 if (!pairfind(request->packet->vps, PW_DIGEST_NONCE, 0)) {
263 rcode = digest_fix(request);
266 * NOOP means "couldn't find the attributes".
269 if (rcode == RLM_MODULE_NOOP) goto error;
271 if (rcode != RLM_MODULE_OK) return rcode;
275 * We require access to the Digest-Nonce-Value
277 nonce = pairfind(request->packet->vps, PW_DIGEST_NONCE, 0);
279 RDEBUG("ERROR: No Digest-Nonce: Cannot perform Digest authentication");
280 return RLM_MODULE_INVALID;
284 * A1 = Digest-User-Name ":" Realm ":" Password
286 vp = pairfind(request->packet->vps, PW_DIGEST_USER_NAME, 0);
288 RDEBUG("ERROR: No Digest-User-Name: Cannot perform Digest authentication");
289 return RLM_MODULE_INVALID;
291 memcpy(&a1[0], &vp->vp_octets[0], vp->length);
297 vp = pairfind(request->packet->vps, PW_DIGEST_REALM, 0);
299 RDEBUG("ERROR: No Digest-Realm: Cannot perform Digest authentication");
300 return RLM_MODULE_INVALID;
302 memcpy(&a1[a1_len], &vp->vp_octets[0], vp->length);
303 a1_len += vp->length;
308 if (passwd->attribute == PW_CLEARTEXT_PASSWORD) {
309 memcpy(&a1[a1_len], &passwd->vp_octets[0], passwd->length);
310 a1_len += passwd->length;
312 RDEBUG2("A1 = %s", a1);
315 RDEBUG2("A1 = %s (using Digest-HA1)", a1);
320 * See which variant we calculate.
321 * Assume MD5 if no Digest-Algorithm attribute received
323 algo = pairfind(request->packet->vps, PW_DIGEST_ALGORITHM, 0);
324 if ((algo == NULL) ||
325 (strcasecmp(algo->vp_strvalue, "MD5") == 0)) {
327 * Set A1 to Digest-HA1 if no User-Password found
329 if (passwd->attribute == PW_DIGEST_HA1) {
330 if (fr_hex2bin(passwd->vp_strvalue, &a1[0], 16) != 16) {
331 RDEBUG2("Invalid text in Digest-HA1");
332 return RLM_MODULE_INVALID;
336 } else if (strcasecmp(algo->vp_strvalue, "MD5-sess") == 0) {
338 * K1 = H(A1) : Digest-Nonce ... : H(A2)
340 * If we find Digest-HA1, we assume it contains
343 if (passwd->attribute == PW_CLEARTEXT_PASSWORD) {
344 fr_md5_calc(hash, &a1[0], a1_len);
345 fr_bin2hex(hash, (char *) &a1[0], 16);
346 } else { /* MUST be Digest-HA1 */
347 memcpy(&a1[0], passwd->vp_strvalue, 32);
355 * Tack on the Digest-Nonce. Length must be even
357 if ((nonce->length & 1) != 0) {
358 RDEBUG("ERROR: Received Digest-Nonce hex string with invalid length: Cannot perform Digest authentication");
359 return RLM_MODULE_INVALID;
361 memcpy(&a1[a1_len], &nonce->vp_octets[0], nonce->length);
362 a1_len += nonce->length;
367 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE, 0);
369 RDEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
370 return RLM_MODULE_INVALID;
374 * Digest-CNonce length must be even
376 if ((vp->length & 1) != 0) {
377 RDEBUG("ERROR: Received Digest-CNonce hex string with invalid length: Cannot perform Digest authentication");
378 return RLM_MODULE_INVALID;
380 memcpy(&a1[a1_len], &vp->vp_octets[0], vp->length);
381 a1_len += vp->length;
383 } else if ((algo != NULL) &&
384 (strcasecmp(algo->vp_strvalue, "MD5") != 0)) {
386 * We check for "MD5-sess" and "MD5".
387 * Anything else is an error.
389 RDEBUG("ERROR: Unknown Digest-Algorithm \"%s\": Cannot perform Digest authentication", vp->vp_strvalue);
390 return RLM_MODULE_INVALID;
394 * A2 = Digest-Method ":" Digest-URI
396 vp = pairfind(request->packet->vps, PW_DIGEST_METHOD, 0);
398 RDEBUG("ERROR: No Digest-Method: Cannot perform Digest authentication");
399 return RLM_MODULE_INVALID;
401 memcpy(&a2[0], &vp->vp_octets[0], vp->length);
407 vp = pairfind(request->packet->vps, PW_DIGEST_URI, 0);
409 RDEBUG("ERROR: No Digest-URI: Cannot perform Digest authentication");
410 return RLM_MODULE_INVALID;
412 memcpy(&a2[a2_len], &vp->vp_octets[0], vp->length);
413 a2_len += vp->length;
416 * QOP is "auth-int", tack on ": Digest-Body-Digest"
418 qop = pairfind(request->packet->vps, PW_DIGEST_QOP, 0);
420 (strcasecmp(qop->vp_strvalue, "auth-int") == 0)) {
424 * Add in Digest-Body-Digest
430 * Must be a hex representation of an MD5 digest.
432 body = pairfind(request->packet->vps, PW_DIGEST_BODY_DIGEST, 0);
434 RDEBUG("ERROR: No Digest-Body-Digest: Cannot perform Digest authentication");
435 return RLM_MODULE_INVALID;
438 if ((a2_len + body->length) > sizeof(a2)) {
439 RDEBUG("ERROR: Digest-Body-Digest is too long");
440 return RLM_MODULE_INVALID;
443 memcpy(a2 + a2_len, body->vp_octets, body->length);
444 a2_len += body->length;
446 } else if ((qop != NULL) &&
447 (strcasecmp(qop->vp_strvalue, "auth") != 0)) {
448 RDEBUG("ERROR: Unknown Digest-QOP \"%s\": Cannot perform Digest authentication", qop->vp_strvalue);
449 return RLM_MODULE_INVALID;
453 RDEBUG2("A2 = %s", a2);
456 * KD = H(A1) : Digest-Nonce ... : H(A2).
457 * Compute MD5 if Digest-Algorithm == "MD5-Sess",
458 * or if we found a User-Password.
460 if (((algo != NULL) &&
461 (strcasecmp(algo->vp_strvalue, "MD5-Sess") == 0)) ||
462 (passwd->attribute == PW_CLEARTEXT_PASSWORD)) {
464 fr_md5_calc(&hash[0], &a1[0], a1_len);
466 memcpy(&hash[0], &a1[0], a1_len);
468 fr_bin2hex(hash, (char *) kd, sizeof(hash));
471 if (debug_flag > 1) {
472 fr_printf_log("H(A1) = ");
473 for (i = 0; i < 16; i++) {
474 fr_printf_log("%02x", hash[i]);
484 memcpy(&kd[kd_len], nonce->vp_octets, nonce->length);
485 kd_len += nonce->length;
488 * No QOP defined. Do RFC 2069 compatibility.
495 } else { /* Digest-QOP MUST be "auth" or "auth-int" */
497 * Tack on ":" Digest-Nonce-Count ":" Digest-CNonce
503 vp = pairfind(request->packet->vps, PW_DIGEST_NONCE_COUNT, 0);
505 RDEBUG("ERROR: No Digest-Nonce-Count: Cannot perform Digest authentication");
506 return RLM_MODULE_INVALID;
508 memcpy(&kd[kd_len], &vp->vp_octets[0], vp->length);
509 kd_len += vp->length;
514 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE, 0);
516 RDEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
517 return RLM_MODULE_INVALID;
519 memcpy(&kd[kd_len], &vp->vp_octets[0], vp->length);
520 kd_len += vp->length;
525 memcpy(&kd[kd_len], &qop->vp_octets[0], qop->length);
526 kd_len += qop->length;
535 fr_md5_calc(&hash[0], &a2[0], a2_len);
537 fr_bin2hex(hash, (char *) kd + kd_len, sizeof(hash));
540 if (debug_flag > 1) {
541 fr_printf_log("H(A2) = ");
542 for (i = 0; i < 16; i++) {
543 fr_printf_log("%02x", hash[i]);
552 RDEBUG2("KD = %s\n", &kd[0]);
555 * Take the hash of KD.
557 fr_md5_calc(&hash[0], &kd[0], kd_len);
558 memcpy(&kd[0], &hash[0], 16);
561 * Get the binary value of Digest-Response
563 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE, 0);
565 RDEBUG("ERROR: No Digest-Response attribute in the request. Cannot perform digest authentication");
566 return RLM_MODULE_INVALID;
569 if (fr_hex2bin(&vp->vp_strvalue[0], &hash[0], vp->length >> 1) != (vp->length >> 1)) {
570 RDEBUG2("Invalid text in Digest-Response");
571 return RLM_MODULE_INVALID;
575 if (debug_flag > 1) {
576 fr_printf_log("EXPECTED ");
577 for (i = 0; i < 16; i++) {
578 fr_printf_log("%02x", kd[i]);
582 fr_printf_log("RECEIVED ");
583 for (i = 0; i < 16; i++) {
584 fr_printf_log("%02x", hash[i]);
591 * And finally, compare the digest in the packet with KD.
593 if (memcmp(&kd[0], &hash[0], 16) == 0) {
594 return RLM_MODULE_OK;
597 RDEBUG("FAILED authentication");
598 return RLM_MODULE_REJECT;
602 * The module name should be the only globally exported symbol.
603 * That is, everything else should be 'static'.
605 * If the module needs to temporarily modify it's instantiation
606 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
607 * The server will then take care of ensuring that the module
608 * is single-threaded.
610 module_t rlm_digest = {
613 RLM_TYPE_CHECK_CONFIG_SAFE, /* type */
614 NULL, /* instantiation */
617 digest_authenticate, /* authentication */
618 digest_authorize, /* authorization */
619 NULL, /* preaccounting */
620 NULL, /* accounting */
621 NULL, /* checksimul */
622 NULL, /* pre-proxy */
623 NULL, /* post-proxy */