reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
ClientAuthManager.cxx
Go to the documentation of this file.
1#include "rutil/ResipAssert.h"
2
7#include "rutil/Logger.hxx"
8#include "rutil/Random.hxx"
10
11#include <utility>
12
13#define RESIPROCATE_SUBSYSTEM Subsystem::DUM
14
15using namespace resip;
16using namespace std;
17
19{
20public:
21 ClientAuthDecorator(bool isProxyCredential, const Auth& auth, const UserProfile::DigestCredential& credential, const Data& authQop, const Data& nonceCountString) :
22 mIsProxyCredential(isProxyCredential), mAuth(auth), mCredential(credential), mAuthQop(authQop), mNonceCountString(nonceCountString) {}
24 virtual void decorateMessage(SipMessage &msg,
25 const Tuple &source,
26 const Tuple &destination,
27 const Data& sigcompId)
28 {
30
31 Auths & target = mIsProxyCredential ? msg.header(h_ProxyAuthorizations) : msg.header(h_Authorizations);
32
33 DebugLog( << " Add auth in response to: " << mAuth);
34 Auth auth;
35 if (ClientAuthExtension::instance().algorithmAndQopSupported(mAuth))
36 {
37 DebugLog(<<"Using extension to make auth response");
38
39 if(mCredential.isPasswordA1Hash)
40 {
42 mCredential.user,
43 mCredential.password,
44 mAuth,
45 cnonce,
46 mAuthQop,
47 mNonceCountString,
48 auth);
49 }
50 else
51 {
53 mCredential.user,
54 mCredential.password,
55 mAuth,
56 cnonce,
57 mAuthQop,
58 mNonceCountString,
59 auth);
60 }
61 }
62 else
63 {
64 if(mCredential.isPasswordA1Hash)
65 {
67 mCredential.user,
68 mCredential.password,
69 mAuth,
70 cnonce,
71 mAuthQop,
72 mNonceCountString,
73 auth);
74 }
75 else
76 {
78 mCredential.user,
79 mCredential.password,
80 mAuth,
81 cnonce,
82 mAuthQop,
83 mNonceCountString,
84 auth);
85 }
86 }
87 target.push_back(auth);
88
89 DebugLog(<<"ClientAuthDecorator, proxy: " << mIsProxyCredential << " " << target.back());
90 }
91 virtual void rollbackMessage(SipMessage& msg)
92 {
93 Auths & target = mIsProxyCredential ? msg.header(h_ProxyAuthorizations) : msg.header(h_Authorizations);
94 target.pop_back();
95 }
96 virtual MessageDecorator* clone() const { return new ClientAuthDecorator(mIsProxyCredential, mAuth, mCredential, mAuthQop, mNonceCountString); }
97private:
98 bool mIsProxyCredential;
99 Auth mAuth;
101 Data mAuthQop;
102 Data mNonceCountString;
103};
104
105
109
110bool
111ClientAuthManager::handle(UserProfile& userProfile, SipMessage& origRequest, const SipMessage& response)
112{
113 try
114 {
115 resip_assert(response.isResponse());
116 resip_assert(origRequest.isRequest());
117
118 DialogSetId id(origRequest);
119
120 const int& code = response.header(h_StatusLine).statusCode();
121 if (code < 101 || code >= 500)
122 {
123 return false;
124 }
125 else if (!(code == 401 || code == 407)) // challenge success
126 {
127 AttemptedAuthMap::iterator it = mAttemptedAuths.find(id);
128 if (it != mAttemptedAuths.end())
129 {
130 DebugLog(<< "ClientAuthManager::handle: transitioning " << id << "to cached");
131
132 // cache the result
133 it->second.authSucceeded();
134 }
135 return false;
136 }
137
138 // 401 or 407...
139 if (!(response.exists(h_WWWAuthenticates) || response.exists(h_ProxyAuthenticates)))
140 {
141 DebugLog(<< "Invalid challenge for " << id << ", nothing to respond to; fail");
142 return false;
143 }
144
145 AuthState& authState = mAttemptedAuths[id];
146
147 // based on the UserProfile and the challenge, store credentials in the
148 // AuthState associated with this DialogSet if the algorithm is supported
149 if (authState.handleChallenge(userProfile, response))
150 {
151 resip_assert(origRequest.header(h_Vias).size() == 1);
152 origRequest.header(h_CSeq).sequence()++;
153 DebugLog(<< "Produced response to digest challenge for " << userProfile);
154 return true;
155 }
156 else
157 {
158 return false;
159 }
160 }
161 catch (BaseException& e)
162 {
163 resip_assert(0);
164 ErrLog(<< "Unexpected exception in ClientAuthManager::handle " << e);
165 return false;
166 }
167}
168
169void
171{
172 AttemptedAuthMap::iterator it = mAttemptedAuths.find(DialogSetId(request));
173 if (it != mAttemptedAuths.end())
174 {
175 it->second.addAuthentication(request);
176 }
177}
178
179void
181{
182 AttemptedAuthMap::iterator it = mAttemptedAuths.find(dsId);
183 if (it != mAttemptedAuths.end())
184 {
185 mAttemptedAuths.erase(it);
186 }
187}
188
189void
190ClientAuthManager::dialogSetDestroyed(const DialogSetId& id)
191{
193}
194
195ClientAuthManager::AuthState::AuthState() :
196 mFailed(false),
197 mCacheUseLimit(0),
198 mCacheUseCount(0)
199{
200}
201
202bool
203ClientAuthManager::AuthState::handleChallenge(UserProfile& userProfile, const SipMessage& challenge)
204{
205 if (mFailed)
206 {
207 return false;
208 }
209
210 // As long we handled at least one WWW or Proxy Authenticate - we are good
211 bool handled = false;
212 if (challenge.exists(h_WWWAuthenticates))
213 {
214 handled = handleChallenge(userProfile, challenge.header(h_WWWAuthenticates), false /* isProxyCredential */);
215 }
216 if (!handled && challenge.exists(h_ProxyAuthenticates))
217 {
218 handled = handleChallenge(userProfile, challenge.header(h_ProxyAuthenticates), true /* isProxyCredential */);
219 }
220
221 if (!handled)
222 {
223 InfoLog(<< "ClientAuthManager::AuthState::handleChallenge failed for: " << challenge);
224 }
225 else
226 {
227 mCacheUseLimit = userProfile.getDigestCacheUseLimit();
228 }
229 return handled;
230}
231
232bool
233ClientAuthManager::AuthState::handleChallenge(UserProfile& userProfile, const Auths& auths, bool isProxyCredential)
234{
235 bool ret = false;
236 for (Auths::const_iterator i = auths.begin(); i != auths.end(); ++i)
237 {
238 if (i->exists(p_realm))
239 {
240 if (mRealms[i->param(p_realm)].handleAuth(userProfile, *i, isProxyCredential))
241 {
242 // We have credentials for the realm and we support the algorithm and qop
243 // We should only be responding to one authenticates header
244 ret = true;
245 break;
246 }
247 }
248 }
249
250 // Now that we have tried all auth headers, make sure we mark any realm states still
251 // Initial as Failed. If any were successful, they have already transitioned to Current.
252 for (Auths::const_iterator i = auths.begin(); i != auths.end(); ++i)
253 {
254 if (i->exists(p_realm))
255 {
256 mRealms[i->param(p_realm)].transitionToFailedIfInitial();
257 }
258 }
259
260 return ret;
261}
262
263void
264ClientAuthManager::AuthState::authSucceeded()
265{
266 for(RealmStates::iterator i = mRealms.begin(); i!=mRealms.end(); i++)
267 {
268 i->second.authSucceeded();
269 }
270 mCacheUseCount++;
271 if(mCacheUseLimit != 0 && mCacheUseCount >= mCacheUseLimit)
272 {
273 // Cache use limit reached - clear auth state
274 mRealms.clear();
275 mCacheUseCount = 0;
276 }
277}
278
279void
280ClientAuthManager::AuthState::addAuthentication(SipMessage& request)
281{
282 request.remove(h_ProxyAuthorizations);
283 request.remove(h_Authorizations);
284
285 if (mFailed) return;
286
287 for(RealmStates::iterator i = mRealms.begin(); i!=mRealms.end(); i++)
288 {
289 i->second.addAuthentication(request);
290 }
291}
292
293ClientAuthManager::RealmState::RealmState() :
294 mIsProxyCredential(false),
295 mState(Initial),
296 mNonceCount(0)
297{
298}
299
301{
302 "initial",
303 "cached",
304 "current",
305 "tryonce",
306 "failed",
307};
308
309const Data&
310ClientAuthManager::RealmState::getStateString(State s)
311{
312 return RealmStates[s];
313}
314
315void
316ClientAuthManager::RealmState::transition(State s)
317{
318 DebugLog(<< "ClientAuthManager::RealmState::transition from " << getStateString(mState) << " to " << getStateString(s));
319 mState = s;
320}
321
322void
323ClientAuthManager::RealmState::authSucceeded()
324{
325 switch(mState)
326 {
327 case Initial:
328 resip_assert(0);
329 break;
330 case Current:
331 case Cached:
332 case TryOnce:
333 transition(Cached);
334 break;
335 case Failed:
336 resip_assert(0);
337 break;
338 };
339}
340
341bool
342ClientAuthManager::RealmState::handleAuth(UserProfile& userProfile, const Auth& auth, bool isProxyCredential)
343{
344 DebugLog(<< "ClientAuthManager::RealmState::handleAuth: " << this << " " << auth << " is proxy: " << isProxyCredential);
345 mIsProxyCredential = isProxyCredential; //this changing dynamically would be very bizarre..should trap w/ enum
346 switch (mState)
347 {
348 case Initial:
349 mAuth = auth;
350 break;
351 case Current:
352 if (auth.exists(p_stale) && auth.param(p_stale) == "true")
353 {
354 DebugLog(<< "Stale nonce:" << auth);
355 mAuth = auth;
356 clear();
357 }
358 else if (auth.exists(p_nonce) && auth.param(p_nonce) != mAuth.param(p_nonce))
359 {
360 DebugLog(<< "Different nonce, was: " << mAuth.param(p_nonce) << " now " << auth.param(p_nonce));
361 mAuth = auth;
362 clear();
363 transition(TryOnce);
364 }
365 else
366 {
367 DebugLog(<< "Challenge response already failed for: " << auth);
368 transition(Failed);
369 return false;
370 }
371 break;
372 case TryOnce:
373 DebugLog(<< "Extra chance still failed: " << auth);
374 transition(Failed);
375 return false;
376 case Cached: //basically 1 free chance, here for interop, may not be
377 //required w/ nonce check in current
378 mAuth = auth;
379 clear();
380 transition(Current);
381 break;
382 case Failed:
383 return false;
384 }
385
386 if (findCredential(userProfile, auth))
387 {
388 // If we are in the Initial state, we are still looking for an Auth header
389 // we can respond to. Since we have found one now, transition to Current state.
390 if (mState == Initial)
391 {
392 transition(Current);
393 }
394 return true;
395 }
396 else
397 {
398 // Allow a failure to find credentials when we are in the Initial state and
399 // looking we are for an Auth header we can respond to.
400 if (mState != Initial)
401 {
402 transition(Failed);
403 }
404 return false;
405 }
406}
407
408void
409ClientAuthManager::RealmState::clear()
410{
411 mNonceCount = 0;
412}
413
414bool
415ClientAuthManager::RealmState::findCredential(UserProfile& userProfile, const Auth& auth)
416{
417 DigestType digestType;
418 if (!(Helper::algorithmAndQopSupported(auth, digestType) ||
419 ClientAuthExtension::instance().algorithmAndQopSupported(auth)))
420 {
421 DebugLog(<<"Unsupported algorithm or qop: " << auth);
422 return false;
423 }
424
425 const Data& realm = auth.param(p_realm);
427 mCredential = userProfile.getDigestCredential(realm);
428
429 if (mCredential.realm.empty())
430 {
431 DebugLog(<< "Got a 401 or 407 but could not find credentials for realm: " << realm);
432 // DebugLog (<< auth);
433 // DebugLog (<< response);
434 return false;
435 }
436
437 // If credential is provided in hash form, check if credential supports Auth algorithm
438 if (mCredential.isPasswordA1Hash)
439 {
440 if (!Helper::isDigestTypeSupportedInResipA1HashString(mCredential.password, digestType))
441 {
442 DebugLog(<< "Password hash for " << mCredential.user << "@" << mCredential.realm << " doesn't contain " << DigestStream::getDigestName(digestType) << " hash.");
443 return false;
444 }
445 }
446 return true;
447}
448
449void
450ClientAuthManager::RealmState::addAuthentication(SipMessage& request)
451{
452 resip_assert(mState != Failed);
453 if (mState == Failed) return;
454
455 Data nonceCountString;
456 Data authQop = Helper::qopOption(mAuth);
457 if(!authQop.empty())
458 {
459 Helper::updateNonceCount(mNonceCount, nonceCountString);
460 }
461
462 // Add client auth decorator so that we ensure any body hashes are calcuated after user defined outbound decorators that
463 // may be modifying the message body
464 std::unique_ptr<MessageDecorator> clientAuthDecorator(new ClientAuthDecorator(mIsProxyCredential, mAuth, mCredential, authQop, nonceCountString));
465 request.addOutboundDecorator(std::move(clientAuthDecorator));
466}
467
468void
469ClientAuthManager::RealmState::transitionToFailedIfInitial()
470{
471 if (mState == Initial)
472 {
473 transition(Failed);
474 }
475}
476
477// bool
478// ClientAuthManager::CompareAuth::operator()(const Auth& lhs, const Auth& rhs) const
479// {
480// if (lhs.param(p_realm) < rhs.param(p_realm))
481// {
482// return true;
483// }
484// else if (lhs.param(p_realm) > rhs.param(p_realm))
485// {
486// return false;
487// }
488// else
489// {
490// return lhs.param(p_username) < rhs.param(p_username);
491// }
492// }
493
494
495/* ====================================================================
496 * The Vovida Software License, Version 1.0
497 *
498 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
499 *
500 * Redistribution and use in source and binary forms, with or without
501 * modification, are permitted provided that the following conditions
502 * are met:
503 *
504 * 1. Redistributions of source code must retain the above copyright
505 * notice, this list of conditions and the following disclaimer.
506 *
507 * 2. Redistributions in binary form must reproduce the above copyright
508 * notice, this list of conditions and the following disclaimer in
509 * the documentation and/or other materials provided with the
510 * distribution.
511 *
512 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
513 * and "Vovida Open Communication Application Library (VOCAL)" must
514 * not be used to endorse or promote products derived from this
515 * software without prior written permission. For written
516 * permission, please contact vocal@vovida.org.
517 *
518 * 4. Products derived from this software may not be called "VOCAL", nor
519 * may "VOCAL" appear in their name, without prior written
520 * permission of Vovida Networks, Inc.
521 *
522 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
523 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
524 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
525 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
526 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
527 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
528 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
529 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
530 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
531 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
532 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
533 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
534 * DAMAGE.
535 *
536 * ====================================================================
537 *
538 * This software consists of voluntary contributions made by Vovida
539 * Networks, Inc. and many individuals on behalf of Vovida Networks,
540 * Inc. For more information on Vovida Networks, Inc., please see
541 * <http://www.vovida.org/>.
542 *
543 */
Data RealmStates[]
#define DebugLog(args_)
Definition Logger.hxx:128
#define InfoLog(args_)
Definition Logger.hxx:131
#define ErrLog(args_)
Definition Logger.hxx:137
#define resip_assert(x)
This file needs to work for both C and C++ code.
ClientAuthDecorator(bool isProxyCredential, const Auth &auth, const UserProfile::DigestCredential &credential, const Data &authQop, const Data &nonceCountString)
virtual MessageDecorator * clone() const
virtual void rollbackMessage(SipMessage &msg)
virtual void decorateMessage(SipMessage &msg, const Tuple &source, const Tuple &destination, const Data &sigcompId)
Represents the "credentials" and "challenge" elements in the SIP grammar.
Definition Auth.hxx:18
bool exists(const Param< Auth > &paramType) const
Definition Auth.cxx:225
const Data & param(const ExtensionParameter &param) const
Const accessor for non-natively-supported parameter types.
The abstract base-class for all exceptions thrown by resip code.
static ClientAuthExtension & instance()
virtual void makeChallengeResponseAuthWithA1(const SipMessage &request, const Data &username, const Data &passwordHashA1, const Auth &challenge, const Data &cnonce, const Data &authQop, const Data &nonceCountString, Auth &auth)
virtual void makeChallengeResponseAuth(const SipMessage &request, const Data &username, const Data &password, const Auth &challenge, const Data &cnonce, const Data &authQop, const Data &nonceCountString, Auth &auth)
virtual bool handle(UserProfile &userProfile, SipMessage &origRequest, const SipMessage &response)
virtual void addAuthentication(SipMessage &origRequest)
virtual void clearAuthenticationState(const DialogSetId &dsId)
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
bool empty() const noexcept
Checks whether the Data is empty.
Definition Data.hxx:591
static const Data & getDigestName(DigestType digestType)
static Auth makeChallengeResponseAuthWithA1(const SipMessage &request, const Data &username, const Data &resipPasswordHashA1, const Auth &challenge, const Data &cnonce, unsigned int &nonceCount, Data &nonceCountString)
Definition Helper.cxx:1506
static void updateNonceCount(unsigned int &nonceCount, Data &nonceCountString)
Definition Helper.cxx:1325
static bool algorithmAndQopSupported(const Auth &challenge)
Definition Helper.cxx:1347
static Data qopOption(const Auth &challenge)
Definition Helper.cxx:1284
static bool isDigestTypeSupportedInResipA1HashString(const Data &resipA1HashString, DigestType digestType)
Definition Helper.cxx:856
static Auth makeChallengeResponseAuth(const SipMessage &request, const Data &username, const Data &password, const Auth &challenge, const Data &cnonce, unsigned int &nonceCount, Data &nonceCountString)
Definition Helper.cxx:1415
void pop_back()
if mParsers vector is not empty, erase the first element
A const_iterator class (bidirectional)
Container class for ParserCategory, used by SipMessage to represent multi-valued headers (Contact,...
iterator end()
Returns an iterator pointing to the last header field value.
T & back()
Returns the last header field value in this container.
iterator begin()
Returns an iterator pointing to the first header field value.
void push_back(const T &t)
Inserts a header field value at the back of this container.
static Data getCryptoRandomHex(unsigned int numBytes)
Definition Random.cxx:394
Represents a SIP message.
bool isRequest() const
Returns true if message is a request, false otherwise.
void addOutboundDecorator(std::unique_ptr< MessageDecorator > md)
Call a MessageDecorator to process the message before it is sent to the transport.
const RequestLine & header(const RequestLineType &l) const
Returns the RequestLine. This is only valid for request messages.
bool isResponse() const
Returns true if message is a response, false otherwise.
bool exists(const HeaderBase &headerType) const
Returns true if the given header field is present, false otherwise.
void remove(const HeaderBase &headerType)
Prevents a header field from being present when the message is prepared for sending to a transport.
Represents a network address.
Definition Tuple.hxx:59
virtual const DigestCredential & getDigestCredential(const Data &realm)
virtual unsigned long getDigestCacheUseLimit() const noexcept
dcm! – add UnusedChecking(_enum) below;
StatusLineType h_StatusLine
Definition Headers.cxx:348