Skip to content

Create and Use InformationLeakTest.getRareResponses (#371) #142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
*/
package de.rub.nds.tlsscanner.core.vector.statistics;

import de.rub.nds.tlsscanner.core.vector.Vector;
import de.rub.nds.tlsscanner.core.vector.VectorResponse;
import de.rub.nds.tlsscanner.core.vector.response.ResponseFingerprint;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.math3.distribution.ChiSquaredDistribution;
import org.apache.commons.math3.stat.inference.ChiSquareTest;
Expand Down Expand Up @@ -119,4 +123,42 @@ protected boolean isFisherExactUsable() {
}
return responseFingerprintSet.size() <= 2;
}

/**
* Get responses that occurred at most {@code mostOccurrences} times across all vectors.
*
* @param mostOccurrences Maximum number of occurrences for a response to be considered rare
* @return List of VectorResponse objects representing rare responses
*/
public List<VectorResponse> getRareResponses(int mostOccurrences) {
// Map from ResponseFingerprint to all vectors that produced this fingerprint
Map<ResponseFingerprint, List<Vector>> fingerprintToVectors = new HashMap<>();
// Map from ResponseFingerprint to total count across all vectors
Map<ResponseFingerprint, Integer> fingerprintTotalCount = new HashMap<>();

for (VectorContainer container : getVectorContainerList()) {
for (ResponseCounter counter : container.getDistinctResponsesCounterList()) {
ResponseFingerprint fingerprint = counter.getFingerprint();
fingerprintToVectors.computeIfAbsent(fingerprint, k -> new ArrayList<>());
if (!fingerprintToVectors.get(fingerprint).contains(container.getVector())) {
fingerprintToVectors.get(fingerprint).add(container.getVector());
}
fingerprintTotalCount.merge(fingerprint, counter.getCounter(), Integer::sum);
}
}

List<VectorResponse> ret = new ArrayList<>();
for (Map.Entry<ResponseFingerprint, List<Vector>> entry : fingerprintToVectors.entrySet()) {
ResponseFingerprint fingerprint = entry.getKey();
List<Vector> vectors = entry.getValue();
Integer totalCount = fingerprintTotalCount.get(fingerprint);

if (totalCount != null && totalCount <= mostOccurrences) {
for (Vector vector : vectors) {
ret.add(new VectorResponse(vector, fingerprint));
}
}
}
return ret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* TLS-Scanner - A TLS configuration and analysis tool based on TLS-Attacker
*
* Copyright 2017-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
*
* Licensed under Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package de.rub.nds.tlsscanner.core.vector.statistics;

import static org.junit.jupiter.api.Assertions.*;

import de.rub.nds.tlsattacker.transport.socket.SocketState;
import de.rub.nds.tlsscanner.core.vector.Vector;
import de.rub.nds.tlsscanner.core.vector.VectorResponse;
import de.rub.nds.tlsscanner.core.vector.response.ResponseFingerprint;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;

public class InformationLeakTestTest {

private static class TestVector implements Vector {
private final String name;

public TestVector(String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}

@Override
public boolean equals(Object obj) {
if (obj instanceof TestVector) {
return name.equals(((TestVector) obj).name);
}
return false;
}

@Override
public int hashCode() {
return name.hashCode();
}
}

private static class SimpleTestInfo extends TestInfo {
@Override
public String getTechnicalName() {
return "SimpleTest";
}

@Override
public List<String> getFieldNames() {
return List.of();
}

@Override
public List<String> getFieldValues() {
return List.of();
}

@Override
public String getPrintableName() {
return "Simple Test";
}

@Override
public boolean equals(Object o) {
return o instanceof SimpleTestInfo;
}

@Override
public int hashCode() {
return getTechnicalName().hashCode();
}
}

@Test
public void testGetRareResponsesWithNoResponses() {
List<VectorResponse> responses = new ArrayList<>();
InformationLeakTest<SimpleTestInfo> test =
new InformationLeakTest<>(new SimpleTestInfo(), responses);

List<VectorResponse> rareResponses = test.getRareResponses(1);
assertTrue(rareResponses.isEmpty());
}

@Test
public void testGetRareResponsesWithUniqueResponse() {
List<VectorResponse> responses = new ArrayList<>();
TestVector vector1 = new TestVector("vector1");
TestVector vector2 = new TestVector("vector2");
TestVector vector3 = new TestVector("vector3");

ResponseFingerprint fingerprint1 =
new ResponseFingerprint(new ArrayList<>(), new ArrayList<>(), SocketState.CLOSED);
ResponseFingerprint fingerprint2 =
new ResponseFingerprint(new ArrayList<>(), new ArrayList<>(), SocketState.TIMEOUT);

// vector1 and vector2 have fingerprint1, vector3 has unique fingerprint2
responses.add(new VectorResponse(vector1, fingerprint1));
responses.add(new VectorResponse(vector2, fingerprint1));
responses.add(new VectorResponse(vector3, fingerprint2));

InformationLeakTest<SimpleTestInfo> test =
new InformationLeakTest<>(new SimpleTestInfo(), responses);

// Get responses that occurred at most once
List<VectorResponse> rareResponses = test.getRareResponses(1);
assertEquals(1, rareResponses.size());
assertEquals(vector3, rareResponses.get(0).getVector());
assertEquals(fingerprint2, rareResponses.get(0).getFingerprint());
}

@Test
public void testGetRareResponsesWithMultipleRareResponses() {
List<VectorResponse> responses = new ArrayList<>();
TestVector vector1 = new TestVector("vector1");
TestVector vector2 = new TestVector("vector2");
TestVector vector3 = new TestVector("vector3");
TestVector vector4 = new TestVector("vector4");

ResponseFingerprint fingerprint1 =
new ResponseFingerprint(new ArrayList<>(), new ArrayList<>(), SocketState.CLOSED);
ResponseFingerprint fingerprint2 =
new ResponseFingerprint(new ArrayList<>(), new ArrayList<>(), SocketState.TIMEOUT);
ResponseFingerprint fingerprint3 =
new ResponseFingerprint(
new ArrayList<>(), new ArrayList<>(), SocketState.DATA_AVAILABLE);

// vector1 and vector2 have fingerprint1, vector3 has fingerprint2, vector4 has fingerprint3
responses.add(new VectorResponse(vector1, fingerprint1));
responses.add(new VectorResponse(vector2, fingerprint1));
responses.add(new VectorResponse(vector3, fingerprint2));
responses.add(new VectorResponse(vector4, fingerprint3));

InformationLeakTest<SimpleTestInfo> test =
new InformationLeakTest<>(new SimpleTestInfo(), responses);

// Get responses that occurred at most twice
List<VectorResponse> rareResponses = test.getRareResponses(2);
assertEquals(4, rareResponses.size()); // All responses occur at most twice

// Get responses that occurred at most once
rareResponses = test.getRareResponses(1);
assertEquals(2, rareResponses.size()); // vector3 and vector4

// Verify the rare responses
boolean foundVector3 = false;
boolean foundVector4 = false;
for (VectorResponse response : rareResponses) {
if (response.getVector().equals(vector3)) {
foundVector3 = true;
assertEquals(fingerprint2, response.getFingerprint());
} else if (response.getVector().equals(vector4)) {
foundVector4 = true;
assertEquals(fingerprint3, response.getFingerprint());
}
}
assertTrue(foundVector3);
assertTrue(foundVector4);
}

@Test
public void testGetRareResponsesWithNoRareResponses() {
List<VectorResponse> responses = new ArrayList<>();
TestVector vector1 = new TestVector("vector1");
TestVector vector2 = new TestVector("vector2");
TestVector vector3 = new TestVector("vector3");

ResponseFingerprint fingerprint1 =
new ResponseFingerprint(new ArrayList<>(), new ArrayList<>(), SocketState.CLOSED);

// All vectors have the same fingerprint
responses.add(new VectorResponse(vector1, fingerprint1));
responses.add(new VectorResponse(vector2, fingerprint1));
responses.add(new VectorResponse(vector3, fingerprint1));

InformationLeakTest<SimpleTestInfo> test =
new InformationLeakTest<>(new SimpleTestInfo(), responses);

// Get responses that occurred at most twice
List<VectorResponse> rareResponses = test.getRareResponses(2);
assertTrue(rareResponses.isEmpty()); // All responses occur 3 times

// Get responses that occurred at most 3 times
rareResponses = test.getRareResponses(3);
assertEquals(3, rareResponses.size()); // All responses occur exactly 3 times
}

@Test
public void testGetRareResponsesIntegrationWithExtendedTest() {
List<VectorResponse> initialResponses = new ArrayList<>();
TestVector vector1 = new TestVector("vector1");
TestVector vector2 = new TestVector("vector2");

ResponseFingerprint fingerprint1 =
new ResponseFingerprint(new ArrayList<>(), new ArrayList<>(), SocketState.CLOSED);
ResponseFingerprint fingerprint2 =
new ResponseFingerprint(new ArrayList<>(), new ArrayList<>(), SocketState.TIMEOUT);

initialResponses.add(new VectorResponse(vector1, fingerprint1));
initialResponses.add(new VectorResponse(vector2, fingerprint2));

InformationLeakTest<SimpleTestInfo> test =
new InformationLeakTest<>(new SimpleTestInfo(), initialResponses);

// Initially both responses are unique
List<VectorResponse> rareResponses = test.getRareResponses(1);
assertEquals(2, rareResponses.size());

// Extend the test with more responses
List<VectorResponse> additionalResponses = new ArrayList<>();
additionalResponses.add(new VectorResponse(vector1, fingerprint1));
additionalResponses.add(new VectorResponse(vector1, fingerprint1));

test.extendTestWithVectorResponses(additionalResponses);

// Now only vector2's response is rare (occurring once)
rareResponses = test.getRareResponses(1);
// Since vector1 now occurs 3 times (1 initial + 2 additional) with fingerprint1,
// but getRareResponses returns one VectorResponse per vector that had that fingerprint,
// we expect 1 response for vector2 (which still occurs once)
assertEquals(1, rareResponses.size());
assertEquals(vector2, rareResponses.get(0).getVector());
assertEquals(fingerprint2, rareResponses.get(0).getFingerprint());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@
import de.rub.nds.tlsscanner.core.constants.TlsProbeType;
import de.rub.nds.tlsscanner.core.task.FingerPrintTask;
import de.rub.nds.tlsscanner.core.task.FingerprintTaskVectorPair;
import de.rub.nds.tlsscanner.core.vector.Vector;
import de.rub.nds.tlsscanner.core.vector.VectorResponse;
import de.rub.nds.tlsscanner.core.vector.response.ResponseFingerprint;
import de.rub.nds.tlsscanner.core.vector.statistics.InformationLeakTest;
import de.rub.nds.tlsscanner.core.vector.statistics.ResponseCounter;
import de.rub.nds.tlsscanner.core.vector.statistics.TestInfo;
import de.rub.nds.tlsscanner.core.vector.statistics.VectorContainer;
import de.rub.nds.tlsscanner.serverscanner.leak.TicketPaddingOracleLastByteTestInfo;
import de.rub.nds.tlsscanner.serverscanner.leak.TicketPaddingOracleSecondByteTestInfo;
import de.rub.nds.tlsscanner.serverscanner.probe.result.VersionDependentSummarizableResult;
Expand All @@ -41,10 +37,8 @@
import de.rub.nds.tlsscanner.serverscanner.report.ServerReport;
import de.rub.nds.tlsscanner.serverscanner.selector.ConfigSelector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
Expand Down Expand Up @@ -183,7 +177,7 @@ private boolean shouldCheckOffset(int offset, int ticketLength) {
private boolean foundDefinitiveResult(
InformationLeakTest<TicketPaddingOracleSecondByteTestInfo> secondByteLeakTest) {
return secondByteLeakTest.isSignificantDistinctAnswers()
&& !getRareResponses(secondByteLeakTest, 1).isEmpty();
&& !secondByteLeakTest.getRareResponses(1).isEmpty();
}

private TicketPaddingOracleResult checkPaddingOracle(ProtocolVersion version) {
Expand Down Expand Up @@ -222,7 +216,7 @@ private TicketPaddingOracleResult checkPaddingOracle(ProtocolVersion version) {
new ArrayList<>();

if (lastByteLeakTest.isSignificantDistinctAnswers()) {
List<VectorResponse> rareResponses = getRareResponses(lastByteLeakTest, 2);
List<VectorResponse> rareResponses = lastByteLeakTest.getRareResponses(2);
LOGGER.debug(
"At Offset {} found significant difference with {} rare response(s)",
offset,
Expand Down Expand Up @@ -273,29 +267,6 @@ && foundDefinitiveResult(secondByteLeakTest)) {
return new TicketPaddingOracleResult(offsetResults);
}

public static <T extends TestInfo> List<VectorResponse> getRareResponses(
InformationLeakTest<T> informationLeakTest, int mostOccurrences) {
Map<ResponseFingerprint, List<Vector>> map = new HashMap<>();
for (VectorContainer container : informationLeakTest.getVectorContainerList()) {
for (ResponseCounter counter : container.getDistinctResponsesCounterList()) {
map.computeIfAbsent(counter.getFingerprint(), k -> new ArrayList<>());
map.get(counter.getFingerprint()).add(container.getVector());
}
}

List<VectorResponse> ret = new ArrayList<>();
for (var entry : map.entrySet()) {
ResponseFingerprint fingerprint = entry.getKey();
List<Vector> vectors = entry.getValue();
if (vectors.size() <= mostOccurrences) {
for (Vector vector : vectors) {
ret.add(new VectorResponse(vector, fingerprint));
}
}
}
return ret;
}

private List<TicketPaddingOracleVector> createPaddingVectorsLastByte(Integer paddingIvOffset) {
List<TicketPaddingOracleVector> vectorList = new ArrayList<>(XOR_VALUES_LAST_BYTE.length);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import de.rub.nds.tlsscanner.core.vector.statistics.InformationLeakTest;
import de.rub.nds.tlsscanner.core.vector.statistics.TestInfo;
import de.rub.nds.tlsscanner.serverscanner.leak.TicketPaddingOracleSecondByteTestInfo;
import de.rub.nds.tlsscanner.serverscanner.probe.SessionTicketPaddingOracleProbe;
import de.rub.nds.tlsscanner.serverscanner.probe.sessionticket.vector.TicketPaddingOracleVectorLast;
import de.rub.nds.tlsscanner.serverscanner.probe.sessionticket.vector.TicketPaddingOracleVectorSecond;
import de.rub.nds.tlsscanner.serverscanner.probe.sessionticket.vector.TicketVector;
Expand Down Expand Up @@ -76,8 +75,7 @@ public TicketPaddingOracleResult(TestResults overallResult) {
private <V extends TicketVector, T extends TestInfo> List<V> getVectorsWithRareResponses(
InformationLeakTest<T> leakTest, Class<V> vectorClass, int maxOccurences) {
List<V> ret = new ArrayList<>();
for (VectorResponse response :
SessionTicketPaddingOracleProbe.getRareResponses(leakTest, maxOccurences)) {
for (VectorResponse response : leakTest.getRareResponses(maxOccurences)) {
ret.add(vectorClass.cast(response.getVector()));
}
return ret;
Expand Down