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>
25 #include "libradius.h"
34 #include "rad_assert.h"
36 static const char rcsid[] = "$Id$";
38 static int digest_authorize(void *instance, REQUEST *request)
42 /* quiet the compiler */
46 * We need both of these attributes to do the authentication.
48 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
50 return RLM_MODULE_NOOP;
54 * Check the sanity of the attribute.
56 if (vp->length != 32) {
57 DEBUG("ERROR: Received invalid Digest-Response attribute (length %d should be 32)", vp->length);
58 return RLM_MODULE_INVALID;
64 vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
66 DEBUG("ERROR: Received Digest-Response without Digest-Attributes");
67 return RLM_MODULE_INVALID;
71 * Loop through the Digest-Attributes, sanity checking them.
73 DEBUG(" rlm_digest: Converting Digest-Attributes to something sane...");
75 int length = vp->length;
77 uint8_t *p = &vp->strvalue[0];
81 * Until this stupidly encoded attribure is exhausted.
85 * The attribute type must be valid
87 if ((p[0] == 0) || (p[0] > 10)) {
88 DEBUG("ERROR: Received Digest-Attributes with invalid sub-attribute %d", p[0]);
89 return RLM_MODULE_INVALID;
92 attrlen = p[1]; /* stupid VSA format */
98 DEBUG("ERROR: Received Digest-Attributes with short sub-attribute %d, of length %d", p[0], attrlen);
99 return RLM_MODULE_INVALID;
105 if (attrlen > length) {
106 DEBUG("ERROR: Received Digest-Attributes with long sub-attribute %d, of length %d", p[0], attrlen);
107 return RLM_MODULE_INVALID;
111 * Create a new attribute, broken out of
112 * the stupid sub-attribute crap.
114 * Didn't they know that VSA's exist?
116 sub = paircreate(PW_DIGEST_REALM - 1 + p[0],
119 exit(1); /* out of memory */
121 memcpy(&sub->strvalue[0], &p[2], attrlen - 2);
122 sub->strvalue[attrlen - 2] = '\0';
123 sub->length = attrlen - 2;
127 vp_print(stdout, sub);
132 * And add it to the request pairs.
134 pairadd(&request->packet->vps, sub);
137 * FIXME: Check for the existence
138 * of the necessary attributes!
143 } /* loop over this one attribute */
146 * Find the next one, if it exists.
148 vp = pairfind(vp->next, PW_DIGEST_ATTRIBUTES);
152 * Everything's OK, add a digest authentication type.
154 if (pairfind(request->config_items, PW_AUTHTYPE) == NULL) {
155 DEBUG("rlm_digest: Adding Auth-Type = DIGEST");
156 pairadd(&request->config_items,
157 pairmake("Auth-Type", "DIGEST", T_OP_EQ));
160 return RLM_MODULE_OK;
164 * Convert a string in hex to one in binary
166 static void hex2bin(uint8_t *out, const uint8_t *in)
171 sscanf(in, "%02x", &tmp);
179 * Perform all of the wondrous variants of digest authentication.
181 static int digest_authenticate(void *instance, REQUEST *request)
184 int a1_len, a2_len, kd_len;
185 uint8_t a1[(MAX_STRING_LEN + 1) * 5]; /* can be 5 attributes */
186 uint8_t a2[(MAX_STRING_LEN + 1) * 3]; /* can be 3 attributes */
187 uint8_t kd[(MAX_STRING_LEN + 1) * 5];
188 uint8_t hash[16]; /* MD5 output */
190 VALUE_PAIR *qop, *nonce;
193 * We require access to the plain-text password.
195 vp = pairfind(request->config_items, PW_PASSWORD);
197 radlog(L_AUTH, "rlm_digest: Configuration item \"User-Password\" is required for authentication.");
198 return RLM_MODULE_INVALID;
202 * We require access to the Digest-Nonce-Value
204 nonce = pairfind(request->packet->vps, PW_DIGEST_NONCE);
206 DEBUG("ERROR: No Digest-Nonce: Cannot perform Digest authentication");
207 return RLM_MODULE_INVALID;
211 * A1 = Digest-User-Name ":" Realm ":" Password
213 vp = pairfind(request->packet->vps, PW_DIGEST_USER_NAME);
215 DEBUG("ERROR: No Digest-User-Name: Cannot perform Digest authentication");
216 return RLM_MODULE_INVALID;
218 memcpy(&a1[0], &vp->strvalue[0], vp->length);
224 vp = pairfind(request->packet->vps, PW_DIGEST_REALM);
226 DEBUG("ERROR: No Digest-Realm: Cannot perform Digest authentication");
227 return RLM_MODULE_INVALID;
229 memcpy(&a1[a1_len], &vp->strvalue[0], vp->length);
230 a1_len += vp->length;
235 vp = pairfind(request->config_items, PW_PASSWORD);
237 DEBUG("ERROR: No User-Password: Cannot perform Digest authentication");
238 return RLM_MODULE_INVALID;
240 memcpy(&a1[a1_len], &vp->strvalue[0], vp->length);
241 a1_len += vp->length;
244 DEBUG2("A1 = %s", a1);
247 * See which variant we calculate.
249 vp = pairfind(request->packet->vps, PW_DIGEST_ALGORITHM);
251 (strcasecmp(vp->strvalue, "MD5-sess") == 0)) {
252 librad_md5_calc(hash, &a1[0], a1_len);
253 memcpy(&a1[0], hash, 16);
260 * Tack on the Digest-Nonce
262 hex2bin(&a1[a1_len], &nonce->strvalue[0]);
263 a1_len += (nonce->length >> 1); /* FIXME: CHECK LENGTH */
268 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
270 DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
271 return RLM_MODULE_INVALID;
274 hex2bin(&a1[a1_len], &vp->strvalue[0]);
275 a1_len += (vp->length >> 1); /* FIXME: CHECK LENGTH */
277 } else if ((vp != NULL) &&
278 (strcasecmp(vp->strvalue, "MD5") != 0)) {
280 * We check for "MD5-sess" and "MD5".
281 * Anything else is an error.
283 DEBUG("ERROR: Unknown Digest-Algorithm \"%s\": Cannot perform Digest authentication", vp->strvalue);
284 return RLM_MODULE_INVALID;
288 * A2 = Digest-Method ":" Digest-URI
290 vp = pairfind(request->packet->vps, PW_DIGEST_METHOD);
292 DEBUG("ERROR: No Digest-Method: Cannot perform Digest authentication");
293 return RLM_MODULE_INVALID;
295 memcpy(&a2[0], &vp->strvalue[0], vp->length);
301 vp = pairfind(request->packet->vps, PW_DIGEST_URI);
303 DEBUG("ERROR: No Digest-URI: Cannot perform Digest authentication");
304 return RLM_MODULE_INVALID;
306 memcpy(&a2[a2_len], &vp->strvalue[0], vp->length);
307 a2_len += vp->length;
310 * QOP is "auth-int", tack on ": Digest-Body-Digest"
312 qop = pairfind(request->packet->vps, PW_DIGEST_QOP);
314 (strcasecmp(qop->strvalue, "auth-int") == 0)) {
318 * Add in Digest-Body-Digest
324 * Must be a hex representation of an MD5 digest.
326 body = pairfind(request->packet->vps, PW_DIGEST_BODY_DIGEST);
328 DEBUG("ERROR: No Digest-Body-Digest: Cannot perform Digest authentication");
329 return RLM_MODULE_INVALID;
332 rad_assert(body->length == 32); /* FIXME: check in 'auth' */
333 hex2bin(&a2[a2_len], &body->strvalue[0]);
334 a2_len += (body->length >> 1);
336 } else if ((qop != NULL) &&
337 (strcasecmp(qop->strvalue, "auth") != 0)) {
338 DEBUG("ERROR: Unknown Digest-QOP \"%s\": Cannot perform Digest authentication", qop->strvalue);
339 return RLM_MODULE_INVALID;
343 DEBUG2("A2 = %s", a2);
346 * KD = H(A1) : Digest-Nonce ... : H(A2)
348 librad_md5_calc(&hash[0], &a1[0], a1_len);
350 for (i = 0; i < 16; i++) {
351 sprintf(&kd[i * 2], "%02x", hash[i]);
357 for (i = 0; i < 16; i++) {
358 printf("%02x", hash[i]);
368 memcpy(&kd[kd_len], nonce->strvalue, nonce->length);
369 kd_len += nonce->length;
372 * No QOP defined. Do RFC 2069 compatibility.
379 } else { /* Digest-QOP MUST be "auth" or "auth-int" */
381 * Tack on ":" Digest-Nonce-Count ":" Digest-CNonce
387 vp = pairfind(request->packet->vps, PW_DIGEST_NONCE_COUNT);
389 DEBUG("ERROR: No Digest-Nonce-Count: Cannot perform Digest authentication");
390 return RLM_MODULE_INVALID;
392 memcpy(&kd[kd_len], &vp->strvalue[0], vp->length);
393 kd_len += vp->length;
398 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
400 DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
401 return RLM_MODULE_INVALID;
403 memcpy(&kd[kd_len], &vp->strvalue[0], vp->length);
404 kd_len += vp->length;
409 memcpy(&kd[kd_len], &qop->strvalue[0], qop->length);
410 kd_len += qop->length;
419 librad_md5_calc(&hash[0], &a2[0], a2_len);
421 for (i = 0; i < 16; i++) {
422 sprintf(&kd[kd_len + (i * 2)], "%02x", hash[i]);
428 for (i = 0; i < 16; i++) {
429 printf("%02x", hash[i]);
438 DEBUG2("KD = %s\n", &kd[0]);
441 * Take the hash of KD.
443 librad_md5_calc(&hash[0], &kd[0], kd_len);
444 memcpy(&kd[0], &hash[0], 16);
447 * Get the binary value of Digest-Response
449 vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
450 rad_assert(vp != NULL);
452 hex2bin(&hash[0], &vp->strvalue[0]);
457 for (i = 0; i < 16; i++) {
458 printf("%02x", kd[i]);
463 for (i = 0; i < 16; i++) {
464 printf("%02x", hash[i]);
471 * And finally, compare the digest in the packet with KD.
473 if (memcmp(&kd[0], &hash[0], 16) == 0) {
474 return RLM_MODULE_OK;
477 DEBUG("rlm_digest: FAILED authentication");
478 return RLM_MODULE_REJECT;
482 * The module name should be the only globally exported symbol.
483 * That is, everything else should be 'static'.
485 * If the module needs to temporarily modify it's instantiation
486 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
487 * The server will then take care of ensuring that the module
488 * is single-threaded.
490 module_t rlm_digest = {
493 NULL, /* initialization */
494 NULL, /* instantiation */
496 digest_authenticate, /* authentication */
497 digest_authorize, /* authorization */
498 NULL, /* preaccounting */
499 NULL, /* accounting */
500 NULL, /* checksimul */
501 NULL, /* pre-proxy */
502 NULL, /* post-proxy */