• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package gov.nist.javax.sip.clientauthutils;
2 
3 /*
4  *
5  * This code has been contributed with permission from:
6  *
7  * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client but has been significantly changed.
8  * It is donated to the JAIN-SIP project as it is common code that many sip clients
9  * need to perform class and others will consitute a set of utility functions
10  * that will implement common operations that ease the life of the developer.
11  *
12  * Acknowledgements:
13  * ----------------
14  *
15  * Fredrik Wickstrom reported that dialog cseq counters are not incremented
16  * when resending requests. He later uncovered additional problems and
17  * proposed a way to fix them (his proposition was taken into account).
18  */
19 
20 import gov.nist.javax.sip.SipStackImpl;
21 import gov.nist.javax.sip.address.SipUri;
22 import gov.nist.javax.sip.message.SIPRequest;
23 import gov.nist.javax.sip.stack.SIPClientTransaction;
24 import gov.nist.javax.sip.stack.SIPTransactionStack;
25 
26 import java.text.ParseException;
27 import java.util.Collection;
28 import java.util.Iterator;
29 import java.util.ListIterator;
30 import java.util.Timer;
31 
32 import javax.sip.ClientTransaction;
33 import javax.sip.DialogState;
34 import javax.sip.InvalidArgumentException;
35 import javax.sip.SipException;
36 import javax.sip.SipProvider;
37 import javax.sip.address.Hop;
38 import javax.sip.address.SipURI;
39 import javax.sip.address.URI;
40 import javax.sip.header.AuthorizationHeader;
41 import javax.sip.header.CSeqHeader;
42 import javax.sip.header.Header;
43 import javax.sip.header.HeaderFactory;
44 import javax.sip.header.ProxyAuthenticateHeader;
45 import javax.sip.header.ProxyAuthorizationHeader;
46 import javax.sip.header.ViaHeader;
47 import javax.sip.header.WWWAuthenticateHeader;
48 import javax.sip.message.Request;
49 import javax.sip.message.Response;
50 
51 /**
52  * The class handles authentication challenges, caches user credentials and takes care (through
53  * the SecurityAuthority interface) about retrieving passwords.
54  *
55  *
56  * @author Emil Ivov
57  * @author Jeroen van Bemmel
58  * @author M. Ranganathan
59  *
60  * @since 2.0
61  */
62 
63 public class AuthenticationHelperImpl implements AuthenticationHelper {
64 
65     /**
66      * Credentials cached so far.
67      */
68     private CredentialsCache cachedCredentials;
69 
70     /**
71      * The account manager for the system. Stores user credentials.
72      */
73     private Object accountManager = null;
74 
75     /*
76      * Header factory for this security manager.
77      */
78     private HeaderFactory headerFactory;
79 
80     private SipStackImpl sipStack;
81 
82     Timer timer;
83 
84     /**
85      * Default constructor for the security manager. There is one Account manager. There is one
86      * SipSecurity manager for every user name,
87      *
88      * @param sipStack -- our stack.
89      * @param accountManager -- an implementation of the AccountManager interface.
90      * @param headerFactory -- header factory.
91      */
AuthenticationHelperImpl(SipStackImpl sipStack, AccountManager accountManager, HeaderFactory headerFactory)92     public AuthenticationHelperImpl(SipStackImpl sipStack, AccountManager accountManager,
93             HeaderFactory headerFactory) {
94         this.accountManager = accountManager;
95         this.headerFactory = headerFactory;
96         this.sipStack = sipStack;
97 
98         this.cachedCredentials = new CredentialsCache(((SIPTransactionStack) sipStack).getTimer());
99     }
100 
101     /**
102      * Default constructor for the security manager. There is one Account manager. There is one
103      * SipSecurity manager for every user name,
104      *
105      * @param sipStack -- our stack.
106      * @param accountManager -- an implementation of the AccountManager interface.
107      * @param headerFactory -- header factory.
108      */
AuthenticationHelperImpl(SipStackImpl sipStack, SecureAccountManager accountManager, HeaderFactory headerFactory)109     public AuthenticationHelperImpl(SipStackImpl sipStack, SecureAccountManager accountManager,
110             HeaderFactory headerFactory) {
111         this.accountManager = accountManager;
112         this.headerFactory = headerFactory;
113         this.sipStack = sipStack;
114 
115         this.cachedCredentials = new CredentialsCache(((SIPTransactionStack) sipStack).getTimer());
116     }
117 
118 
119     /*
120      * (non-Javadoc)
121      *
122      * @see gov.nist.javax.sip.clientauthutils.AuthenticationHelper#handleChallenge(javax.sip.message.Response,
123      *      javax.sip.ClientTransaction, javax.sip.SipProvider)
124      */
handleChallenge(Response challenge, ClientTransaction challengedTransaction, SipProvider transactionCreator, int cacheTime)125     public ClientTransaction handleChallenge(Response challenge,
126             ClientTransaction challengedTransaction, SipProvider transactionCreator, int cacheTime)
127             throws SipException, NullPointerException {
128         try {
129             if (sipStack.isLoggingEnabled()) {
130                 sipStack.getStackLogger().logDebug("handleChallenge: " + challenge);
131             }
132 
133             SIPRequest challengedRequest = ((SIPRequest) challengedTransaction.getRequest());
134 
135             Request reoriginatedRequest = null;
136             /*
137              * If the challenged request is part of a Dialog and the
138              * Dialog is confirmed the re-originated request should be
139              * generated as an in-Dialog request.
140              */
141             if (  challengedRequest.getToTag() != null  ||
142                     challengedTransaction.getDialog() == null ||
143                     challengedTransaction.getDialog().getState() != DialogState.CONFIRMED)  {
144                 reoriginatedRequest = (Request) challengedRequest.clone();
145             } else {
146                 /*
147                  * Re-originate the request by consulting the dialog. In particular
148                  * the route set could change between the original request and the
149                  * in-dialog challenge.
150                  */
151                 reoriginatedRequest =
152                     challengedTransaction.getDialog().createRequest(challengedRequest.getMethod());
153                 Iterator<String> headerNames = challengedRequest.getHeaderNames();
154                 while (headerNames.hasNext()) {
155                     String headerName = headerNames.next();
156                     if ( reoriginatedRequest.getHeader(headerName) != null) {
157                         ListIterator<Header> iterator = reoriginatedRequest.getHeaders(headerName);
158                         while (iterator.hasNext()) {
159                             reoriginatedRequest.addHeader(iterator.next());
160                         }
161                     }
162                 }
163             }
164 
165 
166 
167             // remove the branch id so that we could use the request in a new
168             // transaction
169             removeBranchID(reoriginatedRequest);
170 
171             if (challenge == null || reoriginatedRequest == null) {
172                 throw new NullPointerException("A null argument was passed to handle challenge.");
173             }
174 
175             ListIterator authHeaders = null;
176 
177             if (challenge.getStatusCode() == Response.UNAUTHORIZED) {
178                 authHeaders = challenge.getHeaders(WWWAuthenticateHeader.NAME);
179             } else if (challenge.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) {
180                 authHeaders = challenge.getHeaders(ProxyAuthenticateHeader.NAME);
181             } else {
182                 throw new IllegalArgumentException("Unexpected status code ");
183             }
184 
185             if (authHeaders == null) {
186                 throw new IllegalArgumentException(
187                         "Could not find WWWAuthenticate or ProxyAuthenticate headers");
188             }
189 
190             // Remove all authorization headers from the request (we'll re-add them
191             // from cache)
192             reoriginatedRequest.removeHeader(AuthorizationHeader.NAME);
193             reoriginatedRequest.removeHeader(ProxyAuthorizationHeader.NAME);
194 
195             // rfc 3261 says that the cseq header should be augmented for the new
196             // request. do it here so that the new dialog (created together with
197             // the new client transaction) takes it into account.
198             // Bug report - Fredrik Wickstrom
199             CSeqHeader cSeq = (CSeqHeader) reoriginatedRequest.getHeader((CSeqHeader.NAME));
200             try {
201                 cSeq.setSeqNumber(cSeq.getSeqNumber() + 1l);
202             } catch (InvalidArgumentException ex) {
203                 throw new SipException("Invalid CSeq -- could not increment : "
204                         + cSeq.getSeqNumber());
205             }
206 
207 
208             /* Resolve this to the next hop based on the previous lookup. If we are not using
209              * lose routing (RFC2543) then just attach hop as a maddr param.
210              */
211             if ( challengedRequest.getRouteHeaders() == null ) {
212                 Hop hop   = ((SIPClientTransaction) challengedTransaction).getNextHop();
213                 SipURI sipUri = (SipURI) reoriginatedRequest.getRequestURI();
214                 // BEGIN android-added
215                 if ( !hop.getHost().equalsIgnoreCase(sipUri.getHost())
216                         && !hop.equals(sipStack.getRouter(challengedRequest).getOutboundProxy()) )
217                 // END android-added
218                 sipUri.setMAddrParam(hop.getHost());
219                 if ( hop.getPort() != -1 ) sipUri.setPort(hop.getPort());
220             }
221             ClientTransaction retryTran = transactionCreator
222             .getNewClientTransaction(reoriginatedRequest);
223 
224             WWWAuthenticateHeader authHeader = null;
225             SipURI requestUri = (SipURI) challengedTransaction.getRequest().getRequestURI();
226             while (authHeaders.hasNext()) {
227                 authHeader = (WWWAuthenticateHeader) authHeaders.next();
228                 String realm = authHeader.getRealm();
229                 AuthorizationHeader authorization = null;
230                 String sipDomain;
231                 if ( this.accountManager instanceof SecureAccountManager ) {
232                     UserCredentialHash credHash =
233                         ((SecureAccountManager)this.accountManager).getCredentialHash(challengedTransaction,realm);
234                     URI uri = reoriginatedRequest.getRequestURI();
235                     sipDomain = credHash.getSipDomain();
236                     authorization = this.getAuthorization(reoriginatedRequest
237                             .getMethod(), uri.toString(),
238                             (reoriginatedRequest.getContent() == null) ? "" : new String(
239                             reoriginatedRequest.getRawContent()), authHeader, credHash);
240                 } else {
241                     UserCredentials userCreds = ((AccountManager) this.accountManager).getCredentials(challengedTransaction, realm);
242                     sipDomain = userCreds.getSipDomain();
243                     if (userCreds == null)
244                          throw new SipException(
245                             "Cannot find user creds for the given user name and realm");
246 
247                     // we haven't yet authenticated this realm since we were
248                     // started.
249 
250                        authorization = this.getAuthorization(reoriginatedRequest
251                                 .getMethod(), reoriginatedRequest.getRequestURI().toString(),
252                                 (reoriginatedRequest.getContent() == null) ? "" : new String(
253                                 reoriginatedRequest.getRawContent()), authHeader, userCreds);
254                 }
255                 if (sipStack.isLoggingEnabled())
256                 	sipStack.getStackLogger().logDebug(
257                         "Created authorization header: " + authorization.toString());
258 
259                 if (cacheTime != 0)
260                     cachedCredentials.cacheAuthorizationHeader(sipDomain,
261                             authorization, cacheTime);
262 
263                 reoriginatedRequest.addHeader(authorization);
264             }
265 
266             if (sipStack.isLoggingEnabled()) {
267                 sipStack.getStackLogger().logDebug(
268                         "Returning authorization transaction." + retryTran);
269             }
270             return retryTran;
271         } catch (SipException ex) {
272             throw ex;
273         } catch (Exception ex) {
274             sipStack.getStackLogger().logError("Unexpected exception ", ex);
275             throw new SipException("Unexpected exception ", ex);
276         }
277     }
278 
279 
280 
281 
282     /**
283      * Generates an authorisation header in response to wwwAuthHeader.
284      *
285      * @param method method of the request being authenticated
286      * @param uri digest-uri
287      * @param requestBody the body of the request.
288      * @param authHeader the challenge that we should respond to
289      * @param userCredentials username and pass
290      *
291      * @return an authorisation header in response to authHeader.
292      *
293      * @throws OperationFailedException if auth header was malformated.
294      */
getAuthorization(String method, String uri, String requestBody, WWWAuthenticateHeader authHeader, UserCredentials userCredentials)295     private AuthorizationHeader getAuthorization(String method, String uri, String requestBody,
296             WWWAuthenticateHeader authHeader, UserCredentials userCredentials) {
297         String response = null;
298 
299         // JvB: authHeader.getQop() is a quoted _list_ of qop values
300         // (e.g. "auth,auth-int") Client is supposed to pick one
301         String qopList = authHeader.getQop();
302         String qop = (qopList != null) ? "auth" : null;
303         String nc_value = "00000001";
304         String cnonce = "xyz";
305 
306         response = MessageDigestAlgorithm.calculateResponse(authHeader.getAlgorithm(),
307                 userCredentials.getUserName(), authHeader.getRealm(), userCredentials
308                         .getPassword(), authHeader.getNonce(), nc_value, // JvB added
309                 cnonce, // JvB added
310                 method, uri, requestBody, qop,sipStack.getStackLogger());// jvb changed
311 
312         AuthorizationHeader authorization = null;
313         try {
314             if (authHeader instanceof ProxyAuthenticateHeader) {
315                 authorization = headerFactory.createProxyAuthorizationHeader(authHeader
316                         .getScheme());
317             } else {
318                 authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme());
319             }
320 
321             authorization.setUsername(userCredentials.getUserName());
322             authorization.setRealm(authHeader.getRealm());
323             authorization.setNonce(authHeader.getNonce());
324             authorization.setParameter("uri", uri);
325             authorization.setResponse(response);
326             if (authHeader.getAlgorithm() != null) {
327                 authorization.setAlgorithm(authHeader.getAlgorithm());
328             }
329 
330             if (authHeader.getOpaque() != null) {
331                 authorization.setOpaque(authHeader.getOpaque());
332             }
333 
334             // jvb added
335             if (qop != null) {
336                 authorization.setQop(qop);
337                 authorization.setCNonce(cnonce);
338                 authorization.setNonceCount(Integer.parseInt(nc_value));
339             }
340 
341             authorization.setResponse(response);
342 
343         } catch (ParseException ex) {
344             throw new RuntimeException("Failed to create an authorization header!");
345         }
346 
347         return authorization;
348     }
349     /**
350      * Generates an authorisation header in response to wwwAuthHeader.
351      *
352      * @param method method of the request being authenticated
353      * @param uri digest-uri
354      * @param requestBody the body of the request.
355      * @param authHeader the challenge that we should respond to
356      * @param userCredentials username and pass
357      *
358      * @return an authorisation header in response to authHeader.
359      *
360      * @throws OperationFailedException if auth header was malformated.
361      */
getAuthorization(String method, String uri, String requestBody, WWWAuthenticateHeader authHeader, UserCredentialHash userCredentials)362     private AuthorizationHeader getAuthorization(String method, String uri, String requestBody,
363             WWWAuthenticateHeader authHeader, UserCredentialHash userCredentials) {
364         String response = null;
365 
366         // JvB: authHeader.getQop() is a quoted _list_ of qop values
367         // (e.g. "auth,auth-int") Client is supposed to pick one
368         String qopList = authHeader.getQop();
369         String qop = (qopList != null) ? "auth" : null;
370         String nc_value = "00000001";
371         String cnonce = "xyz";
372 
373         response = MessageDigestAlgorithm.calculateResponse(authHeader.getAlgorithm(),
374               userCredentials.getHashUserDomainPassword(), authHeader.getNonce(), nc_value, // JvB added
375                 cnonce, // JvB added
376                 method, uri, requestBody, qop,sipStack.getStackLogger());// jvb changed
377 
378         AuthorizationHeader authorization = null;
379         try {
380             if (authHeader instanceof ProxyAuthenticateHeader) {
381                 authorization = headerFactory.createProxyAuthorizationHeader(authHeader
382                         .getScheme());
383             } else {
384                 authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme());
385             }
386 
387             authorization.setUsername(userCredentials.getUserName());
388             authorization.setRealm(authHeader.getRealm());
389             authorization.setNonce(authHeader.getNonce());
390             authorization.setParameter("uri", uri);
391             authorization.setResponse(response);
392             if (authHeader.getAlgorithm() != null) {
393                 authorization.setAlgorithm(authHeader.getAlgorithm());
394             }
395 
396             if (authHeader.getOpaque() != null) {
397                 authorization.setOpaque(authHeader.getOpaque());
398             }
399 
400             // jvb added
401             if (qop != null) {
402                 authorization.setQop(qop);
403                 authorization.setCNonce(cnonce);
404                 authorization.setNonceCount(Integer.parseInt(nc_value));
405             }
406 
407             authorization.setResponse(response);
408 
409         } catch (ParseException ex) {
410             throw new RuntimeException("Failed to create an authorization header!");
411         }
412 
413         return authorization;
414     }
415     /**
416      * Removes all via headers from <tt>request</tt> and replaces them with a new one, equal to
417      * the one that was top most.
418      *
419      * @param request the Request whose branchID we'd like to remove.
420      *
421      */
removeBranchID(Request request)422     private void removeBranchID(Request request) {
423 
424         ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
425 
426         viaHeader.removeParameter("branch");
427 
428     }
429 
430     /*
431      * (non-Javadoc)
432      *
433      * @see gov.nist.javax.sip.clientauthutils.AuthenticationHelper#attachAuthenticationHeaders(javax.sip.message.Request)
434      */
setAuthenticationHeaders(Request request)435     public void setAuthenticationHeaders(Request request) {
436         SIPRequest sipRequest = (SIPRequest) request;
437 
438         String callId = sipRequest.getCallId().getCallId();
439 
440         request.removeHeader(AuthorizationHeader.NAME);
441         Collection<AuthorizationHeader> authHeaders = this.cachedCredentials
442                 .getCachedAuthorizationHeaders(callId);
443         if (authHeaders == null) {
444         	if (sipStack.isLoggingEnabled())
445         		sipStack.getStackLogger().logDebug(
446                     "Could not find authentication headers for " + callId);
447             return;
448         }
449 
450         for (AuthorizationHeader authHeader : authHeaders) {
451             request.addHeader(authHeader);
452         }
453 
454     }
455 
456     /*
457      * (non-Javadoc)
458      *
459      * @see gov.nist.javax.sip.clientauthutils.AuthenticationHelper#removeCachedAuthenticationHeaders(java.lang.String)
460      */
removeCachedAuthenticationHeaders(String callId)461     public void removeCachedAuthenticationHeaders(String callId) {
462         if (callId == null)
463             throw new NullPointerException("Null callId argument ");
464         this.cachedCredentials.removeAuthenticationHeader(callId);
465 
466     }
467 
468 }
469