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