PageRenderTime 29ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/servers/sip-presence/xdm/server/core/xcap-control/sbb/src/main/java/org/openxdm/xcap/server/slee/AuthenticationProxySbb.java

http://mobicents.googlecode.com/
Java | 561 lines | 403 code | 25 blank | 133 comment | 80 complexity | b4772a1fe7209c70f61c502d3cef1eca MD5 | raw file
Possible License(s): LGPL-3.0, GPL-3.0, LGPL-2.1, GPL-2.0, CC-BY-SA-3.0, CC0-1.0, Apache-2.0, BSD-3-Clause
  1. /*
  2. * JBoss, Home of Professional Open Source
  3. * Copyright 2011, Red Hat, Inc. and individual contributors
  4. * by the @authors tag. See the copyright.txt in the distribution for a
  5. * full listing of individual contributors.
  6. *
  7. * This is free software; you can redistribute it and/or modify it
  8. * under the terms of the GNU Lesser General Public License as
  9. * published by the Free Software Foundation; either version 2.1 of
  10. * the License, or (at your option) any later version.
  11. *
  12. * This software is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this software; if not, write to the Free
  19. * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  20. * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  21. */
  22. package org.openxdm.xcap.server.slee;
  23. import java.io.IOException;
  24. import java.security.NoSuchAlgorithmException;
  25. import javax.servlet.http.HttpServletRequest;
  26. import javax.servlet.http.HttpServletResponse;
  27. import javax.slee.ActivityContextInterface;
  28. import javax.slee.RolledBackContext;
  29. import javax.slee.SbbContext;
  30. import javax.slee.facilities.Tracer;
  31. import org.mobicents.slee.ChildRelationExt;
  32. import org.mobicents.slee.enabler.userprofile.UserProfile;
  33. import org.mobicents.slee.enabler.userprofile.UserProfileControlSbbLocalObject;
  34. import org.mobicents.slee.xdm.server.ServerConfiguration;
  35. import org.openxdm.xcap.common.error.InternalServerErrorException;
  36. import org.openxdm.xcap.common.http.HttpConstant;
  37. import org.openxdm.xcap.server.slee.auth.RFC2617AuthQopDigest;
  38. import org.openxdm.xcap.server.slee.auth.RFC2617ChallengeParamGenerator;
  39. /**
  40. *
  41. * @author aayush.bhatnagar
  42. * @author martins
  43. *
  44. * The authentication proxy only authenticates remote requests, if local
  45. * uses asserted id if present, if not defines no user but does not
  46. * fails.
  47. *
  48. * From the OMA-TS-XDM-core specification:
  49. *
  50. * The Aggregation Proxy SHALL act as an HTTP Proxy defined in [RFC2616]
  51. * with the following clarifications. The Aggregation Proxy:
  52. *
  53. * 1. SHALL be configured as an HTTP reverse proxy (see [RFC3040]);
  54. *
  55. * 2. SHALL support authenticating the XDM Client; in case the GAA is
  56. * used according to [3GPP TS 33.222], the mutual authentication SHALL
  57. * be supported; or SHALL assert the XDM Client identity by inserting
  58. * the X-XCAPAsserted- Identity extension header to the HTTP requests
  59. * after a successful HTTP Digest Authentication as defined in Section
  60. * 6.3.2, in case the GAA is not used.
  61. *
  62. * 3. SHALL forward the XCAP requests to the corresponding XDM Server,
  63. * and forward the response back to the XDM Client;
  64. *
  65. * 4. SHALL protect the XCAP traffic by enabling TLS transport security
  66. * mechanism. The TLS resumption procedure SHALL be used as specified in
  67. * [RFC2818].
  68. *
  69. * When realized with 3GPP IMS or 3GPP2 MMD networks, the Aggregation
  70. * Proxy SHALL act as an Authentication Proxy defined in [3GPP TS
  71. * 33.222] with the following clarifications. The Aggregation Proxy:
  72. * SHALL check whether an XDM Client identity has been inserted in
  73. * X-3GPP-Intended-Identity header of HTTP request.
  74. *
  75. * ??? If the X-3GPP-Intended-Identity is included , the Aggregation Proxy
  76. * SHALL check the value in the header is allowed to be used by the
  77. * authenticated identity.
  78. *
  79. * ??? If the X-3GPP-Intended-Identity is not included, the Aggregation
  80. * Proxy SHALL insert the authenticated identity in the
  81. * X-3GPP-Asserted-Identity header of the HTTP request.
  82. *
  83. * TODO: GAA is not supported as of now. It is FFS on how we go about
  84. * GAA support. TODO: TLS is not supported as of now.
  85. */
  86. public abstract class AuthenticationProxySbb implements javax.slee.Sbb,
  87. AuthenticationProxy {
  88. private static Tracer logger;
  89. private static final RFC2617ChallengeParamGenerator challengeParamGenerator = new RFC2617ChallengeParamGenerator();
  90. private static final ServerConfiguration CONFIGURATION = ServerConfiguration.getInstance();
  91. public static final String HEADER_X_3GPP_Asserted_Identity = "X-3GPP-Asserted-Identity";
  92. public static final String HEADER_X_XCAP_Asserted_Identity = "X-XCAP-Asserted-Identity";
  93. /*
  94. * (non-Javadoc)
  95. *
  96. * @see
  97. * org.openxdm.xcap.server.slee.AuthenticationProxySbbLocalObject#authenticate
  98. * (javax.servlet.http.HttpServletRequest,
  99. * javax.servlet.http.HttpServletResponse)
  100. */
  101. public String authenticate(HttpServletRequest request,
  102. HttpServletResponse response) throws InternalServerErrorException {
  103. if (logger.isFineEnabled()) {
  104. logger.fine("Authenticating request");
  105. }
  106. /**
  107. * On receiving an HTTP request that does not contain the Authorization
  108. * header field, the AP shall: a) challenge the user by generating a 401
  109. * Unauthorized response according to the procedures specified in TS 133
  110. * 222 [6] and RFC 2617 [3]; and b) forward the 401 Unauthorized
  111. * response to the sender of the HTTP request.
  112. */
  113. try {
  114. String user = null;
  115. if(!CONFIGURATION.getLocalXcapAuthentication() && request.getRemoteAddr().equals(request.getLocalAddr())) {
  116. if (logger.isInfoEnabled()) {
  117. logger.info("Skipping authentication for local request.");
  118. }
  119. if (CONFIGURATION.getAllowAssertedUserIDs()) {
  120. // use asserted id header if present
  121. user = request.getHeader(HEADER_X_3GPP_Asserted_Identity);
  122. if (user == null) {
  123. user = request.getHeader(HEADER_X_XCAP_Asserted_Identity);
  124. }
  125. if (logger.isInfoEnabled()) {
  126. logger.info("Asserted user: "+user);
  127. }
  128. }
  129. }
  130. else {
  131. if (CONFIGURATION.getAllowAssertedUserIDs()) {
  132. // use asserted id header if present
  133. user = request.getHeader(HEADER_X_3GPP_Asserted_Identity);
  134. if (user == null) {
  135. user = request.getHeader(HEADER_X_XCAP_Asserted_Identity);
  136. }
  137. if (logger.isInfoEnabled()) {
  138. logger.info("Asserted user: "+user);
  139. }
  140. }
  141. if (user == null) {
  142. // use http digest authentication
  143. if (logger.isInfoEnabled()) {
  144. logger.info("Remote request without asserted user, using http digest authentication");
  145. }
  146. if (request.getHeader(HttpConstant.HEADER_AUTHORIZATION) == null) {
  147. challengeRequest(request, response);
  148. } else {
  149. user = checkAuthenticatedCredentials(request, response);
  150. if (user != null) {
  151. if (logger.isFineEnabled()) {
  152. logger.fine("Authentication suceed");
  153. }
  154. } else {
  155. if (logger.isFineEnabled()) {
  156. logger.fine("Authentication failed");
  157. }
  158. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  159. response.getWriter().close();
  160. }
  161. }
  162. }
  163. }
  164. return user;
  165. } catch (Throwable e) {
  166. throw new InternalServerErrorException(e.getMessage(), e);
  167. }
  168. }
  169. /**
  170. *
  171. * @param request
  172. * @param response
  173. * @throws IOException
  174. * @throws NoSuchAlgorithmException
  175. * @throws InternalServerErrorException
  176. */
  177. private void challengeRequest(HttpServletRequest request,
  178. HttpServletResponse response) throws IOException,
  179. NoSuchAlgorithmException, InternalServerErrorException {
  180. if (logger.isFineEnabled())
  181. logger
  182. .fine("Authorization header is missing...challenging the request");
  183. response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
  184. /**
  185. * If a qop directive is sent by the server in the challenge, then the
  186. * challenge response MUST contain the nonce-count and cnonce
  187. * parameters. This will be checked later on.
  188. */
  189. String opaque = challengeParamGenerator.generateOpaque();
  190. final String challengeParams = "Digest nonce=\"" + challengeParamGenerator.getNonce(opaque)
  191. + "\", realm=\"" + getRealm()
  192. + "\", opaque=\"" + opaque
  193. + "\", qop=\"auth\"";
  194. response.setHeader(HttpConstant.HEADER_WWW_AUTHENTICATE,
  195. challengeParams);
  196. if (logger.isFineEnabled()) {
  197. logger.fine("Sending response with header "+HttpConstant.HEADER_WWW_AUTHENTICATE+" challenge params: "+challengeParams);
  198. }
  199. // send to client
  200. response.getWriter().close();
  201. }
  202. /**
  203. *
  204. * @param request
  205. * @param response
  206. * @return null if authentication failed, authenticated user@domain otherwise
  207. * @throws InternalServerErrorException
  208. */
  209. private String checkAuthenticatedCredentials(HttpServletRequest request,
  210. HttpServletResponse response) throws InternalServerErrorException {
  211. /**
  212. * On receiving an HTTP request that contains the Authorization header
  213. * field, the AP shall:
  214. *
  215. * a)use the value of that username parameter of the Authorization
  216. * header field to authenticate the user;
  217. *
  218. * b)apply the procedures specified in RFC 2617 [3] for authentication;
  219. *
  220. * c)if the HTTP request contains an X 3GPP Intended Identity header
  221. * field (TS 124 109 [5]), then the AP may verify that the user identity
  222. * belongs to the subscriber. This verification of the user identity
  223. * shall be performed dependant on the subscriber's application specific
  224. * or AP specific user security settings;
  225. *
  226. * d)if authentication is successful, remove the Authorization header
  227. * field from the HTTP request;
  228. *
  229. * e)insert an HTTP X 3GPP Asserted Identity header field (TS 124 109
  230. * [5]) that contains the asserted identity or a list of identities;
  231. *
  232. * We wont be implementing points d and e, as they are applicable only
  233. * if the Authentication Proxy had to forward the request to the XDM
  234. * over the network. Here it is co-located with the XDM server.
  235. */
  236. String authHeaderParams = request
  237. .getHeader(HttpConstant.HEADER_AUTHORIZATION);
  238. if (logger.isFineEnabled()) {
  239. logger.fine("Authorization header included with value: "+authHeaderParams);
  240. }
  241. // 6 is "Digest".length(), lets skip the header value till that index
  242. final int digestParamsStart = 6;
  243. if (authHeaderParams.length() > digestParamsStart) {
  244. authHeaderParams = authHeaderParams.substring(digestParamsStart);
  245. }
  246. String username = null;
  247. String password = null;
  248. String realm = null;
  249. String nonce = null;
  250. String uri = null;
  251. String cnonce = null;
  252. String nc = null;
  253. String qop = null;
  254. String resp = null;
  255. String opaque = null;
  256. for(String param : authHeaderParams.split(",")) {
  257. int i = param.indexOf('=');
  258. if (i > 0 && i < (param.length()-1)) {
  259. String paramName = param.substring(0,i).trim();
  260. String paramValue = param.substring(i+1).trim();
  261. if (paramName.equals("username")) {
  262. if (paramValue.length()>2) {
  263. username = paramValue.substring(1, paramValue.length()-1);
  264. if (logger.isFineEnabled()) {
  265. logger.fine("Username param with value "+username);
  266. }
  267. }
  268. else {
  269. if (logger.isFineEnabled()) {
  270. logger.fine("Ignoring invalid param "+paramName+" value "+paramValue);
  271. }
  272. }
  273. }
  274. else if (paramName.equals("nonce")) {
  275. if (paramValue.length()>2) {
  276. nonce = paramValue.substring(1, paramValue.length()-1);
  277. if (logger.isFineEnabled()) {
  278. logger.fine("Nonce param with value "+nonce);
  279. }
  280. }
  281. else {
  282. if (logger.isFineEnabled()) {
  283. logger.fine("Ignoring invalid param "+paramName+" value "+paramValue);
  284. }
  285. }
  286. }
  287. else if (paramName.equals("cnonce")) {
  288. if (paramValue.length()>2) {
  289. cnonce = paramValue.substring(1, paramValue.length()-1);
  290. if (logger.isFineEnabled()) {
  291. logger.fine("CNonce param with value "+cnonce);
  292. }
  293. }
  294. else {
  295. if (logger.isFineEnabled()) {
  296. logger.fine("Ignoring invalid param "+paramName+" value "+paramValue);
  297. }
  298. }
  299. }
  300. else if (paramName.equals("realm")) {
  301. if (paramValue.length()>2) {
  302. realm = paramValue.substring(1, paramValue.length()-1);
  303. if (logger.isFineEnabled()) {
  304. logger.fine("Realm param with value "+realm);
  305. }
  306. }
  307. else {
  308. if (logger.isFineEnabled()) {
  309. logger.fine("Ignoring invalid param "+paramName+" value "+paramValue);
  310. }
  311. }
  312. }
  313. else if (paramName.equals("nc")) {
  314. nc = paramValue;
  315. if (logger.isFineEnabled()) {
  316. logger.fine("Nonce-count param with value "+nc);
  317. }
  318. }
  319. else if (paramName.equals("response")) {
  320. if (paramValue.length()>2) {
  321. resp = paramValue.substring(1, paramValue.length()-1);
  322. if (logger.isFineEnabled()) {
  323. logger.fine("Response param with value "+resp);
  324. }
  325. }
  326. else {
  327. if (logger.isFineEnabled()) {
  328. logger.fine("Ignoring invalid param "+paramName+" value "+paramValue);
  329. }
  330. }
  331. }
  332. else if (paramName.equals("uri")) {
  333. if (paramValue.length()>2) {
  334. uri = paramValue.substring(1, paramValue.length()-1);
  335. if (logger.isFineEnabled()) {
  336. logger.fine("Digest uri param with value "+uri);
  337. }
  338. }
  339. else {
  340. if (logger.isFineEnabled()) {
  341. logger.fine("Ignoring invalid param "+paramName+" value "+paramValue);
  342. }
  343. }
  344. }
  345. else if (paramName.equals("opaque")) {
  346. if (paramValue.length()>2) {
  347. opaque = paramValue.substring(1, paramValue.length()-1);
  348. if (logger.isFineEnabled()) {
  349. logger.fine("Opaque param with value "+opaque);
  350. }
  351. }
  352. else {
  353. if (logger.isFineEnabled()) {
  354. logger.fine("Ignoring invalid param "+paramName+" value "+paramValue);
  355. }
  356. }
  357. }
  358. else if (paramName.equals("qop")) {
  359. if (paramValue.charAt(0) == '"') {
  360. if (paramValue.length()>2) {
  361. qop = paramValue.substring(1, paramValue.length()-1);
  362. }
  363. else {
  364. if (logger.isFineEnabled()) {
  365. logger.fine("Ignoring invalid param "+paramName+" value "+paramValue);
  366. }
  367. }
  368. }
  369. else {
  370. qop = paramValue;
  371. }
  372. if (logger.isFineEnabled()) {
  373. logger.fine("Qop param with value "+qop);
  374. }
  375. }
  376. }
  377. else {
  378. if (logger.isFineEnabled()) {
  379. logger.fine("Ignoring invalid param "+param);
  380. }
  381. }
  382. }
  383. /**
  384. * The client response to a WWW-Authenticate challenge for a protection
  385. * space starts an authentication session with that protection space.
  386. * The authentication session lasts until the client receives another
  387. * WWW-Authenticate challenge from any server in the protection space. A
  388. * client should remember the username, password, nonce, nonce count and
  389. * opaque values associated with an authentication session to use to
  390. * construct the Authorization header in future requests within that
  391. * protection space.
  392. */
  393. if (username == null || realm == null || nonce == null || cnonce == null || nc == null
  394. || uri == null || resp == null || opaque == null) {
  395. logger
  396. .severe("A required parameter is missing in the challenge response");
  397. // FIXME should be replied with BAD REQUEST 400
  398. return null;
  399. }
  400. // verify opaque vs nonce
  401. if (challengeParamGenerator.getNonce(opaque).equals(nonce)) {
  402. if (logger.isFineEnabled())
  403. logger.fine("Nonce provided matches the one generated using opaque as seed");
  404. }
  405. else {
  406. if (logger.isFineEnabled())
  407. logger.fine("Authentication failed, nonce provided doesn't match the one generated using opaque as seed");
  408. return null;
  409. }
  410. if (!qop.equals("auth")) {
  411. if (logger.isFineEnabled())
  412. logger.fine("Authentication failed, qop value "+qop+" unsupported");
  413. return null;
  414. }
  415. // get user password
  416. UserProfile userProfile = getUserProfileControlSbb().find(username);
  417. if (userProfile == null) {
  418. if (logger.isFineEnabled())
  419. logger.fine("Authentication failed, profile not found for user "+username);
  420. return null;
  421. }
  422. else {
  423. password = userProfile.getPassword();
  424. }
  425. final String digest = new RFC2617AuthQopDigest(username, realm, password, nonce, nc, cnonce, request.getMethod().toUpperCase(), uri).digest();
  426. if (digest != null && digest.equals(resp)) {
  427. if (logger.isFineEnabled())
  428. logger.fine("authentication response is matching");
  429. /**
  430. * Add the cnonce,nc and qop as received in the Authorization header
  431. * of the request. We need to add the Authentication-Info header and
  432. * set these values.
  433. *
  434. * Authentication-Info: qop=auth-int,
  435. * rspauth="6629fae49394a05397450978507c4ef1",
  436. * cnonce="6629fae49393a05397450978507c4ef1", nc=00000001
  437. */
  438. String params = "cnonce=\"" + cnonce + "\", nc=" + nc + ", qop="
  439. + qop + ", rspauth=\"" + digest+"\"";
  440. response.addHeader("Authentication-Info", params);
  441. return username;
  442. } else {
  443. if (logger.isFineEnabled())
  444. logger.fine("authentication response digest received ("+resp+") didn't match the one calculated ("+digest+")");
  445. return null;
  446. }
  447. }
  448. /**
  449. * Get the authentication scheme
  450. *
  451. * @return the scheme name
  452. */
  453. public String getScheme() {
  454. return "Digest";
  455. }
  456. /**
  457. * get the authentication realm
  458. *
  459. * @return the realm name
  460. */
  461. public String getRealm() {
  462. return CONFIGURATION.getAuthenticationRealm();
  463. }
  464. // -- user profile enabler child relation
  465. public abstract ChildRelationExt getUserProfileControlChildRelation();
  466. protected UserProfileControlSbbLocalObject getUserProfileControlSbb() {
  467. try {
  468. return (UserProfileControlSbbLocalObject) getUserProfileControlChildRelation()
  469. .create(ChildRelationExt.DEFAULT_CHILD_NAME);
  470. } catch (Exception e) {
  471. logger.severe("Failed to create child sbb", e);
  472. return null;
  473. }
  474. }
  475. // -- sbb object lifecycle
  476. public void setSbbContext(SbbContext context) {
  477. sbbContext = context;
  478. if (logger == null) {
  479. logger = sbbContext.getTracer(this.getClass().getSimpleName());
  480. }
  481. }
  482. public void unsetSbbContext() {
  483. }
  484. public void sbbCreate() throws javax.slee.CreateException {
  485. }
  486. public void sbbPostCreate() throws javax.slee.CreateException {
  487. }
  488. public void sbbActivate() {
  489. }
  490. public void sbbPassivate() {
  491. }
  492. public void sbbRemove() {
  493. }
  494. public void sbbLoad() {
  495. }
  496. public void sbbStore() {
  497. }
  498. public void sbbExceptionThrown(Exception exception, Object event,
  499. ActivityContextInterface activity) {
  500. }
  501. public void sbbRolledBack(RolledBackContext context) {
  502. }
  503. protected SbbContext getSbbContext() {
  504. return sbbContext;
  505. }
  506. private SbbContext sbbContext; // This SBB's SbbContext
  507. }