1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.proxy.handlers.http.digest;
21
22 import java.io.UnsupportedEncodingException;
23 import java.security.NoSuchAlgorithmException;
24 import java.security.SecureRandom;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.StringTokenizer;
30
31 import org.apache.mina.core.filterchain.IoFilter.NextFilter;
32 import org.apache.mina.proxy.ProxyAuthException;
33 import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
34 import org.apache.mina.proxy.handlers.http.HttpProxyConstants;
35 import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
36 import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
37 import org.apache.mina.proxy.session.ProxyIoSession;
38 import org.apache.mina.proxy.utils.StringUtilities;
39 import org.apache.mina.util.Base64;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43
44
45
46
47
48
49
50 public class HttpDigestAuthLogicHandler extends AbstractAuthLogicHandler {
51
52 private final static Logger logger = LoggerFactory
53 .getLogger(HttpDigestAuthLogicHandler.class);
54
55
56
57
58 private HashMap<String, String> directives = null;
59
60
61
62
63 private HttpProxyResponse response;
64
65 private static SecureRandom rnd;
66
67 static {
68
69 try {
70 rnd = SecureRandom.getInstance("SHA1PRNG");
71 } catch (NoSuchAlgorithmException e) {
72 throw new RuntimeException(e);
73 }
74 }
75
76 public HttpDigestAuthLogicHandler(final ProxyIoSession proxyIoSession)
77 throws ProxyAuthException {
78 super(proxyIoSession);
79
80 if (request == null || !(request instanceof HttpProxyRequest)) {
81 throw new IllegalArgumentException(
82 "request parameter should be a non null HttpProxyRequest instance");
83 }
84
85 HttpProxyRequest req = (HttpProxyRequest) request;
86 req.checkRequiredProperty(HttpProxyConstants.USER_PROPERTY);
87 req.checkRequiredProperty(HttpProxyConstants.PWD_PROPERTY);
88 }
89
90 @Override
91 public void doHandshake(NextFilter nextFilter) throws ProxyAuthException {
92 logger.debug(" doHandshake()");
93
94 if (step > 0 && directives == null) {
95 throw new ProxyAuthException(
96 "Authentication challenge not received");
97 } else {
98 HttpProxyRequest req = (HttpProxyRequest) request;
99 Map<String, List<String>> headers = req.getHeaders() != null ? req
100 .getHeaders() : new HashMap<String, List<String>>();
101
102 if (step > 0) {
103 logger.debug(" sending DIGEST challenge response");
104
105 HashMap<String, String> map = new HashMap<String, String>();
106 map.put("username", req.getProperties().get(
107 HttpProxyConstants.USER_PROPERTY));
108 StringUtilities.copyDirective(directives, map, "realm");
109 StringUtilities.copyDirective(directives, map, "uri");
110 StringUtilities.copyDirective(directives, map, "opaque");
111 StringUtilities.copyDirective(directives, map, "nonce");
112 String algorithm = StringUtilities.copyDirective(directives,
113 map, "algorithm");
114
115
116 if (algorithm != null && !"md5".equalsIgnoreCase(algorithm)
117 && !"md5-sess".equalsIgnoreCase(algorithm)) {
118 throw new ProxyAuthException(
119 "Unknown algorithm required by server");
120 }
121
122
123 String qop = directives.get("qop");
124 if (qop != null) {
125 StringTokenizer st = new StringTokenizer(qop, ",");
126 String token = null;
127
128 while (st.hasMoreTokens()) {
129 String tk = st.nextToken();
130 if ("auth".equalsIgnoreCase(token)) {
131 break;
132 } else {
133 int pos = Arrays.binarySearch(
134 DigestUtilities.SUPPORTED_QOPS, tk);
135 if (pos > -1) {
136 token = tk;
137 }
138 }
139 }
140
141 if (token != null) {
142 map.put("qop", token);
143
144 byte[] nonce = new byte[8];
145 rnd.nextBytes(nonce);
146
147 try {
148 String cnonce = new String(Base64
149 .encodeBase64(nonce), proxyIoSession
150 .getCharsetName());
151 map.put("cnonce", cnonce);
152 } catch (UnsupportedEncodingException e) {
153 throw new ProxyAuthException(
154 "Unable to encode cnonce", e);
155 }
156 } else {
157 throw new ProxyAuthException(
158 "No supported qop option available");
159 }
160 }
161
162 map.put("nc", "00000001");
163 map.put("uri", req.getHttpURI());
164
165
166 try {
167 map.put("response", DigestUtilities
168 .computeResponseValue(proxyIoSession.getSession(),
169 map, req.getHttpVerb().toUpperCase(),
170 req.getProperties().get(
171 HttpProxyConstants.PWD_PROPERTY),
172 proxyIoSession.getCharsetName(), response
173 .getBody()));
174
175 } catch (Exception e) {
176 throw new ProxyAuthException(
177 "Digest response computing failed", e);
178 }
179
180
181 StringBuilder sb = new StringBuilder("Digest ");
182 boolean addSeparator = false;
183
184 for (String key : map.keySet()) {
185
186 if (addSeparator) {
187 sb.append(", ");
188 } else {
189 addSeparator = true;
190 }
191
192 boolean quotedValue = !"qop".equals(key)
193 && !"nc".equals(key);
194 sb.append(key);
195 if (quotedValue) {
196 sb.append("=\"").append(map.get(key)).append('\"');
197 } else {
198 sb.append('=').append(map.get(key));
199 }
200 }
201
202 StringUtilities.addValueToHeader(headers,
203 "Proxy-Authorization", sb.toString(), true);
204 }
205
206 StringUtilities.addValueToHeader(headers, "Keep-Alive",
207 HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true);
208 StringUtilities.addValueToHeader(headers, "Proxy-Connection",
209 "keep-Alive", true);
210 req.setHeaders(headers);
211
212 writeRequest(nextFilter, req);
213 step++;
214 }
215 }
216
217 @Override
218 public void handleResponse(final HttpProxyResponse response)
219 throws ProxyAuthException {
220 this.response = response;
221
222 if (step == 0) {
223 if (response.getStatusCode() != 401
224 && response.getStatusCode() != 407) {
225 throw new ProxyAuthException(
226 "Received unexpected response code ("
227 + response.getStatusLine() + ").");
228 }
229
230
231
232 List<String> values = response.getHeaders().get(
233 "Proxy-Authenticate");
234 String challengeResponse = null;
235
236 for (String s : values) {
237 if (s.startsWith("Digest")) {
238 challengeResponse = s;
239 break;
240 }
241 }
242
243 if (challengeResponse == null) {
244 throw new ProxyAuthException(
245 "Server doesn't support digest authentication method !");
246 }
247
248 try {
249 directives = StringUtilities.parseDirectives(challengeResponse
250 .substring(7).getBytes(proxyIoSession.getCharsetName()));
251 } catch (Exception e) {
252 throw new ProxyAuthException(
253 "Parsing of server digest directives failed", e);
254 }
255 step = 1;
256 } else {
257 throw new ProxyAuthException("Received unexpected response code ("
258 + response.getStatusLine() + ").");
259 }
260 }
261 }