/* * Copyright (c) 2016 Dell EMC Software * All Rights Reserved */ package com.iwave.ext.windows.winrm.ntlm.state; import org.apache.commons.codec.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpClientConnection; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.auth.AuthScope; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpCoreContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.iwave.ext.windows.winrm.ntlm.NTLMCrypt; import com.iwave.ext.windows.winrm.ntlm.NTLMMessage; import com.iwave.ext.windows.winrm.ntlm.NTLMType3Message; import com.iwave.ext.windows.winrm.ntlm.NTLMUtils; import com.iwave.ext.windows.winrm.ntlm.TargetInformation; /** * This is the state that we will be in immediately after sending after receiving a type 2 message and prior to sending a * type 3 message. It will only accept an HttpRequest that has an NTLM type 3 message. * */ public final class SendingType3State extends NTLMState { /** * The logger for this class. */ private static final Logger LOG = LoggerFactory.getLogger(SendingType3State.class); /** The bytes from the type 1 message as they appeared on the wire. */ private byte[] msg1; /** The bytes from the type 2 message as they appeared on the wire. */ private byte[] msg2; /** The server challenge. */ private byte[] challenge; /** The target information from the server. */ private TargetInformation information; /** The next state. */ private NTLMState next; /** * Constructor for the type 3 message. * * @param msg1 * the type 1 message * @param msg2 * the type 2 message * @param challenge * the challenge from the server * @param information * the target information from the server */ public SendingType3State(byte[] msg1, byte[] msg2, byte[] challenge, TargetInformation information) { this.msg1 = msg1; this.msg2 = msg2; this.challenge = challenge; this.information = information; } @Override public boolean accepts(HttpRequest request) { // Only accept HttpRequests that have an NTLM type 3 message NTLMMessage msg = NTLMUtils.getNTLMMessage(request); if (msg != null && msg.getMessageType() == NTLMType3Message.TYPE) { return true; } return false; } @Override public void handle(HttpRequest request, HttpClientConnection conn, HttpContext context) { try { NTLMType3Message type3 = (NTLMType3Message) NTLMUtils.getNTLMMessage(request); HttpHost host = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST); CredentialsProvider provider = (CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER); String password; String user; String domain = ""; if (host == null) { password = provider.getCredentials(AuthScope.ANY).getPassword(); user = provider.getCredentials(AuthScope.ANY).getUserPrincipal().getName(); } else { password = provider.getCredentials(new AuthScope(host)).getPassword(); user = provider.getCredentials(new AuthScope(host)).getUserPrincipal().getName(); } if (user != null) { // DOMAIN \ USER String[] parts = StringUtils.split(user, '\\'); if (parts.length == 2) { user = parts[1]; domain = parts[0]; } } else { throw new RuntimeException("Anonymous NTLM sessions are not supported. You must provide a user."); } NTLMCrypt crypt; // If the timestamp is empty we make one if (information.get(TargetInformation.MSV_AV_TIMESTAMP) == null) { information.put(TargetInformation.MSV_AV_TIMESTAMP, NTLMUtils.toMicrosoftTimestamp(System.currentTimeMillis())); } // https://msdn.microsoft.com/en-us/library/cc236700.aspx // https://code.google.com/p/ntlm-java/ // The above two links are the only resources I was able to find to help me understand ntlmv2. The microsoft one // isn't as good as the google code one, but the google code one is incomplete, so it's a good place to start // understanding, but then you will need the microsoft one to complete the protocol // This it to indicate we are providing a MIC in the header // https://msdn.microsoft.com/en-us/library/cc236646.aspx information.put(TargetInformation.MSV_AV_FLAGS, NTLMUtils.convertInt(2)); // Need to specify the domain we are targeting, http-client doesn't populate this one type3.setTargetname(domain.getBytes(CharEncoding.UTF_16LE)); // Save the nonce because it comes from the lmresp, which will get overwritten with 0's shortly byte[] nonce = type3.getNonce(); // This is the calculation for the NTLMv2 key, the details of which can be found at // https://msdn.microsoft.com/en-us/library/cc236700.aspx byte[] responseKeyNT = NTLMUtils.calculateHmacMD5(NTLMUtils.md4(password.getBytes(CharEncoding.UTF_16LE)), NTLMUtils.concat(user.toUpperCase().getBytes(CharEncoding.UTF_16LE), domain.getBytes(CharEncoding.UTF_16LE))); byte[] temp = NTLMUtils.concat(new byte[] { 0x01 }, new byte[] { 0x01 }, new byte[6], information.get(TargetInformation.MSV_AV_TIMESTAMP), nonce, new byte[4], information.build()); byte[] ntProofStr = NTLMUtils.calculateHmacMD5(responseKeyNT, NTLMUtils.concat(challenge, temp)); byte[] keyExchangeKey = NTLMUtils.calculateHmacMD5(responseKeyNT, ntProofStr); byte[] rkey = NTLMUtils.rc4(type3.getSessionkey(), keyExchangeKey); byte[] ntChallengeResponse = NTLMUtils.concat(ntProofStr, temp); // NTLMv2 specifies these values for the ntlm and lm resp. Lm must be 0's type3.setNtlmresp(ntChallengeResponse); type3.setLmresp(new byte[24]); // MIC is a message integrity check, and it must be calculated with the MIC set to 0 type3.setMic(new byte[16]); // This is the calculation for the mic, the details of which can be found at // https://technet.microsoft.com/ru-ru/cc236678 type3.setMic(NTLMUtils.calculateHmacMD5(rkey, NTLMUtils.concat(msg1, msg2, type3.toHeaderBytes()))); crypt = new NTLMCrypt(rkey); // This is NTLM v1. Keeping it here in case it's ever needed, but it shouldn't be // Change the header value, as http client is generating it wrong // type3.setNtlmresp(NTLMUtils.getNTLM2SessionResponse(password, challenge, type3.getNonce())); // crypt = new NTLMCrypt( // NTLMUtils.calculateV1Key(challenge, type3.getNonce(), type3.getSessionkey(), password)); // Reset the header request.setHeader(NTLMUtils.buildNtlmHeader(type3)); // If we got to this point, we will be encrypting all communication, including this message EncryptingState state = new EncryptingState(crypt); state.handle(request, conn, context); next = state.getNextState(); } catch (Exception e) { LOG.error("Could not initialize encryption mechanism.", e); next = NewState.INSTANCE; } } @Override public NTLMState getNextState() { return next; } }