Changeset 218
- Timestamp:
- 09/30/06 14:22:21 (2 years ago)
- Files:
-
- trunk/plugins/authenticate (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/plugins/authenticate
r217 r218 18 18 use Digest::MD5 qw(md5_base64 md5_hex md5); 19 19 use MIME::Base64; 20 use Config; 20 21 use bytes; 21 22 use constant DEFAULT_REALM => 'Protected Area'; … … 226 227 } 227 228 228 sub _secret() {229 # MersenneTwister is not a cryptographically secure PRNG unless fed through a hash function.230 return md5(&rand());231 }232 233 229 # a secret used to protect first-time requests 234 my $_SERVER_SECRET ;230 my $_SERVER_SECRET = ''; 235 231 236 232 sub init { 237 233 my $self = shift; 234 235 # Poor randomness, but all we might have. 236 *_secret = sub { md5(rand().time().$_SERVER_SECRET) }; 238 237 239 238 # Try to get secure random numbers. Without, security does not withstand reasonably determined attack. 240 239 eval { 241 240 require Math::Random::MT::Auto; 242 Math::Random::MT::Auto->import('rand','srand');243 241 my $init; 244 eval { &srand('/dev/random'); $init = 1; } if -c "/dev/random";245 eval { require Win32::API; &srand('win32'); $init = 1; };242 eval { Math::Random::MT::Auto::srand('/dev/urandom'); $init = 1; } if -c "/dev/random"; 243 eval { require Win32::API; Math::Random::MT::Auto::srand('win32'); $init = 1; }; 246 244 $self->log(LOGWARN,"Could not seed PRNG. You need to call Math::Random::MT::Auto::srand yourself.") unless $init; 245 246 # MersenneTwister is not cryptographically secure by itself. The authors suggest using 247 # a hash over eight random values. 248 *secret = sub { md5( join("",map({ Math::Random::MT::Auto::irand() } 1..8)) . $_SERVER_SECRET ); }; 247 249 }; 248 250 $self->log(LOGWARN,"Could not load suitable PRNG. Digest auth will be insecure. Please install Math::Random::MT::Auto.") if ($@); 249 251 250 $_SERVER_SECRET = _secret;252 $_SERVER_SECRET = &_secret; 251 253 cleanup(); 252 254 } 253 255 254 # records nonce-count, timestamp, secret and usernamefor sessions256 # records nonce-count, timestamp, and secret for sessions 255 257 my %_SESSIONS; 256 258 … … 300 302 $_SESSIONS{$session}[0] = 1; 301 303 $_SESSIONS{$session}[1] = int($timestamp); 302 $secret = $_SESSIONS{$session}[2] = _secret;304 $secret = $_SESSIONS{$session}[2] = &_secret; 303 305 } else { 304 306 undef $session; 305 307 } 306 308 307 my $nonce = $timestamp . ":" . ( defined $session? $session : _secret ) . ":" . $user . ":";309 my $nonce = $timestamp . ":" . ( defined $session? $session : &_secret ) . ":" . $user . ":"; 308 310 $nonce .= md5($nonce . $secret); 309 311 my $header = "Digest realm=" . quoted_string($self->config->auth_realm) . ", " . … … 313 315 "stale=" . (defined $session?'true':'false') . ", " . 314 316 "nonce=".quoted_string(encode_base64($nonce,"")); 315 $self->log(LOG INFO,"Sent header [$header]");317 $self->log(LOGDEBUG,"Sent header [$header]"); 316 318 $self->client->headers_out->header("WWW-Authenticate",$header); 317 319 $self->client->headers_out->header_remove('Authentication-Info'); … … 335 337 336 338 my $auth = $hd->header('Authorization'); 337 $self->log(LOG INFO, "$want_type ".(defined $auth?"(got: $auth)":"(no authorization header)"));339 $self->log(LOGDEBUG, ucfirst($want_type) . " authentication for realm " . $self->config->auth_realm); 338 340 339 341 no warnings "uninitialized"; … … 419 421 # If the nonce doesn't match what it should be, something fishy is going on: A user tries to act as a different user, 420 422 # take over another user's session id, or it could be a replay attack. 421 # Unfortunately, this can also happen after a server restart. Force a relogin , however.423 # Unfortunately, this can also happen after a server restart. Force a relogin. 422 424 my $secret = ( $s? $$s[2] : $_SERVER_SECRET ); 423 425 $nonce = "$timestamp:$session:$user:" . md5("$timestamp:$session:$user:$secret"); … … 445 447 446 448 # Create response authentication. 447 $A2 = md5_hex( ':' . $param{uri} ); # TODO: provide auth-int? . ($param{qop} eq 'auth-int'? ':' . $bodyhash : '')449 $A2 = md5_hex( ':' . $param{uri} ); 448 450 $response = md5_hex( $hash->[0] . ':' . encode_base64($param{nonce},"") . ':' . 449 451 $param{nc} . ':' . $param{cnonce} . ':' . 'auth' . ':' . 450 452 $A2 ); 451 453 $self->client->headers_out->header('Authentication-Info',"qop=auth, cnonce=".quoted_string($param{cnonce}).", nc=$param{nc}, rspauth=".quoted_string($response)); 454 # TODO: providing qop=auth-int on resonses is difficult at best. Browsers don't seem to accept 455 # headers in chunked encoding trailer, and calculating the body hash up front can result in a 456 # tremendous performance impact. I assume the major browsers don't check it anyway, so let it be for now. 452 457 453 458 $hd->header('Digest-Session',$session); 454 459 if (!defined $_SESSIONS{$session}[0]) { 455 # successful login, establish server-side state460 # Successful login, establish server-side state. 456 461 $_SESSIONS{$session} = [ -1 ]; 457 462 $self->send('login',$param{username}); 458 # need another round-trip, since we want to tie the session id to the user name 459 # but avoid as much server-side state as possible without compromising security. 463 # Need another round-trip, since we want to tie the session id to the user name but avoid extra server-side state. 460 464 return $self->send_digest_challenge($session,$param{username}); 461 465 } … … 465 469 # Change nonce once per minute to reduce replay attack time window. 466 470 return $self->send_digest_challenge($session,$param{username}) if $timestamp < time()-60; 471 $self->log(LOGINFO, "Identified user as $param{username} via Digest"); 467 472 return OK; 468 473 } … … 472 477 473 478 # All checks passed. 479 $self->log(LOGINFO, "Identified user as $param{username} via Digest"); 474 480 return OK; 475 481 }
