/** * Generates the WWW-Authenticate header. * * <p>The header MUST follow this template : * * <pre> * WWW-Authenticate = "WWW-Authenticate" ":" "Digest" * digest-challenge * * digest-challenge = 1#( realm | [ domain ] | nOnce | * [ digest-opaque ] |[ stale ] | [ algorithm ] ) * * realm = "realm" "=" realm-value * realm-value = quoted-string * domain = "domain" "=" <"> 1#URI <"> * nonce = "nonce" "=" nonce-value * nonce-value = quoted-string * opaque = "opaque" "=" quoted-string * stale = "stale" "=" ( "true" | "false" ) * algorithm = "algorithm" "=" ( "MD5" | token ) * </pre> * * @param request HTTP Servlet request * @param response HTTP Servlet response * @param config Login configuration describing how authentication should be performed * @param nOnce nonce token */ protected void setAuthenticateHeader( SipServletRequestImpl request, SipServletResponseImpl response, SipLoginConfig config, String nOnce) { // Get the realm name String realmName = config.getRealmName(); if (realmName == null) realmName = request.getServerName() + ":" + request.getServerPort(); byte[] buffer = null; synchronized (md5Helper) { buffer = md5Helper.digest(nOnce.getBytes()); } String authenticateHeader = "Digest realm=\"" + realmName + "\", " + "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\"" + md5Encoder.encode(buffer) + "\""; // There are different headers for different types of auth if (response.getStatus() == SipServletResponseImpl.SC_PROXY_AUTHENTICATION_REQUIRED) { response.setHeader("Proxy-Authenticate", authenticateHeader); } else { response.setHeader("WWW-Authenticate", authenticateHeader); } }
/** * Return the Principal associated with the specified username, which matches the digest * calculated using the given parameters using the method described in RFC 2069; otherwise return * <code>null</code>. * * @param username Username of the Principal to look up * @param clientDigest Digest which has been submitted by the client * @param nOnce Unique (or supposedly unique) token which has been used for this request * @param realm Realm name * @param md5a2 Second MD5 digest used to calculate the digest : MD5(Method + ":" + uri) */ public Principal authenticate( String username, String clientDigest, String nOnce, String nc, String cnonce, String qop, String realm, String md5a2) { /* System.out.println("Digest : " + clientDigest); System.out.println("************ Digest info"); System.out.println("Username:"******"ClientSigest:" + clientDigest); System.out.println("nOnce:" + nOnce); System.out.println("nc:" + nc); System.out.println("cnonce:" + cnonce); System.out.println("qop:" + qop); System.out.println("realm:" + realm); System.out.println("md5a2:" + md5a2); */ String md5a1 = getDigest(username, realm); if (md5a1 == null) return null; String serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":" + cnonce + ":" + qop + ":" + md5a2; String serverDigest = md5Encoder.encode(md5Helper.digest(serverDigestValue.getBytes())); // System.out.println("Server digest : " + serverDigest); if (serverDigest.equals(clientDigest)) return getPrincipal(username); else return null; }
/** Return the digest associated with given principal's user name. */ protected String getDigest(String username, String realmName) { if (md5Helper == null) { try { md5Helper = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { log.error("Couldn't get MD5 digest: ", e); throw new IllegalStateException(e.getMessage()); } } if (hasMessageDigest()) { // Use pre-generated digest return getPassword(username); } String digestValue = username + ":" + realmName + ":" + getPassword(username); byte[] valueBytes = null; try { valueBytes = digestValue.getBytes(getDigestCharset()); } catch (UnsupportedEncodingException uee) { log.error("Illegal digestEncoding: " + getDigestEncoding(), uee); throw new IllegalArgumentException(uee.getMessage()); } byte[] digest = null; // Bugzilla 32137 synchronized (md5Helper) { digest = md5Helper.digest(valueBytes); } return md5Encoder.encode(digest); }
/** * Generate a unique token. The token is generated according to the following pattern. NOnceToken * = Base64 ( MD5 ( client-IP ":" time-stamp ":" private-key ) ). * * @param request HTTP Servlet request */ protected String generateNonce(Request request) { long currentTime = System.currentTimeMillis(); synchronized (lastTimestampLock) { if (currentTime > lastTimestamp) { lastTimestamp = currentTime; } else { currentTime = ++lastTimestamp; } } String ipTimeKey = request.getRemoteAddr() + ":" + currentTime + ":" + getKey(); byte[] buffer = ConcurrentMessageDigest.digestMD5(ipTimeKey.getBytes(StandardCharsets.ISO_8859_1)); String nonce = currentTime + ":" + MD5Encoder.encode(buffer); NonceInfo info = new NonceInfo(currentTime, getNonceCountWindowSize()); synchronized (nonces) { nonces.put(nonce, info); } return nonce; }
public Principal authenticate(Realm realm) { // Second MD5 digest used to calculate the digest : // MD5(Method + ":" + uri) String a2 = method + ":" + uri; byte[] buffer = ConcurrentMessageDigest.digestMD5(a2.getBytes(StandardCharsets.ISO_8859_1)); String md5a2 = MD5Encoder.encode(buffer); return realm.authenticate(userName, response, nonce, nc, cnonce, qop, realmName, md5a2); }
/** Return the digest associated with given principal's user name. */ protected String getDigest(String username, String realmName) { if (md5Helper == null) { try { md5Helper = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new IllegalStateException(); } } String digestValue = username + ":" + realmName + ":" + getPassword(username); byte[] digest = md5Helper.digest(digestValue.getBytes()); return md5Encoder.encode(digest); }
/** * Generate a unique token. The token is generated according to the following pattern. NOnceToken * = Base64 ( MD5 ( client-IP ":" time-stamp ":" private-key ) ). * * @param request HTTP Servlet request */ protected String generateNOnce(SipServletRequestImpl request) { long currentTime = System.currentTimeMillis(); String nOnceValue = request.getRemoteAddr() + ":" + currentTime + ":" + key; byte[] buffer = null; synchronized (md5Helper) { buffer = md5Helper.digest(nOnceValue.getBytes()); } nOnceValue = md5Encoder.encode(buffer); return nOnceValue; }
/** * Parse the specified authorization credentials, and return the associated Principal that these * credentials authenticate (if any) from the specified Realm. If there is no such Principal, * return <code>null</code>. * * @param request HTTP servlet request * @param authorization Authorization credentials from this request * @param realm Realm used to authenticate Principals */ protected static Principal findPrincipal( SipServletRequestImpl request, String authorization, Realm realm) { // System.out.println("Authorization token : " + authorization); // Validate the authorization credentials format if (authorization == null) return (null); if (!authorization.startsWith("Digest ")) return (null); authorization = authorization.substring(7).trim(); // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132 // The solution of 37132 doesn't work with : // response="2d05f1206becab904c1f311f205b405b",cnonce="5644k1k670",username="******",nc=00000001,qop=auth,nonce="b6c73ab509830b8c0897984f6b0526e8",realm="sip-servlets-realm",opaque="9ed6d115d11f505f9ee20f6a68654cc2",uri="sip:192.168.1.142",algorithm=MD5 // That's why I am going back to simple comma (Vladimir). TODO: Review this. String[] tokens = authorization.split(","); // (?=(?:[^\"]*\"[^\"]*\")+$)"); String userName = null; String realmName = null; String nOnce = null; String nc = null; String cnonce = null; String qop = null; String uri = null; String response = null; String method = request.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) return null; String currentTokenName = currentToken.substring(0, equalSign).trim(); String currentTokenValue = currentToken.substring(equalSign + 1).trim(); if ("username".equals(currentTokenName)) userName = removeQuotes(currentTokenValue); if ("realm".equals(currentTokenName)) realmName = removeQuotes(currentTokenValue, true); if ("nonce".equals(currentTokenName)) nOnce = removeQuotes(currentTokenValue); if ("nc".equals(currentTokenName)) nc = removeQuotes(currentTokenValue); if ("cnonce".equals(currentTokenName)) cnonce = removeQuotes(currentTokenValue); if ("qop".equals(currentTokenName)) qop = removeQuotes(currentTokenValue); if ("uri".equals(currentTokenName)) uri = removeQuotes(currentTokenValue); if ("response".equals(currentTokenName)) response = removeQuotes(currentTokenValue); } if ((userName == null) || (realmName == null) || (nOnce == null) || (uri == null) || (response == null)) return null; // Second MD5 digest used to calculate the digest : // MD5(Method + ":" + uri) String a2 = method + ":" + uri; // System.out.println("A2:" + a2); byte[] buffer = null; synchronized (md5Helper) { buffer = md5Helper.digest(a2.getBytes()); } String md5a2 = md5Encoder.encode(buffer); return (realm.authenticate(userName, response, nOnce, nc, cnonce, qop, realmName, md5a2)); }
/** * Return the Principal associated with the specified username, which matches the digest * calculated using the given parameters using the method described in RFC 2069; otherwise return * <code>null</code>. * * @param username Username of the Principal to look up * @param clientDigest Digest which has been submitted by the client * @param nonce Unique (or supposedly unique) token which has been used for this request * @param realm Realm name * @param md5a2 Second MD5 digest used to calculate the digest : MD5(Method + ":" + uri) */ @Override public Principal authenticate( String username, String clientDigest, String nonce, String nc, String cnonce, String qop, String realm, String md5a2) { // In digest auth, digests are always lower case String md5a1 = getDigest(username, realm).toLowerCase(Locale.ENGLISH); if (md5a1 == null) return null; String serverDigestValue; if (qop == null) { serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2; } else { serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + md5a2; } byte[] valueBytes = null; try { valueBytes = serverDigestValue.getBytes(getDigestCharset()); } catch (UnsupportedEncodingException uee) { log.error("Illegal digestEncoding: " + getDigestEncoding(), uee); throw new IllegalArgumentException(uee.getMessage()); } String serverDigest = null; // Bugzilla 32137 synchronized (md5Helper) { serverDigest = md5Encoder.encode(md5Helper.digest(valueBytes)); } if (log.isDebugEnabled()) { log.debug( "Digest : " + clientDigest + " Username:"******" ClientSigest:" + clientDigest + " nonce:" + nonce + " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop + " realm:" + realm + "md5a2:" + md5a2 + " Server digest:" + serverDigest); } if (serverDigest.equals(clientDigest)) { return getPrincipal(username); } return null; }
public boolean validate(Request request) { if ((userName == null) || (realmName == null) || (nonce == null) || (uri == null) || (response == null)) { return false; } // Validate the URI - should match the request line sent by client if (validateUri) { String uriQuery; String query = request.getQueryString(); if (query == null) { uriQuery = request.getRequestURI(); } else { uriQuery = request.getRequestURI() + "?" + query; } if (!uri.equals(uriQuery)) { // Some clients (older Android) use an absolute URI for // DIGEST but a relative URI in the request line. // request. 2.3.5 < fixed Android version <= 4.0.3 String host = request.getHeader("host"); String scheme = request.getScheme(); if (host != null && !uriQuery.startsWith(scheme)) { StringBuilder absolute = new StringBuilder(); absolute.append(scheme); absolute.append("://"); absolute.append(host); absolute.append(uriQuery); if (!uri.equals(absolute.toString())) { return false; } } else { return false; } } } // Validate the Realm name String lcRealm = getRealmName(request.getContext()); if (!lcRealm.equals(realmName)) { return false; } // Validate the opaque string if (!opaque.equals(opaqueReceived)) { return false; } // Validate nonce int i = nonce.indexOf(":"); if (i < 0 || (i + 1) == nonce.length()) { return false; } long nonceTime; try { nonceTime = Long.parseLong(nonce.substring(0, i)); } catch (NumberFormatException nfe) { return false; } String md5clientIpTimeKey = nonce.substring(i + 1); long currentTime = System.currentTimeMillis(); if ((currentTime - nonceTime) > nonceValidity) { nonceStale = true; synchronized (nonces) { nonces.remove(nonce); } } String serverIpTimeKey = request.getRemoteAddr() + ":" + nonceTime + ":" + key; byte[] buffer = ConcurrentMessageDigest.digestMD5(serverIpTimeKey.getBytes(StandardCharsets.ISO_8859_1)); String md5ServerIpTimeKey = MD5Encoder.encode(buffer); if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) { return false; } // Validate qop if (qop != null && !QOP.equals(qop)) { return false; } // Validate cnonce and nc // Check if presence of nc and Cnonce is consistent with presence of qop if (qop == null) { if (cnonce != null || nc != null) { return false; } } else { if (cnonce == null || nc == null) { return false; } // RFC 2617 says nc must be 8 digits long. Older Android clients // use 6. 2.3.5 < fixed Android version <= 4.0.3 if (nc.length() < 6 || nc.length() > 8) { return false; } long count; try { count = Long.parseLong(nc, 16); } catch (NumberFormatException nfe) { return false; } NonceInfo info; synchronized (nonces) { info = nonces.get(nonce); } if (info == null) { // Nonce is valid but not in cache. It must have dropped out // of the cache - force a re-authentication nonceStale = true; } else { if (!info.nonceCountValid(count)) { return false; } } } return true; }