Changeset 218

Show
Ignore:
Timestamp:
09/30/06 14:22:21 (2 years ago)
Author:
jwalt
Message:
  • fix usage of PRNG
  • make logging end-user compatible
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/plugins/authenticate

    r217 r218  
    1818use Digest::MD5 qw(md5_base64 md5_hex md5); 
    1919use MIME::Base64; 
     20use Config; 
    2021use bytes; 
    2122use constant DEFAULT_REALM => 'Protected Area'; 
     
    226227} 
    227228 
    228 sub _secret() { 
    229     # MersenneTwister is not a cryptographically secure PRNG unless fed through a hash function. 
    230     return md5(&rand()); 
    231 } 
    232  
    233229# a secret used to protect first-time requests 
    234 my $_SERVER_SECRET
     230my $_SERVER_SECRET = ''
    235231 
    236232sub init { 
    237233    my $self = shift; 
     234 
     235    # Poor randomness, but all we might have. 
     236    *_secret = sub { md5(rand().time().$_SERVER_SECRET) }; 
    238237 
    239238    # Try to get secure random numbers. Without, security does not withstand reasonably determined attack. 
    240239    eval { 
    241240        require Math::Random::MT::Auto; 
    242         Math::Random::MT::Auto->import('rand','srand'); 
    243241        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; }; 
    246244        $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 ); }; 
    247249    }; 
    248250    $self->log(LOGWARN,"Could not load suitable PRNG. Digest auth will be insecure. Please install Math::Random::MT::Auto.") if ($@); 
    249251 
    250     $_SERVER_SECRET = _secret; 
     252    $_SERVER_SECRET = &_secret; 
    251253    cleanup(); 
    252254} 
    253255 
    254 # records nonce-count, timestamp, secret and username for sessions 
     256# records nonce-count, timestamp, and secret for sessions 
    255257my %_SESSIONS; 
    256258 
     
    300302        $_SESSIONS{$session}[0] = 1; 
    301303        $_SESSIONS{$session}[1] = int($timestamp); 
    302         $secret = $_SESSIONS{$session}[2] = _secret; 
     304        $secret = $_SESSIONS{$session}[2] = &_secret; 
    303305    } else { 
    304306        undef $session; 
    305307    } 
    306308 
    307     my $nonce = $timestamp . ":" . ( defined $session? $session : _secret ) . ":" . $user . ":"; 
     309    my $nonce = $timestamp . ":" . ( defined $session? $session : &_secret ) . ":" . $user . ":"; 
    308310    $nonce .= md5($nonce . $secret); 
    309311    my $header = "Digest realm=" . quoted_string($self->config->auth_realm) . ", " . 
     
    313315        "stale=" . (defined $session?'true':'false') . ", " . 
    314316        "nonce=".quoted_string(encode_base64($nonce,"")); 
    315     $self->log(LOGINFO,"Sent header [$header]"); 
     317    $self->log(LOGDEBUG,"Sent header [$header]"); 
    316318    $self->client->headers_out->header("WWW-Authenticate",$header); 
    317319    $self->client->headers_out->header_remove('Authentication-Info'); 
     
    335337 
    336338    my $auth = $hd->header('Authorization'); 
    337     $self->log(LOGINFO, "$want_type ".(defined $auth?"(got: $auth)":"(no authorization header)")); 
     339    $self->log(LOGDEBUG, ucfirst($want_type) . " authentication for realm " . $self->config->auth_realm); 
    338340 
    339341    no warnings "uninitialized"; 
     
    419421        # If the nonce doesn't match what it should be, something fishy is going on: A user tries to act as a different user,  
    420422        # 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
    422424        my $secret = ( $s? $$s[2] : $_SERVER_SECRET ); 
    423425        $nonce = "$timestamp:$session:$user:" . md5("$timestamp:$session:$user:$secret"); 
     
    445447 
    446448        # 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} ); 
    448450        $response = md5_hex( $hash->[0] . ':' . encode_base64($param{nonce},"") . ':' . 
    449451                                $param{nc} . ':' . $param{cnonce} . ':' . 'auth' . ':' . 
    450452                                $A2 ); 
    451453        $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.  
    452457 
    453458        $hd->header('Digest-Session',$session); 
    454459        if (!defined $_SESSIONS{$session}[0]) { 
    455             # successful login, establish server-side state 
     460            # Successful login, establish server-side state. 
    456461            $_SESSIONS{$session} = [ -1 ]; 
    457462            $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. 
    460464            return $self->send_digest_challenge($session,$param{username}); 
    461465        } 
     
    465469            # Change nonce once per minute to reduce replay attack time window. 
    466470            return $self->send_digest_challenge($session,$param{username}) if $timestamp < time()-60; 
     471            $self->log(LOGINFO, "Identified user as $param{username} via Digest"); 
    467472            return OK; 
    468473        } 
     
    472477 
    473478        # All checks passed. 
     479        $self->log(LOGINFO, "Identified user as $param{username} via Digest"); 
    474480        return OK; 
    475481    }