001: /**
002: * $RCSfile$
003: * $Revision: $
004: * $Date: $
005: *
006: * Copyright (C) 2007 Jive Software. All rights reserved.
007: *
008: * This software is published under the terms of the GNU Public License (GPL),
009: * a copy of which is included in this distribution.
010: */package org.jivesoftware.openfire.sip.tester.security;
011:
012: import org.jivesoftware.openfire.sip.tester.Log;
013: import org.jivesoftware.openfire.sip.tester.stack.SIPConfig;
014: import org.jivesoftware.openfire.sip.tester.stack.SipManager;
015:
016: import javax.sip.ClientTransaction;
017: import javax.sip.InvalidArgumentException;
018: import javax.sip.SipException;
019: import javax.sip.SipProvider;
020: import javax.sip.address.Address;
021: import javax.sip.address.SipURI;
022: import javax.sip.address.URI;
023: import javax.sip.header.*;
024: import javax.sip.message.Request;
025: import javax.sip.message.Response;
026: import java.text.ParseException;
027: import java.util.ListIterator;
028:
029: /**
030: * <p/>
031: * Title: Netsite TudoMais
032: * </p>
033: * <p/>
034: * Description:JAIN-SIP Audio/Video phone application
035: * </p>
036: * <p/>
037: * Copyright: Copyright (c) 2006
038: * </p>
039: * <p/>
040: * Organisation: CTBC Telecom / Netsite
041: * </p>
042: *
043: * @author Thiago Rocha Camargo (thiago@jivesoftware.com)
044: */
045:
046: public class SipSecurityManager {
047: /**
048: */
049: private HeaderFactory headerFactory = null;
050:
051: /**
052: */
053: private SipProvider transactionCreator = null;
054:
055: private SipManager sipManCallback = null;
056:
057: /**
058: * Credentials cached so far.
059: */
060: CredentialsCache cachedCredentials = new CredentialsCache();
061:
062: public UserCredentials defaultCredentials = null;
063:
064: public SipSecurityManager() {
065:
066: }
067:
068: public UserCredentials getDefaultCredentials() {
069: return defaultCredentials;
070: }
071:
072: public void setDefaultCredentials(UserCredentials defaultCredentials) {
073: this .defaultCredentials = defaultCredentials;
074: }
075:
076: /**
077: * set the header factory to be used when creating authorization headers
078: *
079: * @uml.property name="headerFactory"
080: */
081: public void setHeaderFactory(HeaderFactory headerFactory) {
082: this .headerFactory = headerFactory;
083: }
084:
085: /**
086: * Verifies whether there are any user credentials registered for the call
087: * that "request" belongs to and appends corresponding authorization headers
088: * if that is the case.
089: *
090: * @param request the request that needs to be attached credentials.
091: */
092: public void appendCredentialsIfNecessary(Request request) {
093: // TODO IMPLEMENT
094: }
095:
096: /**
097: * Uses securityAuthority to determinie a set of valid user credentials for
098: * the specified Response (Challenge) and appends it to the challenged
099: * request so that it could be retransmitted.
100: * <p/>
101: * Fredrik Wickstrom reported that dialog cseq counters are not incremented
102: * when resending requests. He later uncovered additional problems and
103: * proposed a way to fix them (his proposition was taken into account).
104: *
105: * @param challenge the 401/407 challenge response
106: * @param challengedTransaction the transaction established by the challenged request
107: * @return a transaction containing a reoriginated request with the
108: * necessary authorization header.
109: * @throws SipSecurityException
110: */
111: public ClientTransaction handleChallenge(Response challenge,
112: ClientTransaction challengedTransaction)
113: throws SipSecurityException, SipException,
114: InvalidArgumentException, ParseException {
115: try {
116:
117: String branchID = challengedTransaction.getBranchId();
118: Request challengedRequest = challengedTransaction
119: .getRequest();
120:
121: Request reoriginatedRequest = (Request) challengedRequest
122: .clone();
123:
124: ListIterator authHeaders = null;
125:
126: if (challenge == null || reoriginatedRequest == null)
127: throw new NullPointerException(
128: "A null argument was passed to handle challenge.");
129:
130: // CallIdHeader callId =
131: // (CallIdHeader)challenge.getHeader(CallIdHeader.NAME);
132:
133: if (challenge.getStatusCode() == Response.UNAUTHORIZED)
134: authHeaders = challenge
135: .getHeaders(WWWAuthenticateHeader.NAME);
136: else if (challenge.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED)
137: authHeaders = challenge
138: .getHeaders(ProxyAuthenticateHeader.NAME);
139:
140: if (authHeaders == null)
141: throw new SecurityException(
142: "Could not find WWWAuthenticate or ProxyAuthenticate headers");
143:
144: // Remove all authorization headers from the request (we'll re-add
145: // them
146: // from cache)
147: reoriginatedRequest.removeHeader(AuthorizationHeader.NAME);
148: reoriginatedRequest
149: .removeHeader(ProxyAuthorizationHeader.NAME);
150:
151: // rfc 3261 says that the cseq header should be augmented for the
152: // new
153: // request. do it here so that the new dialog (created together with
154: // the new client transaction) takes it into account.
155: // Bug report - Fredrik Wickstrom
156: CSeqHeader cSeq = (CSeqHeader) reoriginatedRequest
157: .getHeader((CSeqHeader.NAME));
158: cSeq.setSequenceNumber(cSeq.getSequenceNumber() + 1);
159:
160: ClientTransaction retryTran = transactionCreator
161: .getNewClientTransaction(reoriginatedRequest);
162:
163: WWWAuthenticateHeader authHeader = null;
164: CredentialsCacheEntry ccEntry = null;
165: while (authHeaders.hasNext()) {
166: authHeader = (WWWAuthenticateHeader) authHeaders.next();
167: String realm = authHeader.getRealm();
168:
169: // Check whether we have cached credentials for authHeader's
170: // realm
171: // make sure that if such credentials exist they get removed.
172: // The
173: // challenge means that there's something wrong with them.
174: ccEntry = cachedCredentials.remove(realm);
175:
176: // Try to guess user name and facilitate user
177: UserCredentials defaultCredentials = new UserCredentials();
178: FromHeader from = (FromHeader) reoriginatedRequest
179: .getHeader(FromHeader.NAME);
180: URI uri = from.getAddress().getURI();
181: if (uri.isSipURI()) {
182: Log.debug("handleChallenge", SIPConfig
183: .getAuthUserName());
184: String user = SIPConfig.getAuthUserName() != null ? SIPConfig
185: .getAuthUserName()
186: : ((SipURI) uri).getUser();
187: defaultCredentials
188: .setAuthUserName(user == null ? SIPConfig
189: .getUserName() : user);
190: }
191:
192: boolean ccEntryHasSeenTran = false;
193:
194: if (ccEntry != null)
195: ccEntryHasSeenTran = ccEntry
196: .processResponse(branchID);
197:
198: // get a new pass
199: if (ccEntry == null // we don't have credentials for the
200: // specified realm
201: || ((!authHeader.isStale() && ccEntryHasSeenTran))) {
202: if (ccEntry == null) {
203: ccEntry = new CredentialsCacheEntry();
204:
205: ccEntry.userCredentials = defaultCredentials;
206:
207: }
208: // put the returned user name in the properties file
209: // so that it appears as a default one next time user is
210: // prompted for pass
211: SIPConfig.setUserName(ccEntry.userCredentials
212: .getUserName());
213: }
214: // encode and send what we have
215: else if (ccEntry != null
216: && (!ccEntryHasSeenTran || authHeader.isStale())) {
217: }
218:
219: // if user canceled or sth else went wrong
220: if (ccEntry.userCredentials == null)
221: throw new SecurityException(
222: "Unable to authenticate with realm "
223: + realm);
224:
225: AuthorizationHeader authorization = this
226: .getAuthorization(
227: reoriginatedRequest.getMethod(),
228: reoriginatedRequest.getRequestURI()
229: .toString(),
230: reoriginatedRequest.getContent() == null ? ""
231: : reoriginatedRequest
232: .getContent()
233: .toString(),
234: authHeader, ccEntry.userCredentials);
235:
236: ccEntry.processRequest(retryTran.getBranchId());
237: cachedCredentials.cacheEntry(realm, ccEntry);
238:
239: reoriginatedRequest.addHeader(authorization);
240:
241: // if there was trouble with the user - make sure we fix it
242: if (uri.isSipURI()) {
243: ((SipURI) uri).setUser(ccEntry.userCredentials
244: .getUserName());
245: Address add = from.getAddress();
246: add.setURI(uri);
247: from.setAddress(add);
248: reoriginatedRequest.setHeader(from);
249: if (challengedRequest.getMethod().equals(
250: Request.REGISTER)) {
251: ToHeader to = (ToHeader) reoriginatedRequest
252: .getHeader(ToHeader.NAME);
253: add.setURI(uri);
254: to.setAddress(add);
255: reoriginatedRequest.setHeader(to);
256:
257: }
258:
259: // very ugly but very necessary
260:
261: sipManCallback.setCurrentlyUsedURI(uri.toString());
262: Log.debug("URI: " + uri.toString());
263:
264: }
265:
266: // if this is a register - fix to as well
267:
268: }
269:
270: return retryTran;
271: } catch (Exception e) {
272: Log.debug("ERRO REG: " + e.toString());
273: return null;
274: }
275:
276: }
277:
278: /**
279: * Generates an authorisation header in response to wwwAuthHeader.
280: *
281: * @param method method of the request being authenticated
282: * @param uri digest-uri
283: * @param authHeader the challenge that we should respond to
284: * @param userCredentials username and pass
285: * @return an authorisation header in response to wwwAuthHeader.
286: */
287: private AuthorizationHeader getAuthorization(String method,
288: String uri, String requestBody,
289: WWWAuthenticateHeader authHeader,
290: UserCredentials userCredentials) throws SecurityException {
291: String response = null;
292: try {
293: Log.debug("getAuthorization", userCredentials
294: .getAuthUserName());
295: response = MessageDigestAlgorithm.calculateResponse(
296: authHeader.getAlgorithm(), userCredentials
297: .getAuthUserName(), authHeader.getRealm(),
298: new String(userCredentials.getPassword()),
299: authHeader.getNonce(),
300: // TODO we should one day implement those two null-s
301: null,// nc-value
302: null,// cnonce
303: method, uri, requestBody, authHeader.getQop());
304: } catch (NullPointerException exc) {
305: throw new SecurityException(
306: "The authenticate header was malformatted");
307: }
308:
309: AuthorizationHeader authorization = null;
310: try {
311: if (authHeader instanceof ProxyAuthenticateHeader) {
312: authorization = headerFactory
313: .createProxyAuthorizationHeader(authHeader
314: .getScheme());
315: } else {
316: authorization = headerFactory
317: .createAuthorizationHeader(authHeader
318: .getScheme());
319: }
320:
321: authorization
322: .setUsername(userCredentials.getAuthUserName());
323: authorization.setRealm(authHeader.getRealm());
324: authorization.setNonce(authHeader.getNonce());
325: authorization.setParameter("uri", uri);
326: authorization.setResponse(response);
327: if (authHeader.getAlgorithm() != null)
328: authorization.setAlgorithm(authHeader.getAlgorithm());
329: if (authHeader.getOpaque() != null)
330: authorization.setOpaque(authHeader.getOpaque());
331:
332: authorization.setResponse(response);
333: } catch (ParseException ex) {
334: throw new SecurityException(
335: "Failed to create an authorization header!");
336: }
337:
338: return authorization;
339: }
340:
341: public void cacheCredentials(String realm,
342: UserCredentials credentials) {
343: CredentialsCacheEntry ccEntry = new CredentialsCacheEntry();
344: ccEntry.userCredentials = credentials;
345:
346: this .cachedCredentials.cacheEntry(realm, ccEntry);
347: }
348:
349: /**
350: * Sets a valid SipProvider that would enable the security manager to map
351: * credentials to transactionsand thus understand when it is suitable to use
352: * cached passwords and when it should go ask the user.
353: *
354: * @param transactionCreator a valid SipProvder instance
355: * @uml.property name="transactionCreator"
356: */
357: public void setTransactionCreator(SipProvider transactionCreator) {
358: this .transactionCreator = transactionCreator;
359: }
360:
361: /**
362: * If the user name was wrong and the user fixes it here we should als
363: * notify the sip manager that the currentlyUsedURI it has is not valid.
364: *
365: * @param sipManCallback a valid instance of SipMaqnager
366: * @uml.property name="sipManCallback"
367: */
368: public void setSipManCallback(SipManager sipManCallback) {
369: this.sipManCallback = sipManCallback;
370: }
371:
372: }
|