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