/[resiprocate]/main/resip/dum/ClientAuthManager.cxx
ViewVC logotype

Contents of /main/resip/dum/ClientAuthManager.cxx

Parent Directory Parent Directory | Revision Log Revision Log


Revision 9891 - (show annotations) (download)
Thu Oct 25 18:46:33 2012 UTC (7 years ago) by sgodin
File MIME type: text/plain
File size: 16029 byte(s)
 -added new UserProfile setting to DUM:  
  DigestCacheUseLimit is used to indicate the maximum number of times a particular 
  Proxy or WWW Authorization header will be used in requests within a dialogset.  
  When this limit is   reached then the next request in the DiaglogSet will go out 
  without digest credentials.  This setting can be used to work around bugs/limitations 
  in third-party implementations that have difficulty properly dealing with
  cached credentials.  A setting of 0 (default) will disable the limit and all requests 
  in a Dialogset will have the same cached Authorization header on them, until they are 
  re-challenged by the far end.  A setting of 1 disables caching entirely and future 
  requests within the dialog set will go out without any authorization headers.

Other Changes
 -resip ClientAuthManager - refactored dialogSetDestroyed and clearAuthenticationState to 
  be more logical
 -resip ClientPagerMessage - modified to allow onSuccess and onFailure callbacks even if queue
  of message contents is not used - allows you to call ClientPagerMessage::getMessageRequest,
  build the request yourself (even one without a body), send using DUM::send and still have 
  your callbacks invoked
1 #include <cassert>
2
3 #include "resip/stack/Helper.hxx"
4 #include "resip/stack/SipMessage.hxx"
5 #include "resip/dum/ClientAuthManager.hxx"
6 #include "resip/dum/UserProfile.hxx"
7 #include "rutil/Logger.hxx"
8 #include "rutil/Random.hxx"
9 #include "resip/dum/ClientAuthExtension.hxx"
10
11 #define RESIPROCATE_SUBSYSTEM Subsystem::DUM
12
13 using namespace resip;
14 using namespace std;
15
16 class ClientAuthDecorator : public MessageDecorator
17 {
18 public:
19 ClientAuthDecorator(bool isProxyCredential, const Auth& auth, const UserProfile::DigestCredential& credential, const Data& authQop, const Data& nonceCountString) :
20 mIsProxyCredential(isProxyCredential), mAuth(auth), mCredential(credential), mAuthQop(authQop), mNonceCountString(nonceCountString) {}
21 virtual ~ClientAuthDecorator() {}
22 virtual void decorateMessage(SipMessage &msg,
23 const Tuple &source,
24 const Tuple &destination,
25 const Data& sigcompId)
26 {
27 Data cnonce = Random::getCryptoRandomHex(16);
28
29 Auths & target = mIsProxyCredential ? msg.header(h_ProxyAuthorizations) : msg.header(h_Authorizations);
30
31 DebugLog( << " Add auth, " << this << " in response to: " << mAuth);
32 Auth auth;
33 if (ClientAuthExtension::instance().algorithmAndQopSupported(mAuth))
34 {
35 DebugLog(<<"Using extension to make auth response");
36
37 if(mCredential.isPasswordA1Hash)
38 {
39 ClientAuthExtension::instance().makeChallengeResponseAuthWithA1(msg,
40 mCredential.user,
41 mCredential.password,
42 mAuth,
43 cnonce,
44 mAuthQop,
45 mNonceCountString,
46 auth);
47 }
48 else
49 {
50 ClientAuthExtension::instance().makeChallengeResponseAuth(msg,
51 mCredential.user,
52 mCredential.password,
53 mAuth,
54 cnonce,
55 mAuthQop,
56 mNonceCountString,
57 auth);
58 }
59 }
60 else
61 {
62 if(mCredential.isPasswordA1Hash)
63 {
64 Helper::makeChallengeResponseAuthWithA1(msg,
65 mCredential.user,
66 mCredential.password,
67 mAuth,
68 cnonce,
69 mAuthQop,
70 mNonceCountString,
71 auth);
72 }
73 else
74 {
75 Helper::makeChallengeResponseAuth(msg,
76 mCredential.user,
77 mCredential.password,
78 mAuth,
79 cnonce,
80 mAuthQop,
81 mNonceCountString,
82 auth);
83 }
84 }
85 target.push_back(auth);
86
87 DebugLog(<<"ClientAuthDecorator, proxy: " << mIsProxyCredential << " " << target.back());
88 }
89 virtual void rollbackMessage(SipMessage& msg)
90 {
91 Auths & target = mIsProxyCredential ? msg.header(h_ProxyAuthorizations) : msg.header(h_Authorizations);
92 target.pop_back();
93 }
94 virtual MessageDecorator* clone() const { return new ClientAuthDecorator(mIsProxyCredential, mAuth, mCredential, mAuthQop, mNonceCountString); }
95 private:
96 bool mIsProxyCredential;
97 Auth mAuth;
98 UserProfile::DigestCredential mCredential;
99 Data mAuthQop;
100 Data mNonceCountString;
101 };
102
103
104 ClientAuthManager::ClientAuthManager()
105 {
106 }
107
108 bool
109 ClientAuthManager::handle(UserProfile& userProfile, SipMessage& origRequest, const SipMessage& response)
110 {
111 try
112 {
113 assert( response.isResponse() );
114 assert( origRequest.isRequest() );
115
116 DialogSetId id(origRequest);
117
118 const int& code = response.header(h_StatusLine).statusCode();
119 if (code < 101 || code >= 500)
120 {
121 return false;
122 }
123 else if (! ( code == 401 || code == 407 )) // challenge success
124 {
125 AttemptedAuthMap::iterator it = mAttemptedAuths.find(id);
126 if (it != mAttemptedAuths.end())
127 {
128 DebugLog (<< "ClientAuthManager::handle: transitioning " << id << "to cached");
129
130 // cache the result
131 it->second.authSucceeded();
132 }
133 return false;
134 }
135
136 // 401 or 407...
137 if (!(response.exists(h_WWWAuthenticates) || response.exists(h_ProxyAuthenticates)))
138 {
139 DebugLog (<< "Invalid challenge for " << id << ", nothing to respond to; fail");
140 return false;
141 }
142
143 AuthState& authState = mAttemptedAuths[id];
144
145 // based on the UserProfile and the challenge, store credentials in the
146 // AuthState associated with this DialogSet if the algorithm is supported
147 if (authState.handleChallenge(userProfile, response))
148 {
149 assert(origRequest.header(h_Vias).size() == 1);
150 origRequest.header(h_CSeq).sequence()++;
151 DebugLog (<< "Produced response to digest challenge for " << userProfile );
152 return true;
153 }
154 else
155 {
156 return false;
157 }
158 }
159 catch(BaseException& e)
160 {
161 assert(0);
162 ErrLog(<< "Unexpected exception in ClientAuthManager::handle " << e);
163 return false;
164 }
165 }
166
167 void
168 ClientAuthManager::addAuthentication(SipMessage& request)
169 {
170 AttemptedAuthMap::iterator it = mAttemptedAuths.find(DialogSetId(request));
171 if (it != mAttemptedAuths.end())
172 {
173 it->second.addAuthentication(request);
174 }
175 }
176
177 void
178 ClientAuthManager::clearAuthenticationState(const DialogSetId& dsId)
179 {
180 AttemptedAuthMap::iterator it = mAttemptedAuths.find(dsId);
181 if (it != mAttemptedAuths.end())
182 {
183 mAttemptedAuths.erase(it);
184 }
185 }
186
187 void
188 ClientAuthManager::dialogSetDestroyed(const DialogSetId& id)
189 {
190 clearAuthenticationState(id);
191 }
192
193 ClientAuthManager::AuthState::AuthState() :
194 mFailed(false),
195 mCacheUseLimit(0),
196 mCacheUseCount(0)
197 {
198 }
199
200 bool
201 ClientAuthManager::AuthState::handleChallenge(UserProfile& userProfile, const SipMessage& challenge)
202 {
203 if (mFailed)
204 {
205 return false;
206 }
207 bool handled = true;
208 if (challenge.exists(h_WWWAuthenticates))
209 {
210 for (Auths::const_iterator i = challenge.header(h_WWWAuthenticates).begin();
211 i != challenge.header(h_WWWAuthenticates).end(); ++i)
212 {
213 if (i->exists(p_realm))
214 {
215 if (!mRealms[i->param(p_realm)].handleAuth(userProfile, *i, false))
216 {
217 handled = false;
218 break;
219 }
220 }
221 else
222 {
223 return false;
224 }
225 }
226 }
227 if (challenge.exists(h_ProxyAuthenticates))
228 {
229 for(Auths::const_iterator i = challenge.header(h_ProxyAuthenticates).begin();
230 i != challenge.header(h_ProxyAuthenticates).end(); ++i)
231 {
232 if (i->exists(p_realm))
233 {
234 if (!mRealms[i->param(p_realm)].handleAuth(userProfile, *i, true))
235 {
236 handled = false;
237 break;
238 }
239 }
240 else
241 {
242 return false;
243 }
244 }
245 }
246 if(!handled)
247 {
248 InfoLog( << "ClientAuthManager::AuthState::handleChallenge failed for: " << challenge);
249 }
250 else
251 {
252 mCacheUseLimit = userProfile.getDigestCacheUseLimit();
253 }
254 return handled;
255 }
256
257 void
258 ClientAuthManager::AuthState::authSucceeded()
259 {
260 for(RealmStates::iterator i = mRealms.begin(); i!=mRealms.end(); i++)
261 {
262 i->second.authSucceeded();
263 }
264 mCacheUseCount++;
265 if(mCacheUseLimit != 0 && mCacheUseCount >= mCacheUseLimit)
266 {
267 // Cache use limit reached - clear auth state
268 mRealms.clear();
269 mCacheUseCount = 0;
270 }
271 }
272
273 void
274 ClientAuthManager::AuthState::addAuthentication(SipMessage& request)
275 {
276 request.remove(h_ProxyAuthorizations);
277 request.remove(h_Authorizations);
278
279 if (mFailed) return;
280
281 for(RealmStates::iterator i = mRealms.begin(); i!=mRealms.end(); i++)
282 {
283 i->second.addAuthentication(request);
284 }
285 }
286
287
288 ClientAuthManager::RealmState::RealmState() :
289 mIsProxyCredential(false),
290 mState(Invalid),
291 mNonceCount(0),
292 mAuthPtr(NULL)
293 {
294 }
295
296 Data RealmStates[] =
297 {
298 "invalid",
299 "cached",
300 "current",
301 "tryonce",
302 "failed",
303 };
304
305 const Data&
306 ClientAuthManager::RealmState::getStateString(State s)
307 {
308 return RealmStates[s];
309 }
310
311 void
312 ClientAuthManager::RealmState::transition(State s)
313 {
314 DebugLog(<< "ClientAuthManager::RealmState::transition from " << getStateString(mState) << " to " << getStateString(s));
315 mState = s;
316 }
317
318 void
319 ClientAuthManager::RealmState::authSucceeded()
320 {
321 switch(mState)
322 {
323 case Invalid:
324 assert(0);
325 break;
326 case Current:
327 case Cached:
328 case TryOnce:
329 transition(Cached);
330 break;
331 case Failed:
332 assert(0);
333 break;
334 };
335 }
336
337 bool
338 ClientAuthManager::RealmState::handleAuth(UserProfile& userProfile, const Auth& auth, bool isProxyCredential)
339 {
340 DebugLog( << "ClientAuthManager::RealmState::handleAuth: " << this << " " << auth << " is proxy: " << isProxyCredential);
341 mIsProxyCredential = isProxyCredential; //this changing dynamically would
342 //be very bizarre..should trap w/ enum
343 switch(mState)
344 {
345 case Invalid:
346 mAuth = auth;
347 transition(Current);
348 break;
349 case Current:
350 if (auth.exists(p_stale) && auth.param(p_stale) == "true")
351 {
352 DebugLog (<< "Stale nonce:" << auth);
353 mAuth = auth;
354 clear();
355 }
356 else if(auth.exists(p_nonce) && auth.param(p_nonce) != mAuth.param(p_nonce))
357 {
358 DebugLog (<< "Different nonce, was: " << mAuth.param(p_nonce) << " now " << auth.param(p_nonce));
359 mAuth = auth;
360 clear();
361 transition(TryOnce);
362 }
363 else
364 {
365 DebugLog( << "Challenge response already failed for: " << auth);
366 transition(Failed);
367 return false;
368 }
369 break;
370 case TryOnce:
371 DebugLog( << "Extra chance still failed: " << auth);
372 transition(Failed);
373 return false;
374 case Cached: //basically 1 free chance, here for interop, may not be
375 //required w/ nonce check in current
376 mAuth = auth;
377 clear();
378 transition(Current);
379 break;
380 case Failed:
381 return false;
382 }
383
384 if (findCredential(userProfile, auth))
385 {
386 return true;
387 }
388 else
389 {
390 transition(Failed);
391 return false;
392 }
393 }
394
395 void
396 ClientAuthManager::RealmState::clear()
397 {
398 mNonceCount = 0;
399 }
400
401 bool
402 ClientAuthManager::RealmState::findCredential(UserProfile& userProfile, const Auth& auth)
403 {
404 if (!(Helper::algorithmAndQopSupported(auth)
405 || (ClientAuthExtension::instance().algorithmAndQopSupported(auth))))
406 {
407 DebugLog(<<"Unsupported algorithm or qop: " << auth);
408 return false;
409 }
410
411 const Data& realm = auth.param(p_realm);
412 //!dcm! -- icky, expose static empty soon...ptr instead of reference?
413 mCredential = userProfile.getDigestCredential(realm);
414 if (mCredential.realm.empty())
415 {
416 DebugLog( << "Got a 401 or 407 but could not find credentials for realm: " << realm);
417 // DebugLog (<< auth);
418 // DebugLog (<< response);
419 return false;
420 }
421 return true;
422 }
423
424 void
425 ClientAuthManager::RealmState::addAuthentication(SipMessage& request)
426 {
427 assert(mState != Failed);
428 if (mState == Failed) return;
429
430 Data nonceCountString;
431 Data authQop = Helper::qopOption(mAuth);
432 if(!authQop.empty())
433 {
434 Helper::updateNonceCount(mNonceCount, nonceCountString);
435 }
436
437 // Add client auth decorator so that we ensure any body hashes are calcuated after user defined outbound decorators that
438 // may be modifying the message body
439 std::auto_ptr<MessageDecorator> clientAuthDecorator(new ClientAuthDecorator(mIsProxyCredential, mAuth, mCredential, authQop, nonceCountString));
440 request.addOutboundDecorator(clientAuthDecorator);
441 }
442
443 // bool
444 // ClientAuthManager::CompareAuth::operator()(const Auth& lhs, const Auth& rhs) const
445 // {
446 // if (lhs.param(p_realm) < rhs.param(p_realm))
447 // {
448 // return true;
449 // }
450 // else if (lhs.param(p_realm) > rhs.param(p_realm))
451 // {
452 // return false;
453 // }
454 // else
455 // {
456 // return lhs.param(p_username) < rhs.param(p_username);
457 // }
458 // }
459
460
461 /* ====================================================================
462 * The Vovida Software License, Version 1.0
463 *
464 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
465 *
466 * Redistribution and use in source and binary forms, with or without
467 * modification, are permitted provided that the following conditions
468 * are met:
469 *
470 * 1. Redistributions of source code must retain the above copyright
471 * notice, this list of conditions and the following disclaimer.
472 *
473 * 2. Redistributions in binary form must reproduce the above copyright
474 * notice, this list of conditions and the following disclaimer in
475 * the documentation and/or other materials provided with the
476 * distribution.
477 *
478 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
479 * and "Vovida Open Communication Application Library (VOCAL)" must
480 * not be used to endorse or promote products derived from this
481 * software without prior written permission. For written
482 * permission, please contact vocal@vovida.org.
483 *
484 * 4. Products derived from this software may not be called "VOCAL", nor
485 * may "VOCAL" appear in their name, without prior written
486 * permission of Vovida Networks, Inc.
487 *
488 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
489 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
490 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
491 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
492 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
493 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
494 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
495 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
496 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
497 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
498 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
499 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
500 * DAMAGE.
501 *
502 * ====================================================================
503 *
504 * This software consists of voluntary contributions made by Vovida
505 * Networks, Inc. and many individuals on behalf of Vovida Networks,
506 * Inc. For more information on Vovida Networks, Inc., please see
507 * <http://www.vovida.org/>.
508 *
509 */

Properties

Name Value
svn:eol-style native
svn:mime-type text/plain

webmaster AT resiprocate DOT org
ViewVC Help
Powered by ViewVC 1.1.27