Skip to content

Commit 375ad87

Browse files
authored
Support for HTTP/3 (server side), a few minor fixes (opensearch-project#20394)
Signed-off-by: Andriy Redko <drreta@gmail.com>
1 parent 18b9df3 commit 375ad87

3 files changed

Lines changed: 149 additions & 8 deletions

File tree

modules/transport-netty4/src/main/java/org/opensearch/http/netty4/Netty4HttpChannel.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,15 @@ public <T> Optional<T> get(String name, Class<T> clazz) {
140140
return (Optional<T>) Optional.of(getNettyChannel());
141141
}
142142

143-
if (SSL_ENGINE_PROPERTY.equalsIgnoreCase(name) && clazz.isAssignableFrom(SSLEngine.class)) {
144-
SSLEngine engine = channel.attr(sslEngineKey).get();
145-
if (engine == null && channel.parent() != null) {
146-
engine = channel.parent().attr(sslEngineKey).get();
147-
}
148-
if (engine != null) {
149-
return (Optional<T>) Optional.of(engine);
143+
if (sslEngineKey != null) {
144+
if (SSL_ENGINE_PROPERTY.equalsIgnoreCase(name) && clazz.isAssignableFrom(SSLEngine.class)) {
145+
SSLEngine engine = channel.attr(sslEngineKey).get();
146+
if (engine == null && channel.parent() != null) {
147+
engine = channel.parent().attr(sslEngineKey).get();
148+
}
149+
if (engine != null) {
150+
return (Optional<T>) Optional.of(engine);
151+
}
150152
}
151153
}
152154

plugins/transport-reactor-netty4/src/main/java/org/opensearch/http/reactor/netty4/ReactorNetty4HttpServerTransport.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.opensearch.http.HttpReadTimeoutException;
2828
import org.opensearch.http.HttpServerChannel;
2929
import org.opensearch.http.reactor.netty4.http3.Http3Utils;
30+
import org.opensearch.http.reactor.netty4.http3.SecureQuicTokenHandler;
3031
import org.opensearch.http.reactor.netty4.ssl.SslUtils;
3132
import org.opensearch.plugins.SecureHttpTransportSettingsProvider;
3233
import org.opensearch.plugins.SecureHttpTransportSettingsProvider.SecureHttpTransportParameters;
@@ -353,7 +354,8 @@ private Optional<HttpServer> configureHttp3(InetSocketAddress socketAddress) thr
353354
)
354355
.handle((req, res) -> incomingRequest(req, res))
355356
.http3Settings(
356-
spec -> spec.idleTimeout(Duration.ofMillis(connectTimeoutMillis))
357+
spec -> spec.tokenHandler(new SecureQuicTokenHandler())
358+
.idleTimeout(Duration.ofMillis(connectTimeoutMillis))
357359
.maxData(SETTING_HTTP_MAX_CONTENT_LENGTH.get(settings).getBytes())
358360
.maxStreamDataBidirectionalLocal(SETTING_H3_MAX_STREAM_LOCAL_LENGTH.get(settings).getBytes())
359361
.maxStreamDataBidirectionalRemote(SETTING_H3_MAX_STREAM_REMOTE_LENGTH.get(settings).getBytes())
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.http.reactor.netty4.http3;
10+
11+
import org.opensearch.common.Randomness;
12+
13+
import javax.crypto.Mac;
14+
import javax.crypto.spec.SecretKeySpec;
15+
16+
import java.net.InetSocketAddress;
17+
import java.security.InvalidKeyException;
18+
import java.security.MessageDigest;
19+
import java.security.NoSuchAlgorithmException;
20+
21+
import io.netty.buffer.ByteBuf;
22+
import io.netty.buffer.Unpooled;
23+
import io.netty.handler.codec.quic.Quic;
24+
import io.netty.handler.codec.quic.QuicTokenHandler;
25+
import io.netty.util.CharsetUtil;
26+
import io.netty.util.NetUtil;
27+
28+
/**
29+
* Secure {@link QuicTokenHandler} which uses HMAC_SHA256.
30+
*/
31+
public class SecureQuicTokenHandler implements QuicTokenHandler {
32+
private static final int HMAC_KEY_LEN = 32;
33+
private static final int HMAC_TAG_LEN = 32;
34+
private static final String HMAC_SHA_256 = "HmacSHA256";
35+
36+
private static final String SERVER_NAME = "opensearch-reactor-netty";
37+
private static final byte[] SERVER_NAME_BYTES = SERVER_NAME.getBytes(CharsetUtil.US_ASCII);
38+
private static final ByteBuf SERVER_NAME_BUFFER = Unpooled.unreleasableBuffer(Unpooled.wrappedBuffer(SERVER_NAME_BYTES)).asReadOnly();
39+
40+
private static final int MAX_TOKEN_LEN = HMAC_TAG_LEN + Quic.MAX_CONN_ID_LEN + NetUtil.LOCALHOST6.getAddress().length
41+
+ SERVER_NAME_BYTES.length;
42+
43+
private final byte[] key;
44+
45+
/**
46+
* Constructs SecureQuicTokenHandler instance
47+
*/
48+
public SecureQuicTokenHandler() {
49+
this.key = new byte[HMAC_KEY_LEN];
50+
Randomness.createSecure().nextBytes(key);
51+
}
52+
53+
/**
54+
* Generate a new token for the given destination connection id and address. This token is written to {@code out}.
55+
* If no token should be generated and so no token validation should take place at all this method should return
56+
* {@code false}.
57+
*
58+
* @param out {@link ByteBuf} into which the token will be written.
59+
* @param dcid the destination connection id. The {@link ByteBuf#readableBytes()} will be at most
60+
* {@link Quic#MAX_CONN_ID_LEN}.
61+
* @param address the {@link InetSocketAddress} of the sender.
62+
* @return {@code true} if a token was written and so validation should happen, {@code false} otherwise.
63+
*/
64+
@Override
65+
public boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address) {
66+
final byte[] addr = address.getAddress().getAddress();
67+
final byte[] buffer = new byte[HMAC_TAG_LEN + addr.length + dcid.readableBytes()];
68+
69+
System.arraycopy(addr, 0, buffer, HMAC_TAG_LEN, addr.length);
70+
dcid.getBytes(dcid.readerIndex(), buffer, HMAC_TAG_LEN + addr.length, dcid.readableBytes());
71+
72+
try {
73+
final Mac mac = Mac.getInstance(HMAC_SHA_256);
74+
mac.init(new SecretKeySpec(key, HMAC_SHA_256));
75+
mac.update(buffer, HMAC_TAG_LEN, addr.length + dcid.readableBytes());
76+
System.arraycopy(mac.doFinal(), 0, buffer, 0, HMAC_TAG_LEN);
77+
} catch (final InvalidKeyException | NoSuchAlgorithmException ex) {
78+
return false;
79+
}
80+
81+
out.writeBytes(SERVER_NAME_BYTES).writeBytes(buffer);
82+
return true;
83+
}
84+
85+
/**
86+
* Validate the token and return the offset, {@code -1} is returned if the token is not valid.
87+
*
88+
* @param token the {@link ByteBuf} that contains the token. The ownership is not transferred.
89+
* @param address the {@link InetSocketAddress} of the sender.
90+
* @return the start index after the token or {@code -1} if the token was not valid.
91+
*/
92+
@Override
93+
public int validateToken(ByteBuf token, InetSocketAddress address) {
94+
final byte[] addr = address.getAddress().getAddress();
95+
final int minLength = SERVER_NAME_BYTES.length + HMAC_TAG_LEN + addr.length;
96+
if (token.readableBytes() <= minLength) {
97+
return -1;
98+
}
99+
100+
if (!SERVER_NAME_BUFFER.equals(token.slice(0, SERVER_NAME_BYTES.length))) {
101+
return -1;
102+
}
103+
104+
final ByteBuf tag = token.slice(SERVER_NAME_BYTES.length, HMAC_TAG_LEN);
105+
final int length = token.readableBytes() - HMAC_TAG_LEN - SERVER_NAME_BYTES.length;
106+
final ByteBuf payload = token.slice(SERVER_NAME_BYTES.length + HMAC_TAG_LEN, length);
107+
try {
108+
final Mac mac = Mac.getInstance(HMAC_SHA_256);
109+
mac.init(new SecretKeySpec(key, HMAC_SHA_256));
110+
for (int i = 0; i < payload.readableBytes(); ++i) {
111+
mac.update(payload.getByte(payload.readerIndex() + i));
112+
}
113+
114+
final byte[] actual = new byte[tag.readableBytes()];
115+
tag.getBytes(tag.readerIndex(), actual, 0, tag.readableBytes());
116+
117+
final byte[] expected = mac.doFinal();
118+
if (!MessageDigest.isEqual(actual, expected)) {
119+
return -1;
120+
}
121+
} catch (final InvalidKeyException | NoSuchAlgorithmException ex) {
122+
return -1;
123+
}
124+
125+
return minLength;
126+
}
127+
128+
/**
129+
* Return the maximal token length.
130+
*
131+
* @return the maximal supported token length.
132+
*/
133+
@Override
134+
public int maxTokenLength() {
135+
return MAX_TOKEN_LEN;
136+
}
137+
}

0 commit comments

Comments
 (0)