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 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>
33 static const char rcsid[] = "$Id$";
35 static int digest_authorize(void *instance, REQUEST *request)
39 /* quiet the compiler */
43 * We need both of these attributes to do the authentication.
45 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
47 return RLM_MODULE_NOOP;
51 * Check the sanity of the attribute.
53 if (vp->length != 32) {
54 DEBUG("ERROR: Received invalid Digest-Response attribute (length %d should be 32)", vp->length);
55 return RLM_MODULE_INVALID;
61 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
63 DEBUG("ERROR: Received Digest-Response without Digest-Attributes");
64 return RLM_MODULE_INVALID;
68 * Everything's OK, add a digest authentication type.
70 if (pairfind(request->config_items, PW_AUTHTYPE) == NULL) {
71 DEBUG("rlm_digest: Adding Auth-Type = DIGEST");
72 pairadd(&request->config_items,
73 pairmake("Auth-Type", "DIGEST", T_OP_EQ));
80 * Perform all of the wondrous variants of digest authentication.
82 static int digest_authenticate(void *instance, REQUEST *request)
85 size_t a1_len, a2_len, kd_len;
86 uint8_t a1[(MAX_STRING_LEN + 1) * 5]; /* can be 5 attributes */
87 uint8_t a2[(MAX_STRING_LEN + 1) * 3]; /* can be 3 attributes */
88 uint8_t kd[(MAX_STRING_LEN + 1) * 5];
89 uint8_t hash[16]; /* MD5 output */
90 VALUE_PAIR *vp, *passwd, *algo;
91 VALUE_PAIR *qop, *nonce;
93 instance = instance; /* -Wunused */
96 * We require access to the plain-text password.
98 passwd = pairfind(request->config_items, PW_DIGEST_HA1);
100 if (passwd->length != 32) {
101 radlog(L_AUTH, "rlm_digest: Digest-HA1 has invalid length, authentication failed.");
102 return RLM_MODULE_INVALID;
105 passwd = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
108 radlog(L_AUTH, "rlm_digest: Cleartext-Password or Digest-HA1 is required for authentication.");
109 return RLM_MODULE_INVALID;
113 * We need these, too.
115 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
117 DEBUG("ERROR: You set 'Auth-Type = Digest' for a request that did not contain any digest attributes!");
118 return RLM_MODULE_INVALID;
122 * Loop through the Digest-Attributes, sanity checking them.
124 DEBUG(" rlm_digest: Converting Digest-Attributes to something sane...");
126 int length = vp->length;
128 uint8_t *p = &vp->vp_octets[0];
132 * Until this stupidly encoded attribure is exhausted.
136 * The attribute type must be valid
138 if ((p[0] == 0) || (p[0] > 10)) {
139 DEBUG("ERROR: Received Digest-Attributes with invalid sub-attribute %d", p[0]);
140 return RLM_MODULE_INVALID;
143 attrlen = p[1]; /* stupid VSA format */
149 DEBUG("ERROR: Received Digest-Attributes with short sub-attribute %d, of length %d", p[0], attrlen);
150 return RLM_MODULE_INVALID;
156 if (attrlen > length) {
157 DEBUG("ERROR: Received Digest-Attributes with long sub-attribute %d, of length %d", p[0], attrlen);
158 return RLM_MODULE_INVALID;
162 * Create a new attribute, broken out of
163 * the stupid sub-attribute crap.
165 * Didn't they know that VSA's exist?
167 sub = paircreate(PW_DIGEST_REALM - 1 + p[0],
170 return RLM_MODULE_FAIL; /* out of memory */
172 memcpy(&sub->vp_octets[0], &p[2], attrlen - 2);
173 sub->vp_octets[attrlen - 2] = '\0';
174 sub->length = attrlen - 2;
178 vp_print(stdout, sub);
183 * And add it to the request pairs.
185 pairadd(&request->packet->vps, sub);
188 * FIXME: Check for the existence
189 * of the necessary attributes!
194 } /* loop over this one attribute */
197 * Find the next one, if it exists.
199 vp = pairfind(vp->next, PW_DIGEST_ATTRIBUTES);
203 * We require access to the Digest-Nonce-Value
205 nonce = pairfind(request->packet->vps, PW_DIGEST_NONCE);
207 DEBUG("ERROR: No Digest-Nonce: Cannot perform Digest authentication");
208 return RLM_MODULE_INVALID;
212 * A1 = Digest-User-Name ":" Realm ":" Password
214 vp = pairfind(request->packet->vps, PW_DIGEST_USER_NAME);
216 DEBUG("ERROR: No Digest-User-Name: Cannot perform Digest authentication");
217 return RLM_MODULE_INVALID;
219 memcpy(&a1[0], &vp->vp_octets[0], vp->length);
225 vp = pairfind(request->packet->vps, PW_DIGEST_REALM);
227 DEBUG("ERROR: No Digest-Realm: Cannot perform Digest authentication");
228 return RLM_MODULE_INVALID;
230 memcpy(&a1[a1_len], &vp->vp_octets[0], vp->length);
231 a1_len += vp->length;
236 if (passwd->attribute == PW_CLEARTEXT_PASSWORD) {
237 memcpy(&a1[a1_len], &passwd->vp_octets[0], passwd->length);
238 a1_len += passwd->length;
240 DEBUG2("A1 = %s", a1);
243 DEBUG2("A1 = %s (using Digest-HA1)", a1);
248 * See which variant we calculate.
249 * Assume MD5 if no Digest-Algorithm attribute received
251 algo = pairfind(request->packet->vps, PW_DIGEST_ALGORITHM);
252 if ((algo == NULL) ||
253 (strcasecmp(algo->vp_strvalue, "MD5") == 0)) {
255 * Set A1 to Digest-HA1 if no User-Password found
257 if (passwd->attribute == PW_DIGEST_HA1) {
258 if (lrad_hex2bin(passwd->vp_strvalue, &a1[0], 16) != 16) {
259 DEBUG2("rlm_digest: Invalid text in Digest-HA1");
260 return RLM_MODULE_INVALID;
264 } else if (strcasecmp(algo->vp_strvalue, "MD5-sess") == 0) {
266 * K1 = H(A1) : Digest-Nonce ... : H(A2)
268 * If we find Digest-HA1, we assume it contains
271 if (passwd->attribute == PW_CLEARTEXT_PASSWORD) {
272 librad_md5_calc(hash, &a1[0], a1_len);
273 lrad_bin2hex(hash, &a1[0], 16);
274 } else { /* MUST be Digest-HA1 */
275 memcpy(&a1[0], passwd->vp_strvalue, 32);
283 * Tack on the Digest-Nonce. Length must be even
285 if ((nonce->length & 1) != 0) {
286 DEBUG("ERROR: Received Digest-Nonce hex string with invalid length: Cannot perform Digest authentication");
287 return RLM_MODULE_INVALID;
289 memcpy(&a1[a1_len], &nonce->vp_octets[0], nonce->length);
290 a1_len += nonce->length;
295 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
297 DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
298 return RLM_MODULE_INVALID;
302 * Digest-CNonce length must be even
304 if ((vp->length & 1) != 0) {
305 DEBUG("ERROR: Received Digest-CNonce hex string with invalid length: Cannot perform Digest authentication");
306 return RLM_MODULE_INVALID;
308 memcpy(&a1[a1_len], &vp->vp_octets[0], vp->length);
309 a1_len += vp->length;
311 } else if ((algo != NULL) &&
312 (strcasecmp(algo->vp_strvalue, "MD5") != 0)) {
314 * We check for "MD5-sess" and "MD5".
315 * Anything else is an error.
317 DEBUG("ERROR: Unknown Digest-Algorithm \"%s\": Cannot perform Digest authentication", vp->vp_strvalue);
318 return RLM_MODULE_INVALID;
322 * A2 = Digest-Method ":" Digest-URI
324 vp = pairfind(request->packet->vps, PW_DIGEST_METHOD);
326 DEBUG("ERROR: No Digest-Method: Cannot perform Digest authentication");
327 return RLM_MODULE_INVALID;
329 memcpy(&a2[0], &vp->vp_octets[0], vp->length);
335 vp = pairfind(request->packet->vps, PW_DIGEST_URI);
337 DEBUG("ERROR: No Digest-URI: Cannot perform Digest authentication");
338 return RLM_MODULE_INVALID;
340 memcpy(&a2[a2_len], &vp->vp_octets[0], vp->length);
341 a2_len += vp->length;
344 * QOP is "auth-int", tack on ": Digest-Body-Digest"
346 qop = pairfind(request->packet->vps, PW_DIGEST_QOP);
348 (strcasecmp(qop->vp_strvalue, "auth-int") == 0)) {
352 * Add in Digest-Body-Digest
358 * Must be a hex representation of an MD5 digest.
360 body = pairfind(request->packet->vps, PW_DIGEST_BODY_DIGEST);
362 DEBUG("ERROR: No Digest-Body-Digest: Cannot perform Digest authentication");
363 return RLM_MODULE_INVALID;
366 if ((a2_len + body->length) > sizeof(a2)) {
367 DEBUG("ERROR: Digest-Body-Digest is too long");
368 return RLM_MODULE_INVALID;
371 memcpy(a2 + a2_len, body->vp_octets, body->length);
372 a2_len += body->length;
374 } else if ((qop != NULL) &&
375 (strcasecmp(qop->vp_strvalue, "auth") != 0)) {
376 DEBUG("ERROR: Unknown Digest-QOP \"%s\": Cannot perform Digest authentication", qop->vp_strvalue);
377 return RLM_MODULE_INVALID;
381 DEBUG2("A2 = %s", a2);
384 * KD = H(A1) : Digest-Nonce ... : H(A2).
385 * Compute MD5 if Digest-Algorithm == "MD5-Sess",
386 * or if we found a User-Password.
388 if (((algo != NULL) &&
389 (strcasecmp(algo->vp_strvalue, "MD5-Sess") == 0)) ||
390 (passwd->attribute == PW_CLEARTEXT_PASSWORD)) {
392 librad_md5_calc(&hash[0], &a1[0], a1_len);
394 memcpy(&hash[0], &a1[0], a1_len);
396 lrad_bin2hex(hash, kd, sizeof(hash));
401 for (i = 0; i < 16; i++) {
402 printf("%02x", hash[i]);
412 memcpy(&kd[kd_len], nonce->vp_octets, nonce->length);
413 kd_len += nonce->length;
416 * No QOP defined. Do RFC 2069 compatibility.
423 } else { /* Digest-QOP MUST be "auth" or "auth-int" */
425 * Tack on ":" Digest-Nonce-Count ":" Digest-CNonce
431 vp = pairfind(request->packet->vps, PW_DIGEST_NONCE_COUNT);
433 DEBUG("ERROR: No Digest-Nonce-Count: Cannot perform Digest authentication");
434 return RLM_MODULE_INVALID;
436 memcpy(&kd[kd_len], &vp->vp_octets[0], vp->length);
437 kd_len += vp->length;
442 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
444 DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
445 return RLM_MODULE_INVALID;
447 memcpy(&kd[kd_len], &vp->vp_octets[0], vp->length);
448 kd_len += vp->length;
453 memcpy(&kd[kd_len], &qop->vp_octets[0], qop->length);
454 kd_len += qop->length;
463 librad_md5_calc(&hash[0], &a2[0], a2_len);
465 lrad_bin2hex(hash, kd + kd_len, sizeof(hash));
470 for (i = 0; i < 16; i++) {
471 printf("%02x", hash[i]);
480 DEBUG2("KD = %s\n", &kd[0]);
483 * Take the hash of KD.
485 librad_md5_calc(&hash[0], &kd[0], kd_len);
486 memcpy(&kd[0], &hash[0], 16);
489 * Get the binary value of Digest-Response
491 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
493 DEBUG("ERROR: No Digest-Response attribute in the request. Cannot perform digest authentication");
494 return RLM_MODULE_INVALID;
497 if (lrad_hex2bin(&vp->vp_octets[0], &hash[0], vp->length >> 1) != (vp->length >> 1)) {
498 DEBUG2("rlm_digest: Invalid text in Digest-Response");
499 return RLM_MODULE_INVALID;
505 for (i = 0; i < 16; i++) {
506 printf("%02x", kd[i]);
511 for (i = 0; i < 16; i++) {
512 printf("%02x", hash[i]);
519 * And finally, compare the digest in the packet with KD.
521 if (memcmp(&kd[0], &hash[0], 16) == 0) {
522 return RLM_MODULE_OK;
525 DEBUG("rlm_digest: FAILED authentication");
526 return RLM_MODULE_REJECT;
530 * The module name should be the only globally exported symbol.
531 * That is, everything else should be 'static'.
533 * If the module needs to temporarily modify it's instantiation
534 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
535 * The server will then take care of ensuring that the module
536 * is single-threaded.
538 module_t rlm_digest = {
542 NULL, /* instantiation */
545 digest_authenticate, /* authentication */
546 digest_authorize, /* authorization */
547 NULL, /* preaccounting */
548 NULL, /* accounting */
549 NULL, /* checksimul */
550 NULL, /* pre-proxy */
551 NULL, /* post-proxy */