/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.io.dav.http; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.StringTokenizer; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ class HTTPDigestAuthentication extends HTTPAuthentication { private static final char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private String myCnonce; private String myQop; private String myLastNonce; private int myNC; private String myCharset; protected HTTPDigestAuthentication (String charset) { myNC = 0; myCharset = charset; } public void init() throws SVNException { String qop = getChallengeParameter("qop"); String selectedQop = null; if (qop != null) { for(StringTokenizer tok = new StringTokenizer(qop,","); tok.hasMoreTokens();) { selectedQop = tok.nextToken().trim(); if ("auth".equals(selectedQop)) { break; } } } if (selectedQop != null && !"auth".equals(selectedQop)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "Digest HTTP auth: ''(0}'' is not supported", selectedQop); SVNErrorManager.error(err, SVNLogType.NETWORK); } myQop = selectedQop; myCnonce = createCnonce(); } public String authenticate() throws SVNException { if (getUserName() == null || getPassword() == null) { return null; } String uname = getUserName(); String nonce = getParameter("nonce"); if (nonce == null || !nonce.equals(myLastNonce)) { myNC = 0; } myNC++; myLastNonce = nonce; String digest = createDigest(uname, getPassword(), myCharset); String uri = getParameter("uri"); String realm = getParameter("realm"); String opaque = getParameter("opaque"); String algorithm = getParameter("algorithm", "MD5"); StringBuffer sb = new StringBuffer(); sb.append("Digest "); sb.append("username=\"" + uname + "\"") .append(", realm=\"" + realm + "\"") .append(", nonce=\"" + nonce + "\"").append(", uri=\"" + uri + "\"") .append(", response=\"" + digest + "\""); String nc = formatNC(myNC); if (myQop != null) { sb.append(", qop=\"" + myQop + "\"") .append(", nc="+ nc) .append(", cnonce=\"" + myCnonce + "\""); } if (algorithm != null) { sb.append(", algorithm=\"" + algorithm + "\""); } if (opaque != null) { sb.append(", opaque=\"" + opaque + "\""); } return sb.toString(); } public String getAuthenticationScheme(){ return "Digest"; } private String createDigest(String uname, char[] pwd, String charset) throws SVNException { final String digAlg = "MD5"; String uri = getParameter("uri"); String realm = getParameter("realm"); String nonce = getParameter("nonce"); String method = getParameter("method"); String algorithm = getParameter("algorithm", "MD5"); MessageDigest md5Helper; try { md5Helper = MessageDigest.getInstance(digAlg); } catch (Exception e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "Unsupported algorithm in HTTP Digest authentication: ''{0}''", digAlg); SVNErrorManager.error(err, SVNLogType.NETWORK); return null; } final char[] tmp = new char[uname.length() + realm.length() + pwd.length + 2]; System.arraycopy(uname.toCharArray(), 0, tmp, 0, uname.length()); tmp[uname.length()] = ':'; System.arraycopy(realm.toCharArray(), 0, tmp, uname.length() + 1, realm.length()); tmp[uname.length() + realm.length() + 1] = ':'; System.arraycopy(pwd, 0, tmp, uname.length() + realm.length() + 2, pwd.length); char[] a1 = tmp; if ("MD5-sess".equals(algorithm)) { String tmp2=encode(md5Helper.digest(HTTPAuthentication.getBytes(tmp, charset))); StringBuffer tmp3 = new StringBuffer(tmp2.length() + nonce.length() + myCnonce.length() + 2); tmp3.append(tmp2); tmp3.append(':'); tmp3.append(nonce); tmp3.append(':'); tmp3.append(myCnonce); a1 = tmp3.toString().toCharArray(); } String md5a1 = encode(md5Helper.digest(HTTPAuthentication.getBytes(a1, charset))); String a2 = method + ":" + uri; String md5a2 = encode(md5Helper.digest(HTTPAuthentication.getBytes(a2, charset))); HTTPAuthentication.clear(tmp); StringBuffer tmp2; if (myQop == null) { tmp2 = new StringBuffer(md5a1.length() + nonce.length() + md5a2.length()); tmp2.append(md5a1); tmp2.append(':'); tmp2.append(nonce); tmp2.append(':'); tmp2.append(md5a2); } else { String qopOption = "auth"; String nc = formatNC(myNC); tmp2 = new StringBuffer(md5a1.length() + nonce.length() + nc.length() + myCnonce.length() + qopOption.length() + md5a2.length() + 5); tmp2.append(md5a1); tmp2.append(':'); tmp2.append(nonce); tmp2.append(':'); tmp2.append(nc); tmp2.append(':'); tmp2.append(myCnonce); tmp2.append(':'); tmp2.append(qopOption); tmp2.append(':'); tmp2.append(md5a2); } return encode(md5Helper.digest(HTTPAuthentication.getBytes(tmp2.toString(), charset))); } private String getParameter(String name) { return getParameter(name, null); } private String getParameter(String name, String defaultValue) { String value = getChallengeParameter(name); if (value == null) { value = defaultValue; } return value; } private String createCnonce() { String cnonce; final String digAlg = "MD5"; MessageDigest md5Helper; try { md5Helper = MessageDigest.getInstance(digAlg); } catch (NoSuchAlgorithmException e) { return null; } cnonce = Long.toString(System.currentTimeMillis()); cnonce = encode(md5Helper.digest(HTTPAuthentication.getBytes(cnonce, myCharset))); return cnonce; } private static String encode(byte[] binaryData) { if (binaryData.length != 16) { return null; } char[] buffer = new char[32]; for (int i = 0; i < 16; i++) { int low = binaryData[i] & 0x0f; int high = (binaryData[i] & 0xf0) >> 4; buffer[i * 2] = HEXADECIMAL[high]; buffer[(i * 2) + 1] = HEXADECIMAL[low]; } return new String(buffer); } private static String formatNC(int nc) { String value = Integer.toHexString(nc); int count = 8 - value.length(); for(int i = 0; i < count; i++) { value = '0' + value; } return value; } }