author | hb |
Tue, 02 Jan 2018 15:03:52 +0530 | |
branch | jmx-rest-api |
changeset 56005 | 90cff2ac77b8 |
parent 47216 | 71c04702a3d5 |
child 59024 | b046ba510bbc |
permissions | -rw-r--r-- |
2 | 1 |
/* |
23010
6dadb192ad81
8029235: Update copyright year to match last edit in jdk8 jdk repository for 2013
lana
parents:
17424
diff
changeset
|
2 |
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. |
2 | 3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
|
5 |
* This code is free software; you can redistribute it and/or modify it |
|
6 |
* under the terms of the GNU General Public License version 2 only, as |
|
5506 | 7 |
* published by the Free Software Foundation. Oracle designates this |
2 | 8 |
* particular file as subject to the "Classpath" exception as provided |
5506 | 9 |
* by Oracle in the LICENSE file that accompanied this code. |
2 | 10 |
* |
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
5506 | 21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
22 |
* or visit www.oracle.com if you need additional information or have any |
|
23 |
* questions. |
|
2 | 24 |
*/ |
25 |
||
26 |
package com.sun.security.sasl.digest; |
|
27 |
||
28 |
import java.security.NoSuchAlgorithmException; |
|
29 |
import java.io.ByteArrayOutputStream; |
|
30 |
import java.io.IOException; |
|
31 |
import java.io.UnsupportedEncodingException; |
|
32 |
import java.util.StringTokenizer; |
|
33 |
import java.util.ArrayList; |
|
34 |
import java.util.List; |
|
35 |
import java.util.Map; |
|
36 |
import java.util.Arrays; |
|
37 |
||
38 |
import java.util.logging.Level; |
|
39 |
||
40 |
import javax.security.sasl.*; |
|
41 |
import javax.security.auth.callback.CallbackHandler; |
|
42 |
import javax.security.auth.callback.PasswordCallback; |
|
43 |
import javax.security.auth.callback.NameCallback; |
|
44 |
import javax.security.auth.callback.Callback; |
|
45 |
import javax.security.auth.callback.UnsupportedCallbackException; |
|
46 |
||
47 |
/** |
|
48 |
* An implementation of the DIGEST-MD5 |
|
49 |
* (<a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>) SASL |
|
50 |
* (<a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>) mechanism. |
|
51 |
* |
|
52 |
* The DIGEST-MD5 SASL mechanism specifies two modes of authentication. |
|
53 |
* - Initial Authentication |
|
54 |
* - Subsequent Authentication - optional, (currently unsupported) |
|
55 |
* |
|
56 |
* Required callbacks: |
|
57 |
* - RealmChoiceCallback |
|
58 |
* shows user list of realms server has offered; handler must choose one |
|
59 |
* from list |
|
60 |
* - RealmCallback |
|
61 |
* shows user the only realm server has offered or none; handler must |
|
62 |
* enter realm to use |
|
63 |
* - NameCallback |
|
64 |
* handler must enter username to use for authentication |
|
65 |
* - PasswordCallback |
|
66 |
* handler must enter password for username to use for authentication |
|
67 |
* |
|
68 |
* Environment properties that affect behavior of implementation: |
|
69 |
* |
|
70 |
* javax.security.sasl.qop |
|
71 |
* quality of protection; list of auth, auth-int, auth-conf; default is "auth" |
|
72 |
* javax.security.sasl.strength |
|
73 |
* auth-conf strength; list of high, medium, low; default is highest |
|
74 |
* available on platform ["high,medium,low"]. |
|
75 |
* high means des3 or rc4 (128); medium des or rc4-56; low is rc4-40; |
|
76 |
* choice of cipher depends on its availablility on platform |
|
77 |
* javax.security.sasl.maxbuf |
|
78 |
* max receive buffer size; default is 65536 |
|
79 |
* javax.security.sasl.sendmaxbuffer |
|
80 |
* max send buffer size; default is 65536; (min with server max recv size) |
|
81 |
* |
|
82 |
* com.sun.security.sasl.digest.cipher |
|
83 |
* name a specific cipher to use; setting must be compatible with the |
|
84 |
* setting of the javax.security.sasl.strength property. |
|
85 |
* |
|
86 |
* @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a> |
|
87 |
* - Simple Authentication and Security Layer (SASL) |
|
88 |
* @see <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a> |
|
89 |
* - Using Digest Authentication as a SASL Mechanism |
|
90 |
* @see <a href="http://java.sun.com/products/jce">Java(TM) |
|
91 |
* Cryptography Extension 1.2.1 (JCE)</a> |
|
92 |
* @see <a href="http://java.sun.com/products/jaas">Java(TM) |
|
93 |
* Authentication and Authorization Service (JAAS)</a> |
|
94 |
* |
|
95 |
* @author Jonathan Bruce |
|
96 |
* @author Rosanna Lee |
|
97 |
*/ |
|
98 |
final class DigestMD5Client extends DigestMD5Base implements SaslClient { |
|
99 |
private static final String MY_CLASS_NAME = DigestMD5Client.class.getName(); |
|
100 |
||
101 |
// Property for specifying cipher explicitly |
|
102 |
private static final String CIPHER_PROPERTY = |
|
103 |
"com.sun.security.sasl.digest.cipher"; |
|
104 |
||
105 |
/* Directives encountered in challenges sent by the server. */ |
|
106 |
private static final String[] DIRECTIVE_KEY = { |
|
107 |
"realm", // >= 0 times |
|
108 |
"qop", // atmost once; default is "auth" |
|
109 |
"algorithm", // exactly once |
|
110 |
"nonce", // exactly once |
|
111 |
"maxbuf", // atmost once; default is 65536 |
|
112 |
"charset", // atmost once; default is ISO 8859-1 |
|
113 |
"cipher", // exactly once if qop is "auth-conf" |
|
114 |
"rspauth", // exactly once in 2nd challenge |
|
115 |
"stale", // atmost once for in subsequent auth (not supported) |
|
116 |
}; |
|
117 |
||
118 |
/* Indices into DIRECTIVE_KEY */ |
|
119 |
private static final int REALM = 0; |
|
120 |
private static final int QOP = 1; |
|
121 |
private static final int ALGORITHM = 2; |
|
122 |
private static final int NONCE = 3; |
|
123 |
private static final int MAXBUF = 4; |
|
124 |
private static final int CHARSET = 5; |
|
125 |
private static final int CIPHER = 6; |
|
126 |
private static final int RESPONSE_AUTH = 7; |
|
127 |
private static final int STALE = 8; |
|
128 |
||
129 |
private int nonceCount; // number of times nonce has been used/seen |
|
130 |
||
131 |
/* User-supplied/generated information */ |
|
132 |
private String specifiedCipher; // cipher explicitly requested by user |
|
133 |
private byte[] cnonce; // client generated nonce |
|
134 |
private String username; |
|
135 |
private char[] passwd; |
|
136 |
private byte[] authzidBytes; // byte repr of authzid |
|
137 |
||
138 |
/** |
|
139 |
* Constructor for DIGEST-MD5 mechanism. |
|
140 |
* |
|
141 |
* @param authzid A non-null String representing the principal |
|
142 |
* for which authorization is being granted.. |
|
143 |
* @param digestURI A non-null String representing detailing the |
|
144 |
* combined protocol and host being used for authentication. |
|
145 |
* @param props The possibly null properties to be used by the SASL |
|
146 |
* mechanism to configure the authentication exchange. |
|
147 |
* @param cbh The non-null CallbackHanlder object for callbacks |
|
148 |
* @throws SaslException if no authentication ID or password is supplied |
|
149 |
*/ |
|
150 |
DigestMD5Client(String authzid, String protocol, String serverName, |
|
10336
0bb1999251f8
7064075: Security libraries don't build with javac -Xlint:all,-deprecation -Werror
jjg
parents:
5506
diff
changeset
|
151 |
Map<String, ?> props, CallbackHandler cbh) throws SaslException { |
2 | 152 |
|
153 |
super(props, MY_CLASS_NAME, 2, protocol + "/" + serverName, cbh); |
|
154 |
||
155 |
// authzID can only be encoded in UTF8 - RFC 2222 |
|
156 |
if (authzid != null) { |
|
157 |
this.authzid = authzid; |
|
158 |
try { |
|
159 |
authzidBytes = authzid.getBytes("UTF8"); |
|
160 |
||
161 |
} catch (UnsupportedEncodingException e) { |
|
162 |
throw new SaslException( |
|
163 |
"DIGEST-MD5: Error encoding authzid value into UTF-8", e); |
|
164 |
} |
|
165 |
} |
|
166 |
||
167 |
if (props != null) { |
|
168 |
specifiedCipher = (String)props.get(CIPHER_PROPERTY); |
|
169 |
||
170 |
logger.log(Level.FINE, "DIGEST60:Explicitly specified cipher: {0}", |
|
171 |
specifiedCipher); |
|
172 |
} |
|
173 |
} |
|
174 |
||
175 |
/** |
|
176 |
* DIGEST-MD5 has no initial response |
|
177 |
* |
|
178 |
* @return false |
|
179 |
*/ |
|
180 |
public boolean hasInitialResponse() { |
|
181 |
return false; |
|
182 |
} |
|
183 |
||
184 |
/** |
|
185 |
* Process the challenge data. |
|
186 |
* |
|
187 |
* The server sends a digest-challenge which the client must reply to |
|
188 |
* in a digest-response. When the authentication is complete, the |
|
189 |
* completed field is set to true. |
|
190 |
* |
|
191 |
* @param challengeData A non-null byte array containing the challenge |
|
192 |
* data from the server. |
|
193 |
* @return A possibly null byte array containing the response to |
|
194 |
* be sent to the server. |
|
195 |
* |
|
196 |
* @throws SaslException If the platform does not have MD5 digest support |
|
197 |
* or if the server sends an invalid challenge. |
|
198 |
*/ |
|
199 |
public byte[] evaluateChallenge(byte[] challengeData) throws SaslException { |
|
200 |
||
201 |
if (challengeData.length > MAX_CHALLENGE_LENGTH) { |
|
202 |
throw new SaslException( |
|
203 |
"DIGEST-MD5: Invalid digest-challenge length. Got: " + |
|
204 |
challengeData.length + " Expected < " + MAX_CHALLENGE_LENGTH); |
|
205 |
} |
|
206 |
||
207 |
/* Extract and process digest-challenge */ |
|
208 |
byte[][] challengeVal; |
|
209 |
||
210 |
switch (step) { |
|
211 |
case 2: |
|
212 |
/* Process server's first challenge (from Step 1) */ |
|
213 |
/* Get realm, qop, maxbuf, charset, algorithm, cipher, nonce |
|
214 |
directives */ |
|
215 |
List<byte[]> realmChoices = new ArrayList<byte[]>(3); |
|
216 |
challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY, |
|
217 |
realmChoices, REALM); |
|
218 |
||
219 |
try { |
|
220 |
processChallenge(challengeVal, realmChoices); |
|
221 |
checkQopSupport(challengeVal[QOP], challengeVal[CIPHER]); |
|
222 |
++step; |
|
223 |
return generateClientResponse(challengeVal[CHARSET]); |
|
224 |
} catch (SaslException e) { |
|
225 |
step = 0; |
|
226 |
clearPassword(); |
|
227 |
throw e; // rethrow |
|
228 |
} catch (IOException e) { |
|
229 |
step = 0; |
|
230 |
clearPassword(); |
|
231 |
throw new SaslException("DIGEST-MD5: Error generating " + |
|
232 |
"digest response-value", e); |
|
233 |
} |
|
234 |
||
235 |
case 3: |
|
236 |
try { |
|
237 |
/* Process server's step 3 (server response to digest response) */ |
|
238 |
/* Get rspauth directive */ |
|
239 |
challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY, |
|
240 |
null, REALM); |
|
241 |
validateResponseValue(challengeVal[RESPONSE_AUTH]); |
|
242 |
||
243 |
||
244 |
/* Initialize SecurityCtx implementation */ |
|
245 |
if (integrity && privacy) { |
|
246 |
secCtx = new DigestPrivacy(true /* client */); |
|
247 |
} else if (integrity) { |
|
248 |
secCtx = new DigestIntegrity(true /* client */); |
|
249 |
} |
|
250 |
||
251 |
return null; // Mechanism has completed. |
|
252 |
} finally { |
|
253 |
clearPassword(); |
|
254 |
step = 0; // Set to invalid state |
|
255 |
completed = true; |
|
256 |
} |
|
257 |
||
258 |
default: |
|
259 |
// No other possible state |
|
260 |
throw new SaslException("DIGEST-MD5: Client at illegal state"); |
|
261 |
} |
|
262 |
} |
|
263 |
||
264 |
||
265 |
/** |
|
266 |
* Record information from the challengeVal array into variables/fields. |
|
267 |
* Check directive values that are multi-valued and ensure that mandatory |
|
268 |
* directives not missing from the digest-challenge. |
|
269 |
* |
|
270 |
* @throws SaslException if a sasl is a the mechanism cannot |
|
271 |
* correcly handle a callbacks or if a violation in the |
|
272 |
* digest challenge format is detected. |
|
273 |
*/ |
|
274 |
private void processChallenge(byte[][] challengeVal, List<byte[]> realmChoices) |
|
275 |
throws SaslException, UnsupportedEncodingException { |
|
276 |
||
277 |
/* CHARSET: optional atmost once */ |
|
278 |
if (challengeVal[CHARSET] != null) { |
|
279 |
if (!"utf-8".equals(new String(challengeVal[CHARSET], encoding))) { |
|
280 |
throw new SaslException("DIGEST-MD5: digest-challenge format " + |
|
281 |
"violation. Unrecognised charset value: " + |
|
282 |
new String(challengeVal[CHARSET])); |
|
283 |
} else { |
|
284 |
encoding = "UTF8"; |
|
285 |
useUTF8 = true; |
|
286 |
} |
|
287 |
} |
|
288 |
||
289 |
/* ALGORITHM: required exactly once */ |
|
290 |
if (challengeVal[ALGORITHM] == null) { |
|
291 |
throw new SaslException("DIGEST-MD5: Digest-challenge format " + |
|
292 |
"violation: algorithm directive missing"); |
|
293 |
} else if (!"md5-sess".equals(new String(challengeVal[ALGORITHM], encoding))) { |
|
294 |
throw new SaslException("DIGEST-MD5: Digest-challenge format " + |
|
295 |
"violation. Invalid value for 'algorithm' directive: " + |
|
296 |
challengeVal[ALGORITHM]); |
|
297 |
} |
|
298 |
||
299 |
/* NONCE: required exactly once */ |
|
300 |
if (challengeVal[NONCE] == null) { |
|
301 |
throw new SaslException("DIGEST-MD5: Digest-challenge format " + |
|
302 |
"violation: nonce directive missing"); |
|
303 |
} else { |
|
304 |
nonce = challengeVal[NONCE]; |
|
305 |
} |
|
306 |
||
307 |
try { |
|
308 |
/* REALM: optional, if multiple, stored in realmChoices */ |
|
309 |
String[] realmTokens = null; |
|
310 |
||
311 |
if (challengeVal[REALM] != null) { |
|
312 |
if (realmChoices == null || realmChoices.size() <= 1) { |
|
313 |
// Only one realm specified |
|
314 |
negotiatedRealm = new String(challengeVal[REALM], encoding); |
|
315 |
} else { |
|
316 |
realmTokens = new String[realmChoices.size()]; |
|
317 |
for (int i = 0; i < realmTokens.length; i++) { |
|
318 |
realmTokens[i] = |
|
319 |
new String(realmChoices.get(i), encoding); |
|
320 |
} |
|
321 |
} |
|
322 |
} |
|
323 |
||
324 |
NameCallback ncb = authzid == null ? |
|
325 |
new NameCallback("DIGEST-MD5 authentication ID: ") : |
|
326 |
new NameCallback("DIGEST-MD5 authentication ID: ", authzid); |
|
327 |
PasswordCallback pcb = |
|
328 |
new PasswordCallback("DIGEST-MD5 password: ", false); |
|
329 |
||
330 |
if (realmTokens == null) { |
|
331 |
// Server specified <= 1 realm |
|
332 |
// If 0, RFC 2831: the client SHOULD solicit a realm from the user. |
|
333 |
RealmCallback tcb = |
|
334 |
(negotiatedRealm == null? new RealmCallback("DIGEST-MD5 realm: ") : |
|
335 |
new RealmCallback("DIGEST-MD5 realm: ", negotiatedRealm)); |
|
336 |
||
337 |
cbh.handle(new Callback[] {tcb, ncb, pcb}); |
|
338 |
||
339 |
/* Acquire realm from RealmCallback */ |
|
340 |
negotiatedRealm = tcb.getText(); |
|
341 |
if (negotiatedRealm == null) { |
|
342 |
negotiatedRealm = ""; |
|
343 |
} |
|
344 |
} else { |
|
345 |
RealmChoiceCallback ccb = new RealmChoiceCallback( |
|
346 |
"DIGEST-MD5 realm: ", |
|
347 |
realmTokens, |
|
348 |
0, false); |
|
349 |
cbh.handle(new Callback[] {ccb, ncb, pcb}); |
|
350 |
||
17424
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
351 |
// Acquire realm from RealmChoiceCallback |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
352 |
int[] selected = ccb.getSelectedIndexes(); |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
353 |
if (selected == null |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
354 |
|| selected[0] < 0 |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
355 |
|| selected[0] >= realmTokens.length) { |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
356 |
throw new SaslException("DIGEST-MD5: Invalid realm chosen"); |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
357 |
} |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
358 |
negotiatedRealm = realmTokens[selected[0]]; |
2 | 359 |
} |
360 |
||
361 |
passwd = pcb.getPassword(); |
|
362 |
pcb.clearPassword(); |
|
363 |
username = ncb.getName(); |
|
364 |
||
17424
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
365 |
} catch (SaslException se) { |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
366 |
throw se; |
cb03ce64ed07
8013855: DigestMD5Client has not checked RealmChoiceCallback value
weijun
parents:
10336
diff
changeset
|
367 |
|
2 | 368 |
} catch (UnsupportedCallbackException e) { |
369 |
throw new SaslException("DIGEST-MD5: Cannot perform callback to " + |
|
370 |
"acquire realm, authentication ID or password", e); |
|
371 |
||
372 |
} catch (IOException e) { |
|
373 |
throw new SaslException( |
|
374 |
"DIGEST-MD5: Error acquiring realm, authentication ID or password", e); |
|
375 |
} |
|
376 |
||
377 |
if (username == null || passwd == null) { |
|
378 |
throw new SaslException( |
|
379 |
"DIGEST-MD5: authentication ID and password must be specified"); |
|
380 |
} |
|
381 |
||
382 |
/* MAXBUF: optional atmost once */ |
|
383 |
int srvMaxBufSize = |
|
384 |
(challengeVal[MAXBUF] == null) ? DEFAULT_MAXBUF |
|
385 |
: Integer.parseInt(new String(challengeVal[MAXBUF], encoding)); |
|
386 |
sendMaxBufSize = |
|
387 |
(sendMaxBufSize == 0) ? srvMaxBufSize |
|
388 |
: Math.min(sendMaxBufSize, srvMaxBufSize); |
|
389 |
} |
|
390 |
||
391 |
/** |
|
392 |
* Parses the 'qop' directive. If 'auth-conf' is specified by |
|
393 |
* the client and offered as a QOP option by the server, then a check |
|
394 |
* is client-side supported ciphers is performed. |
|
395 |
* |
|
396 |
* @throws IOException |
|
397 |
*/ |
|
398 |
private void checkQopSupport(byte[] qopInChallenge, byte[] ciphersInChallenge) |
|
399 |
throws IOException { |
|
400 |
||
401 |
/* QOP: optional; if multiple, merged earlier */ |
|
402 |
String qopOptions; |
|
403 |
||
404 |
if (qopInChallenge == null) { |
|
405 |
qopOptions = "auth"; |
|
406 |
} else { |
|
407 |
qopOptions = new String(qopInChallenge, encoding); |
|
408 |
} |
|
409 |
||
410 |
// process |
|
411 |
String[] serverQopTokens = new String[3]; |
|
412 |
byte[] serverQop = parseQop(qopOptions, serverQopTokens, |
|
413 |
true /* ignore unrecognized tokens */); |
|
414 |
byte serverAllQop = combineMasks(serverQop); |
|
415 |
||
416 |
switch (findPreferredMask(serverAllQop, qop)) { |
|
417 |
case 0: |
|
418 |
throw new SaslException("DIGEST-MD5: No common protection " + |
|
419 |
"layer between client and server"); |
|
420 |
||
421 |
case NO_PROTECTION: |
|
422 |
negotiatedQop = "auth"; |
|
423 |
// buffer sizes not applicable |
|
424 |
break; |
|
425 |
||
426 |
case INTEGRITY_ONLY_PROTECTION: |
|
427 |
negotiatedQop = "auth-int"; |
|
428 |
integrity = true; |
|
429 |
rawSendSize = sendMaxBufSize - 16; |
|
430 |
break; |
|
431 |
||
432 |
case PRIVACY_PROTECTION: |
|
433 |
negotiatedQop = "auth-conf"; |
|
434 |
privacy = integrity = true; |
|
435 |
rawSendSize = sendMaxBufSize - 26; |
|
436 |
checkStrengthSupport(ciphersInChallenge); |
|
437 |
break; |
|
438 |
} |
|
439 |
||
440 |
if (logger.isLoggable(Level.FINE)) { |
|
441 |
logger.log(Level.FINE, "DIGEST61:Raw send size: {0}", |
|
25522
10d789df41bb
8049892: Replace uses of 'new Integer()' with appropriate alternative across core classes
prr
parents:
23010
diff
changeset
|
442 |
rawSendSize); |
2 | 443 |
} |
444 |
} |
|
445 |
||
446 |
/** |
|
447 |
* Processes the 'cipher' digest-challenge directive. This allows the |
|
448 |
* mechanism to check for client-side support against the list of |
|
449 |
* supported ciphers send by the server. If no match is found, |
|
450 |
* the mechanism aborts. |
|
451 |
* |
|
452 |
* @throws SaslException If an error is encountered in processing |
|
453 |
* the cipher digest-challenge directive or if no client-side |
|
454 |
* support is found. |
|
455 |
*/ |
|
456 |
private void checkStrengthSupport(byte[] ciphersInChallenge) |
|
457 |
throws IOException { |
|
458 |
||
459 |
/* CIPHER: required exactly once if qop=auth-conf */ |
|
460 |
if (ciphersInChallenge == null) { |
|
461 |
throw new SaslException("DIGEST-MD5: server did not specify " + |
|
462 |
"cipher to use for 'auth-conf'"); |
|
463 |
} |
|
464 |
||
465 |
// First determine ciphers that server supports |
|
466 |
String cipherOptions = new String(ciphersInChallenge, encoding); |
|
467 |
StringTokenizer parser = new StringTokenizer(cipherOptions, ", \t\n"); |
|
468 |
int tokenCount = parser.countTokens(); |
|
469 |
String token = null; |
|
470 |
byte[] serverCiphers = { UNSET, |
|
471 |
UNSET, |
|
472 |
UNSET, |
|
473 |
UNSET, |
|
474 |
UNSET }; |
|
475 |
String[] serverCipherStrs = new String[serverCiphers.length]; |
|
476 |
||
477 |
// Parse ciphers in challenge; mark each that server supports |
|
478 |
for (int i = 0; i < tokenCount; i++) { |
|
479 |
token = parser.nextToken(); |
|
480 |
for (int j = 0; j < CIPHER_TOKENS.length; j++) { |
|
481 |
if (token.equals(CIPHER_TOKENS[j])) { |
|
482 |
serverCiphers[j] |= CIPHER_MASKS[j]; |
|
483 |
serverCipherStrs[j] = token; // keep for replay to server |
|
484 |
logger.log(Level.FINE, "DIGEST62:Server supports {0}", token); |
|
485 |
} |
|
486 |
} |
|
487 |
} |
|
488 |
||
489 |
// Determine which ciphers are available on client |
|
490 |
byte[] clntCiphers = getPlatformCiphers(); |
|
491 |
||
492 |
// Take intersection of server and client supported ciphers |
|
493 |
byte inter = 0; |
|
494 |
for (int i = 0; i < serverCiphers.length; i++) { |
|
495 |
serverCiphers[i] &= clntCiphers[i]; |
|
496 |
inter |= serverCiphers[i]; |
|
497 |
} |
|
498 |
||
499 |
if (inter == UNSET) { |
|
500 |
throw new SaslException( |
|
501 |
"DIGEST-MD5: Client supports none of these cipher suites: " + |
|
502 |
cipherOptions); |
|
503 |
} |
|
504 |
||
505 |
// now have a clear picture of user / client; client / server |
|
506 |
// cipher options. Leverage strength array against what is |
|
507 |
// supported to choose a cipher. |
|
508 |
negotiatedCipher = findCipherAndStrength(serverCiphers, serverCipherStrs); |
|
509 |
||
510 |
if (negotiatedCipher == null) { |
|
511 |
throw new SaslException("DIGEST-MD5: Unable to negotiate " + |
|
512 |
"a strength level for 'auth-conf'"); |
|
513 |
} |
|
514 |
logger.log(Level.FINE, "DIGEST63:Cipher suite: {0}", negotiatedCipher); |
|
515 |
} |
|
516 |
||
517 |
/** |
|
518 |
* Steps through the ordered 'strength' array, and compares it with |
|
519 |
* the 'supportedCiphers' array. The cipher returned represents |
|
520 |
* the best possible cipher based on the strength preference and the |
|
521 |
* available ciphers on both the server and client environments. |
|
522 |
* |
|
523 |
* @param tokens The array of cipher tokens sent by server |
|
524 |
* @return The agreed cipher. |
|
525 |
*/ |
|
526 |
private String findCipherAndStrength(byte[] supportedCiphers, |
|
527 |
String[] tokens) { |
|
528 |
byte s; |
|
529 |
for (int i = 0; i < strength.length; i++) { |
|
530 |
if ((s=strength[i]) != 0) { |
|
531 |
for (int j = 0; j < supportedCiphers.length; j++) { |
|
532 |
||
533 |
// If user explicitly requested cipher, then it |
|
534 |
// must be the one we choose |
|
535 |
||
536 |
if (s == supportedCiphers[j] && |
|
537 |
(specifiedCipher == null || |
|
538 |
specifiedCipher.equals(tokens[j]))) { |
|
539 |
switch (s) { |
|
540 |
case HIGH_STRENGTH: |
|
541 |
negotiatedStrength = "high"; |
|
542 |
break; |
|
543 |
case MEDIUM_STRENGTH: |
|
544 |
negotiatedStrength = "medium"; |
|
545 |
break; |
|
546 |
case LOW_STRENGTH: |
|
547 |
negotiatedStrength = "low"; |
|
548 |
break; |
|
549 |
} |
|
550 |
||
551 |
return tokens[j]; |
|
552 |
} |
|
553 |
} |
|
554 |
} |
|
555 |
} |
|
556 |
||
557 |
return null; // none found |
|
558 |
} |
|
559 |
||
560 |
/** |
|
561 |
* Returns digest-response suitable for an initial authentication. |
|
562 |
* |
|
563 |
* The following are qdstr-val (quoted string values) as per RFC 2831, |
|
564 |
* which means that any embedded quotes must be escaped. |
|
565 |
* realm-value |
|
566 |
* nonce-value |
|
567 |
* username-value |
|
568 |
* cnonce-value |
|
569 |
* authzid-value |
|
32275
17eeb583a331
8133802: replace some <tt> tags (obsolete in html5) in security-libs docs
avstepan
parents:
25859
diff
changeset
|
570 |
* @return {@code digest-response} in a byte array |
2 | 571 |
* @throws SaslException if there is an error generating the |
572 |
* response value or the cnonce value. |
|
573 |
*/ |
|
574 |
private byte[] generateClientResponse(byte[] charset) throws IOException { |
|
575 |
||
576 |
ByteArrayOutputStream digestResp = new ByteArrayOutputStream(); |
|
577 |
||
578 |
if (useUTF8) { |
|
579 |
digestResp.write("charset=".getBytes(encoding)); |
|
580 |
digestResp.write(charset); |
|
581 |
digestResp.write(','); |
|
582 |
} |
|
583 |
||
584 |
digestResp.write(("username=\"" + |
|
585 |
quotedStringValue(username) + "\",").getBytes(encoding)); |
|
586 |
||
587 |
if (negotiatedRealm.length() > 0) { |
|
588 |
digestResp.write(("realm=\"" + |
|
589 |
quotedStringValue(negotiatedRealm) + "\",").getBytes(encoding)); |
|
590 |
} |
|
591 |
||
592 |
digestResp.write("nonce=\"".getBytes(encoding)); |
|
593 |
writeQuotedStringValue(digestResp, nonce); |
|
594 |
digestResp.write('"'); |
|
595 |
digestResp.write(','); |
|
596 |
||
597 |
nonceCount = getNonceCount(nonce); |
|
598 |
digestResp.write(("nc=" + |
|
599 |
nonceCountToHex(nonceCount) + ",").getBytes(encoding)); |
|
600 |
||
601 |
cnonce = generateNonce(); |
|
602 |
digestResp.write("cnonce=\"".getBytes(encoding)); |
|
603 |
writeQuotedStringValue(digestResp, cnonce); |
|
604 |
digestResp.write("\",".getBytes(encoding)); |
|
605 |
digestResp.write(("digest-uri=\"" + digestUri + "\",").getBytes(encoding)); |
|
606 |
||
607 |
digestResp.write("maxbuf=".getBytes(encoding)); |
|
608 |
digestResp.write(String.valueOf(recvMaxBufSize).getBytes(encoding)); |
|
609 |
digestResp.write(','); |
|
610 |
||
611 |
try { |
|
612 |
digestResp.write("response=".getBytes(encoding)); |
|
613 |
digestResp.write(generateResponseValue("AUTHENTICATE", |
|
614 |
digestUri, negotiatedQop, username, |
|
615 |
negotiatedRealm, passwd, nonce, cnonce, |
|
616 |
nonceCount, authzidBytes)); |
|
617 |
digestResp.write(','); |
|
618 |
} catch (Exception e) { |
|
619 |
throw new SaslException( |
|
620 |
"DIGEST-MD5: Error generating response value", e); |
|
621 |
} |
|
622 |
||
623 |
digestResp.write(("qop=" + negotiatedQop).getBytes(encoding)); |
|
624 |
||
625 |
if (negotiatedCipher != null) { |
|
626 |
digestResp.write((",cipher=\"" + negotiatedCipher + "\"").getBytes(encoding)); |
|
627 |
} |
|
628 |
||
629 |
if (authzidBytes != null) { |
|
630 |
digestResp.write(",authzid=\"".getBytes(encoding)); |
|
631 |
writeQuotedStringValue(digestResp, authzidBytes); |
|
632 |
digestResp.write("\"".getBytes(encoding)); |
|
633 |
} |
|
634 |
||
635 |
if (digestResp.size() > MAX_RESPONSE_LENGTH) { |
|
636 |
throw new SaslException ("DIGEST-MD5: digest-response size too " + |
|
637 |
"large. Length: " + digestResp.size()); |
|
638 |
} |
|
639 |
return digestResp.toByteArray(); |
|
640 |
} |
|
641 |
||
642 |
||
643 |
/** |
|
644 |
* From RFC 2831, Section 2.1.3: Step Three |
|
645 |
* [Server] sends a message formatted as follows: |
|
646 |
* response-auth = "rspauth" "=" response-value |
|
647 |
* where response-value is calculated as above, using the values sent in |
|
648 |
* step two, except that if qop is "auth", then A2 is |
|
649 |
* |
|
650 |
* A2 = { ":", digest-uri-value } |
|
651 |
* |
|
652 |
* And if qop is "auth-int" or "auth-conf" then A2 is |
|
653 |
* |
|
654 |
* A2 = { ":", digest-uri-value, ":00000000000000000000000000000000" } |
|
655 |
*/ |
|
656 |
private void validateResponseValue(byte[] fromServer) throws SaslException { |
|
657 |
if (fromServer == null) { |
|
658 |
throw new SaslException("DIGEST-MD5: Authenication failed. " + |
|
659 |
"Expecting 'rspauth' authentication success message"); |
|
660 |
} |
|
661 |
||
662 |
try { |
|
663 |
byte[] expected = generateResponseValue("", |
|
664 |
digestUri, negotiatedQop, username, negotiatedRealm, |
|
665 |
passwd, nonce, cnonce, nonceCount, authzidBytes); |
|
666 |
if (!Arrays.equals(expected, fromServer)) { |
|
667 |
/* Server's rspauth value does not match */ |
|
668 |
throw new SaslException( |
|
669 |
"Server's rspauth value does not match what client expects"); |
|
670 |
} |
|
671 |
} catch (NoSuchAlgorithmException e) { |
|
672 |
throw new SaslException( |
|
673 |
"Problem generating response value for verification", e); |
|
674 |
} catch (IOException e) { |
|
675 |
throw new SaslException( |
|
676 |
"Problem generating response value for verification", e); |
|
677 |
} |
|
678 |
} |
|
679 |
||
680 |
/** |
|
681 |
* Returns the number of requests (including current request) |
|
682 |
* that the client has sent in response to nonceValue. |
|
683 |
* This is 1 the first time nonceValue is seen. |
|
684 |
* |
|
685 |
* We don't cache nonce values seen, and we don't support subsequent |
|
686 |
* authentication, so the value is always 1. |
|
687 |
*/ |
|
688 |
private static int getNonceCount(byte[] nonceValue) { |
|
689 |
return 1; |
|
690 |
} |
|
691 |
||
692 |
private void clearPassword() { |
|
693 |
if (passwd != null) { |
|
694 |
for (int i = 0; i < passwd.length; i++) { |
|
695 |
passwd[i] = 0; |
|
696 |
} |
|
697 |
passwd = null; |
|
698 |
} |
|
699 |
} |
|
700 |
} |