/
Http2RSTFloodProtectionTest.java
120 lines (100 loc) · 4.38 KB
/
Http2RSTFloodProtectionTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package io.quarkus.vertx.http.http2;
import static io.vertx.core.http.HttpMethod.GET;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.File;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.vertx.core.runtime.VertxCoreRecorder;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.net.JdkSSLEngineOptions;
import io.vertx.core.net.JksOptions;
import io.vertx.ext.web.Router;
import me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;
/**
* Reproduce CVE-2023-44487.
*/
@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "ssl-test", password = "secret", formats = {
Format.JKS, Format.PKCS12, Format.PEM }))
@DisabledOnOs(OS.WINDOWS)
public class Http2RSTFloodProtectionTest {
private static final String configuration = """
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks
quarkus.http.ssl.certificate.key-store-password=secret
""";
@TestHTTPResource(value = "/ping", ssl = true)
URL sslUrl;
@TestHTTPResource(value = "/ping")
URL url;
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(MyBean.class)
.addAsResource(new StringAsset(configuration), "application.properties")
.addAsResource(new File("target/certs/ssl-test-keystore.jks"), "server-keystore.jks"));
@Test
void testRstFloodProtectionWithTlsEnabled() throws Exception {
Assumptions.assumeTrue(JdkSSLEngineOptions.isAlpnAvailable()); //don't run on JDK8
HttpClientOptions options = new HttpClientOptions()
.setUseAlpn(true)
.setProtocolVersion(HttpVersion.HTTP_2)
.setSsl(true)
.setTrustOptions(new JksOptions().setPath(new File("target/certs/ssl-test-truststore.jks").getAbsolutePath())
.setPassword("secret"));
var client = VertxCoreRecorder.getVertx().get().createHttpClient(options);
int port = sslUrl.getPort();
run(client, port, false);
}
@Test
public void testRstFloodProtection() throws InterruptedException {
HttpClientOptions options = new HttpClientOptions()
.setProtocolVersion(HttpVersion.HTTP_2)
.setHttp2ClearTextUpgrade(true);
var client = VertxCoreRecorder.getVertx().get().createHttpClient(options);
run(client, url.getPort(), true);
}
void run(HttpClient client, int port, boolean plain) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
client.connectionHandler(conn -> conn.goAwayHandler(ga -> {
Assertions.assertEquals(11, ga.getErrorCode());
latch.countDown();
}));
if (plain) {
// Emit a first request to establish a connection.
// It's HTTP/1 so, does not count in the number of requests.
client.request(GET, port, "localhost", "/ping")
.compose(HttpClientRequest::send);
}
for (int i = 0; i < 250; i++) { // must be higher than the Netty limit (200 / 30s)
client.request(GET, port, "localhost", "/ping")
.onSuccess(req -> req.end().onComplete(v -> req.reset()));
}
if (!latch.await(10, TimeUnit.SECONDS)) {
fail("RST flood protection failed");
}
}
@ApplicationScoped
public static class MyBean {
public void register(@Observes Router router) {
router.get("/ping").handler(rc -> {
// Do nothing.
});
}
}
}