package com.owera.xaps.base.http; import java.io.IOException; import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.digest.DigestUtils; import com.owera.common.db.NoAvailableConnectionException; import com.owera.common.log.Context; import com.owera.xaps.base.BaseCache; import com.owera.xaps.base.Log; import com.owera.xaps.base.NoDataAvailableException; import com.owera.xaps.dbi.util.SystemParameters; import com.owera.xaps.tr069.HTTPReqResData; import com.owera.xaps.tr069.SessionData; import com.owera.xaps.tr069.exception.TR069AuthenticationException; import com.owera.xaps.tr069.exception.TR069Exception; public class DigestAuthenticator { /** * MD5 message digest provider. * @throws TR069Exception */ // protected static MessageDigest md5Helper; private static void sendChallenge(HttpServletRequest req, HttpServletResponse res) { String nonce = DigestUtils.md5Hex(req.getRemoteAddr() + ":" + System.currentTimeMillis() + ":MortenRuler"); setAuthenticateHeader(res, nonce); try { res.sendError(HttpServletResponse.SC_UNAUTHORIZED); } catch (IOException ioe) { Log.warn(DigestAuthenticator.class, "Unable to make challenge", ioe); } } public static boolean authenticate(HTTPReqResData reqRes) throws TR069AuthenticationException { String authorization = reqRes.getReq().getHeader("authorization"); if (authorization == null) { Log.notice(DigestAuthenticator.class, "Send challenge to CPE, located on IP-address " + reqRes.getReq().getRemoteHost()); sendChallenge(reqRes.getReq(), reqRes.getRes()); return false; } else { return (verify(reqRes, authorization)); } } /** * Generates the WWW-Authenticate header. * * @param request HTTP Servlet request * @param response HTTP Servlet response * @param nonce nonce token */ private static void setAuthenticateHeader(HttpServletResponse res, String nonce) { String realm = Util.getRealm(); String authenticateHeader = "Digest realm=\"" + realm + "\", " + "qop=\"auth\", nonce=\"" + nonce + "\", " + "opaque=\"" + DigestUtils.md5Hex(nonce) + "\""; res.setHeader("WWW-Authenticate", authenticateHeader); } private static String passwordMd5(String username, String password, String method, String uri, String nonce, String nc, String cnonce, String qop) { String realm = Util.getRealm(); String a1 = username + ":" + realm + ":" + password; String md5a1 = DigestUtils.md5Hex(a1); String a2 = method + ":" + uri; String md5a2 = DigestUtils.md5Hex(a2); String a3 = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + md5a2; String md5a3 = DigestUtils.md5Hex(a3); return md5a3; } /** * Verifies login against database * * @param request HTTP servlet request * @param authorization Authorization credentials from this request * @throws TR069AuthenticationException */ private static boolean verify(HTTPReqResData reqRes, String authorization) throws TR069AuthenticationException { Log.debug(DigestAuthenticator.class, "Digest verification of CPE starts, located on IP-address " + reqRes.getReq().getRemoteHost()); authorization = authorization.trim(); authorization = Util.removePrefix(authorization, "digest"); authorization = authorization.trim(); String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)"); String username = null; String realm = null; String nonce = null; String nc = null; String cnonce = null; String qop = null; String uri = null; String response = null; String method = reqRes.getReq().getMethod(); for (int i = 0; i < tokens.length; i++) { String currentToken = tokens[i]; if (currentToken.length() == 0) continue; int equalSign = currentToken.indexOf('='); if (equalSign < 0) throw new TR069AuthenticationException("Digest challenge response has incorrect format (CPE IP address: " + reqRes.getReq().getRemoteHost() + ")", null, HttpServletResponse.SC_FORBIDDEN); String currentTokenName = currentToken.substring(0, equalSign).trim(); String currentTokenValue = currentToken.substring(equalSign + 1).trim(); if ("username".equals(currentTokenName)) username = Util.removeQuotes(currentTokenValue); if ("realm".equals(currentTokenName)) realm = Util.removeQuotes(currentTokenValue, true); if ("nonce".equals(currentTokenName)) nonce = Util.removeQuotes(currentTokenValue); if ("nc".equals(currentTokenName)) nc = Util.removeQuotes(currentTokenValue); if ("cnonce".equals(currentTokenName)) cnonce = Util.removeQuotes(currentTokenValue); if ("qop".equals(currentTokenName)) qop = Util.removeQuotes(currentTokenValue); if ("uri".equals(currentTokenName)) uri = Util.removeQuotes(currentTokenValue); if ("response".equals(currentTokenName)) response = Util.removeQuotes(currentTokenValue); } if ((username == null) || username.length() < 6 || (realm == null) || (nonce == null) || (uri == null) || (response == null)) throw new TR069AuthenticationException("Digest challenge response does not contain all necessary parameters (CPE IP address: " + reqRes.getReq().getRemoteHost() + ") (username: " + username + ")", null, HttpServletResponse.SC_FORBIDDEN); // Do database read parameters and then perform verification String unitId = Util.username2unitId(username); Context.put(Context.X, unitId, BaseCache.SESSIONDATA_CACHE_TIMEOUT); Log.debug(DigestAuthenticator.class, "Digest verification identifed unit id " + unitId + " from CPE IP-address " + reqRes.getReq().getRemoteHost()); try { SessionData sessionData = reqRes.getSessionData(); sessionData.setUnitId(unitId); sessionData.updateParametersFromDB(unitId); // String secret = sessionData.getOweraParameters().getValue(SystemParameters.SHARED_SECRET); BaseCache.putSessionData(unitId, sessionData); // if (secret == null) String secret = sessionData.getOweraParameters().getValue(SystemParameters.SECRET); if (secret != null && secret.length() > 16 && !passwordMd5(username, secret, method, uri, nonce, nc, cnonce, qop).equals(response)) secret = secret.substring(0, 16); // if (secret == null) // secret = sessionData.getOweraParameters().getValue(SystemParameters.TR069_SECRET); if (secret == null) { throw new TR069AuthenticationException("No ACS Password found in database (CPE IP address: " + reqRes.getReq().getRemoteHost() + ") (username: " + username + ")", null, HttpServletResponse.SC_FORBIDDEN); } else { String sharedSecretMd5 = passwordMd5(username, secret, method, uri, nonce, nc, cnonce, qop); if (!sharedSecretMd5.equals(response)) { throw new TR069AuthenticationException("Incorrect ACS Password (CPE IP address: " + reqRes.getReq().getRemoteHost() + ") (username: " + username + ")", null, HttpServletResponse.SC_FORBIDDEN); } else { Log.notice(DigestAuthenticator.class, "Authentication verified (CPE IP address: " + reqRes.getReq().getRemoteHost() + ")"); return true; } } } catch (NoAvailableConnectionException e) { throw new TR069AuthenticationException("Authentication failed because of no available database connections (CPE IP address: " + reqRes.getReq().getRemoteHost() + ") (username: " + username + ")", e, HttpServletResponse.SC_SERVICE_UNAVAILABLE); } catch (SQLException e) { throw new TR069AuthenticationException("Authentication failed because of database error (CPE IP address: " + reqRes.getReq().getRemoteHost() + ") (username: " + username + ")", e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } catch (NoDataAvailableException e) { throw new TR069AuthenticationException("Authentication failed because unitid was not found (CPE IP address: " + reqRes.getReq().getRemoteHost() + ") (username: " + username + ")", e, HttpServletResponse.SC_FORBIDDEN); } } }