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