View Javadoc
1   /*
2     (C) Copyright IBM Corp. 2006, 2009
3   
4     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
5     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
6     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
7   
8     You can obtain a current copy of the Eclipse Public License from
9     http://www.opensource.org/licenses/eclipse-1.0.php
10  
11    @author : Alexander Wolf-Reber, IBM, a.wolf-reber@de.ibm.com
12   * 
13   * Change History
14   * Flag       Date        Prog         Description
15   * -------------------------------------------------------------------------------
16   * 1536711    2006-08-15  lupusalex    NullPointerException causes client call to never return 
17   * 1516242    2006-11-27  lupusalex    Support of OpenPegasus local authentication
18   * 1565892    2006-11-28  lupusalex    Make SBLIM client JSR48 compliant
19   * 1627832    2007-01-08  lupuslaex    Incorrect retry behaviour on HTTP 401
20   * 1892046    2008-02-13  blaschke-oss Basic/digest authentication problem for Japanese users
21   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
22   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
23   * 2531371    2009-02-10  raman_arora  Upgrade client to JDK 1.5 (Phase 2)
24   * 2714989    2009-03-26  blaschke-oss Code cleanup from redundant null check et al
25   */
26  package org.metricshub.wbem.sblim.cimclient.internal.http;
27  
28  /*-
29   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
30   * WBEM Java Client
31   * ჻჻჻჻჻჻
32   * Copyright 2023 - 2025 MetricsHub
33   * ჻჻჻჻჻჻
34   * Licensed under the Apache License, Version 2.0 (the "License");
35   * you may not use this file except in compliance with the License.
36   * You may obtain a copy of the License at
37   *
38   *      http://www.apache.org/licenses/LICENSE-2.0
39   *
40   * Unless required by applicable law or agreed to in writing, software
41   * distributed under the License is distributed on an "AS IS" BASIS,
42   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43   * See the License for the specific language governing permissions and
44   * limitations under the License.
45   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
46   */
47  
48  import java.io.UnsupportedEncodingException;
49  import java.net.PasswordAuthentication;
50  import java.net.URI;
51  import java.security.MessageDigest;
52  import java.security.NoSuchAlgorithmException;
53  import java.util.Vector;
54  
55  /**
56   * Implements HTTP basic and digest authentication
57   */
58  public class WwwAuthInfo extends AuthorizationInfo {
59  
60  	/**
61  	 * Default ctor.
62  	 */
63  	public WwwAuthInfo() {
64  		super();
65  	}
66  
67  	/*
68  	 * (non-Javadoc)
69  	 *
70  	 * @see java.lang.Object#toString()
71  	 */
72  	@Override
73  	public String toString() {
74  		StringBuffer result = new StringBuffer();
75  
76  		String _nc = Long.toHexString(this.iNc);
77  
78  		if (this.iScheme.equalsIgnoreCase("Digest")) {
79  			if (this.iRealm == null) { // support for some ICAT CIMOMs buggy
80  				// Digest authentication
81  				try {
82  					MessageDigest messageDigest = null;
83  					messageDigest = MessageDigest.getInstance("MD5");
84  
85  					messageDigest.update(
86  						getBytes(
87  							(this.iCredentials != null && this.iCredentials.getUserName() != null)
88  								? String.valueOf(this.iCredentials.getPassword())
89  								: "null",
90  							"UTF-8"
91  						)
92  					);
93  					String pass = HttpClient.convertToHexString(messageDigest.digest());
94  					return (
95  						"Digest username=" +
96  						(
97  							(this.iCredentials != null && this.iCredentials.getUserName() != null)
98  								? this.iCredentials.getUserName()
99  								: "null"
100 						) +
101 						", response=" +
102 						pass
103 					);
104 				} catch (NoSuchAlgorithmException e) {
105 					// TODO log
106 					e.printStackTrace();
107 				}
108 			}
109 
110 			result.append(this.iScheme);
111 			result.append(" username=\"");
112 			result.append(
113 				(this.iCredentials != null && this.iCredentials.getUserName() != null)
114 					? this.iCredentials.getUserName()
115 					: "null"
116 			);
117 			result.append("\"");
118 			if (this.iRealm != null) {
119 				result.append(", realm=\"");
120 				result.append(this.iRealm);
121 				result.append("\"");
122 			}
123 			if (this.iNonce != null) {
124 				result.append(", nonce=\"");
125 				result.append(this.iNonce);
126 				result.append("\"");
127 			}
128 			result.append(", uri=\"");
129 			result.append(this.iUri);
130 			result.append("\", response=\"");
131 			result.append(this.iResponse);
132 			result.append("\"");
133 			// if (algorithm != null) {
134 			// result.append(", algorithm=");
135 			// result.append(algorithm);
136 			// }
137 			if (this.iCnonce != null) {
138 				result.append(", cnonce=\"");
139 				result.append(this.iCnonce);
140 				result.append("\"");
141 			}
142 			if (this.iOpaque != null) {
143 				result.append(", opaque=\"");
144 				result.append(this.iOpaque);
145 				result.append("\"");
146 			}
147 			if (this.iQop != null) {
148 				result.append(", qop=");
149 				result.append(this.iQop);
150 			}
151 			if (this.iNc > -1) {
152 				result.append(", nc=");
153 				result.append("00000000".substring(_nc.length()));
154 				result.append(_nc);
155 			}
156 		} else if (this.iScheme.equalsIgnoreCase("Basic")) {
157 			result.append("Basic ");
158 
159 			StringBuffer tmp = new StringBuffer();
160 			tmp.append(
161 				(this.iCredentials != null && this.iCredentials.getUserName() != null)
162 					? this.iCredentials.getUserName()
163 					: "null"
164 			);
165 			tmp.append(':');
166 			tmp.append(
167 				(this.iCredentials != null && this.iCredentials.getPassword() != null)
168 					? String.valueOf(this.iCredentials.getPassword())
169 					: "null"
170 			);
171 
172 			result.append(BASE64Encoder.encode(getBytes(tmp.toString(), "UTF-8")));
173 		}
174 		return result.toString();
175 	}
176 
177 	/**
178 	 * Splits a comma-separated string into multiple substrings
179 	 *
180 	 * @param pLine
181 	 *            The comma-separated string
182 	 * @return The array of substrings (excluding commas)
183 	 */
184 	public static String[] split(String pLine) {
185 		Vector<String> elem = new Vector<String>();
186 		int start = 0;
187 		int end;
188 		while ((end = pLine.indexOf(',')) > -1) {
189 			elem.add(pLine.substring(start, end));
190 			start = end + 1;
191 		}
192 		if (end < pLine.length()) elem.add(pLine.substring(start));
193 		String[] result = new String[elem.size()];
194 		elem.toArray(result);
195 		return result;
196 	}
197 
198 	private static byte[] getBytes(String str, String encoding) {
199 		byte[] bytes;
200 		try {
201 			bytes = str.getBytes(encoding);
202 		} catch (UnsupportedEncodingException e) {
203 			bytes = str.getBytes();
204 		}
205 		return bytes;
206 	}
207 
208 	/*
209 	 * (non-Javadoc)
210 	 *
211 	 * @see
212 	 * org.sblim.cimclient.internal.http.AuthorizationInfo#updateAuthenticationInfo
213 	 * (org.sblim.cimclient.internal.http.Challenge, java.net.URI,
214 	 * java.lang.String)
215 	 */
216 	/**
217 	 * @param authenticate
218 	 */
219 	@Override
220 	public void updateAuthenticationInfo(Challenge challenge, String authenticate, URI url, String requestMethod)
221 		throws NoSuchAlgorithmException {
222 		setURI(url.getPath());
223 		HttpHeader params = challenge.getParams();
224 
225 		this.iScheme = challenge.getScheme();
226 
227 		if (!this.iScheme.equalsIgnoreCase("Digest")) {
228 			return;
229 		}
230 
231 		this.iRealm = params.getField("realm");
232 
233 		String algorithm = params.getField("algorithm");
234 		String opaque = params.getField("opaque");
235 		String nonce = params.getField("nonce");
236 		String qop = params.getField("qop");
237 
238 		this.iAlgorithm = (algorithm != null) ? algorithm : this.iAlgorithm;
239 		this.iOpaque = (opaque != null) ? opaque : this.iOpaque;
240 		this.iNonce = (nonce != null) ? nonce : this.iNonce;
241 		this.iQop = (qop != null) ? qop : this.iQop;
242 
243 		MessageDigest messageDigest = null;
244 
245 		messageDigest = MessageDigest.getInstance("MD5");
246 
247 		if (qop != null || ("md5-sess".equalsIgnoreCase(algorithm))) {
248 			long time = System.currentTimeMillis();
249 			byte[] b = new byte[8];
250 
251 			b[0] = (byte) ((time >> 0) & 0xFF);
252 			b[1] = (byte) ((time >> 8) & 0xFF);
253 			b[2] = (byte) ((time >> 16) & 0xFF);
254 			b[3] = (byte) ((time >> 24) & 0xFF);
255 			b[4] = (byte) ((time >> 32) & 0xFF);
256 			b[5] = (byte) ((time >> 40) & 0xFF);
257 			b[6] = (byte) ((time >> 48) & 0xFF);
258 			b[7] = (byte) ((time >> 56) & 0xFF);
259 
260 			messageDigest.reset();
261 			messageDigest.update(b);
262 
263 			this.iCnonce = HttpClient.convertToHexString(messageDigest.digest());
264 		}
265 		if (qop != null) {
266 			String[] list_qop = split(qop);
267 			for (int i = 0; i < list_qop.length; i++) {
268 				if (list_qop[i].equalsIgnoreCase("auth-int")) {
269 					qop = "auth-int"; // this one has higher priority
270 					break;
271 				}
272 				if ((list_qop[i].equalsIgnoreCase("auth"))) qop = "auth";
273 			}
274 			setQop(qop);
275 		}
276 		String nc1 = params.getField("nc");
277 		long challengeNc = 1;
278 		if (nc1 != null) {
279 			try {
280 				challengeNc = Long.parseLong(nc1, 16);
281 			} catch (Exception e) {
282 				// Logger logger = SessionProperties.getGlobalProperties()
283 				// .getLogger();
284 				// if (logger.isLoggable(Level.WARNING)) {
285 				// logger.log(Level.WARNING,
286 				// "exception while parsing challenge NC", e);
287 				// }
288 			}
289 			if (getNc() == challengeNc) {
290 				setNc(++challengeNc);
291 			}
292 		} else {
293 			setNc(challengeNc = 1);
294 		}
295 
296 		messageDigest.reset();
297 		PasswordAuthentication credentials1 = getCredentials();
298 		this.iA1 = credentials1.getUserName() + ":" + getRealm() + ":" + String.valueOf(credentials1.getPassword());
299 		// System.out.println("Base:"+A1);
300 		messageDigest.update(getBytes(this.iA1, "UTF-8"));
301 
302 		if ("md5-sess".equalsIgnoreCase(algorithm)) {
303 			messageDigest.update(getBytes((":" + getNonce() + ":" + getCnonce()), "UTF-8"));
304 		}
305 
306 		byte[] digest1 = messageDigest.digest();
307 		String sessionKey = HttpClient.convertToHexString(digest1);
308 
309 		// System.out.println("A1:"+sessionKey);
310 		String method = requestMethod;
311 
312 		// if (con instanceof HttpURLConnection)
313 		// method = ((HttpURLConnection) con).getRequestMethod();
314 
315 		String A2 = method + ":" + getURI();
316 
317 		if ("auth-int".equalsIgnoreCase(qop)) {
318 			messageDigest.reset();
319 			// messageDigest.update(readFully(con)); //TODO
320 			messageDigest.update(new byte[] {}); // TODO this must be the
321 			// entity body, not the
322 			// message body
323 			A2 = A2 + ":" + HttpClient.convertToHexString(messageDigest.digest());
324 		}
325 		messageDigest.reset();
326 		messageDigest.update(getBytes(A2, "UTF-8"));
327 		A2 = HttpClient.convertToHexString(messageDigest.digest());
328 
329 		messageDigest.reset();
330 		if (qop == null) {
331 			messageDigest.update(getBytes((sessionKey + ":" + nonce + ":" + A2), "UTF-8"));
332 		} else {
333 			String _nc = Long.toHexString(challengeNc);
334 			messageDigest.update(
335 				getBytes(
336 					(
337 						sessionKey +
338 						":" +
339 						nonce +
340 						":" +
341 						"00000000".substring(_nc.length()) +
342 						_nc +
343 						":" +
344 						getCnonce() +
345 						":" +
346 						qop +
347 						":" +
348 						A2
349 					),
350 					"UTF-8"
351 				)
352 			);
353 		}
354 		this.iResponse = HttpClient.convertToHexString(messageDigest.digest());
355 		// TODO handle digest-required header
356 	}
357 
358 	/*
359 	 * (non-Javadoc)
360 	 *
361 	 * @see
362 	 * org.sblim.cimclient.internal.http.AuthorizationInfo#getHeaderFieldName()
363 	 */
364 	@Override
365 	public String getHeaderFieldName() {
366 		return "Authorization";
367 	}
368 
369 	/*
370 	 * (non-Javadoc)
371 	 *
372 	 * @see
373 	 * org.sblim.cimclient.internal.http.AuthorizationInfo#isSentOnFirstRequest
374 	 * ()
375 	 */
376 	@Override
377 	public boolean isSentOnFirstRequest() {
378 		return false;
379 	}
380 
381 	@Override
382 	public boolean isKeptAlive() {
383 		return true;
384 	}
385 }