001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.proxy.handlers.http.digest; 021 022import java.io.UnsupportedEncodingException; 023import java.security.MessageDigest; 024import java.security.NoSuchAlgorithmException; 025import java.util.HashMap; 026 027import javax.security.sasl.AuthenticationException; 028 029import org.apache.mina.core.session.IoSession; 030import org.apache.mina.proxy.session.ProxyIoSession; 031import org.apache.mina.proxy.utils.ByteUtilities; 032import org.apache.mina.proxy.utils.StringUtilities; 033 034/** 035 * DigestUtilities.java - A class supporting the HTTP DIGEST authentication (see RFC 2617). 036 * 037 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 038 * @since MINA 2.0.0-M3 039 */ 040public class DigestUtilities { 041 042 public final static String SESSION_HA1 = DigestUtilities.class + ".SessionHA1"; 043 044 private static MessageDigest md5; 045 046 static { 047 // Initialize secure random generator 048 try { 049 md5 = MessageDigest.getInstance("MD5"); 050 } catch (NoSuchAlgorithmException e) { 051 throw new RuntimeException(e); 052 } 053 } 054 055 /** 056 * The supported qualities of protections. 057 */ 058 public final static String[] SUPPORTED_QOPS = new String[] { "auth", "auth-int" }; 059 060 /** 061 * Computes the response to the DIGEST challenge. 062 * 063 * @param session the current session 064 * @param map the map holding the directives sent by the proxy 065 * @param method the HTTP verb 066 * @param pwd the password 067 * @param charsetName the name of the charset used for the challenge 068 * @param body the html body to be hashed for integrity calculations 069 * @return The response 070 * @throws AuthenticationException if we weren't able to find a directive value in the map 071 * @throws UnsupportedEncodingException If we weren't able to encode to ISO 8859_1 the username or realm, 072 * or if we weren't able to encode the charsetName 073 */ 074 public static String computeResponseValue(IoSession session, HashMap<String, String> map, String method, 075 String pwd, String charsetName, String body) throws AuthenticationException, UnsupportedEncodingException{ 076 077 byte[] hA1; 078 StringBuilder sb; 079 boolean isMD5Sess = "md5-sess".equalsIgnoreCase(StringUtilities.getDirectiveValue(map, "algorithm", false)); 080 081 if (!isMD5Sess || (session.getAttribute(SESSION_HA1) == null)) { 082 // Build A1 083 sb = new StringBuilder(); 084 sb.append(StringUtilities.stringTo8859_1(StringUtilities.getDirectiveValue(map, "username", true))).append( 085 ':'); 086 087 String realm = StringUtilities.stringTo8859_1(StringUtilities.getDirectiveValue(map, "realm", false)); 088 089 if (realm != null) { 090 sb.append(realm); 091 } 092 093 sb.append(':').append(pwd); 094 095 if (isMD5Sess) { 096 byte[] prehA1; 097 098 synchronized (md5) { 099 md5.reset(); 100 prehA1 = md5.digest(sb.toString().getBytes(charsetName)); 101 } 102 103 sb = new StringBuilder(); 104 sb.append(ByteUtilities.asHex(prehA1)); 105 sb.append(':').append( 106 StringUtilities.stringTo8859_1(StringUtilities.getDirectiveValue(map, "nonce", true))); 107 sb.append(':').append( 108 StringUtilities.stringTo8859_1(StringUtilities.getDirectiveValue(map, "cnonce", true))); 109 110 synchronized (md5) { 111 md5.reset(); 112 hA1 = md5.digest(sb.toString().getBytes(charsetName)); 113 } 114 115 session.setAttribute(SESSION_HA1, hA1); 116 } else { 117 synchronized (md5) { 118 md5.reset(); 119 hA1 = md5.digest(sb.toString().getBytes(charsetName)); 120 } 121 } 122 } else { 123 hA1 = (byte[]) session.getAttribute(SESSION_HA1); 124 } 125 126 sb = new StringBuilder(method); 127 sb.append(':'); 128 sb.append(StringUtilities.getDirectiveValue(map, "uri", false)); 129 130 String qop = StringUtilities.getDirectiveValue(map, "qop", false); 131 132 if ("auth-int".equalsIgnoreCase(qop)) { 133 ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION); 134 byte[] hEntity; 135 136 synchronized (md5) { 137 md5.reset(); 138 hEntity = md5.digest(body.getBytes(proxyIoSession.getCharsetName())); 139 } 140 141 sb.append(':').append(hEntity); 142 } 143 144 byte[] hA2; 145 synchronized (md5) { 146 md5.reset(); 147 hA2 = md5.digest(sb.toString().getBytes(charsetName)); 148 } 149 150 sb = new StringBuilder(); 151 sb.append(ByteUtilities.asHex(hA1)); 152 sb.append(':').append(StringUtilities.getDirectiveValue(map, "nonce", true)); 153 sb.append(":00000001:"); 154 155 sb.append(StringUtilities.getDirectiveValue(map, "cnonce", true)); 156 sb.append(':').append(qop).append(':'); 157 sb.append(ByteUtilities.asHex(hA2)); 158 159 byte[] hFinal; 160 161 synchronized (md5) { 162 md5.reset(); 163 hFinal = md5.digest(sb.toString().getBytes(charsetName)); 164 } 165 166 return ByteUtilities.asHex(hFinal); 167 } 168}