1 /*
   2  * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.security.ntlm;
  27 
  28 import static com.sun.security.ntlm.Version.*;
  29 import java.io.IOException;
  30 import java.io.UnsupportedEncodingException;
  31 import java.security.InvalidKeyException;
  32 import java.security.MessageDigest;
  33 import java.security.NoSuchAlgorithmException;
  34 import java.security.spec.InvalidKeySpecException;
  35 import java.util.Arrays;
  36 import java.util.Locale;
  37 import javax.crypto.BadPaddingException;
  38 import javax.crypto.Cipher;
  39 import javax.crypto.IllegalBlockSizeException;
  40 import javax.crypto.Mac;
  41 import javax.crypto.NoSuchPaddingException;
  42 import javax.crypto.SecretKey;
  43 import javax.crypto.SecretKeyFactory;
  44 import javax.crypto.spec.DESKeySpec;
  45 import javax.crypto.spec.SecretKeySpec;
  46 
  47 /**
  48  * NTLM authentication implemented according to MS-NLMP, version 12.1
  49  * @since 1.7
  50  */
  51 class NTLM {
  52 
  53     private final SecretKeyFactory fac;
  54     private final Cipher cipher;
  55     private final MessageDigest md4;
  56     private final Mac hmac;
  57     private final MessageDigest md5;
  58     private static final boolean DEBUG =
  59             java.security.AccessController.doPrivileged(
  60                     new sun.security.action.GetBooleanAction("ntlm.debug"))
  61                         .booleanValue();
  62 
  63     final Version v;
  64 
  65     final boolean writeLM;
  66     final boolean writeNTLM;
  67 
  68     protected NTLM(String version) throws NTLMException {
  69         if (version == null) version = "LMv2/NTLMv2";
  70         switch (version) {
  71             case "LM": v = NTLM; writeLM = true; writeNTLM = false; break;
  72             case "NTLM": v = NTLM; writeLM = false; writeNTLM = true; break;
  73             case "LM/NTLM": v = NTLM; writeLM = writeNTLM = true; break;
  74             case "NTLM2": v = NTLM2; writeLM = writeNTLM = true; break;
  75             case "LMv2": v = NTLMv2; writeLM = true; writeNTLM = false; break;
  76             case "NTLMv2": v = NTLMv2; writeLM = false; writeNTLM = true; break;
  77             case "LMv2/NTLMv2": v = NTLMv2; writeLM = writeNTLM = true; break;
  78             default: throw new NTLMException(NTLMException.BAD_VERSION,
  79                     "Unknown version " + version);
  80         }
  81         try {
  82             fac = SecretKeyFactory.getInstance ("DES");
  83             cipher = Cipher.getInstance ("DES/ECB/NoPadding");
  84             md4 = sun.security.provider.MD4.getInstance();
  85             hmac = Mac.getInstance("HmacMD5");
  86             md5 = MessageDigest.getInstance("MD5");
  87         } catch (NoSuchPaddingException e) {
  88             throw new AssertionError();
  89         } catch (NoSuchAlgorithmException e) {
  90             throw new AssertionError();
  91         }
  92     }
  93 
  94     /**
  95      * Prints out a formatted string, called in various places inside then NTLM
  96      * implementation for debugging/logging purposes. When the system property
  97      * "ntlm.debug" is set, <code>System.out.printf(format, args)</code> is
  98      * called. This method is designed to be overridden by child classes to
  99      * match their own debugging/logging mechanisms.
 100      * @param format a format string
 101      * @param args the arguments referenced by <code>format</code>
 102      * @see java.io.PrintStream#printf(java.lang.String, java.lang.Object[])
 103      */
 104     public void debug(String format, Object... args) {
 105         if (DEBUG) {
 106             System.out.printf(format, args);
 107         }
 108     }
 109 
 110     /**
 111      * Prints out the content of a byte array, called in various places inside
 112      * the NTLM implementation for debugging/logging purposes. When the system
 113      * property "ntlm.debug" is set, the hexdump of the array is printed into
 114      * System.out. This method is designed to be overridden by child classes to
 115      * match their own debugging/logging mechanisms.
 116      * @param bytes the byte array to print out
 117      */
 118     public void debug(byte[] bytes) {
 119         if (DEBUG) {
 120             try {
 121                 new sun.misc.HexDumpEncoder().encodeBuffer(bytes, System.out);
 122             } catch (IOException ioe) {
 123                 // Impossible
 124             }
 125         }
 126     }
 127 
 128     /**
 129      * Reading an NTLM packet
 130      */
 131     static class Reader {
 132 
 133         private final byte[] internal;
 134 
 135         Reader(byte[] data) {
 136             internal = data;
 137         }
 138 
 139         int readInt(int offset) throws NTLMException {
 140             try {
 141                 return (internal[offset] & 0xff) +
 142                         ((internal[offset+1] & 0xff) << 8) +
 143                         ((internal[offset+2] & 0xff) << 16) +
 144                         ((internal[offset+3] & 0xff) << 24);
 145             } catch (ArrayIndexOutOfBoundsException ex) {
 146                 throw new NTLMException(NTLMException.PACKET_READ_ERROR,
 147                         "Input message incorrect size");
 148             }
 149         }
 150 
 151         int readShort(int offset) throws NTLMException {
 152             try {
 153                 return (internal[offset] & 0xff) +
 154                         ((internal[offset+1] & 0xff << 8));
 155             } catch (ArrayIndexOutOfBoundsException ex) {
 156                 throw new NTLMException(NTLMException.PACKET_READ_ERROR,
 157                         "Input message incorrect size");
 158             }
 159         }
 160 
 161         byte[] readBytes(int offset, int len) throws NTLMException {
 162             try {
 163                 return Arrays.copyOfRange(internal, offset, offset + len);
 164             } catch (ArrayIndexOutOfBoundsException ex) {
 165                 throw new NTLMException(NTLMException.PACKET_READ_ERROR,
 166                         "Input message incorrect size");
 167             }
 168         }
 169 
 170         byte[] readSecurityBuffer(int offset) throws NTLMException {
 171             int pos = readInt(offset+4);
 172             if (pos == 0) return null;
 173             try {
 174                 return Arrays.copyOfRange(
 175                         internal, pos, pos + readShort(offset));
 176             } catch (ArrayIndexOutOfBoundsException ex) {
 177                 throw new NTLMException(NTLMException.PACKET_READ_ERROR,
 178                         "Input message incorrect size");
 179             }
 180         }
 181 
 182         String readSecurityBuffer(int offset, boolean unicode)
 183                 throws NTLMException {
 184             byte[] raw = readSecurityBuffer(offset);
 185             try {
 186                 return raw == null ? null : new String(
 187                         raw, unicode ? "UnicodeLittleUnmarked" : "ISO8859_1");
 188             } catch (UnsupportedEncodingException ex) {
 189                 throw new NTLMException(NTLMException.PACKET_READ_ERROR,
 190                         "Invalid input encoding");
 191             }
 192         }
 193     }
 194 
 195     /**
 196      * Writing an NTLM packet
 197      */
 198     static class Writer {
 199 
 200         private byte[] internal;    // buffer
 201         private int current;        // current written content interface buffer
 202 
 203         /**
 204          * Starts writing a NTLM packet
 205          * @param type NEGOTIATE || CHALLENGE || AUTHENTICATE
 206          * @param len the base length, without security buffers
 207          */
 208         Writer(int type, int len) {
 209             assert len < 256;
 210             internal = new byte[256];
 211             current = len;
 212             System.arraycopy (
 213                     new byte[] {'N','T','L','M','S','S','P',0,(byte)type},
 214                     0, internal, 0, 9);
 215         }
 216 
 217         void writeShort(int offset, int number) {
 218             internal[offset] = (byte)(number);
 219             internal[offset+1] = (byte)(number >> 8);
 220         }
 221 
 222         void writeInt(int offset, int number) {
 223             internal[offset] = (byte)(number);
 224             internal[offset+1] = (byte)(number >> 8);
 225             internal[offset+2] = (byte)(number >> 16);
 226             internal[offset+3] = (byte)(number >> 24);
 227         }
 228 
 229         void writeBytes(int offset, byte[] data) {
 230             System.arraycopy(data, 0, internal, offset, data.length);
 231         }
 232 
 233         void writeSecurityBuffer(int offset, byte[] data) {
 234             if (data == null) {
 235                 writeShort(offset+4, current);
 236             } else {
 237                 int len = data.length;
 238                 if (current + len > internal.length) {
 239                     internal = Arrays.copyOf(internal, current + len + 256);
 240                 }
 241                 writeShort(offset, len);
 242                 writeShort(offset+2, len);
 243                 writeShort(offset+4, current);
 244                 System.arraycopy(data, 0, internal, current, len);
 245                 current += len;
 246             }
 247         }
 248 
 249         void writeSecurityBuffer(int offset, String str, boolean unicode) {
 250             try {
 251                 writeSecurityBuffer(offset, str == null ? null : str.getBytes(
 252                         unicode ? "UnicodeLittleUnmarked" : "ISO8859_1"));
 253             } catch (UnsupportedEncodingException ex) {
 254                 assert false;
 255             }
 256         }
 257 
 258         byte[] getBytes() {
 259             return Arrays.copyOf(internal, current);
 260         }
 261     }
 262 
 263     // LM/NTLM
 264 
 265     /* Convert a 7 byte array to an 8 byte array (for a des key with parity)
 266      * input starts at offset off
 267      */
 268     byte[] makeDesKey (byte[] input, int off) {
 269         int[] in = new int [input.length];
 270         for (int i=0; i<in.length; i++ ) {
 271             in[i] = input[i]<0 ? input[i]+256: input[i];
 272         }
 273         byte[] out = new byte[8];
 274         out[0] = (byte)in[off+0];
 275         out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
 276         out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
 277         out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
 278         out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
 279         out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
 280         out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
 281         out[7] = (byte)((in[off+6] << 1) & 0xFF);
 282         return out;
 283     }
 284 
 285     byte[] calcLMHash (byte[] pwb) {
 286         byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
 287         byte[] pwb1 = new byte [14];
 288         int len = pwb.length;
 289         if (len > 14)
 290             len = 14;
 291         System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */
 292 
 293         try {
 294             DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));
 295             DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));
 296 
 297             SecretKey key1 = fac.generateSecret (dks1);
 298             SecretKey key2 = fac.generateSecret (dks2);
 299             cipher.init (Cipher.ENCRYPT_MODE, key1);
 300             byte[] out1 = cipher.doFinal (magic, 0, 8);
 301             cipher.init (Cipher.ENCRYPT_MODE, key2);
 302             byte[] out2 = cipher.doFinal (magic, 0, 8);
 303             byte[] result = new byte [21];
 304             System.arraycopy (out1, 0, result, 0, 8);
 305             System.arraycopy (out2, 0, result, 8, 8);
 306             return result;
 307         } catch (InvalidKeyException ive) {
 308             // Will not happen, all key material are 8 bytes
 309             assert false;
 310         } catch (InvalidKeySpecException ikse) {
 311             // Will not happen, we only feed DESKeySpec to DES factory
 312             assert false;
 313         } catch (IllegalBlockSizeException ibse) {
 314             // Will not happen, we encrypt 8 bytes
 315             assert false;
 316         } catch (BadPaddingException bpe) {
 317             // Will not happen, this is encryption
 318             assert false;
 319         }
 320         return null;    // will not happen, we returned already
 321     }
 322 
 323     byte[] calcNTHash (byte[] pw) {
 324         byte[] out = md4.digest (pw);
 325         byte[] result = new byte [21];
 326         System.arraycopy (out, 0, result, 0, 16);
 327         return result;
 328     }
 329 
 330     /* key is a 21 byte array. Split it into 3 7 byte chunks,
 331      * Convert each to 8 byte DES keys, encrypt the text arg with
 332      * each key and return the three results in a sequential []
 333      */
 334     byte[] calcResponse (byte[] key, byte[] text) {
 335         try {
 336             assert key.length == 21;
 337             DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
 338             DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
 339             DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
 340             SecretKey key1 = fac.generateSecret(dks1);
 341             SecretKey key2 = fac.generateSecret(dks2);
 342             SecretKey key3 = fac.generateSecret(dks3);
 343             cipher.init(Cipher.ENCRYPT_MODE, key1);
 344             byte[] out1 = cipher.doFinal(text, 0, 8);
 345             cipher.init(Cipher.ENCRYPT_MODE, key2);
 346             byte[] out2 = cipher.doFinal(text, 0, 8);
 347             cipher.init(Cipher.ENCRYPT_MODE, key3);
 348             byte[] out3 = cipher.doFinal(text, 0, 8);
 349             byte[] result = new byte[24];
 350             System.arraycopy(out1, 0, result, 0, 8);
 351             System.arraycopy(out2, 0, result, 8, 8);
 352             System.arraycopy(out3, 0, result, 16, 8);
 353             return result;
 354         } catch (IllegalBlockSizeException ex) {    // None will happen
 355             assert false;
 356         } catch (BadPaddingException ex) {
 357             assert false;
 358         } catch (InvalidKeySpecException ex) {
 359             assert false;
 360         } catch (InvalidKeyException ex) {
 361             assert false;
 362         }
 363         return null;
 364     }
 365 
 366     // LMv2/NTLMv2
 367 
 368     byte[] hmacMD5(byte[] key, byte[] text) {
 369         try {
 370             SecretKeySpec skey =
 371                     new SecretKeySpec(Arrays.copyOf(key, 16), "HmacMD5");
 372             hmac.init(skey);
 373             return hmac.doFinal(text);
 374         } catch (InvalidKeyException ex) {
 375             assert false;
 376         } catch (RuntimeException e) {
 377             assert false;
 378         }
 379         return null;
 380     }
 381 
 382     byte[] calcV2(byte[] nthash, String text, byte[] blob, byte[] challenge) {
 383         try {
 384             byte[] ntlmv2hash = hmacMD5(nthash,
 385                     text.getBytes("UnicodeLittleUnmarked"));
 386             byte[] cn = new byte[blob.length+8];
 387             System.arraycopy(challenge, 0, cn, 0, 8);
 388             System.arraycopy(blob, 0, cn, 8, blob.length);
 389             byte[] result = new byte[16+blob.length];
 390             System.arraycopy(hmacMD5(ntlmv2hash, cn), 0, result, 0, 16);
 391             System.arraycopy(blob, 0, result, 16, blob.length);
 392             return result;
 393         } catch (UnsupportedEncodingException ex) {
 394             assert false;
 395         }
 396         return null;
 397     }
 398 
 399     // NTLM2 LM/NTLM
 400 
 401     static byte[] ntlm2LM(byte[] nonce) {
 402         return Arrays.copyOf(nonce, 24);
 403     }
 404 
 405     byte[] ntlm2NTLM(byte[] ntlmHash, byte[] nonce, byte[] challenge) {
 406         byte[] b = Arrays.copyOf(challenge, 16);
 407         System.arraycopy(nonce, 0, b, 8, 8);
 408         byte[] sesshash = Arrays.copyOf(md5.digest(b), 8);
 409         return calcResponse(ntlmHash, sesshash);
 410     }
 411 
 412     // Password in ASCII and UNICODE
 413 
 414     static byte[] getP1(char[] password) {
 415         try {
 416             return new String(password).toUpperCase(
 417                                     Locale.ENGLISH).getBytes("ISO8859_1");
 418         } catch (UnsupportedEncodingException ex) {
 419             return null;
 420         }
 421     }
 422 
 423     static byte[] getP2(char[] password) {
 424         try {
 425             return new String(password).getBytes("UnicodeLittleUnmarked");
 426         } catch (UnsupportedEncodingException ex) {
 427             return null;
 428         }
 429     }
 430 }