1 /*
2 jGuard is a security framework based on top of jaas (java authentication and authorization security).
3 it is written for web applications, to resolve simply, access control problems.
4 version $Name: $
5 http://sourceforge.net/projects/jguard/
6
7 Copyright (C) 2004 Charles GAY
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23
24 jGuard project home page:
25 http://sourceforge.net/projects/jguard/
26
27 */
28 package net.sf.jguard.ext.authentication.callbacks;
29
30 import java.io.UnsupportedEncodingException;
31 import java.security.cert.X509Certificate;
32 import java.util.Arrays;
33 import java.util.List;
34
35 import javax.security.auth.callback.Callback;
36 import javax.security.auth.callback.CallbackHandler;
37 import javax.security.auth.callback.NameCallback;
38 import javax.security.auth.callback.PasswordCallback;
39
40 import net.sf.jguard.core.CoreConstants;
41 import net.sf.jguard.ext.authentication.certificates.CertificateConverter;
42
43 import org.bouncycastle.util.encoders.Base64;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48 * Utility class for {@link CallbackHandler}.
49 * @author <a href="mailto:diabolo512@users.sourceforge.net ">Charles Gay</a>
50 *
51 */
52 public class CallbackHandlerUtils {
53 private static final String DIGEST_REALM = "Digest realm=\"";
54 private static final Logger logger = LoggerFactory.getLogger(CallbackHandlerUtils.class.getName());
55 private static final String ISO_8859_1 = "ISO-8859-1";
56 private static final String BASIC = "Basic ";
57
58 public static final String JAVAX_SERVLET_REQUEST_X509CERTIFICATE = "javax.servlet.request.X509Certificate";
59
60 public static void fillBasicCredentials(Callback[] callbacks, String login, String password) {
61 for(int i=0;i<callbacks.length;i++){
62 if(callbacks[i] instanceof NameCallback){
63 NameCallback nc = (NameCallback)callbacks[i];
64 nc.setName(login);
65
66 }else if(callbacks[i] instanceof PasswordCallback){
67 PasswordCallback pc = (PasswordCallback)callbacks[i];
68 pc.setPassword(password.toCharArray());
69 }else if (callbacks[i] instanceof JCaptchaCallback){
70 JCaptchaCallback jc = (JCaptchaCallback)callbacks[i];
71 //we skip JCaptcha because we cannot provide
72 //CAPTCHA challenge through BASIC authentication
73 jc.setSkipJCaptchaChallenge(true);
74 }
75 }
76 }
77
78 public static boolean grabClientCertCredentials(Callback[] callbacks,
79 Object[] objects) {
80 X509Certificate[] certificates = null;
81 javax.security.cert.X509Certificate[] oldCerts = null;
82 if(objects == null || objects.length==0){
83 return false;
84 }
85
86 if(objects instanceof X509Certificate[]) {
87 certificates= (X509Certificate[]) objects;
88 //convert old X509 certificates into new X509 certificates
89 }else if(objects instanceof javax.security.cert.X509Certificate[]) {
90 oldCerts = (javax.security.cert.X509Certificate[])objects;
91 List<X509Certificate> newCerts = null;
92 for(int i =0;i<oldCerts.length;i++){
93 newCerts = Arrays.asList(certificates);
94 newCerts.add(CertificateConverter.convertOldToNew(oldCerts[i]));
95 }
96 certificates = (X509Certificate[]) newCerts.toArray();
97 }else{
98 logger.warn(" X509certificates are needed but not provided by the client ");
99 return false;
100 }
101 CallbackHandlerUtils.fillCertCredentials(callbacks,certificates);
102
103 return true;
104 }
105
106 public static boolean grabBasicCredentials(String encodedLoginAndPwd,String encoding,Callback[] callbacks){
107 boolean result = false;
108 String login="";
109 String password="";
110 if(encodedLoginAndPwd==null ||encodedLoginAndPwd.equals("")){
111 login =CoreConstants.GUEST;
112 password =CoreConstants.GUEST;
113
114 }else{
115 encodedLoginAndPwd = encodedLoginAndPwd.substring(6).trim();
116 String decodedLoginAndPassword = null;
117
118
119 if(encoding==null){
120 encoding=CallbackHandlerUtils.ISO_8859_1;
121 }
122 logger.debug(encoding);
123
124 try {
125 decodedLoginAndPassword = new String(Base64.decode(encodedLoginAndPwd.getBytes()),encoding);
126 } catch (UnsupportedEncodingException e) {
127 e.printStackTrace();
128 logger.debug(" encoding "+encoding+" is not supported by the platform ");
129 }
130
131 String[] parts = decodedLoginAndPassword.split(":");
132 if(parts.length == 2 ){
133 login = parts[0].trim();
134 password = parts[1].trim();
135
136 result = true;
137 }
138 if(("".equals(login) && "".equals(password))||(parts.length==0)){
139 login =CoreConstants.GUEST;
140 password =CoreConstants.GUEST;
141 }
142
143 }
144
145 CallbackHandlerUtils.fillBasicCredentials(callbacks,login,password);
146 return result;
147 }
148
149 /**
150 * construct a header value to simulate a Basic authentication with the provided credentials.
151 * @param login
152 * @param password
153 * @param encoding
154 * @return header
155 */
156 public static String buildBasicAuthHeader(String login,String password,String encoding){
157 if(encoding==null){
158 encoding=CallbackHandlerUtils.ISO_8859_1;
159 }
160 StringBuffer decodedString = new StringBuffer();
161 decodedString.append(login);
162 decodedString.append(" : ");
163 decodedString.append(password);
164 String encodedString;
165 try {
166 encodedString = new String(Base64.encode(decodedString.toString().getBytes(encoding)));
167 } catch (UnsupportedEncodingException e) {
168 encodedString = new String(Base64.encode(decodedString.toString().getBytes()));
169 }
170 StringBuffer header = new StringBuffer();
171 header.append(CallbackHandlerUtils.BASIC);
172 header.append(encodedString);
173 header.append("==");
174 return header.toString();
175 }
176
177
178 public static String buildDigestChallenge(String realm){
179 //TODO buildDigestChallenge method is not complete
180 StringBuffer responseValue= new StringBuffer();
181 //what about domain which defines the protection space?
182
183 //realm
184 responseValue.append(CallbackHandlerUtils.DIGEST_REALM);
185 responseValue.append(realm);
186 responseValue.append("\"");
187 responseValue.append(",");
188 //quality of protection qop
189 responseValue.append("qop=\"");
190 responseValue.append(getQop());
191 responseValue.append("\"");
192 responseValue.append(",");
193
194 responseValue.append("nonce=\"");
195 responseValue.append(getNonce());
196 responseValue.append("\"");
197 responseValue.append(",");
198 //opaque
199 responseValue.append("opaque=");
200 responseValue.append("\"");
201 responseValue.append(getOpaque());
202 responseValue.append("\"");
203 //algorithm
204 responseValue.append("algorithm=");
205 responseValue.append("\"");
206 responseValue.append(getAlgorithm());
207 responseValue.append("\"");
208 //stale
209 responseValue.append("stale=");
210 responseValue.append("\"");
211 responseValue.append(getStale());
212 responseValue.append("\"");
213
214 return responseValue.toString();
215 }
216
217
218 /**
219 * A flag, indicating that the previous request from the client was
220 rejected because the nonce value was stale. If stale is TRUE
221 (case-insensitive), the client may wish to simply retry the request
222 with a new encrypted response, without reprompting the user for a
223 new username and password. The server should only set stale to TRUE
224 if it receives a request for which the nonce is invalid but with a
225 valid digest for that nonce (indicating that the client knows the
226 correct username/password). If stale is FALSE, or anything other
227 than TRUE, or the stale directive is not present, the username
228 and/or password are invalid, and new values must be obtained
229 * @return
230 */
231 private static String getStale() {
232 return "false";
233 }
234
235 /**
236 * This directive is optional, but is made so only for backward
237 compatibility with RFC 2069 [6]; it SHOULD be used by all
238 implementations compliant with this version of the Digest scheme.
239 If present, it is a quoted string of one or more tokens indicating
240 the "quality of protection" values supported by the server. The
241 value "auth" indicates authentication; the value "auth-int"
242 indicates authentication with integrity protection; see the
243 descriptions below for calculating the response directive value for
244 the application of this choice. Unrecognized options MUST be
245 ignored.
246 * @return
247 */
248 private static String getQop() {
249 return "auth,auth-int";
250 }
251
252 /**
253 * A string of data, specified by the server, which should be returned
254 by the client unchanged in the Authorization header of subsequent
255 requests with URIs in the same protection space. It is recommended
256 that this string be base64 or hexadecimal data.
257 * @return
258 */
259 private static String getOpaque() {
260 return "5ccc069c403ebaf9f0171e9517f40e41";
261 }
262
263 /**
264 *
265 A string indicating a pair of algorithms used to produce the digest
266 and a checksum. If this is not present it is assumed to be "MD5".
267 If the algorithm is not understood, the challenge should be ignored
268 (and a different one used, if there is more than one).
269
270 In this document the string obtained by applying the digest
271 algorithm to the data "data" with secret "secret" will be denoted
272 by KD(secret, data), and the string obtained by applying the
273 checksum algorithm to the data "data" will be denoted H(data). The
274 notation unq(X) means the value of the quoted-string X without the
275 surrounding quotes.
276
277 For the "MD5" and "MD5-sess" algorithms
278
279 H(data) = MD5(data)
280
281 and
282
283 KD(secret, data) = H(concat(secret, ":", data))
284
285 i.e., the digest is the MD5 of the secret concatenated with a colon
286 concatenated with the data. The "MD5-sess" algorithm is intended to
287 allow efficient 3rd party authentication servers; for the
288 difference in usage, see the description in section 3.2.2.2.
289 * @return
290 */
291 private static String getAlgorithm() {
292 return "MD5";
293 }
294
295 /**
296 * //nonce
297
298 A server-specified data string which should be uniquely generated
299 each time a 401 response is made. It is recommended that this
300 string be base64 or hexadecimal data. Specifically, since the
301 string is passed in the header lines as a quoted string, the
302 double-quote character is not allowed.
303
304 The contents of the nonce are implementation dependent. The quality
305 of the implementation depends on a good choice. A nonce might, for
306 example, be constructed as the base 64 encoding of
307
308 time-stamp H(time-stamp ":" ETag ":" private-key)
309
310 where time-stamp is a server-generated time or other non-repeating
311 value, ETag is the value of the HTTP ETag header associated with
312 the requested entity, and private-key is data known only to the
313 server. With a nonce of this form a server would recalculate the
314 hash portion after receiving the client authentication header and
315 reject the request if it did not match the nonce from that header
316 or if the time-stamp value is not recent enough. In this way the
317 server can limit the time of the nonce's validity. The inclusion of
318 the ETag prevents a replay request for an updated version of the
319 resource. (Note: including the IP address of the client in the
320 nonce would appear to offer the server the ability to limit the
321 reuse of the nonce to the same client that originally got it.
322 However, that would break proxy farms, where requests from a single
323 user often go through different proxies in the farm. Also, IP
324 address spoofing is not that hard.)
325
326 An implementation might choose not to accept a previously used
327 nonce or a previously used digest, in order to protect against a
328 replay attack. Or, an implementation might choose to use one-time
329 nonces or digests for POST or PUT requests and a time-stamp for GET
330 requests. For more details on the issues involved see section 4.
331 of this document. The nonce is opaque to the client.
332 * @param request
333 * @return
334 */
335 private static String getNonce(){
336 return "dcd98b7102dd2f0e8b11d0f600bfb0c093";
337 }
338
339 public static void fillCertCredentials(Callback[] callbacks,X509Certificate[] certificates) {
340 for(int i=0;i<callbacks.length;i++){
341 if(callbacks[i] instanceof CertificatesCallback){
342 CertificatesCallback cc = (CertificatesCallback)callbacks[i];
343 cc.setCertificates(certificates);
344 break;
345 }
346 }
347 }
348 }