/*
* Copyright (c) 2016 Dell EMC Software
* All Rights Reserved
*/
package com.iwave.ext.windows.winrm.encryption;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpMessage;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpRequestExecutor;
import com.iwave.ext.windows.winrm.ntlm.NTLMEncryptedRequestHandler;
/**
* This class takes the Http Request and puts it on the wire. It is at this point that we determine if we want to
* encrypt/decrypt the messages. The basic flow for messages is (we are client):
*
* <pre>
* Client -----Request-----> Host
* Client <-----401--------- Host
* Client --Initiate Auth--> Host
* ... Details specific to the auth implementation ...
* </pre>
*
* Within the 401 message will be a header indicating the types of negotiation that are available. Based on this we can
* determine how/if the messages will be encrypted.
*
* @author Jason Forand
*
*/
public class EncryptedHttpRequestExecutor extends HttpRequestExecutor {
/**
* Since there is no guarantee that this executor will be recreated/reinitialized for each host we connect to, we need to
* keep one request handler for each connection.
*/
private Map<Object, EncryptedRequestHandler> handlers = new HashMap<Object, EncryptedRequestHandler>();
/**
* Determines whether the communication should be encrypted or not. Setting this to false is only recommended if there is
* some sort of bug that arises in production.
*/
private static final boolean ENCRYPTED = Boolean.parseBoolean(System.getProperty("http.ntlm.encrypted", "true"));
@Override
protected HttpResponse doSendRequest(HttpRequest request, HttpClientConnection conn, HttpContext context)
throws IOException, HttpException {
if (ENCRYPTED) {
EncryptedRequestHandler handler = getHandler(request, conn, context);
if (handler != null) {
handler.alterRequest(request, conn, context);
}
}
return super.doSendRequest(request, conn, context);
}
@Override
protected HttpResponse doReceiveResponse(HttpRequest request, HttpClientConnection conn, HttpContext context)
throws HttpException, IOException {
HttpResponse response = super.doReceiveResponse(request, conn, context);
if (ENCRYPTED) {
EncryptedRequestHandler handler = getHandler(response, conn, context);
if (handler != null) {
handler.alterResponse(response, conn, context);
}
}
return response;
}
/**
* Retrieves the handler for the connection, or creates a new one.
*
* @param message
* the message to process
* @param conn
* the connection to retrieve the handler for
* @param context
* the context to retrieve the handler for
* @return an encrypted request handler that can process the message, or null if one doesn't exist
*/
private EncryptedRequestHandler getHandler(HttpMessage message, HttpClientConnection conn, HttpContext context) {
// This is our guarantee of unicity. We are unable to use connection, because it changes occasionally.
HttpHost host = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
if (host == null) {
throw new RuntimeException("Context does not contain a host.");
}
EncryptedRequestHandler handler = handlers.get(host);
if (handler == null || !handler.accepts(message)) {
handler = null;
handlers.remove(host);
for (EncryptedRequestHandler h : getAvailableHandlers()) {
if (h.accepts(message)) {
handler = h;
handlers.put(host, handler);
break;
}
}
}
return handler;
}
/**
* Retrieves all of the possible virgin request handlers.
*
* @return the request handlers
*/
private EncryptedRequestHandler[] getAvailableHandlers() {
// Each of our handlers is stateful, so in the event that they can accept the message, we need a new instance of the
// handler in order to start preserving state, rather than just a static method to check if the message is acceptable
return new EncryptedRequestHandler[] { new NTLMEncryptedRequestHandler() };
}
}