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>
34 static const char rcsid[] = "$Id$";
36 static int digest_authorize(void *instance, REQUEST *request)
40 /* quiet the compiler */
44 * We need both of these attributes to do the authentication.
46 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
48 return RLM_MODULE_NOOP;
52 * Check the sanity of the attribute.
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;
62 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
64 DEBUG("ERROR: Received Digest-Response without Digest-Attributes");
65 return RLM_MODULE_INVALID;
69 * Everything's OK, add a digest authentication type.
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));
81 * Perform all of the wondrous variants of digest authentication.
83 static int digest_authenticate(void *instance, REQUEST *request)
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;
94 instance = instance; /* -Wunused */
97 * We require access to the plain-text password.
99 passwd = pairfind(request->config_items, PW_PASSWORD);
100 #ifdef PW_MD5_PASSWORD
101 if (!passwd) passwd = pairfind(request->config_items, PW_MD5_PASSWORD);
104 radlog(L_AUTH, "rlm_digest: Configuration item \"User-Password\" or MD5-Password is required for authentication.");
105 return RLM_MODULE_INVALID;
109 * We need these, too.
111 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
113 DEBUG("ERROR: You set 'Auth-Type = Digest' for a request that did not contain any digest attributes!");
114 return RLM_MODULE_INVALID;
118 * Loop through the Digest-Attributes, sanity checking them.
120 DEBUG(" rlm_digest: Converting Digest-Attributes to something sane...");
122 int length = vp->length;
124 uint8_t *p = &vp->strvalue[0];
128 * Until this stupidly encoded attribure is exhausted.
132 * The attribute type must be valid
134 if ((p[0] == 0) || (p[0] > 10)) {
135 DEBUG("ERROR: Received Digest-Attributes with invalid sub-attribute %d", p[0]);
136 return RLM_MODULE_INVALID;
139 attrlen = p[1]; /* stupid VSA format */
145 DEBUG("ERROR: Received Digest-Attributes with short sub-attribute %d, of length %d", p[0], attrlen);
146 return RLM_MODULE_INVALID;
152 if (attrlen > length) {
153 DEBUG("ERROR: Received Digest-Attributes with long sub-attribute %d, of length %d", p[0], attrlen);
154 return RLM_MODULE_INVALID;
158 * Create a new attribute, broken out of
159 * the stupid sub-attribute crap.
161 * Didn't they know that VSA's exist?
163 sub = paircreate(PW_DIGEST_REALM - 1 + p[0],
166 return RLM_MODULE_FAIL; /* out of memory */
168 memcpy(&sub->strvalue[0], &p[2], attrlen - 2);
169 sub->strvalue[attrlen - 2] = '\0';
170 sub->length = attrlen - 2;
174 vp_print(stdout, sub);
179 * And add it to the request pairs.
181 pairadd(&request->packet->vps, sub);
184 * FIXME: Check for the existence
185 * of the necessary attributes!
190 } /* loop over this one attribute */
193 * Find the next one, if it exists.
195 vp = pairfind(vp->next, PW_DIGEST_ATTRIBUTES);
199 * We require access to the Digest-Nonce-Value
201 nonce = pairfind(request->packet->vps, PW_DIGEST_NONCE);
203 DEBUG("ERROR: No Digest-Nonce: Cannot perform Digest authentication");
204 return RLM_MODULE_INVALID;
208 * A1 = Digest-User-Name ":" Realm ":" Password
210 vp = pairfind(request->packet->vps, PW_DIGEST_USER_NAME);
212 DEBUG("ERROR: No Digest-User-Name: Cannot perform Digest authentication");
213 return RLM_MODULE_INVALID;
215 memcpy(&a1[0], &vp->strvalue[0], vp->length);
221 vp = pairfind(request->packet->vps, PW_DIGEST_REALM);
223 DEBUG("ERROR: No Digest-Realm: Cannot perform Digest authentication");
224 return RLM_MODULE_INVALID;
226 memcpy(&a1[a1_len], &vp->strvalue[0], vp->length);
227 a1_len += vp->length;
232 if (passwd->attribute == PW_USER_PASSWORD) {
233 memcpy(&a1[a1_len], &passwd->strvalue[0], passwd->length);
234 a1_len += passwd->length;
236 DEBUG2("A1 = %s", a1);
239 DEBUG2("A1 = %s (using MD5-Password)", a1);
244 * See which variant we calculate.
245 * Assume MD5 if no Digest-Algorithm attribute received
247 algo = pairfind(request->packet->vps, PW_DIGEST_ALGORITHM);
248 if ((algo == NULL) ||
249 (strcasecmp(algo->strvalue, "MD5") == 0)) {
251 * Set A1 to MD5-Password if no User-Password found
253 if (passwd->attribute != PW_USER_PASSWORD) {
254 memcpy(&a1[0], passwd->strvalue, 16);
257 } else if (strcasecmp(algo->strvalue, "MD5-sess") == 0) {
259 * K1 = H(A1) : Digest-Nonce ... : H(A2)
261 * If we find MD5-Password, we assume it contains
264 if (passwd->attribute == PW_USER_PASSWORD) {
265 librad_md5_calc(hash, &a1[0], a1_len);
266 lrad_bin2hex(hash, &a1[0], 16);
268 lrad_bin2hex(passwd->strvalue, &a1[0], 16);
276 * Tack on the Digest-Nonce. Length must be even
278 if ((nonce->length & 1) != 0) {
279 DEBUG("ERROR: Received Digest-Nonce hex string with invalid length: Cannot perform Digest authentication");
280 return RLM_MODULE_INVALID;
282 memcpy(&a1[a1_len], &nonce->strvalue[0], nonce->length);
283 a1_len += nonce->length;
288 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
290 DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
291 return RLM_MODULE_INVALID;
295 * Digest-CNonce length must be even
297 if ((vp->length & 1) != 0) {
298 DEBUG("ERROR: Received Digest-CNonce hex string with invalid length: Cannot perform Digest authentication");
299 return RLM_MODULE_INVALID;
301 memcpy(&a1[a1_len], &vp->strvalue[0], vp->length);
302 a1_len += vp->length;
304 } else if ((algo != NULL) &&
305 (strcasecmp(algo->strvalue, "MD5") != 0)) {
307 * We check for "MD5-sess" and "MD5".
308 * Anything else is an error.
310 DEBUG("ERROR: Unknown Digest-Algorithm \"%s\": Cannot perform Digest authentication", vp->strvalue);
311 return RLM_MODULE_INVALID;
315 * A2 = Digest-Method ":" Digest-URI
317 vp = pairfind(request->packet->vps, PW_DIGEST_METHOD);
319 DEBUG("ERROR: No Digest-Method: Cannot perform Digest authentication");
320 return RLM_MODULE_INVALID;
322 memcpy(&a2[0], &vp->strvalue[0], vp->length);
328 vp = pairfind(request->packet->vps, PW_DIGEST_URI);
330 DEBUG("ERROR: No Digest-URI: Cannot perform Digest authentication");
331 return RLM_MODULE_INVALID;
333 memcpy(&a2[a2_len], &vp->strvalue[0], vp->length);
334 a2_len += vp->length;
337 * QOP is "auth-int", tack on ": Digest-Body-Digest"
339 qop = pairfind(request->packet->vps, PW_DIGEST_QOP);
341 (strcasecmp(qop->strvalue, "auth-int") == 0)) {
345 * Add in Digest-Body-Digest
351 * Must be a hex representation of an MD5 digest.
353 body = pairfind(request->packet->vps, PW_DIGEST_BODY_DIGEST);
355 DEBUG("ERROR: No Digest-Body-Digest: Cannot perform Digest authentication");
356 return RLM_MODULE_INVALID;
359 if ((a2_len + body->length) > sizeof(a2)) {
360 DEBUG("ERROR: Digest-Body-Digest is too long");
361 return RLM_MODULE_INVALID;
364 memcpy(a2 + a2_len, body->strvalue, body->length);
365 a2_len += body->length;
367 } else if ((qop != NULL) &&
368 (strcasecmp(qop->strvalue, "auth") != 0)) {
369 DEBUG("ERROR: Unknown Digest-QOP \"%s\": Cannot perform Digest authentication", qop->strvalue);
370 return RLM_MODULE_INVALID;
374 DEBUG2("A2 = %s", a2);
377 * KD = H(A1) : Digest-Nonce ... : H(A2).
378 * Compute MD5 if Digest-Algorithm == "MD5-Sess",
379 * or if we found a User-Password.
381 if (((algo != NULL) &&
382 (strcasecmp(algo->strvalue, "MD5-Sess") == 0)) ||
383 (passwd->attribute == PW_USER_PASSWORD)) {
385 librad_md5_calc(&hash[0], &a1[0], a1_len);
387 memcpy(&hash[0], &a1[0], a1_len);
389 lrad_bin2hex(hash, kd, sizeof(hash));
394 for (i = 0; i < 16; i++) {
395 printf("%02x", hash[i]);
405 memcpy(&kd[kd_len], nonce->strvalue, nonce->length);
406 kd_len += nonce->length;
409 * No QOP defined. Do RFC 2069 compatibility.
416 } else { /* Digest-QOP MUST be "auth" or "auth-int" */
418 * Tack on ":" Digest-Nonce-Count ":" Digest-CNonce
424 vp = pairfind(request->packet->vps, PW_DIGEST_NONCE_COUNT);
426 DEBUG("ERROR: No Digest-Nonce-Count: Cannot perform Digest authentication");
427 return RLM_MODULE_INVALID;
429 memcpy(&kd[kd_len], &vp->strvalue[0], vp->length);
430 kd_len += vp->length;
435 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
437 DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
438 return RLM_MODULE_INVALID;
440 memcpy(&kd[kd_len], &vp->strvalue[0], vp->length);
441 kd_len += vp->length;
446 memcpy(&kd[kd_len], &qop->strvalue[0], qop->length);
447 kd_len += qop->length;
456 librad_md5_calc(&hash[0], &a2[0], a2_len);
458 lrad_bin2hex(hash, kd + kd_len, sizeof(hash));
463 for (i = 0; i < 16; i++) {
464 printf("%02x", hash[i]);
473 DEBUG2("KD = %s\n", &kd[0]);
476 * Take the hash of KD.
478 librad_md5_calc(&hash[0], &kd[0], kd_len);
479 memcpy(&kd[0], &hash[0], 16);
482 * Get the binary value of Digest-Response
484 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
486 DEBUG("ERROR: No Digest-Response attribute in the request. Cannot perform digest authentication");
487 return RLM_MODULE_INVALID;
490 lrad_hex2bin(&vp->strvalue[0], &hash[0], vp->length >> 1);
495 for (i = 0; i < 16; i++) {
496 printf("%02x", kd[i]);
501 for (i = 0; i < 16; i++) {
502 printf("%02x", hash[i]);
509 * And finally, compare the digest in the packet with KD.
511 if (memcmp(&kd[0], &hash[0], 16) == 0) {
512 return RLM_MODULE_OK;
515 DEBUG("rlm_digest: FAILED authentication");
516 return RLM_MODULE_REJECT;
520 * The module name should be the only globally exported symbol.
521 * That is, everything else should be 'static'.
523 * If the module needs to temporarily modify it's instantiation
524 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
525 * The server will then take care of ensuring that the module
526 * is single-threaded.
528 module_t rlm_digest = {
531 NULL, /* initialization */
532 NULL, /* instantiation */
534 digest_authenticate, /* authentication */
535 digest_authorize, /* authorization */
536 NULL, /* preaccounting */
537 NULL, /* accounting */
538 NULL, /* checksimul */
539 NULL, /* pre-proxy */
540 NULL, /* post-proxy */