mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngineImpl.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /*
michael@0 2 * ====================================================================
michael@0 3 *
michael@0 4 * Licensed to the Apache Software Foundation (ASF) under one or more
michael@0 5 * contributor license agreements. See the NOTICE file distributed with
michael@0 6 * this work for additional information regarding copyright ownership.
michael@0 7 * The ASF licenses this file to You under the Apache License, Version 2.0
michael@0 8 * (the "License"); you may not use this file except in compliance with
michael@0 9 * the License. You may obtain a copy of the License at
michael@0 10 *
michael@0 11 * http://www.apache.org/licenses/LICENSE-2.0
michael@0 12 *
michael@0 13 * Unless required by applicable law or agreed to in writing, software
michael@0 14 * distributed under the License is distributed on an "AS IS" BASIS,
michael@0 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0 16 * See the License for the specific language governing permissions and
michael@0 17 * limitations under the License.
michael@0 18 * ====================================================================
michael@0 19 *
michael@0 20 * This software consists of voluntary contributions made by many
michael@0 21 * individuals on behalf of the Apache Software Foundation. For more
michael@0 22 * information on the Apache Software Foundation, please see
michael@0 23 * <http://www.apache.org/>.
michael@0 24 *
michael@0 25 */
michael@0 26
michael@0 27 package ch.boye.httpclientandroidlib.impl.auth;
michael@0 28
michael@0 29 import java.security.Key;
michael@0 30 import java.security.MessageDigest;
michael@0 31 import java.util.Arrays;
michael@0 32
michael@0 33 import javax.crypto.Cipher;
michael@0 34 import javax.crypto.spec.SecretKeySpec;
michael@0 35
michael@0 36 import org.mozilla.apache.commons.codec.binary.Base64;
michael@0 37 import ch.boye.httpclientandroidlib.util.EncodingUtils;
michael@0 38
michael@0 39 /**
michael@0 40 * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM
michael@0 41 * authentication protocol.
michael@0 42 *
michael@0 43 * @since 4.1
michael@0 44 */
michael@0 45 final class NTLMEngineImpl implements NTLMEngine {
michael@0 46
michael@0 47 // Flags we use
michael@0 48 protected final static int FLAG_UNICODE_ENCODING = 0x00000001;
michael@0 49 protected final static int FLAG_TARGET_DESIRED = 0x00000004;
michael@0 50 protected final static int FLAG_NEGOTIATE_SIGN = 0x00000010;
michael@0 51 protected final static int FLAG_NEGOTIATE_SEAL = 0x00000020;
michael@0 52 protected final static int FLAG_NEGOTIATE_NTLM = 0x00000200;
michael@0 53 protected final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
michael@0 54 protected final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000;
michael@0 55 protected final static int FLAG_NEGOTIATE_128 = 0x20000000;
michael@0 56 protected final static int FLAG_NEGOTIATE_KEY_EXCH = 0x40000000;
michael@0 57
michael@0 58 /** Secure random generator */
michael@0 59 private static final java.security.SecureRandom RND_GEN;
michael@0 60 static {
michael@0 61 java.security.SecureRandom rnd = null;
michael@0 62 try {
michael@0 63 rnd = java.security.SecureRandom.getInstance("SHA1PRNG");
michael@0 64 } catch (Exception e) {
michael@0 65 }
michael@0 66 RND_GEN = rnd;
michael@0 67 }
michael@0 68
michael@0 69 /** Character encoding */
michael@0 70 static final String DEFAULT_CHARSET = "ASCII";
michael@0 71
michael@0 72 /** The character set to use for encoding the credentials */
michael@0 73 private String credentialCharset = DEFAULT_CHARSET;
michael@0 74
michael@0 75 /** The signature string as bytes in the default encoding */
michael@0 76 private static byte[] SIGNATURE;
michael@0 77
michael@0 78 static {
michael@0 79 byte[] bytesWithoutNull = EncodingUtils.getBytes("NTLMSSP", "ASCII");
michael@0 80 SIGNATURE = new byte[bytesWithoutNull.length + 1];
michael@0 81 System.arraycopy(bytesWithoutNull, 0, SIGNATURE, 0, bytesWithoutNull.length);
michael@0 82 SIGNATURE[bytesWithoutNull.length] = (byte) 0x00;
michael@0 83 }
michael@0 84
michael@0 85 /**
michael@0 86 * Returns the response for the given message.
michael@0 87 *
michael@0 88 * @param message
michael@0 89 * the message that was received from the server.
michael@0 90 * @param username
michael@0 91 * the username to authenticate with.
michael@0 92 * @param password
michael@0 93 * the password to authenticate with.
michael@0 94 * @param host
michael@0 95 * The host.
michael@0 96 * @param domain
michael@0 97 * the NT domain to authenticate in.
michael@0 98 * @return The response.
michael@0 99 * @throws HttpException
michael@0 100 * If the messages cannot be retrieved.
michael@0 101 */
michael@0 102 final String getResponseFor(String message, String username, String password,
michael@0 103 String host, String domain) throws NTLMEngineException {
michael@0 104
michael@0 105 final String response;
michael@0 106 if (message == null || message.trim().equals("")) {
michael@0 107 response = getType1Message(host, domain);
michael@0 108 } else {
michael@0 109 Type2Message t2m = new Type2Message(message);
michael@0 110 response = getType3Message(username, password, host, domain, t2m.getChallenge(), t2m
michael@0 111 .getFlags(), t2m.getTarget(), t2m.getTargetInfo());
michael@0 112 }
michael@0 113 return response;
michael@0 114 }
michael@0 115
michael@0 116 /**
michael@0 117 * Creates the first message (type 1 message) in the NTLM authentication
michael@0 118 * sequence. This message includes the user name, domain and host for the
michael@0 119 * authentication session.
michael@0 120 *
michael@0 121 * @param host
michael@0 122 * the computer name of the host requesting authentication.
michael@0 123 * @param domain
michael@0 124 * The domain to authenticate with.
michael@0 125 * @return String the message to add to the HTTP request header.
michael@0 126 */
michael@0 127 String getType1Message(String host, String domain) throws NTLMEngineException {
michael@0 128 return new Type1Message(domain, host).getResponse();
michael@0 129 }
michael@0 130
michael@0 131 /**
michael@0 132 * Creates the type 3 message using the given server nonce. The type 3
michael@0 133 * message includes all the information for authentication, host, domain,
michael@0 134 * username and the result of encrypting the nonce sent by the server using
michael@0 135 * the user's password as the key.
michael@0 136 *
michael@0 137 * @param user
michael@0 138 * The user name. This should not include the domain name.
michael@0 139 * @param password
michael@0 140 * The password.
michael@0 141 * @param host
michael@0 142 * The host that is originating the authentication request.
michael@0 143 * @param domain
michael@0 144 * The domain to authenticate within.
michael@0 145 * @param nonce
michael@0 146 * the 8 byte array the server sent.
michael@0 147 * @return The type 3 message.
michael@0 148 * @throws NTLMEngineException
michael@0 149 * If {@encrypt(byte[],byte[])} fails.
michael@0 150 */
michael@0 151 String getType3Message(String user, String password, String host, String domain,
michael@0 152 byte[] nonce, int type2Flags, String target, byte[] targetInformation)
michael@0 153 throws NTLMEngineException {
michael@0 154 return new Type3Message(domain, host, user, password, nonce, type2Flags, target,
michael@0 155 targetInformation).getResponse();
michael@0 156 }
michael@0 157
michael@0 158 /**
michael@0 159 * @return Returns the credentialCharset.
michael@0 160 */
michael@0 161 String getCredentialCharset() {
michael@0 162 return credentialCharset;
michael@0 163 }
michael@0 164
michael@0 165 /**
michael@0 166 * @param credentialCharset
michael@0 167 * The credentialCharset to set.
michael@0 168 */
michael@0 169 void setCredentialCharset(String credentialCharset) {
michael@0 170 this.credentialCharset = credentialCharset;
michael@0 171 }
michael@0 172
michael@0 173 /** Strip dot suffix from a name */
michael@0 174 private static String stripDotSuffix(String value) {
michael@0 175 int index = value.indexOf(".");
michael@0 176 if (index != -1)
michael@0 177 return value.substring(0, index);
michael@0 178 return value;
michael@0 179 }
michael@0 180
michael@0 181 /** Convert host to standard form */
michael@0 182 private static String convertHost(String host) {
michael@0 183 return stripDotSuffix(host);
michael@0 184 }
michael@0 185
michael@0 186 /** Convert domain to standard form */
michael@0 187 private static String convertDomain(String domain) {
michael@0 188 return stripDotSuffix(domain);
michael@0 189 }
michael@0 190
michael@0 191 private static int readULong(byte[] src, int index) throws NTLMEngineException {
michael@0 192 if (src.length < index + 4)
michael@0 193 throw new NTLMEngineException("NTLM authentication - buffer too small for DWORD");
michael@0 194 return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8)
michael@0 195 | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24);
michael@0 196 }
michael@0 197
michael@0 198 private static int readUShort(byte[] src, int index) throws NTLMEngineException {
michael@0 199 if (src.length < index + 2)
michael@0 200 throw new NTLMEngineException("NTLM authentication - buffer too small for WORD");
michael@0 201 return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8);
michael@0 202 }
michael@0 203
michael@0 204 private static byte[] readSecurityBuffer(byte[] src, int index) throws NTLMEngineException {
michael@0 205 int length = readUShort(src, index);
michael@0 206 int offset = readULong(src, index + 4);
michael@0 207 if (src.length < offset + length)
michael@0 208 throw new NTLMEngineException(
michael@0 209 "NTLM authentication - buffer too small for data item");
michael@0 210 byte[] buffer = new byte[length];
michael@0 211 System.arraycopy(src, offset, buffer, 0, length);
michael@0 212 return buffer;
michael@0 213 }
michael@0 214
michael@0 215 /** Calculate a challenge block */
michael@0 216 private static byte[] makeRandomChallenge() throws NTLMEngineException {
michael@0 217 if (RND_GEN == null) {
michael@0 218 throw new NTLMEngineException("Random generator not available");
michael@0 219 }
michael@0 220 byte[] rval = new byte[8];
michael@0 221 synchronized (RND_GEN) {
michael@0 222 RND_GEN.nextBytes(rval);
michael@0 223 }
michael@0 224 return rval;
michael@0 225 }
michael@0 226
michael@0 227 /** Calculate an NTLM2 challenge block */
michael@0 228 private static byte[] makeNTLM2RandomChallenge() throws NTLMEngineException {
michael@0 229 if (RND_GEN == null) {
michael@0 230 throw new NTLMEngineException("Random generator not available");
michael@0 231 }
michael@0 232 byte[] rval = new byte[24];
michael@0 233 synchronized (RND_GEN) {
michael@0 234 RND_GEN.nextBytes(rval);
michael@0 235 }
michael@0 236 // 8-byte challenge, padded with zeros to 24 bytes.
michael@0 237 Arrays.fill(rval, 8, 24, (byte) 0x00);
michael@0 238 return rval;
michael@0 239 }
michael@0 240
michael@0 241 /**
michael@0 242 * Calculates the LM Response for the given challenge, using the specified
michael@0 243 * password.
michael@0 244 *
michael@0 245 * @param password
michael@0 246 * The user's password.
michael@0 247 * @param challenge
michael@0 248 * The Type 2 challenge from the server.
michael@0 249 *
michael@0 250 * @return The LM Response.
michael@0 251 */
michael@0 252 static byte[] getLMResponse(String password, byte[] challenge)
michael@0 253 throws NTLMEngineException {
michael@0 254 byte[] lmHash = lmHash(password);
michael@0 255 return lmResponse(lmHash, challenge);
michael@0 256 }
michael@0 257
michael@0 258 /**
michael@0 259 * Calculates the NTLM Response for the given challenge, using the specified
michael@0 260 * password.
michael@0 261 *
michael@0 262 * @param password
michael@0 263 * The user's password.
michael@0 264 * @param challenge
michael@0 265 * The Type 2 challenge from the server.
michael@0 266 *
michael@0 267 * @return The NTLM Response.
michael@0 268 */
michael@0 269 static byte[] getNTLMResponse(String password, byte[] challenge)
michael@0 270 throws NTLMEngineException {
michael@0 271 byte[] ntlmHash = ntlmHash(password);
michael@0 272 return lmResponse(ntlmHash, challenge);
michael@0 273 }
michael@0 274
michael@0 275 /**
michael@0 276 * Calculates the NTLMv2 Response for the given challenge, using the
michael@0 277 * specified authentication target, username, password, target information
michael@0 278 * block, and client challenge.
michael@0 279 *
michael@0 280 * @param target
michael@0 281 * The authentication target (i.e., domain).
michael@0 282 * @param user
michael@0 283 * The username.
michael@0 284 * @param password
michael@0 285 * The user's password.
michael@0 286 * @param targetInformation
michael@0 287 * The target information block from the Type 2 message.
michael@0 288 * @param challenge
michael@0 289 * The Type 2 challenge from the server.
michael@0 290 * @param clientChallenge
michael@0 291 * The random 8-byte client challenge.
michael@0 292 *
michael@0 293 * @return The NTLMv2 Response.
michael@0 294 */
michael@0 295 static byte[] getNTLMv2Response(String target, String user, String password,
michael@0 296 byte[] challenge, byte[] clientChallenge, byte[] targetInformation)
michael@0 297 throws NTLMEngineException {
michael@0 298 byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
michael@0 299 byte[] blob = createBlob(clientChallenge, targetInformation);
michael@0 300 return lmv2Response(ntlmv2Hash, challenge, blob);
michael@0 301 }
michael@0 302
michael@0 303 /**
michael@0 304 * Calculates the LMv2 Response for the given challenge, using the specified
michael@0 305 * authentication target, username, password, and client challenge.
michael@0 306 *
michael@0 307 * @param target
michael@0 308 * The authentication target (i.e., domain).
michael@0 309 * @param user
michael@0 310 * The username.
michael@0 311 * @param password
michael@0 312 * The user's password.
michael@0 313 * @param challenge
michael@0 314 * The Type 2 challenge from the server.
michael@0 315 * @param clientChallenge
michael@0 316 * The random 8-byte client challenge.
michael@0 317 *
michael@0 318 * @return The LMv2 Response.
michael@0 319 */
michael@0 320 static byte[] getLMv2Response(String target, String user, String password,
michael@0 321 byte[] challenge, byte[] clientChallenge) throws NTLMEngineException {
michael@0 322 byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
michael@0 323 return lmv2Response(ntlmv2Hash, challenge, clientChallenge);
michael@0 324 }
michael@0 325
michael@0 326 /**
michael@0 327 * Calculates the NTLM2 Session Response for the given challenge, using the
michael@0 328 * specified password and client challenge.
michael@0 329 *
michael@0 330 * @param password
michael@0 331 * The user's password.
michael@0 332 * @param challenge
michael@0 333 * The Type 2 challenge from the server.
michael@0 334 * @param clientChallenge
michael@0 335 * The random 8-byte client challenge.
michael@0 336 *
michael@0 337 * @return The NTLM2 Session Response. This is placed in the NTLM response
michael@0 338 * field of the Type 3 message; the LM response field contains the
michael@0 339 * client challenge, null-padded to 24 bytes.
michael@0 340 */
michael@0 341 static byte[] getNTLM2SessionResponse(String password, byte[] challenge,
michael@0 342 byte[] clientChallenge) throws NTLMEngineException {
michael@0 343 try {
michael@0 344 byte[] ntlmHash = ntlmHash(password);
michael@0 345
michael@0 346 // Look up MD5 algorithm (was necessary on jdk 1.4.2)
michael@0 347 // This used to be needed, but java 1.5.0_07 includes the MD5
michael@0 348 // algorithm (finally)
michael@0 349 // Class x = Class.forName("gnu.crypto.hash.MD5");
michael@0 350 // Method updateMethod = x.getMethod("update",new
michael@0 351 // Class[]{byte[].class});
michael@0 352 // Method digestMethod = x.getMethod("digest",new Class[0]);
michael@0 353 // Object mdInstance = x.newInstance();
michael@0 354 // updateMethod.invoke(mdInstance,new Object[]{challenge});
michael@0 355 // updateMethod.invoke(mdInstance,new Object[]{clientChallenge});
michael@0 356 // byte[] digest = (byte[])digestMethod.invoke(mdInstance,new
michael@0 357 // Object[0]);
michael@0 358
michael@0 359 MessageDigest md5 = MessageDigest.getInstance("MD5");
michael@0 360 md5.update(challenge);
michael@0 361 md5.update(clientChallenge);
michael@0 362 byte[] digest = md5.digest();
michael@0 363
michael@0 364 byte[] sessionHash = new byte[8];
michael@0 365 System.arraycopy(digest, 0, sessionHash, 0, 8);
michael@0 366 return lmResponse(ntlmHash, sessionHash);
michael@0 367 } catch (Exception e) {
michael@0 368 if (e instanceof NTLMEngineException)
michael@0 369 throw (NTLMEngineException) e;
michael@0 370 throw new NTLMEngineException(e.getMessage(), e);
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374 /**
michael@0 375 * Creates the LM Hash of the user's password.
michael@0 376 *
michael@0 377 * @param password
michael@0 378 * The password.
michael@0 379 *
michael@0 380 * @return The LM Hash of the given password, used in the calculation of the
michael@0 381 * LM Response.
michael@0 382 */
michael@0 383 private static byte[] lmHash(String password) throws NTLMEngineException {
michael@0 384 try {
michael@0 385 byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII");
michael@0 386 int length = Math.min(oemPassword.length, 14);
michael@0 387 byte[] keyBytes = new byte[14];
michael@0 388 System.arraycopy(oemPassword, 0, keyBytes, 0, length);
michael@0 389 Key lowKey = createDESKey(keyBytes, 0);
michael@0 390 Key highKey = createDESKey(keyBytes, 7);
michael@0 391 byte[] magicConstant = "KGS!@#$%".getBytes("US-ASCII");
michael@0 392 Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
michael@0 393 des.init(Cipher.ENCRYPT_MODE, lowKey);
michael@0 394 byte[] lowHash = des.doFinal(magicConstant);
michael@0 395 des.init(Cipher.ENCRYPT_MODE, highKey);
michael@0 396 byte[] highHash = des.doFinal(magicConstant);
michael@0 397 byte[] lmHash = new byte[16];
michael@0 398 System.arraycopy(lowHash, 0, lmHash, 0, 8);
michael@0 399 System.arraycopy(highHash, 0, lmHash, 8, 8);
michael@0 400 return lmHash;
michael@0 401 } catch (Exception e) {
michael@0 402 throw new NTLMEngineException(e.getMessage(), e);
michael@0 403 }
michael@0 404 }
michael@0 405
michael@0 406 /**
michael@0 407 * Creates the NTLM Hash of the user's password.
michael@0 408 *
michael@0 409 * @param password
michael@0 410 * The password.
michael@0 411 *
michael@0 412 * @return The NTLM Hash of the given password, used in the calculation of
michael@0 413 * the NTLM Response and the NTLMv2 and LMv2 Hashes.
michael@0 414 */
michael@0 415 private static byte[] ntlmHash(String password) throws NTLMEngineException {
michael@0 416 try {
michael@0 417 byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked");
michael@0 418 MD4 md4 = new MD4();
michael@0 419 md4.update(unicodePassword);
michael@0 420 return md4.getOutput();
michael@0 421 } catch (java.io.UnsupportedEncodingException e) {
michael@0 422 throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e);
michael@0 423 }
michael@0 424 }
michael@0 425
michael@0 426 /**
michael@0 427 * Creates the NTLMv2 Hash of the user's password.
michael@0 428 *
michael@0 429 * @param target
michael@0 430 * The authentication target (i.e., domain).
michael@0 431 * @param user
michael@0 432 * The username.
michael@0 433 * @param password
michael@0 434 * The password.
michael@0 435 *
michael@0 436 * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2
michael@0 437 * Responses.
michael@0 438 */
michael@0 439 private static byte[] ntlmv2Hash(String target, String user, String password)
michael@0 440 throws NTLMEngineException {
michael@0 441 try {
michael@0 442 byte[] ntlmHash = ntlmHash(password);
michael@0 443 HMACMD5 hmacMD5 = new HMACMD5(ntlmHash);
michael@0 444 // Upper case username, mixed case target!!
michael@0 445 hmacMD5.update(user.toUpperCase().getBytes("UnicodeLittleUnmarked"));
michael@0 446 hmacMD5.update(target.getBytes("UnicodeLittleUnmarked"));
michael@0 447 return hmacMD5.getOutput();
michael@0 448 } catch (java.io.UnsupportedEncodingException e) {
michael@0 449 throw new NTLMEngineException("Unicode not supported! " + e.getMessage(), e);
michael@0 450 }
michael@0 451 }
michael@0 452
michael@0 453 /**
michael@0 454 * Creates the LM Response from the given hash and Type 2 challenge.
michael@0 455 *
michael@0 456 * @param hash
michael@0 457 * The LM or NTLM Hash.
michael@0 458 * @param challenge
michael@0 459 * The server challenge from the Type 2 message.
michael@0 460 *
michael@0 461 * @return The response (either LM or NTLM, depending on the provided hash).
michael@0 462 */
michael@0 463 private static byte[] lmResponse(byte[] hash, byte[] challenge) throws NTLMEngineException {
michael@0 464 try {
michael@0 465 byte[] keyBytes = new byte[21];
michael@0 466 System.arraycopy(hash, 0, keyBytes, 0, 16);
michael@0 467 Key lowKey = createDESKey(keyBytes, 0);
michael@0 468 Key middleKey = createDESKey(keyBytes, 7);
michael@0 469 Key highKey = createDESKey(keyBytes, 14);
michael@0 470 Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
michael@0 471 des.init(Cipher.ENCRYPT_MODE, lowKey);
michael@0 472 byte[] lowResponse = des.doFinal(challenge);
michael@0 473 des.init(Cipher.ENCRYPT_MODE, middleKey);
michael@0 474 byte[] middleResponse = des.doFinal(challenge);
michael@0 475 des.init(Cipher.ENCRYPT_MODE, highKey);
michael@0 476 byte[] highResponse = des.doFinal(challenge);
michael@0 477 byte[] lmResponse = new byte[24];
michael@0 478 System.arraycopy(lowResponse, 0, lmResponse, 0, 8);
michael@0 479 System.arraycopy(middleResponse, 0, lmResponse, 8, 8);
michael@0 480 System.arraycopy(highResponse, 0, lmResponse, 16, 8);
michael@0 481 return lmResponse;
michael@0 482 } catch (Exception e) {
michael@0 483 throw new NTLMEngineException(e.getMessage(), e);
michael@0 484 }
michael@0 485 }
michael@0 486
michael@0 487 /**
michael@0 488 * Creates the LMv2 Response from the given hash, client data, and Type 2
michael@0 489 * challenge.
michael@0 490 *
michael@0 491 * @param hash
michael@0 492 * The NTLMv2 Hash.
michael@0 493 * @param clientData
michael@0 494 * The client data (blob or client challenge).
michael@0 495 * @param challenge
michael@0 496 * The server challenge from the Type 2 message.
michael@0 497 *
michael@0 498 * @return The response (either NTLMv2 or LMv2, depending on the client
michael@0 499 * data).
michael@0 500 */
michael@0 501 private static byte[] lmv2Response(byte[] hash, byte[] challenge, byte[] clientData)
michael@0 502 throws NTLMEngineException {
michael@0 503 HMACMD5 hmacMD5 = new HMACMD5(hash);
michael@0 504 hmacMD5.update(challenge);
michael@0 505 hmacMD5.update(clientData);
michael@0 506 byte[] mac = hmacMD5.getOutput();
michael@0 507 byte[] lmv2Response = new byte[mac.length + clientData.length];
michael@0 508 System.arraycopy(mac, 0, lmv2Response, 0, mac.length);
michael@0 509 System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length);
michael@0 510 return lmv2Response;
michael@0 511 }
michael@0 512
michael@0 513 /**
michael@0 514 * Creates the NTLMv2 blob from the given target information block and
michael@0 515 * client challenge.
michael@0 516 *
michael@0 517 * @param targetInformation
michael@0 518 * The target information block from the Type 2 message.
michael@0 519 * @param clientChallenge
michael@0 520 * The random 8-byte client challenge.
michael@0 521 *
michael@0 522 * @return The blob, used in the calculation of the NTLMv2 Response.
michael@0 523 */
michael@0 524 private static byte[] createBlob(byte[] clientChallenge, byte[] targetInformation) {
michael@0 525 byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 };
michael@0 526 byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
michael@0 527 byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
michael@0 528 long time = System.currentTimeMillis();
michael@0 529 time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
michael@0 530 time *= 10000; // tenths of a microsecond.
michael@0 531 // convert to little-endian byte array.
michael@0 532 byte[] timestamp = new byte[8];
michael@0 533 for (int i = 0; i < 8; i++) {
michael@0 534 timestamp[i] = (byte) time;
michael@0 535 time >>>= 8;
michael@0 536 }
michael@0 537 byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8
michael@0 538 + unknown1.length + targetInformation.length];
michael@0 539 int offset = 0;
michael@0 540 System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length);
michael@0 541 offset += blobSignature.length;
michael@0 542 System.arraycopy(reserved, 0, blob, offset, reserved.length);
michael@0 543 offset += reserved.length;
michael@0 544 System.arraycopy(timestamp, 0, blob, offset, timestamp.length);
michael@0 545 offset += timestamp.length;
michael@0 546 System.arraycopy(clientChallenge, 0, blob, offset, 8);
michael@0 547 offset += 8;
michael@0 548 System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
michael@0 549 offset += unknown1.length;
michael@0 550 System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length);
michael@0 551 return blob;
michael@0 552 }
michael@0 553
michael@0 554 /**
michael@0 555 * Creates a DES encryption key from the given key material.
michael@0 556 *
michael@0 557 * @param bytes
michael@0 558 * A byte array containing the DES key material.
michael@0 559 * @param offset
michael@0 560 * The offset in the given byte array at which the 7-byte key
michael@0 561 * material starts.
michael@0 562 *
michael@0 563 * @return A DES encryption key created from the key material starting at
michael@0 564 * the specified offset in the given byte array.
michael@0 565 */
michael@0 566 private static Key createDESKey(byte[] bytes, int offset) {
michael@0 567 byte[] keyBytes = new byte[7];
michael@0 568 System.arraycopy(bytes, offset, keyBytes, 0, 7);
michael@0 569 byte[] material = new byte[8];
michael@0 570 material[0] = keyBytes[0];
michael@0 571 material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1);
michael@0 572 material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2);
michael@0 573 material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3);
michael@0 574 material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4);
michael@0 575 material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5);
michael@0 576 material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6);
michael@0 577 material[7] = (byte) (keyBytes[6] << 1);
michael@0 578 oddParity(material);
michael@0 579 return new SecretKeySpec(material, "DES");
michael@0 580 }
michael@0 581
michael@0 582 /**
michael@0 583 * Applies odd parity to the given byte array.
michael@0 584 *
michael@0 585 * @param bytes
michael@0 586 * The data whose parity bits are to be adjusted for odd parity.
michael@0 587 */
michael@0 588 private static void oddParity(byte[] bytes) {
michael@0 589 for (int i = 0; i < bytes.length; i++) {
michael@0 590 byte b = bytes[i];
michael@0 591 boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3)
michael@0 592 ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0;
michael@0 593 if (needsParity) {
michael@0 594 bytes[i] |= (byte) 0x01;
michael@0 595 } else {
michael@0 596 bytes[i] &= (byte) 0xfe;
michael@0 597 }
michael@0 598 }
michael@0 599 }
michael@0 600
michael@0 601 /** NTLM message generation, base class */
michael@0 602 static class NTLMMessage {
michael@0 603 /** The current response */
michael@0 604 private byte[] messageContents = null;
michael@0 605
michael@0 606 /** The current output position */
michael@0 607 private int currentOutputPosition = 0;
michael@0 608
michael@0 609 /** Constructor to use when message contents are not yet known */
michael@0 610 NTLMMessage() {
michael@0 611 }
michael@0 612
michael@0 613 /** Constructor to use when message contents are known */
michael@0 614 NTLMMessage(String messageBody, int expectedType) throws NTLMEngineException {
michael@0 615 messageContents = Base64.decodeBase64(EncodingUtils.getBytes(messageBody,
michael@0 616 DEFAULT_CHARSET));
michael@0 617 // Look for NTLM message
michael@0 618 if (messageContents.length < SIGNATURE.length)
michael@0 619 throw new NTLMEngineException("NTLM message decoding error - packet too short");
michael@0 620 int i = 0;
michael@0 621 while (i < SIGNATURE.length) {
michael@0 622 if (messageContents[i] != SIGNATURE[i])
michael@0 623 throw new NTLMEngineException(
michael@0 624 "NTLM message expected - instead got unrecognized bytes");
michael@0 625 i++;
michael@0 626 }
michael@0 627
michael@0 628 // Check to be sure there's a type 2 message indicator next
michael@0 629 int type = readULong(SIGNATURE.length);
michael@0 630 if (type != expectedType)
michael@0 631 throw new NTLMEngineException("NTLM type " + Integer.toString(expectedType)
michael@0 632 + " message expected - instead got type " + Integer.toString(type));
michael@0 633
michael@0 634 currentOutputPosition = messageContents.length;
michael@0 635 }
michael@0 636
michael@0 637 /**
michael@0 638 * Get the length of the signature and flags, so calculations can adjust
michael@0 639 * offsets accordingly.
michael@0 640 */
michael@0 641 protected int getPreambleLength() {
michael@0 642 return SIGNATURE.length + 4;
michael@0 643 }
michael@0 644
michael@0 645 /** Get the message length */
michael@0 646 protected int getMessageLength() {
michael@0 647 return currentOutputPosition;
michael@0 648 }
michael@0 649
michael@0 650 /** Read a byte from a position within the message buffer */
michael@0 651 protected byte readByte(int position) throws NTLMEngineException {
michael@0 652 if (messageContents.length < position + 1)
michael@0 653 throw new NTLMEngineException("NTLM: Message too short");
michael@0 654 return messageContents[position];
michael@0 655 }
michael@0 656
michael@0 657 /** Read a bunch of bytes from a position in the message buffer */
michael@0 658 protected void readBytes(byte[] buffer, int position) throws NTLMEngineException {
michael@0 659 if (messageContents.length < position + buffer.length)
michael@0 660 throw new NTLMEngineException("NTLM: Message too short");
michael@0 661 System.arraycopy(messageContents, position, buffer, 0, buffer.length);
michael@0 662 }
michael@0 663
michael@0 664 /** Read a ushort from a position within the message buffer */
michael@0 665 protected int readUShort(int position) throws NTLMEngineException {
michael@0 666 return NTLMEngineImpl.readUShort(messageContents, position);
michael@0 667 }
michael@0 668
michael@0 669 /** Read a ulong from a position within the message buffer */
michael@0 670 protected int readULong(int position) throws NTLMEngineException {
michael@0 671 return NTLMEngineImpl.readULong(messageContents, position);
michael@0 672 }
michael@0 673
michael@0 674 /** Read a security buffer from a position within the message buffer */
michael@0 675 protected byte[] readSecurityBuffer(int position) throws NTLMEngineException {
michael@0 676 return NTLMEngineImpl.readSecurityBuffer(messageContents, position);
michael@0 677 }
michael@0 678
michael@0 679 /**
michael@0 680 * Prepares the object to create a response of the given length.
michael@0 681 *
michael@0 682 * @param length
michael@0 683 * the maximum length of the response to prepare, not
michael@0 684 * including the type and the signature (which this method
michael@0 685 * adds).
michael@0 686 */
michael@0 687 protected void prepareResponse(int maxlength, int messageType) {
michael@0 688 messageContents = new byte[maxlength];
michael@0 689 currentOutputPosition = 0;
michael@0 690 addBytes(SIGNATURE);
michael@0 691 addULong(messageType);
michael@0 692 }
michael@0 693
michael@0 694 /**
michael@0 695 * Adds the given byte to the response.
michael@0 696 *
michael@0 697 * @param b
michael@0 698 * the byte to add.
michael@0 699 */
michael@0 700 protected void addByte(byte b) {
michael@0 701 messageContents[currentOutputPosition] = b;
michael@0 702 currentOutputPosition++;
michael@0 703 }
michael@0 704
michael@0 705 /**
michael@0 706 * Adds the given bytes to the response.
michael@0 707 *
michael@0 708 * @param bytes
michael@0 709 * the bytes to add.
michael@0 710 */
michael@0 711 protected void addBytes(byte[] bytes) {
michael@0 712 for (int i = 0; i < bytes.length; i++) {
michael@0 713 messageContents[currentOutputPosition] = bytes[i];
michael@0 714 currentOutputPosition++;
michael@0 715 }
michael@0 716 }
michael@0 717
michael@0 718 /** Adds a USHORT to the response */
michael@0 719 protected void addUShort(int value) {
michael@0 720 addByte((byte) (value & 0xff));
michael@0 721 addByte((byte) (value >> 8 & 0xff));
michael@0 722 }
michael@0 723
michael@0 724 /** Adds a ULong to the response */
michael@0 725 protected void addULong(int value) {
michael@0 726 addByte((byte) (value & 0xff));
michael@0 727 addByte((byte) (value >> 8 & 0xff));
michael@0 728 addByte((byte) (value >> 16 & 0xff));
michael@0 729 addByte((byte) (value >> 24 & 0xff));
michael@0 730 }
michael@0 731
michael@0 732 /**
michael@0 733 * Returns the response that has been generated after shrinking the
michael@0 734 * array if required and base64 encodes the response.
michael@0 735 *
michael@0 736 * @return The response as above.
michael@0 737 */
michael@0 738 String getResponse() {
michael@0 739 byte[] resp;
michael@0 740 if (messageContents.length > currentOutputPosition) {
michael@0 741 byte[] tmp = new byte[currentOutputPosition];
michael@0 742 for (int i = 0; i < currentOutputPosition; i++) {
michael@0 743 tmp[i] = messageContents[i];
michael@0 744 }
michael@0 745 resp = tmp;
michael@0 746 } else {
michael@0 747 resp = messageContents;
michael@0 748 }
michael@0 749 return EncodingUtils.getAsciiString(Base64.encodeBase64(resp));
michael@0 750 }
michael@0 751
michael@0 752 }
michael@0 753
michael@0 754 /** Type 1 message assembly class */
michael@0 755 static class Type1Message extends NTLMMessage {
michael@0 756 protected byte[] hostBytes;
michael@0 757 protected byte[] domainBytes;
michael@0 758
michael@0 759 /** Constructor. Include the arguments the message will need */
michael@0 760 Type1Message(String domain, String host) throws NTLMEngineException {
michael@0 761 super();
michael@0 762 try {
michael@0 763 // Strip off domain name from the host!
michael@0 764 host = convertHost(host);
michael@0 765 // Use only the base domain name!
michael@0 766 domain = convertDomain(domain);
michael@0 767
michael@0 768 hostBytes = host.getBytes("UnicodeLittleUnmarked");
michael@0 769 domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked");
michael@0 770 } catch (java.io.UnsupportedEncodingException e) {
michael@0 771 throw new NTLMEngineException("Unicode unsupported: " + e.getMessage(), e);
michael@0 772 }
michael@0 773 }
michael@0 774
michael@0 775 /**
michael@0 776 * Getting the response involves building the message before returning
michael@0 777 * it
michael@0 778 */
michael@0 779 @Override
michael@0 780 String getResponse() {
michael@0 781 // Now, build the message. Calculate its length first, including
michael@0 782 // signature or type.
michael@0 783 int finalLength = 32 + hostBytes.length + domainBytes.length;
michael@0 784
michael@0 785 // Set up the response. This will initialize the signature, message
michael@0 786 // type, and flags.
michael@0 787 prepareResponse(finalLength, 1);
michael@0 788
michael@0 789 // Flags. These are the complete set of flags we support.
michael@0 790 addULong(FLAG_NEGOTIATE_NTLM | FLAG_NEGOTIATE_NTLM2 | FLAG_NEGOTIATE_SIGN
michael@0 791 | FLAG_NEGOTIATE_SEAL |
michael@0 792 /*
michael@0 793 * FLAG_NEGOTIATE_ALWAYS_SIGN | FLAG_NEGOTIATE_KEY_EXCH |
michael@0 794 */
michael@0 795 FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED | FLAG_NEGOTIATE_128);
michael@0 796
michael@0 797 // Domain length (two times).
michael@0 798 addUShort(domainBytes.length);
michael@0 799 addUShort(domainBytes.length);
michael@0 800
michael@0 801 // Domain offset.
michael@0 802 addULong(hostBytes.length + 32);
michael@0 803
michael@0 804 // Host length (two times).
michael@0 805 addUShort(hostBytes.length);
michael@0 806 addUShort(hostBytes.length);
michael@0 807
michael@0 808 // Host offset (always 32).
michael@0 809 addULong(32);
michael@0 810
michael@0 811 // Host String.
michael@0 812 addBytes(hostBytes);
michael@0 813
michael@0 814 // Domain String.
michael@0 815 addBytes(domainBytes);
michael@0 816
michael@0 817 return super.getResponse();
michael@0 818 }
michael@0 819
michael@0 820 }
michael@0 821
michael@0 822 /** Type 2 message class */
michael@0 823 static class Type2Message extends NTLMMessage {
michael@0 824 protected byte[] challenge;
michael@0 825 protected String target;
michael@0 826 protected byte[] targetInfo;
michael@0 827 protected int flags;
michael@0 828
michael@0 829 Type2Message(String message) throws NTLMEngineException {
michael@0 830 super(message, 2);
michael@0 831
michael@0 832 // Parse out the rest of the info we need from the message
michael@0 833 // The nonce is the 8 bytes starting from the byte in position 24.
michael@0 834 challenge = new byte[8];
michael@0 835 readBytes(challenge, 24);
michael@0 836
michael@0 837 flags = readULong(20);
michael@0 838 if ((flags & FLAG_UNICODE_ENCODING) == 0)
michael@0 839 throw new NTLMEngineException(
michael@0 840 "NTLM type 2 message has flags that make no sense: "
michael@0 841 + Integer.toString(flags));
michael@0 842 // Do the target!
michael@0 843 target = null;
michael@0 844 // The TARGET_DESIRED flag is said to not have understood semantics
michael@0 845 // in Type2 messages, so use the length of the packet to decide
michael@0 846 // how to proceed instead
michael@0 847 if (getMessageLength() >= 12 + 8) {
michael@0 848 byte[] bytes = readSecurityBuffer(12);
michael@0 849 if (bytes.length != 0) {
michael@0 850 try {
michael@0 851 target = new String(bytes, "UnicodeLittleUnmarked");
michael@0 852 } catch (java.io.UnsupportedEncodingException e) {
michael@0 853 throw new NTLMEngineException(e.getMessage(), e);
michael@0 854 }
michael@0 855 }
michael@0 856 }
michael@0 857
michael@0 858 // Do the target info!
michael@0 859 targetInfo = null;
michael@0 860 // TARGET_DESIRED flag cannot be relied on, so use packet length
michael@0 861 if (getMessageLength() >= 40 + 8) {
michael@0 862 byte[] bytes = readSecurityBuffer(40);
michael@0 863 if (bytes.length != 0) {
michael@0 864 targetInfo = bytes;
michael@0 865 }
michael@0 866 }
michael@0 867 }
michael@0 868
michael@0 869 /** Retrieve the challenge */
michael@0 870 byte[] getChallenge() {
michael@0 871 return challenge;
michael@0 872 }
michael@0 873
michael@0 874 /** Retrieve the target */
michael@0 875 String getTarget() {
michael@0 876 return target;
michael@0 877 }
michael@0 878
michael@0 879 /** Retrieve the target info */
michael@0 880 byte[] getTargetInfo() {
michael@0 881 return targetInfo;
michael@0 882 }
michael@0 883
michael@0 884 /** Retrieve the response flags */
michael@0 885 int getFlags() {
michael@0 886 return flags;
michael@0 887 }
michael@0 888
michael@0 889 }
michael@0 890
michael@0 891 /** Type 3 message assembly class */
michael@0 892 static class Type3Message extends NTLMMessage {
michael@0 893 // Response flags from the type2 message
michael@0 894 protected int type2Flags;
michael@0 895
michael@0 896 protected byte[] domainBytes;
michael@0 897 protected byte[] hostBytes;
michael@0 898 protected byte[] userBytes;
michael@0 899
michael@0 900 protected byte[] lmResp;
michael@0 901 protected byte[] ntResp;
michael@0 902
michael@0 903 /** Constructor. Pass the arguments we will need */
michael@0 904 Type3Message(String domain, String host, String user, String password, byte[] nonce,
michael@0 905 int type2Flags, String target, byte[] targetInformation)
michael@0 906 throws NTLMEngineException {
michael@0 907 // Save the flags
michael@0 908 this.type2Flags = type2Flags;
michael@0 909
michael@0 910 // Strip off domain name from the host!
michael@0 911 host = convertHost(host);
michael@0 912 // Use only the base domain name!
michael@0 913 domain = convertDomain(domain);
michael@0 914
michael@0 915 // Use the new code to calculate the responses, including v2 if that
michael@0 916 // seems warranted.
michael@0 917 try {
michael@0 918 if (targetInformation != null && target != null) {
michael@0 919 byte[] clientChallenge = makeRandomChallenge();
michael@0 920 ntResp = getNTLMv2Response(target, user, password, nonce, clientChallenge,
michael@0 921 targetInformation);
michael@0 922 lmResp = getLMv2Response(target, user, password, nonce, clientChallenge);
michael@0 923 } else {
michael@0 924 if ((type2Flags & FLAG_NEGOTIATE_NTLM2) != 0) {
michael@0 925 // NTLM2 session stuff is requested
michael@0 926 byte[] clientChallenge = makeNTLM2RandomChallenge();
michael@0 927
michael@0 928 ntResp = getNTLM2SessionResponse(password, nonce, clientChallenge);
michael@0 929 lmResp = clientChallenge;
michael@0 930
michael@0 931 // All the other flags we send (signing, sealing, key
michael@0 932 // exchange) are supported, but they don't do anything
michael@0 933 // at all in an
michael@0 934 // NTLM2 context! So we're done at this point.
michael@0 935 } else {
michael@0 936 ntResp = getNTLMResponse(password, nonce);
michael@0 937 lmResp = getLMResponse(password, nonce);
michael@0 938 }
michael@0 939 }
michael@0 940 } catch (NTLMEngineException e) {
michael@0 941 // This likely means we couldn't find the MD4 hash algorithm -
michael@0 942 // fail back to just using LM
michael@0 943 ntResp = new byte[0];
michael@0 944 lmResp = getLMResponse(password, nonce);
michael@0 945 }
michael@0 946
michael@0 947 try {
michael@0 948 domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked");
michael@0 949 hostBytes = host.getBytes("UnicodeLittleUnmarked");
michael@0 950 userBytes = user.getBytes("UnicodeLittleUnmarked");
michael@0 951 } catch (java.io.UnsupportedEncodingException e) {
michael@0 952 throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e);
michael@0 953 }
michael@0 954 }
michael@0 955
michael@0 956 /** Assemble the response */
michael@0 957 @Override
michael@0 958 String getResponse() {
michael@0 959 int ntRespLen = ntResp.length;
michael@0 960 int lmRespLen = lmResp.length;
michael@0 961
michael@0 962 int domainLen = domainBytes.length;
michael@0 963 int hostLen = hostBytes.length;
michael@0 964 int userLen = userBytes.length;
michael@0 965
michael@0 966 // Calculate the layout within the packet
michael@0 967 int lmRespOffset = 64;
michael@0 968 int ntRespOffset = lmRespOffset + lmRespLen;
michael@0 969 int domainOffset = ntRespOffset + ntRespLen;
michael@0 970 int userOffset = domainOffset + domainLen;
michael@0 971 int hostOffset = userOffset + userLen;
michael@0 972 int sessionKeyOffset = hostOffset + hostLen;
michael@0 973 int finalLength = sessionKeyOffset + 0;
michael@0 974
michael@0 975 // Start the response. Length includes signature and type
michael@0 976 prepareResponse(finalLength, 3);
michael@0 977
michael@0 978 // LM Resp Length (twice)
michael@0 979 addUShort(lmRespLen);
michael@0 980 addUShort(lmRespLen);
michael@0 981
michael@0 982 // LM Resp Offset
michael@0 983 addULong(lmRespOffset);
michael@0 984
michael@0 985 // NT Resp Length (twice)
michael@0 986 addUShort(ntRespLen);
michael@0 987 addUShort(ntRespLen);
michael@0 988
michael@0 989 // NT Resp Offset
michael@0 990 addULong(ntRespOffset);
michael@0 991
michael@0 992 // Domain length (twice)
michael@0 993 addUShort(domainLen);
michael@0 994 addUShort(domainLen);
michael@0 995
michael@0 996 // Domain offset.
michael@0 997 addULong(domainOffset);
michael@0 998
michael@0 999 // User Length (twice)
michael@0 1000 addUShort(userLen);
michael@0 1001 addUShort(userLen);
michael@0 1002
michael@0 1003 // User offset
michael@0 1004 addULong(userOffset);
michael@0 1005
michael@0 1006 // Host length (twice)
michael@0 1007 addUShort(hostLen);
michael@0 1008 addUShort(hostLen);
michael@0 1009
michael@0 1010 // Host offset
michael@0 1011 addULong(hostOffset);
michael@0 1012
michael@0 1013 // 4 bytes of zeros - not sure what this is
michael@0 1014 addULong(0);
michael@0 1015
michael@0 1016 // Message length
michael@0 1017 addULong(finalLength);
michael@0 1018
michael@0 1019 // Flags. Currently: NEGOTIATE_NTLM + UNICODE_ENCODING +
michael@0 1020 // TARGET_DESIRED + NEGOTIATE_128
michael@0 1021 addULong(FLAG_NEGOTIATE_NTLM | FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED
michael@0 1022 | FLAG_NEGOTIATE_128 | (type2Flags & FLAG_NEGOTIATE_NTLM2)
michael@0 1023 | (type2Flags & FLAG_NEGOTIATE_SIGN) | (type2Flags & FLAG_NEGOTIATE_SEAL)
michael@0 1024 | (type2Flags & FLAG_NEGOTIATE_KEY_EXCH)
michael@0 1025 | (type2Flags & FLAG_NEGOTIATE_ALWAYS_SIGN));
michael@0 1026
michael@0 1027 // Add the actual data
michael@0 1028 addBytes(lmResp);
michael@0 1029 addBytes(ntResp);
michael@0 1030 addBytes(domainBytes);
michael@0 1031 addBytes(userBytes);
michael@0 1032 addBytes(hostBytes);
michael@0 1033
michael@0 1034 return super.getResponse();
michael@0 1035 }
michael@0 1036 }
michael@0 1037
michael@0 1038 static void writeULong(byte[] buffer, int value, int offset) {
michael@0 1039 buffer[offset] = (byte) (value & 0xff);
michael@0 1040 buffer[offset + 1] = (byte) (value >> 8 & 0xff);
michael@0 1041 buffer[offset + 2] = (byte) (value >> 16 & 0xff);
michael@0 1042 buffer[offset + 3] = (byte) (value >> 24 & 0xff);
michael@0 1043 }
michael@0 1044
michael@0 1045 static int F(int x, int y, int z) {
michael@0 1046 return ((x & y) | (~x & z));
michael@0 1047 }
michael@0 1048
michael@0 1049 static int G(int x, int y, int z) {
michael@0 1050 return ((x & y) | (x & z) | (y & z));
michael@0 1051 }
michael@0 1052
michael@0 1053 static int H(int x, int y, int z) {
michael@0 1054 return (x ^ y ^ z);
michael@0 1055 }
michael@0 1056
michael@0 1057 static int rotintlft(int val, int numbits) {
michael@0 1058 return ((val << numbits) | (val >>> (32 - numbits)));
michael@0 1059 }
michael@0 1060
michael@0 1061 /**
michael@0 1062 * Cryptography support - MD4. The following class was based loosely on the
michael@0 1063 * RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java.
michael@0 1064 * Code correctness was verified by looking at MD4.java from the jcifs
michael@0 1065 * library (http://jcifs.samba.org). It was massaged extensively to the
michael@0 1066 * final form found here by Karl Wright (kwright@metacarta.com).
michael@0 1067 */
michael@0 1068 static class MD4 {
michael@0 1069 protected int A = 0x67452301;
michael@0 1070 protected int B = 0xefcdab89;
michael@0 1071 protected int C = 0x98badcfe;
michael@0 1072 protected int D = 0x10325476;
michael@0 1073 protected long count = 0L;
michael@0 1074 protected byte[] dataBuffer = new byte[64];
michael@0 1075
michael@0 1076 MD4() {
michael@0 1077 }
michael@0 1078
michael@0 1079 void update(byte[] input) {
michael@0 1080 // We always deal with 512 bits at a time. Correspondingly, there is
michael@0 1081 // a buffer 64 bytes long that we write data into until it gets
michael@0 1082 // full.
michael@0 1083 int curBufferPos = (int) (count & 63L);
michael@0 1084 int inputIndex = 0;
michael@0 1085 while (input.length - inputIndex + curBufferPos >= dataBuffer.length) {
michael@0 1086 // We have enough data to do the next step. Do a partial copy
michael@0 1087 // and a transform, updating inputIndex and curBufferPos
michael@0 1088 // accordingly
michael@0 1089 int transferAmt = dataBuffer.length - curBufferPos;
michael@0 1090 System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt);
michael@0 1091 count += transferAmt;
michael@0 1092 curBufferPos = 0;
michael@0 1093 inputIndex += transferAmt;
michael@0 1094 processBuffer();
michael@0 1095 }
michael@0 1096
michael@0 1097 // If there's anything left, copy it into the buffer and leave it.
michael@0 1098 // We know there's not enough left to process.
michael@0 1099 if (inputIndex < input.length) {
michael@0 1100 int transferAmt = input.length - inputIndex;
michael@0 1101 System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt);
michael@0 1102 count += transferAmt;
michael@0 1103 curBufferPos += transferAmt;
michael@0 1104 }
michael@0 1105 }
michael@0 1106
michael@0 1107 byte[] getOutput() {
michael@0 1108 // Feed pad/length data into engine. This must round out the input
michael@0 1109 // to a multiple of 512 bits.
michael@0 1110 int bufferIndex = (int) (count & 63L);
michael@0 1111 int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex);
michael@0 1112 byte[] postBytes = new byte[padLen + 8];
michael@0 1113 // Leading 0x80, specified amount of zero padding, then length in
michael@0 1114 // bits.
michael@0 1115 postBytes[0] = (byte) 0x80;
michael@0 1116 // Fill out the last 8 bytes with the length
michael@0 1117 for (int i = 0; i < 8; i++) {
michael@0 1118 postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i));
michael@0 1119 }
michael@0 1120
michael@0 1121 // Update the engine
michael@0 1122 update(postBytes);
michael@0 1123
michael@0 1124 // Calculate final result
michael@0 1125 byte[] result = new byte[16];
michael@0 1126 writeULong(result, A, 0);
michael@0 1127 writeULong(result, B, 4);
michael@0 1128 writeULong(result, C, 8);
michael@0 1129 writeULong(result, D, 12);
michael@0 1130 return result;
michael@0 1131 }
michael@0 1132
michael@0 1133 protected void processBuffer() {
michael@0 1134 // Convert current buffer to 16 ulongs
michael@0 1135 int[] d = new int[16];
michael@0 1136
michael@0 1137 for (int i = 0; i < 16; i++) {
michael@0 1138 d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8)
michael@0 1139 + ((dataBuffer[i * 4 + 2] & 0xff) << 16)
michael@0 1140 + ((dataBuffer[i * 4 + 3] & 0xff) << 24);
michael@0 1141 }
michael@0 1142
michael@0 1143 // Do a round of processing
michael@0 1144 int AA = A;
michael@0 1145 int BB = B;
michael@0 1146 int CC = C;
michael@0 1147 int DD = D;
michael@0 1148 round1(d);
michael@0 1149 round2(d);
michael@0 1150 round3(d);
michael@0 1151 A += AA;
michael@0 1152 B += BB;
michael@0 1153 C += CC;
michael@0 1154 D += DD;
michael@0 1155
michael@0 1156 }
michael@0 1157
michael@0 1158 protected void round1(int[] d) {
michael@0 1159 A = rotintlft((A + F(B, C, D) + d[0]), 3);
michael@0 1160 D = rotintlft((D + F(A, B, C) + d[1]), 7);
michael@0 1161 C = rotintlft((C + F(D, A, B) + d[2]), 11);
michael@0 1162 B = rotintlft((B + F(C, D, A) + d[3]), 19);
michael@0 1163
michael@0 1164 A = rotintlft((A + F(B, C, D) + d[4]), 3);
michael@0 1165 D = rotintlft((D + F(A, B, C) + d[5]), 7);
michael@0 1166 C = rotintlft((C + F(D, A, B) + d[6]), 11);
michael@0 1167 B = rotintlft((B + F(C, D, A) + d[7]), 19);
michael@0 1168
michael@0 1169 A = rotintlft((A + F(B, C, D) + d[8]), 3);
michael@0 1170 D = rotintlft((D + F(A, B, C) + d[9]), 7);
michael@0 1171 C = rotintlft((C + F(D, A, B) + d[10]), 11);
michael@0 1172 B = rotintlft((B + F(C, D, A) + d[11]), 19);
michael@0 1173
michael@0 1174 A = rotintlft((A + F(B, C, D) + d[12]), 3);
michael@0 1175 D = rotintlft((D + F(A, B, C) + d[13]), 7);
michael@0 1176 C = rotintlft((C + F(D, A, B) + d[14]), 11);
michael@0 1177 B = rotintlft((B + F(C, D, A) + d[15]), 19);
michael@0 1178 }
michael@0 1179
michael@0 1180 protected void round2(int[] d) {
michael@0 1181 A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3);
michael@0 1182 D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5);
michael@0 1183 C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9);
michael@0 1184 B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13);
michael@0 1185
michael@0 1186 A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3);
michael@0 1187 D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5);
michael@0 1188 C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9);
michael@0 1189 B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13);
michael@0 1190
michael@0 1191 A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3);
michael@0 1192 D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5);
michael@0 1193 C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9);
michael@0 1194 B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13);
michael@0 1195
michael@0 1196 A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3);
michael@0 1197 D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5);
michael@0 1198 C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9);
michael@0 1199 B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13);
michael@0 1200
michael@0 1201 }
michael@0 1202
michael@0 1203 protected void round3(int[] d) {
michael@0 1204 A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3);
michael@0 1205 D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9);
michael@0 1206 C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11);
michael@0 1207 B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15);
michael@0 1208
michael@0 1209 A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3);
michael@0 1210 D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9);
michael@0 1211 C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11);
michael@0 1212 B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15);
michael@0 1213
michael@0 1214 A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3);
michael@0 1215 D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9);
michael@0 1216 C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11);
michael@0 1217 B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15);
michael@0 1218
michael@0 1219 A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3);
michael@0 1220 D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9);
michael@0 1221 C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11);
michael@0 1222 B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15);
michael@0 1223
michael@0 1224 }
michael@0 1225
michael@0 1226 }
michael@0 1227
michael@0 1228 /**
michael@0 1229 * Cryptography support - HMACMD5 - algorithmically based on various web
michael@0 1230 * resources by Karl Wright
michael@0 1231 */
michael@0 1232 static class HMACMD5 {
michael@0 1233 protected byte[] ipad;
michael@0 1234 protected byte[] opad;
michael@0 1235 protected MessageDigest md5;
michael@0 1236
michael@0 1237 HMACMD5(byte[] key) throws NTLMEngineException {
michael@0 1238 try {
michael@0 1239 md5 = MessageDigest.getInstance("MD5");
michael@0 1240 } catch (Exception ex) {
michael@0 1241 // Umm, the algorithm doesn't exist - throw an
michael@0 1242 // NTLMEngineException!
michael@0 1243 throw new NTLMEngineException(
michael@0 1244 "Error getting md5 message digest implementation: " + ex.getMessage(), ex);
michael@0 1245 }
michael@0 1246
michael@0 1247 // Initialize the pad buffers with the key
michael@0 1248 ipad = new byte[64];
michael@0 1249 opad = new byte[64];
michael@0 1250
michael@0 1251 int keyLength = key.length;
michael@0 1252 if (keyLength > 64) {
michael@0 1253 // Use MD5 of the key instead, as described in RFC 2104
michael@0 1254 md5.update(key);
michael@0 1255 key = md5.digest();
michael@0 1256 keyLength = key.length;
michael@0 1257 }
michael@0 1258 int i = 0;
michael@0 1259 while (i < keyLength) {
michael@0 1260 ipad[i] = (byte) (key[i] ^ (byte) 0x36);
michael@0 1261 opad[i] = (byte) (key[i] ^ (byte) 0x5c);
michael@0 1262 i++;
michael@0 1263 }
michael@0 1264 while (i < 64) {
michael@0 1265 ipad[i] = (byte) 0x36;
michael@0 1266 opad[i] = (byte) 0x5c;
michael@0 1267 i++;
michael@0 1268 }
michael@0 1269
michael@0 1270 // Very important: update the digest with the ipad buffer
michael@0 1271 md5.reset();
michael@0 1272 md5.update(ipad);
michael@0 1273
michael@0 1274 }
michael@0 1275
michael@0 1276 /** Grab the current digest. This is the "answer". */
michael@0 1277 byte[] getOutput() {
michael@0 1278 byte[] digest = md5.digest();
michael@0 1279 md5.update(opad);
michael@0 1280 return md5.digest(digest);
michael@0 1281 }
michael@0 1282
michael@0 1283 /** Update by adding a complete array */
michael@0 1284 void update(byte[] input) {
michael@0 1285 md5.update(input);
michael@0 1286 }
michael@0 1287
michael@0 1288 /** Update the algorithm */
michael@0 1289 void update(byte[] input, int offset, int length) {
michael@0 1290 md5.update(input, offset, length);
michael@0 1291 }
michael@0 1292
michael@0 1293 }
michael@0 1294
michael@0 1295 public String generateType1Msg(
michael@0 1296 final String domain,
michael@0 1297 final String workstation) throws NTLMEngineException {
michael@0 1298 return getType1Message(workstation, domain);
michael@0 1299 }
michael@0 1300
michael@0 1301 public String generateType3Msg(
michael@0 1302 final String username,
michael@0 1303 final String password,
michael@0 1304 final String domain,
michael@0 1305 final String workstation,
michael@0 1306 final String challenge) throws NTLMEngineException {
michael@0 1307 Type2Message t2m = new Type2Message(challenge);
michael@0 1308 return getType3Message(
michael@0 1309 username,
michael@0 1310 password,
michael@0 1311 workstation,
michael@0 1312 domain,
michael@0 1313 t2m.getChallenge(),
michael@0 1314 t2m.getFlags(),
michael@0 1315 t2m.getTarget(),
michael@0 1316 t2m.getTargetInfo());
michael@0 1317 }
michael@0 1318
michael@0 1319 }

mercurial