| /* | |
| * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package com.sun.security.ntlm; | |
| import java.math.BigInteger; | |
| import java.util.Arrays; | |
| import java.util.Date; | |
| import java.util.Locale; | |
| /** | |
| * The NTLM client. Not multi-thread enabled.<p> | |
| * Example: | |
| * <pre> | |
| * Client client = new Client(null, "host", "dummy", | |
| * "REALM", "t0pSeCr3t".toCharArray()); | |
| * byte[] type1 = client.type1(); | |
| * // Send type1 to server and receive response as type2 | |
| * byte[] type3 = client.type3(type2, nonce); | |
| * // Send type3 to server | |
| * </pre> | |
| */ | |
| public final class Client extends NTLM { | |
| private final String hostname; | |
| private final String username; | |
| private String domain; | |
| private byte[] pw1, pw2; | |
| /** | |
| * Creates an NTLM Client instance. | |
| * @param version the NTLM version to use, which can be: | |
| * <ul> | |
| * <li>LM/NTLM: Original NTLM v1 | |
| * <li>LM: Original NTLM v1, LM only | |
| * <li>NTLM: Original NTLM v1, NTLM only | |
| * <li>NTLM2: NTLM v1 with Client Challenge | |
| * <li>LMv2/NTLMv2: NTLM v2 | |
| * <li>LMv2: NTLM v2, LM only | |
| * <li>NTLMv2: NTLM v2, NTLM only | |
| * </ul> | |
| * If null, "LMv2/NTLMv2" will be used. | |
| * @param hostname hostname of the client, can be null | |
| * @param username username to be authenticated, must not be null | |
| * @param domain domain of {@code username}, can be null | |
| * @param password password for {@code username}, must not be not null. | |
| * This method does not make any modification to this parameter, it neither | |
| * needs to access the content of this parameter after this method call, | |
| * so you are free to modify or nullify this parameter after this call. | |
| * @throws NTLMException if {@code username} or {@code password} is null, | |
| * or {@code version} is illegal. | |
| * | |
| */ | |
| public Client(String version, String hostname, String username, | |
| String domain, char[] password) throws NTLMException { | |
| super(version); | |
| if ((username == null || password == null)) { | |
| throw new NTLMException(NTLMException.PROTOCOL, | |
| "username/password cannot be null"); | |
| } | |
| this.hostname = hostname; | |
| this.username = username; | |
| this.domain = domain == null ? "" : domain; | |
| this.pw1 = getP1(password); | |
| this.pw2 = getP2(password); | |
| debug("NTLM Client: (h,u,t,version(v)) = (%s,%s,%s,%s(%s))\n", | |
| hostname, username, domain, version, v.toString()); | |
| } | |
| /** | |
| * Generates the Type 1 message | |
| * @return the message generated | |
| */ | |
| public byte[] type1() { | |
| Writer p = new Writer(1, 32); | |
| // Negotiate always sign, Negotiate NTLM, | |
| // Request Target, Negotiate OEM, Negotiate unicode | |
| int flags = 0x8207; | |
| if (v != Version.NTLM) { | |
| flags |= 0x80000; | |
| } | |
| p.writeInt(12, flags); | |
| debug("NTLM Client: Type 1 created\n"); | |
| debug(p.getBytes()); | |
| return p.getBytes(); | |
| } | |
| /** | |
| * Generates the Type 3 message | |
| * @param type2 the responding Type 2 message from server, must not be null | |
| * @param nonce random 8-byte array to be used in message generation, | |
| * must not be null except for original NTLM v1 | |
| * @return the message generated | |
| * @throws NTLMException if the incoming message is invalid, or | |
| * {@code nonce} is null for NTLM v1. | |
| */ | |
| public byte[] type3(byte[] type2, byte[] nonce) throws NTLMException { | |
| if (type2 == null || (v != Version.NTLM && nonce == null) || | |
| (nonce != null && nonce.length != 8)) { | |
| throw new NTLMException(NTLMException.PROTOCOL, | |
| "type2 cannot be null, and nonce must be 8-byte long"); | |
| } | |
| debug("NTLM Client: Type 2 received\n"); | |
| debug(type2); | |
| Reader r = new Reader(type2); | |
| byte[] challenge = r.readBytes(24, 8); | |
| int inputFlags = r.readInt(20); | |
| boolean unicode = (inputFlags & 1) == 1; | |
| // IE uses domainFromServer to generate an alist if server has not | |
| // provided one. Firefox/WebKit do not. Neither do we. | |
| //String domainFromServer = r.readSecurityBuffer(12, unicode); | |
| int flags = 0x88200 | (inputFlags & 3); | |
| Writer p = new Writer(3, 64); | |
| byte[] lm = null, ntlm = null; | |
| p.writeSecurityBuffer(28, domain, unicode); | |
| p.writeSecurityBuffer(36, username, unicode); | |
| p.writeSecurityBuffer(44, hostname, unicode); | |
| if (v == Version.NTLM) { | |
| byte[] lmhash = calcLMHash(pw1); | |
| byte[] nthash = calcNTHash(pw2); | |
| if (writeLM) lm = calcResponse (lmhash, challenge); | |
| if (writeNTLM) ntlm = calcResponse (nthash, challenge); | |
| } else if (v == Version.NTLM2) { | |
| byte[] nthash = calcNTHash(pw2); | |
| lm = ntlm2LM(nonce); | |
| ntlm = ntlm2NTLM(nthash, nonce, challenge); | |
| } else { | |
| byte[] nthash = calcNTHash(pw2); | |
| if (writeLM) lm = calcV2(nthash, | |
| username.toUpperCase(Locale.US)+domain, nonce, challenge); | |
| if (writeNTLM) { | |
| // Some client create a alist even if server does not send | |
| // one: (i16)2 (i16)len target_in_unicode (i16)0 (i16) 0 | |
| byte[] alist = ((inputFlags & 0x800000) != 0) ? | |
| r.readSecurityBuffer(40) : new byte[0]; | |
| byte[] blob = new byte[32+alist.length]; | |
| System.arraycopy(new byte[]{1,1,0,0,0,0,0,0}, 0, blob, 0, 8); | |
| // TS | |
| byte[] time = BigInteger.valueOf(new Date().getTime()) | |
| .add(new BigInteger("11644473600000")) | |
| .multiply(BigInteger.valueOf(10000)) | |
| .toByteArray(); | |
| for (int i=0; i<time.length; i++) { | |
| blob[8+time.length-i-1] = time[i]; | |
| } | |
| System.arraycopy(nonce, 0, blob, 16, 8); | |
| System.arraycopy(new byte[]{0,0,0,0}, 0, blob, 24, 4); | |
| System.arraycopy(alist, 0, blob, 28, alist.length); | |
| System.arraycopy(new byte[]{0,0,0,0}, 0, | |
| blob, 28+alist.length, 4); | |
| ntlm = calcV2(nthash, username.toUpperCase(Locale.US)+domain, | |
| blob, challenge); | |
| } | |
| } | |
| p.writeSecurityBuffer(12, lm); | |
| p.writeSecurityBuffer(20, ntlm); | |
| p.writeSecurityBuffer(52, new byte[0]); | |
| p.writeInt(60, flags); | |
| debug("NTLM Client: Type 3 created\n"); | |
| debug(p.getBytes()); | |
| return p.getBytes(); | |
| } | |
| /** | |
| * Returns the domain value provided by server after the authentication | |
| * is complete, or the domain value provided by the client before it. | |
| * @return the domain | |
| */ | |
| public String getDomain() { | |
| return domain; | |
| } | |
| /** | |
| * Disposes any password-derived information. | |
| */ | |
| public void dispose() { | |
| Arrays.fill(pw1, (byte)0); | |
| Arrays.fill(pw2, (byte)0); | |
| } | |
| } |