| package gov.nist.javax.sip.clientauthutils; |
| |
| /* |
| * |
| * This code has been contributed with permission from: |
| * |
| * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client but has been significantly changed. |
| * It is donated to the JAIN-SIP project as it is common code that many sip clients |
| * need to perform class and others will consitute a set of utility functions |
| * that will implement common operations that ease the life of the developer. |
| * |
| * Acknowledgements: |
| * ---------------- |
| * |
| * Fredrik Wickstrom reported that dialog cseq counters are not incremented |
| * when resending requests. He later uncovered additional problems and |
| * proposed a way to fix them (his proposition was taken into account). |
| */ |
| |
| import gov.nist.javax.sip.SipStackImpl; |
| import gov.nist.javax.sip.address.SipUri; |
| import gov.nist.javax.sip.message.SIPRequest; |
| import gov.nist.javax.sip.stack.SIPClientTransaction; |
| import gov.nist.javax.sip.stack.SIPTransactionStack; |
| |
| import java.text.ParseException; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.ListIterator; |
| import java.util.Timer; |
| |
| import javax.sip.ClientTransaction; |
| import javax.sip.DialogState; |
| import javax.sip.InvalidArgumentException; |
| import javax.sip.SipException; |
| import javax.sip.SipProvider; |
| import javax.sip.address.Hop; |
| import javax.sip.address.SipURI; |
| import javax.sip.address.URI; |
| import javax.sip.header.AuthorizationHeader; |
| import javax.sip.header.CSeqHeader; |
| import javax.sip.header.Header; |
| import javax.sip.header.HeaderFactory; |
| import javax.sip.header.ProxyAuthenticateHeader; |
| import javax.sip.header.ProxyAuthorizationHeader; |
| import javax.sip.header.ViaHeader; |
| import javax.sip.header.WWWAuthenticateHeader; |
| import javax.sip.message.Request; |
| import javax.sip.message.Response; |
| |
| /** |
| * The class handles authentication challenges, caches user credentials and takes care (through |
| * the SecurityAuthority interface) about retrieving passwords. |
| * |
| * |
| * @author Emil Ivov |
| * @author Jeroen van Bemmel |
| * @author M. Ranganathan |
| * |
| * @since 2.0 |
| */ |
| |
| public class AuthenticationHelperImpl implements AuthenticationHelper { |
| |
| /** |
| * Credentials cached so far. |
| */ |
| private CredentialsCache cachedCredentials; |
| |
| /** |
| * The account manager for the system. Stores user credentials. |
| */ |
| private Object accountManager = null; |
| |
| /* |
| * Header factory for this security manager. |
| */ |
| private HeaderFactory headerFactory; |
| |
| private SipStackImpl sipStack; |
| |
| Timer timer; |
| |
| /** |
| * Default constructor for the security manager. There is one Account manager. There is one |
| * SipSecurity manager for every user name, |
| * |
| * @param sipStack -- our stack. |
| * @param accountManager -- an implementation of the AccountManager interface. |
| * @param headerFactory -- header factory. |
| */ |
| public AuthenticationHelperImpl(SipStackImpl sipStack, AccountManager accountManager, |
| HeaderFactory headerFactory) { |
| this.accountManager = accountManager; |
| this.headerFactory = headerFactory; |
| this.sipStack = sipStack; |
| |
| this.cachedCredentials = new CredentialsCache(((SIPTransactionStack) sipStack).getTimer()); |
| } |
| |
| /** |
| * Default constructor for the security manager. There is one Account manager. There is one |
| * SipSecurity manager for every user name, |
| * |
| * @param sipStack -- our stack. |
| * @param accountManager -- an implementation of the AccountManager interface. |
| * @param headerFactory -- header factory. |
| */ |
| public AuthenticationHelperImpl(SipStackImpl sipStack, SecureAccountManager accountManager, |
| HeaderFactory headerFactory) { |
| this.accountManager = accountManager; |
| this.headerFactory = headerFactory; |
| this.sipStack = sipStack; |
| |
| this.cachedCredentials = new CredentialsCache(((SIPTransactionStack) sipStack).getTimer()); |
| } |
| |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see gov.nist.javax.sip.clientauthutils.AuthenticationHelper#handleChallenge(javax.sip.message.Response, |
| * javax.sip.ClientTransaction, javax.sip.SipProvider) |
| */ |
| public ClientTransaction handleChallenge(Response challenge, |
| ClientTransaction challengedTransaction, SipProvider transactionCreator, int cacheTime) |
| throws SipException, NullPointerException { |
| try { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("handleChallenge: " + challenge); |
| } |
| |
| SIPRequest challengedRequest = ((SIPRequest) challengedTransaction.getRequest()); |
| |
| Request reoriginatedRequest = null; |
| /* |
| * If the challenged request is part of a Dialog and the |
| * Dialog is confirmed the re-originated request should be |
| * generated as an in-Dialog request. |
| */ |
| if ( challengedRequest.getToTag() != null || |
| challengedTransaction.getDialog() == null || |
| challengedTransaction.getDialog().getState() != DialogState.CONFIRMED) { |
| reoriginatedRequest = (Request) challengedRequest.clone(); |
| } else { |
| /* |
| * Re-originate the request by consulting the dialog. In particular |
| * the route set could change between the original request and the |
| * in-dialog challenge. |
| */ |
| reoriginatedRequest = |
| challengedTransaction.getDialog().createRequest(challengedRequest.getMethod()); |
| Iterator<String> headerNames = challengedRequest.getHeaderNames(); |
| while (headerNames.hasNext()) { |
| String headerName = headerNames.next(); |
| if ( reoriginatedRequest.getHeader(headerName) != null) { |
| ListIterator<Header> iterator = reoriginatedRequest.getHeaders(headerName); |
| while (iterator.hasNext()) { |
| reoriginatedRequest.addHeader(iterator.next()); |
| } |
| } |
| } |
| } |
| |
| |
| |
| // remove the branch id so that we could use the request in a new |
| // transaction |
| removeBranchID(reoriginatedRequest); |
| |
| if (challenge == null || reoriginatedRequest == null) { |
| throw new NullPointerException("A null argument was passed to handle challenge."); |
| } |
| |
| ListIterator authHeaders = null; |
| |
| if (challenge.getStatusCode() == Response.UNAUTHORIZED) { |
| authHeaders = challenge.getHeaders(WWWAuthenticateHeader.NAME); |
| } else if (challenge.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) { |
| authHeaders = challenge.getHeaders(ProxyAuthenticateHeader.NAME); |
| } else { |
| throw new IllegalArgumentException("Unexpected status code "); |
| } |
| |
| if (authHeaders == null) { |
| throw new IllegalArgumentException( |
| "Could not find WWWAuthenticate or ProxyAuthenticate headers"); |
| } |
| |
| // Remove all authorization headers from the request (we'll re-add them |
| // from cache) |
| reoriginatedRequest.removeHeader(AuthorizationHeader.NAME); |
| reoriginatedRequest.removeHeader(ProxyAuthorizationHeader.NAME); |
| |
| // rfc 3261 says that the cseq header should be augmented for the new |
| // request. do it here so that the new dialog (created together with |
| // the new client transaction) takes it into account. |
| // Bug report - Fredrik Wickstrom |
| CSeqHeader cSeq = (CSeqHeader) reoriginatedRequest.getHeader((CSeqHeader.NAME)); |
| try { |
| cSeq.setSeqNumber(cSeq.getSeqNumber() + 1l); |
| } catch (InvalidArgumentException ex) { |
| throw new SipException("Invalid CSeq -- could not increment : " |
| + cSeq.getSeqNumber()); |
| } |
| |
| |
| /* Resolve this to the next hop based on the previous lookup. If we are not using |
| * lose routing (RFC2543) then just attach hop as a maddr param. |
| */ |
| if ( challengedRequest.getRouteHeaders() == null ) { |
| Hop hop = ((SIPClientTransaction) challengedTransaction).getNextHop(); |
| SipURI sipUri = (SipURI) reoriginatedRequest.getRequestURI(); |
| // BEGIN android-added |
| if ( !hop.getHost().equalsIgnoreCase(sipUri.getHost()) ) |
| // END android-added |
| sipUri.setMAddrParam(hop.getHost()); |
| if ( hop.getPort() != -1 ) sipUri.setPort(hop.getPort()); |
| } |
| ClientTransaction retryTran = transactionCreator |
| .getNewClientTransaction(reoriginatedRequest); |
| |
| WWWAuthenticateHeader authHeader = null; |
| SipURI requestUri = (SipURI) challengedTransaction.getRequest().getRequestURI(); |
| while (authHeaders.hasNext()) { |
| authHeader = (WWWAuthenticateHeader) authHeaders.next(); |
| String realm = authHeader.getRealm(); |
| AuthorizationHeader authorization = null; |
| String sipDomain; |
| if ( this.accountManager instanceof SecureAccountManager ) { |
| UserCredentialHash credHash = |
| ((SecureAccountManager)this.accountManager).getCredentialHash(challengedTransaction,realm); |
| URI uri = reoriginatedRequest.getRequestURI(); |
| sipDomain = credHash.getSipDomain(); |
| authorization = this.getAuthorization(reoriginatedRequest |
| .getMethod(), uri.toString(), |
| (reoriginatedRequest.getContent() == null) ? "" : new String( |
| reoriginatedRequest.getRawContent()), authHeader, credHash); |
| } else { |
| UserCredentials userCreds = ((AccountManager) this.accountManager).getCredentials(challengedTransaction, realm); |
| sipDomain = userCreds.getSipDomain(); |
| if (userCreds == null) |
| throw new SipException( |
| "Cannot find user creds for the given user name and realm"); |
| |
| // we haven't yet authenticated this realm since we were |
| // started. |
| |
| authorization = this.getAuthorization(reoriginatedRequest |
| .getMethod(), reoriginatedRequest.getRequestURI().toString(), |
| (reoriginatedRequest.getContent() == null) ? "" : new String( |
| reoriginatedRequest.getRawContent()), authHeader, userCreds); |
| } |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "Created authorization header: " + authorization.toString()); |
| |
| if (cacheTime != 0) |
| cachedCredentials.cacheAuthorizationHeader(sipDomain, |
| authorization, cacheTime); |
| |
| reoriginatedRequest.addHeader(authorization); |
| } |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Returning authorization transaction." + retryTran); |
| } |
| return retryTran; |
| } catch (SipException ex) { |
| throw ex; |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Unexpected exception ", ex); |
| throw new SipException("Unexpected exception ", ex); |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Generates an authorisation header in response to wwwAuthHeader. |
| * |
| * @param method method of the request being authenticated |
| * @param uri digest-uri |
| * @param requestBody the body of the request. |
| * @param authHeader the challenge that we should respond to |
| * @param userCredentials username and pass |
| * |
| * @return an authorisation header in response to authHeader. |
| * |
| * @throws OperationFailedException if auth header was malformated. |
| */ |
| private AuthorizationHeader getAuthorization(String method, String uri, String requestBody, |
| WWWAuthenticateHeader authHeader, UserCredentials userCredentials) { |
| String response = null; |
| |
| // JvB: authHeader.getQop() is a quoted _list_ of qop values |
| // (e.g. "auth,auth-int") Client is supposed to pick one |
| String qopList = authHeader.getQop(); |
| String qop = (qopList != null) ? "auth" : null; |
| String nc_value = "00000001"; |
| String cnonce = "xyz"; |
| |
| response = MessageDigestAlgorithm.calculateResponse(authHeader.getAlgorithm(), |
| userCredentials.getUserName(), authHeader.getRealm(), userCredentials |
| .getPassword(), authHeader.getNonce(), nc_value, // JvB added |
| cnonce, // JvB added |
| method, uri, requestBody, qop,sipStack.getStackLogger());// jvb changed |
| |
| AuthorizationHeader authorization = null; |
| try { |
| if (authHeader instanceof ProxyAuthenticateHeader) { |
| authorization = headerFactory.createProxyAuthorizationHeader(authHeader |
| .getScheme()); |
| } else { |
| authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme()); |
| } |
| |
| authorization.setUsername(userCredentials.getUserName()); |
| authorization.setRealm(authHeader.getRealm()); |
| authorization.setNonce(authHeader.getNonce()); |
| authorization.setParameter("uri", uri); |
| authorization.setResponse(response); |
| if (authHeader.getAlgorithm() != null) { |
| authorization.setAlgorithm(authHeader.getAlgorithm()); |
| } |
| |
| if (authHeader.getOpaque() != null) { |
| authorization.setOpaque(authHeader.getOpaque()); |
| } |
| |
| // jvb added |
| if (qop != null) { |
| authorization.setQop(qop); |
| authorization.setCNonce(cnonce); |
| authorization.setNonceCount(Integer.parseInt(nc_value)); |
| } |
| |
| authorization.setResponse(response); |
| |
| } catch (ParseException ex) { |
| throw new RuntimeException("Failed to create an authorization header!"); |
| } |
| |
| return authorization; |
| } |
| /** |
| * Generates an authorisation header in response to wwwAuthHeader. |
| * |
| * @param method method of the request being authenticated |
| * @param uri digest-uri |
| * @param requestBody the body of the request. |
| * @param authHeader the challenge that we should respond to |
| * @param userCredentials username and pass |
| * |
| * @return an authorisation header in response to authHeader. |
| * |
| * @throws OperationFailedException if auth header was malformated. |
| */ |
| private AuthorizationHeader getAuthorization(String method, String uri, String requestBody, |
| WWWAuthenticateHeader authHeader, UserCredentialHash userCredentials) { |
| String response = null; |
| |
| // JvB: authHeader.getQop() is a quoted _list_ of qop values |
| // (e.g. "auth,auth-int") Client is supposed to pick one |
| String qopList = authHeader.getQop(); |
| String qop = (qopList != null) ? "auth" : null; |
| String nc_value = "00000001"; |
| String cnonce = "xyz"; |
| |
| response = MessageDigestAlgorithm.calculateResponse(authHeader.getAlgorithm(), |
| userCredentials.getHashUserDomainPassword(), authHeader.getNonce(), nc_value, // JvB added |
| cnonce, // JvB added |
| method, uri, requestBody, qop,sipStack.getStackLogger());// jvb changed |
| |
| AuthorizationHeader authorization = null; |
| try { |
| if (authHeader instanceof ProxyAuthenticateHeader) { |
| authorization = headerFactory.createProxyAuthorizationHeader(authHeader |
| .getScheme()); |
| } else { |
| authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme()); |
| } |
| |
| authorization.setUsername(userCredentials.getUserName()); |
| authorization.setRealm(authHeader.getRealm()); |
| authorization.setNonce(authHeader.getNonce()); |
| authorization.setParameter("uri", uri); |
| authorization.setResponse(response); |
| if (authHeader.getAlgorithm() != null) { |
| authorization.setAlgorithm(authHeader.getAlgorithm()); |
| } |
| |
| if (authHeader.getOpaque() != null) { |
| authorization.setOpaque(authHeader.getOpaque()); |
| } |
| |
| // jvb added |
| if (qop != null) { |
| authorization.setQop(qop); |
| authorization.setCNonce(cnonce); |
| authorization.setNonceCount(Integer.parseInt(nc_value)); |
| } |
| |
| authorization.setResponse(response); |
| |
| } catch (ParseException ex) { |
| throw new RuntimeException("Failed to create an authorization header!"); |
| } |
| |
| return authorization; |
| } |
| /** |
| * Removes all via headers from <tt>request</tt> and replaces them with a new one, equal to |
| * the one that was top most. |
| * |
| * @param request the Request whose branchID we'd like to remove. |
| * |
| */ |
| private void removeBranchID(Request request) { |
| |
| ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); |
| |
| viaHeader.removeParameter("branch"); |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see gov.nist.javax.sip.clientauthutils.AuthenticationHelper#attachAuthenticationHeaders(javax.sip.message.Request) |
| */ |
| public void setAuthenticationHeaders(Request request) { |
| SIPRequest sipRequest = (SIPRequest) request; |
| |
| String callId = sipRequest.getCallId().getCallId(); |
| |
| request.removeHeader(AuthorizationHeader.NAME); |
| Collection<AuthorizationHeader> authHeaders = this.cachedCredentials |
| .getCachedAuthorizationHeaders(callId); |
| if (authHeaders == null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "Could not find authentication headers for " + callId); |
| return; |
| } |
| |
| for (AuthorizationHeader authHeader : authHeaders) { |
| request.addHeader(authHeader); |
| } |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see gov.nist.javax.sip.clientauthutils.AuthenticationHelper#removeCachedAuthenticationHeaders(java.lang.String) |
| */ |
| public void removeCachedAuthenticationHeaders(String callId) { |
| if (callId == null) |
| throw new NullPointerException("Null callId argument "); |
| this.cachedCredentials.removeAuthenticationHeader(callId); |
| |
| } |
| |
| } |