View Javadoc

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 }