reSIProcate/DialogUsageManager  9680
ClientAuthManager.cxx
Go to the documentation of this file.
00001 #include <cassert>
00002 
00003 #include "resip/stack/Helper.hxx"
00004 #include "resip/stack/SipMessage.hxx"
00005 #include "resip/dum/ClientAuthManager.hxx"
00006 #include "resip/dum/UserProfile.hxx"
00007 #include "rutil/Logger.hxx"
00008 #include "rutil/Random.hxx"
00009 #include "resip/dum/ClientAuthExtension.hxx"
00010 
00011 #define RESIPROCATE_SUBSYSTEM Subsystem::DUM
00012 
00013 using namespace resip;
00014 using namespace std;
00015 
00016 class ClientAuthDecorator : public MessageDecorator
00017 {
00018 public:
00019    ClientAuthDecorator(bool isProxyCredential, const Auth& auth, const UserProfile::DigestCredential& credential, const Data& authQop, const Data& nonceCountString) :
00020       mIsProxyCredential(isProxyCredential), mAuth(auth), mCredential(credential), mAuthQop(authQop), mNonceCountString(nonceCountString) {}
00021    virtual ~ClientAuthDecorator() {}
00022    virtual void decorateMessage(SipMessage &msg, 
00023                                 const Tuple &source,
00024                                 const Tuple &destination,
00025                                 const Data& sigcompId)
00026    {
00027       Data cnonce = Random::getCryptoRandomHex(16);
00028 
00029       Auths & target = mIsProxyCredential ? msg.header(h_ProxyAuthorizations) : msg.header(h_Authorizations);
00030    
00031       DebugLog( << " Add auth, " << this << " in response to: " << mAuth);
00032       Auth auth;
00033       if (ClientAuthExtension::instance().algorithmAndQopSupported(mAuth))
00034       {
00035          DebugLog(<<"Using extension to make auth response");
00036       
00037          if(mCredential.isPasswordA1Hash)
00038          {
00039             ClientAuthExtension::instance().makeChallengeResponseAuthWithA1(msg,
00040                                                             mCredential.user,
00041                                                             mCredential.password,
00042                                                             mAuth, 
00043                                                             cnonce,
00044                                                             mAuthQop, 
00045                                                             mNonceCountString,
00046                                                             auth);      
00047          }
00048          else
00049          {
00050             ClientAuthExtension::instance().makeChallengeResponseAuth(msg,
00051                                                             mCredential.user,
00052                                                             mCredential.password,
00053                                                             mAuth, 
00054                                                             cnonce,
00055                                                             mAuthQop, 
00056                                                             mNonceCountString,
00057                                                             auth);      
00058          }
00059       }
00060       else
00061       {
00062          if(mCredential.isPasswordA1Hash)
00063          {
00064             Helper::makeChallengeResponseAuthWithA1(msg, 
00065                                            mCredential.user, 
00066                                            mCredential.password, 
00067                                            mAuth, 
00068                                            cnonce, 
00069                                            mAuthQop, 
00070                                            mNonceCountString, 
00071                                            auth);
00072          }
00073          else
00074          {
00075             Helper::makeChallengeResponseAuth(msg, 
00076                                            mCredential.user, 
00077                                            mCredential.password, 
00078                                            mAuth, 
00079                                            cnonce, 
00080                                            mAuthQop, 
00081                                            mNonceCountString, 
00082                                            auth);
00083          }
00084       }
00085       target.push_back(auth);
00086    
00087       DebugLog(<<"ClientAuthDecorator, proxy: " << mIsProxyCredential << " " << target.back());
00088    }
00089    virtual void rollbackMessage(SipMessage& msg) 
00090    {
00091       Auths & target = mIsProxyCredential ? msg.header(h_ProxyAuthorizations) : msg.header(h_Authorizations);
00092       target.pop_back();
00093    }  
00094    virtual MessageDecorator* clone() const { return new ClientAuthDecorator(mIsProxyCredential, mAuth, mCredential, mAuthQop, mNonceCountString); }
00095 private:
00096     bool mIsProxyCredential;
00097     Auth mAuth;
00098     UserProfile::DigestCredential mCredential;
00099     Data mAuthQop;
00100     Data mNonceCountString;
00101 };
00102 
00103 ClientAuthManager::ClientAuthManager() 
00104 {
00105 }
00106 
00107 
00108 bool 
00109 ClientAuthManager::handle(UserProfile& userProfile, SipMessage& origRequest, const SipMessage& response)
00110 {
00111    try
00112    {
00113       assert( response.isResponse() );
00114       assert( origRequest.isRequest() );
00115       
00116       DialogSetId id(origRequest);
00117 
00118       const int& code = response.header(h_StatusLine).statusCode();
00119       if (code < 101 || code >= 500)
00120       {
00121          return false;
00122       }
00123       else if (! (  code == 401 || code == 407 )) // challenge success
00124       {
00125          AttemptedAuthMap::iterator it = mAttemptedAuths.find(id);     
00126          if (it != mAttemptedAuths.end())
00127          {
00128             DebugLog (<< "ClientAuthManager::handle: transitioning " << id << "to cached");         
00129 
00130             // cache the result
00131             it->second.authSucceeded();
00132          }      
00133          return false;
00134       }   
00135 
00136       if (!(response.exists(h_WWWAuthenticates) || response.exists(h_ProxyAuthenticates)))
00137       {
00138          DebugLog (<< "Invalid challenge for " << id  << ", nothing to respond to; fail");         
00139          return false;
00140       }
00141    
00142       AuthState& authState = mAttemptedAuths[id];
00143 
00144       // based on the UserProfile and the challenge, store credentials in the
00145       // AuthState associated with this DialogSet if the algorithm is supported
00146       if (authState.handleChallenge(userProfile, response))
00147       {
00148          assert(origRequest.header(h_Vias).size() == 1);
00149          origRequest.header(h_CSeq).sequence()++;
00150          DebugLog (<< "Produced response to digest challenge for " << userProfile );
00151          return true;
00152       }
00153       else
00154       {
00155          return false;
00156       }
00157    }
00158    catch(BaseException& e)
00159    {
00160       assert(0);
00161       ErrLog(<< "Unexpected exception in ClientAuthManager::handle " << e);
00162       return false;
00163    }      
00164 }
00165 
00166 void
00167 ClientAuthManager::addAuthentication(SipMessage& request)
00168 {
00169    AttemptedAuthMap::iterator it = mAttemptedAuths.find(DialogSetId(request));
00170    if (it != mAttemptedAuths.end())
00171    {
00172       it->second.addAuthentication(request);
00173    }
00174 }
00175 
00176 void 
00177 ClientAuthManager::clearAuthenticationState(const DialogSetId& dsId)
00178 {
00179    dialogSetDestroyed(dsId);
00180 }
00181 
00182 ClientAuthManager::AuthState::AuthState() :
00183    mFailed(false)
00184 {
00185 }
00186 
00187 
00188 bool 
00189 ClientAuthManager::AuthState::handleChallenge(UserProfile& userProfile, const SipMessage& challenge)
00190 {
00191    if (mFailed)
00192    {
00193       return false;
00194    }   
00195    bool handled = true;   
00196    if (challenge.exists(h_WWWAuthenticates))
00197    {
00198       for (Auths::const_iterator i = challenge.header(h_WWWAuthenticates).begin();  
00199            i != challenge.header(h_WWWAuthenticates).end(); ++i)                    
00200       {    
00201          if (i->exists(p_realm))
00202          {
00203             if (!mRealms[i->param(p_realm)].handleAuth(userProfile, *i, false))
00204             {
00205                handled = false;
00206                break;
00207             }
00208          }
00209          else
00210          {
00211             return false;
00212          }
00213       }
00214    }
00215    if (challenge.exists(h_ProxyAuthenticates))
00216    {
00217       for (Auths::const_iterator i = challenge.header(h_ProxyAuthenticates).begin();  
00218            i != challenge.header(h_ProxyAuthenticates).end(); ++i)                    
00219       {    
00220          if (i->exists(p_realm))
00221          {
00222             if (!mRealms[i->param(p_realm)].handleAuth(userProfile, *i, true))
00223             {
00224                handled = false;
00225                break;
00226             }
00227          }
00228       else
00229       {
00230          return false;
00231       }
00232       }
00233       if(!handled)
00234       {
00235          InfoLog( << "ClientAuthManager::AuthState::handleChallenge failed for: " << challenge);
00236       }
00237    }
00238    return handled;
00239 }
00240 
00241 void 
00242 ClientAuthManager::AuthState::authSucceeded()
00243 {
00244    for(RealmStates::iterator i = mRealms.begin(); i!=mRealms.end(); i++)
00245    {
00246       i->second.authSucceeded();
00247    }
00248 }
00249 
00250 void 
00251 ClientAuthManager::AuthState::addAuthentication(SipMessage& request)
00252 {
00253    request.remove(h_ProxyAuthorizations);
00254    request.remove(h_Authorizations);  
00255 
00256    if (mFailed) return;
00257 
00258    for(RealmStates::iterator i = mRealms.begin(); i!=mRealms.end(); i++)
00259    {
00260       i->second.addAuthentication(request);
00261    }
00262 }
00263    
00264 
00265 ClientAuthManager::RealmState::RealmState() :
00266    mIsProxyCredential(false),
00267    mState(Invalid),
00268    mNonceCount(0),
00269    mAuthPtr(NULL)
00270 {
00271 }
00272 
00273 Data RealmStates[] = 
00274 {
00275    "invalid",
00276    "cached",
00277    "current",
00278    "tryonce",
00279    "failed",
00280 };
00281 
00282 const Data&
00283 ClientAuthManager::RealmState::getStateString(State s) 
00284 {
00285    return RealmStates[s];
00286 }
00287 
00288 void 
00289 ClientAuthManager::RealmState::transition(State s)
00290 {
00291    DebugLog(<< "ClientAuthManager::RealmState::transition from " << getStateString(mState) << " to " << getStateString(s));
00292    mState = s;
00293 }
00294 
00295 void
00296 ClientAuthManager::RealmState::authSucceeded()
00297 {
00298    switch(mState)
00299    {
00300       case Invalid:
00301          assert(0);
00302          break;
00303       case Current:
00304       case Cached:
00305       case TryOnce:
00306          transition(Cached);
00307          break;
00308       case Failed:
00309          assert(0);
00310          break;         
00311    };
00312 }
00313 
00314 bool 
00315 ClientAuthManager::RealmState::handleAuth(UserProfile& userProfile, const Auth& auth, bool isProxyCredential)
00316 {   
00317    DebugLog( << "ClientAuthManager::RealmState::handleAuth: " << this << " " << auth << " is proxy: " << isProxyCredential);
00318    mIsProxyCredential = isProxyCredential;   //this changing dynamically would
00319                                              //be very bizarre..should trap w/ enum
00320    switch(mState)
00321    {
00322       case Invalid:
00323          mAuth = auth;
00324          transition(Current);
00325          break;         
00326       case Current:
00327          if (auth.exists(p_stale) && auth.param(p_stale) == "true")
00328          {
00329             DebugLog (<< "Stale nonce:" <<  auth);
00330             mAuth = auth;
00331             clear();
00332          }
00333          else if(auth.exists(p_nonce) && auth.param(p_nonce) != mAuth.param(p_nonce))
00334          {
00335             DebugLog (<< "Different nonce, was: " << mAuth.param(p_nonce) << " now " << auth.param(p_nonce));
00336             mAuth = auth;
00337             clear();
00338             transition(TryOnce);            
00339          }
00340          else
00341          {
00342             DebugLog( << "Challenge response already failed for: " << auth);
00343             transition(Failed);            
00344             return false;
00345          }
00346          break;         
00347       case TryOnce:
00348          DebugLog( << "Extra chance still failed: " << auth);
00349          transition(Failed);
00350          return false;
00351       case Cached: //basically 1 free chance, here for interop, may not be
00352                    //required w/ nonce check in current
00353          mAuth = auth;
00354          clear();
00355          transition(Current);
00356          break;         
00357       case Failed:
00358          return false;
00359    }
00360 
00361    if (findCredential(userProfile, auth))
00362    {
00363       return true;
00364    }
00365    else
00366    {
00367       transition(Failed);
00368       return false;
00369    }
00370 }
00371 
00372 void
00373 ClientAuthManager::RealmState::clear()
00374 {
00375    mNonceCount = 0;
00376 }
00377                      
00378 bool 
00379 ClientAuthManager::RealmState::findCredential(UserProfile& userProfile, const Auth& auth)
00380 {
00381    if (!(Helper::algorithmAndQopSupported(auth) 
00382          || (ClientAuthExtension::instance().algorithmAndQopSupported(auth))))
00383    {
00384       DebugLog(<<"Unsupported algorithm or qop: " << auth);
00385       return false;
00386    }
00387 
00388    const Data& realm = auth.param(p_realm);                   
00390    mCredential = userProfile.getDigestCredential(realm);
00391    if ( mCredential.realm.empty() )                       
00392    {                                        
00393       DebugLog( << "Got a 401 or 407 but could not find credentials for realm: " << realm);
00394 //      DebugLog (<< auth);
00395 //      DebugLog (<< response);
00396       return false;
00397    }                     
00398    return true;   
00399 }
00400 
00401 void 
00402 ClientAuthManager::RealmState::addAuthentication(SipMessage& request)
00403 {
00404    assert(mState != Failed);
00405    if (mState == Failed) return;
00406 
00407    Data nonceCountString;
00408    Data authQop = Helper::qopOption(mAuth);
00409    if(!authQop.empty())
00410    {
00411        Helper::updateNonceCount(mNonceCount, nonceCountString);
00412    }
00413    
00414    // Add client auth decorator so that we ensure any body hashes are calcuated after user defined outbound decorators that
00415    // may be modifying the message body
00416    std::auto_ptr<MessageDecorator> clientAuthDecorator(new ClientAuthDecorator(mIsProxyCredential, mAuth, mCredential, authQop, nonceCountString));
00417    request.addOutboundDecorator(clientAuthDecorator);
00418 }
00419 
00420 void ClientAuthManager::dialogSetDestroyed(const DialogSetId& id)
00421 {
00422    if ( mAttemptedAuths.find(id) != mAttemptedAuths.end())
00423    {
00424       mAttemptedAuths.erase(id);
00425    }
00426 }
00427 
00428 // bool
00429 // ClientAuthManager::CompareAuth::operator()(const Auth& lhs, const Auth& rhs) const
00430 // {
00431 //    if (lhs.param(p_realm) < rhs.param(p_realm))
00432 //    {
00433 //       return true;
00434 //    }
00435 //    else if (lhs.param(p_realm) > rhs.param(p_realm))
00436 //    {
00437 //       return false;
00438 //    }
00439 //    else
00440 //    {
00441 //       return lhs.param(p_username) < rhs.param(p_username);
00442 //    }
00443 // }
00444 
00445 
00446 
00447 /* ====================================================================
00448  * The Vovida Software License, Version 1.0 
00449  * 
00450  * Copyright (c) 2000 Vovida Networks, Inc.  All rights reserved.
00451  * 
00452  * Redistribution and use in source and binary forms, with or without
00453  * modification, are permitted provided that the following conditions
00454  * are met:
00455  * 
00456  * 1. Redistributions of source code must retain the above copyright
00457  *    notice, this list of conditions and the following disclaimer.
00458  * 
00459  * 2. Redistributions in binary form must reproduce the above copyright
00460  *    notice, this list of conditions and the following disclaimer in
00461  *    the documentation and/or other materials provided with the
00462  *    distribution.
00463  * 
00464  * 3. The names "VOCAL", "Vovida Open Communication Application Library",
00465  *    and "Vovida Open Communication Application Library (VOCAL)" must
00466  *    not be used to endorse or promote products derived from this
00467  *    software without prior written permission. For written
00468  *    permission, please contact vocal@vovida.org.
00469  *
00470  * 4. Products derived from this software may not be called "VOCAL", nor
00471  *    may "VOCAL" appear in their name, without prior written
00472  *    permission of Vovida Networks, Inc.
00473  * 
00474  * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
00475  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00476  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
00477  * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
00478  * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
00479  * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
00480  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00481  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00482  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
00483  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00484  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
00485  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
00486  * DAMAGE.
00487  * 
00488  * ====================================================================
00489  * 
00490  * This software consists of voluntary contributions made by Vovida
00491  * Networks, Inc. and many individuals on behalf of Vovida Networks,
00492  * Inc.  For more information on Vovida Networks, Inc., please see
00493  * <http://www.vovida.org/>.
00494  *
00495  */