Wed, 31 Dec 2014 07:22:50 +0100
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 | } |