/**
* TLS-Attacker - A Modular Penetration Testing Framework for TLS
*
* Copyright 2014-2016 Ruhr University Bochum / Hackmanit GmbH
*
* Licensed under Apache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*/
package de.rub.nds.tlsattacker.tls.workflow;
import de.rub.nds.tlsattacker.modifiablevariable.ModifiableVariable;
import de.rub.nds.tlsattacker.tls.constants.ConnectionEnd;
import de.rub.nds.tlsattacker.tls.exceptions.ModificationException;
import de.rub.nds.tlsattacker.tls.protocol.ModifiableVariableHolder;
import de.rub.nds.tlsattacker.tls.protocol.ProtocolMessage;
import de.rub.nds.tlsattacker.tls.protocol.ProtocolMessageTypeHolder;
import de.rub.nds.tlsattacker.tls.constants.ProtocolMessageType;
import de.rub.nds.tlsattacker.tls.constants.HandshakeMessageType;
import de.rub.nds.tlsattacker.tls.protocol.handshake.HandshakeMessage;
import java.lang.reflect.Field;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
*
* @author Juraj Somorovsky <juraj.somorovsky@rub.de>
*/
public final class TlsContextAnalyzer {
private static final Logger LOGGER = LogManager.getLogger(TlsContextAnalyzer.class);
public enum AnalyzerResponse {
ALERT,
NO_ALERT,
NO_MODIFICATION
};
private TlsContextAnalyzer() {
}
/**
* Searches for the next protocol message sent by our peer.
*
* @param tlsContext
* @param position
* @return
*/
public static ProtocolMessage getNextProtocolMessageFromPeer(TlsContext tlsContext, int position) {
ConnectionEnd peer = tlsContext.getMyConnectionEnd().getPeer();
for (int i = position; i < tlsContext.getWorkflowTrace().getProtocolMessages().size(); i++) {
ProtocolMessage pm = tlsContext.getWorkflowTrace().getProtocolMessages().get(i);
if (peer == pm.getMessageIssuer()) {
return pm;
}
}
return null;
}
/**
* Checks whether the configured protocol message order was equal to the
* executed protocol message order.
*
* @param tlsContext
* @return true in case the message workflow was same as the executed one or
* if our peer responds with a fatal alert after a protocol message
* modification
*/
public static boolean checkConfiguredProtocolMessagesOrder(TlsContext tlsContext) {
List<ProtocolMessage> protocolMessages = tlsContext.getWorkflowTrace().getProtocolMessages();
List<ProtocolMessageTypeHolder> configuredProtocolMessageOrder = tlsContext.getPreconfiguredProtocolMessages();
int min = (protocolMessages.size() < configuredProtocolMessageOrder.size()) ? protocolMessages.size()
: configuredProtocolMessageOrder.size();
LOGGER.info("The configured message order contains {}, there are {} protocol messages",
configuredProtocolMessageOrder.size(), protocolMessages.size());
for (int i = 0; i < min; i++) {
ProtocolMessageTypeHolder typeWorkflow = new ProtocolMessageTypeHolder(protocolMessages.get(i));
ProtocolMessageTypeHolder typeConfigured = configuredProtocolMessageOrder.get(i);
if (!typeConfigured.equals(typeWorkflow)) {
ProtocolMessage pm = getNextProtocolMessageFromPeer(tlsContext, i - 1);
if (pm.getProtocolMessageType() != ProtocolMessageType.ALERT) {
LOGGER.info("The configured message order was not equal to the executed one. Our peer has NOT "
+ "responded with an Alert. Verify the message flow manually");
return false;
} else {
LOGGER.info("The configured message order was not equal to the executed one, but our peer has "
+ "responded with an Alert, everything seems to go well.");
return true;
}
}
}
LOGGER.info("The configured message order was equal to the executed one");
return true;
}
/**
* Returns true in case the workflow contains a modified message and the
* message, which is then followed by an alert issued by our peer
*
* @param tlsContext
* @return
*/
public static AnalyzerResponse containsAlertAfterModifiedMessage(TlsContext tlsContext) {
int position = getModifiedMessagePosition(tlsContext);
if (position == -1) {
return AnalyzerResponse.NO_MODIFICATION;
} else {
ProtocolMessage pm = getNextProtocolMessageFromPeer(tlsContext, position);
if (pm != null && pm.getProtocolMessageType() == ProtocolMessageType.ALERT) {
return AnalyzerResponse.ALERT;
} else {
return AnalyzerResponse.NO_ALERT;
}
}
}
/**
* Returns true in case the workflow contains a message, which has not been
* sent by our peer and this message is followed by an alert. This test is
* executed only in the handshake messages.
*
* @param tlsContext
* @return
*/
public static AnalyzerResponse containsAlertAfterMissingMessage(TlsContext tlsContext) {
int position = getMissingMessagePosition(tlsContext);
if (position == -1) {
return AnalyzerResponse.NO_MODIFICATION;
} else {
ProtocolMessage pm = getNextProtocolMessageFromPeer(tlsContext, position);
if (pm != null && pm.getProtocolMessageType() == ProtocolMessageType.ALERT) {
return AnalyzerResponse.ALERT;
} else {
return AnalyzerResponse.NO_ALERT;
}
}
}
/**
* Returns true in case the workflow contains a message, which has been sent
* directly after an unexpected message.
*
* @param tlsContext
* @return
*/
public static AnalyzerResponse containsAlertAfterUnexpectedMessage(TlsContext tlsContext) {
int position = getUnexpectedMessagePosition(tlsContext);
if (position == -1) {
return AnalyzerResponse.NO_MODIFICATION;
} else {
ProtocolMessage pm = getNextProtocolMessageFromPeer(tlsContext, position);
if (pm != null && pm.getProtocolMessageType() == ProtocolMessageType.ALERT) {
return AnalyzerResponse.ALERT;
} else {
return AnalyzerResponse.NO_ALERT;
}
}
}
public static boolean containsFullWorkflowWithModifiedMessage(TlsContext tlsContext) {
return containsFullWorkflow(tlsContext) && containsModifiedMessage(tlsContext);
}
public static boolean containsFullWorkflowWithMissingMessage(TlsContext tlsContext) {
return containsFullWorkflow(tlsContext) && containsMissingMessage(tlsContext);
}
public static boolean containsServerFinishedWithModifiedHandshake(TlsContext tlsContext) {
return containsServerFinishedMessage(tlsContext) && containsModifiedHandshake(tlsContext);
}
/**
* The workflow was executed successfully
*
* @param tlsContext
* @return
*/
public static boolean containsFullWorkflow(TlsContext tlsContext) {
List<ProtocolMessage> protocolMessages = tlsContext.getWorkflowTrace().getProtocolMessages();
List<ProtocolMessageTypeHolder> configuredProtocolMessageOrder = tlsContext.getPreconfiguredProtocolMessages();
if (protocolMessages.size() != configuredProtocolMessageOrder.size()) {
return false;
}
for (int i = 0; i < protocolMessages.size(); i++) {
ProtocolMessage pm = protocolMessages.get(i);
ProtocolMessageTypeHolder typeConfigured = configuredProtocolMessageOrder.get(i);
if (!typeConfigured.equals(new ProtocolMessageTypeHolder(pm))) {
return false;
}
}
return true;
}
/**
* Returns true in case there is a message with a modification issued by our
* peer
*
* @param tlsContext
* @return
*/
public static boolean containsModifiedMessage(TlsContext tlsContext) {
return (getModifiedMessagePosition(tlsContext) != -1);
}
private static int getModifiedMessagePosition(TlsContext tlsContext) {
int position = 0;
for (ProtocolMessage pm : tlsContext.getWorkflowTrace().getProtocolMessages()) {
if ((pm.getMessageIssuer() == tlsContext.getMyConnectionEnd())
&& (containsModifiableVariableModification(pm))) {
return position;
}
position++;
}
return -1;
}
/**
* Returns true in case there is a modification in the handshake
*
* @param tlsContext
* @return
*/
public static boolean containsModifiedHandshake(TlsContext tlsContext) {
int unexpected = getUnexpectedMessagePosition(tlsContext);
int finished = getServerFinishedMessagePosition(tlsContext);
if (unexpected != -1) {
if (finished == -1) {
return true;
} else {
return unexpected < finished;
}
}
return false;
}
/**
* Returns true in case the workflow contains a message, which was
* configured, but is not going to be sent by our peer. It considers only
* Handshake and CCS messages
*
* @param tlsContext
* @return
*/
public static boolean containsMissingMessage(TlsContext tlsContext) {
return (getMissingMessagePosition(tlsContext) != -1);
}
private static int getMissingMessagePosition(TlsContext tlsContext) {
int position = 0;
for (ProtocolMessage pm : tlsContext.getWorkflowTrace().getProtocolMessages()) {
if ((pm.getMessageIssuer() == tlsContext.getMyConnectionEnd())
&& (!pm.isGoingToBeSent())
&& (pm.getProtocolMessageType() == ProtocolMessageType.HANDSHAKE || pm.getProtocolMessageType() == ProtocolMessageType.CHANGE_CIPHER_SPEC)) {
return position;
}
position++;
}
return -1;
}
/**
* Returns true in case the workflow contains an unexpected message issued
* by our peer
*
* @param tlsContext
* @return
*/
public static boolean containsUnexpectedMessage(TlsContext tlsContext) {
return (getUnexpectedMessagePosition(tlsContext) != -1);
}
private static int getUnexpectedMessagePosition(TlsContext tlsContext) {
List<ProtocolMessage> protocolMessages = tlsContext.getWorkflowTrace().getProtocolMessages();
List<ProtocolMessageTypeHolder> configuredProtocolMessageOrder = tlsContext.getPreconfiguredProtocolMessages();
int min = (protocolMessages.size() < configuredProtocolMessageOrder.size()) ? protocolMessages.size()
: configuredProtocolMessageOrder.size();
for (int i = 0; i < min; i++) {
ProtocolMessage pm = protocolMessages.get(i);
ProtocolMessageTypeHolder typeConfigured = configuredProtocolMessageOrder.get(i);
if ((pm.getMessageIssuer() == tlsContext.getMyConnectionEnd())
&& (!typeConfigured.equals(new ProtocolMessageTypeHolder(pm)))) {
return i;
}
}
return -1;
}
/**
* Returns true in case the workflow a server Finished Message
*
* @param tlsContext
* @return
*/
public static boolean containsServerFinishedMessage(TlsContext tlsContext) {
return (getServerFinishedMessagePosition(tlsContext) != -1);
}
private static int getServerFinishedMessagePosition(TlsContext tlsContext) {
List<ProtocolMessage> protocolMessages = tlsContext.getWorkflowTrace().getProtocolMessages();
for (int i = 0; i < protocolMessages.size(); i++) {
ProtocolMessage pm = protocolMessages.get(i);
if ((pm.getMessageIssuer() != tlsContext.getMyConnectionEnd())
&& (pm.getProtocolMessageType() == ProtocolMessageType.HANDSHAKE)) {
HandshakeMessage hm = (HandshakeMessage) pm;
if (hm.getHandshakeMessageType() == HandshakeMessageType.FINISHED) {
return i;
}
}
}
return -1;
}
/**
* Analyzes the modifiable variable holder and returns true in case this
* holder contains a modification
*
* @param object
* @return
*/
public static boolean containsModifiableVariableModification(ProtocolMessage object) {
for (ModifiableVariableHolder holder : object.getAllModifiableVariableHolders()) {
for (Field f : holder.getAllModifiableVariableFields()) {
if (containsModifiableVariableModification(holder, f)) {
return true;
}
}
}
return false;
}
/**
* Analyzes the modifiable variable holder and a specific field and returns
* true in case this holder contains a modification in the given field
*
* @param object
* @param field
* @return
*/
private static boolean containsModifiableVariableModification(ModifiableVariableHolder object, Field field) {
try {
field.setAccessible(true);
ModifiableVariable mv = (ModifiableVariable) field.get(object);
return (mv != null && mv.getModification() != null && mv.isOriginalValueModified());
} catch (IllegalAccessException | IllegalArgumentException ex) {
throw new ModificationException(ex.getLocalizedMessage(), ex);
}
}
}