Skip to content

Commit e82afbc

Browse files
authored
Add support to explicit configure session tickets to be used (java-native-access#634)
Motivation: Sometimes you want to set the session tickets that should be used explicit to be able to use the same on on multiple servers. Modifications: Add required code to be able to configure the session tickets explicit. Result: Be able to manually set the session tickets
1 parent 849181c commit e82afbc

File tree

8 files changed

+413
-21
lines changed

8 files changed

+413
-21
lines changed

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/BoringSSL.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ static long SSLContext_new(boolean server, String[] applicationProtocols,
4545
BoringSSLKeylogCallback keylogCallback,
4646
BoringSSLSessionCallback sessionCallback,
4747
BoringSSLPrivateKeyMethod privateKeyMethod,
48+
BoringSSLSessionTicketCallback sessionTicketCallback,
4849
int verifyMode,
4950
byte[][] subjectNames) {
5051
return SSLContext_new0(server, toWireFormat(applicationProtocols),
5152
handshakeCompleteCallback, certificateCallback, verifyCallback, servernameCallback,
52-
keylogCallback, sessionCallback, privateKeyMethod, verifyMode, subjectNames);
53+
keylogCallback, sessionCallback, privateKeyMethod, sessionTicketCallback, verifyMode, subjectNames);
5354
}
5455

5556
private static byte[] toWireFormat(String[] applicationProtocols) {
@@ -74,10 +75,14 @@ private static native long SSLContext_new0(boolean server,
7475
Object servernameCallback, Object keylogCallback,
7576
Object sessionCallback,
7677
Object privateKeyMethod,
78+
Object sessionTicketCallback,
7779
int verifyDepth, byte[][] subjectNames);
7880
static native void SSLContext_set_early_data_enabled(long context, boolean enabled);
7981
static native long SSLContext_setSessionCacheSize(long context, long size);
8082
static native long SSLContext_setSessionCacheTimeout(long context, long size);
83+
84+
static native void SSLContext_setSessionTicketKeys(long context, boolean enableCallback);
85+
8186
static native void SSLContext_free(long context);
8287
static long SSL_new(long context, boolean server, String hostname) {
8388
return SSL_new0(context, server, tlsExtHostName(hostname));
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2023 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.incubator.codec.quic;
17+
18+
import io.netty.util.internal.PlatformDependent;
19+
20+
final class BoringSSLSessionTicketCallback {
21+
22+
// As we dont assume to have a lot of keys configured we will just use an array for now as a data store.
23+
private volatile byte[][] sessionKeys;
24+
25+
// Accessed via JNI.
26+
byte[] findSessionTicket(byte[] keyname) {
27+
byte[][] keys = this.sessionKeys;
28+
if (keys == null || keys.length == 0) {
29+
return null;
30+
}
31+
if (keyname == null) {
32+
return keys[0];
33+
}
34+
35+
for (int i = 0; i < keys.length; i++) {
36+
byte[] key = keys[i];
37+
if (PlatformDependent.equals(keyname, 0, key, 1, keyname.length)) {
38+
return key;
39+
}
40+
}
41+
return null;
42+
}
43+
44+
void setSessionTicketKeys(SslSessionTicketKey[] keys) {
45+
if (keys != null && keys.length != 0) {
46+
byte[][] sessionKeys = new byte[keys.length][];
47+
for(int i = 0; i < keys.length; ++i) {
48+
SslSessionTicketKey key = keys[i];
49+
byte[] binaryKey = new byte[49];
50+
// We mark the first key as preferred by using 1 as byte marker
51+
binaryKey[0] = i == 0 ? (byte) 1 : (byte) 0;
52+
int dstCurPos = 1;
53+
System.arraycopy(key.name, 0, binaryKey, dstCurPos, 16);
54+
dstCurPos += 16;
55+
System.arraycopy(key.hmacKey, 0, binaryKey, dstCurPos, 16);
56+
dstCurPos += 16;
57+
System.arraycopy(key.aesKey, 0, binaryKey, dstCurPos, 16);
58+
sessionKeys[i] = binaryKey;
59+
}
60+
this.sessionKeys = sessionKeys;
61+
} else {
62+
sessionKeys = null;
63+
}
64+
}
65+
}

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicSslContext.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public abstract class QuicSslContext extends SslContext {
3333
@Override
3434
public abstract QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort);
3535

36+
@Override
37+
public abstract QuicSslSessionContext sessionContext();
38+
3639
static X509Certificate[] toX509Certificates0(InputStream stream)
3740
throws CertificateException {
3841
return SslContext.toX509Certificates(stream);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2023 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.incubator.codec.quic;
17+
18+
import javax.net.ssl.SSLSessionContext;
19+
20+
/**
21+
* {@link SSLSessionContext} which also supports advanced operations.
22+
*/
23+
public interface QuicSslSessionContext extends SSLSessionContext {
24+
25+
/**
26+
* Sets the {@link SslSessionTicketKey}s that should be used. The first key of the array is used for encryption
27+
* and decryption while the rest of the array is only used for decryption. This allows you to better handling
28+
* rotating of the keys. The rotating is the responsibility of the user.
29+
* If {@code null} is used for {@code keys} a key will automatically generated by the library and also rotated.
30+
*
31+
* @param keys the tickets to use.
32+
*/
33+
void setTicketKeys(SslSessionTicketKey... keys);
34+
}

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicSslContext.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import javax.net.ssl.KeyManager;
2828
import javax.net.ssl.KeyManagerFactory;
2929
import javax.net.ssl.SSLSession;
30-
import javax.net.ssl.SSLSessionContext;
3130
import javax.net.ssl.TrustManager;
3231
import javax.net.ssl.TrustManagerFactory;
3332
import javax.net.ssl.X509ExtendedKeyManager;
@@ -53,6 +52,7 @@
5352
import java.util.function.LongFunction;
5453

5554
import static io.netty.util.internal.ObjectUtil.checkNotNull;
55+
import static java.util.Objects.requireNonNull;
5656

5757
final class QuicheQuicSslContext extends QuicSslContext {
5858
final ClientAuth clientAuth;
@@ -64,6 +64,9 @@ final class QuicheQuicSslContext extends QuicSslContext {
6464
private final QuicheQuicSslSessionContext sessionCtx;
6565
private final QuicheQuicSslEngineMap engineMap = new QuicheQuicSslEngineMap();
6666
private final QuicClientSessionCache sessionCache;
67+
68+
private final BoringSSLSessionTicketCallback sessionTicketCallback = new BoringSSLSessionTicketCallback();
69+
6770
final NativeSslContext nativeSslContext;
6871

6972
QuicheQuicSslContext(boolean server, long sessionTimeout, long sessionCacheSize,
@@ -112,7 +115,8 @@ final class QuicheQuicSslContext extends QuicSslContext {
112115
new BoringSSLCertificateVerifyCallback(engineMap, trustManager),
113116
mapping == null ? null : new BoringSSLTlsextServernameCallback(engineMap, mapping),
114117
keylog == null ? null : new BoringSSLKeylogCallback(engineMap, keylog),
115-
server ? null : new BoringSSLSessionCallback(engineMap, sessionCache), privateKeyMethod, verifyMode,
118+
server ? null : new BoringSSLSessionCallback(engineMap, sessionCache), privateKeyMethod,
119+
sessionTicketCallback, verifyMode,
116120
BoringSSL.subjectNames(trustManager.getAcceptedIssuers())));
117121
apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
118122
if (this.sessionCache != null) {
@@ -275,7 +279,7 @@ public QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peer
275279
}
276280

277281
@Override
278-
public SSLSessionContext sessionContext() {
282+
public QuicSslSessionContext sessionContext() {
279283
return sessionCtx;
280284
}
281285

@@ -338,6 +342,12 @@ void setSessionCacheSize(int size) throws IllegalArgumentException {
338342
}
339343
}
340344

345+
void setSessionTicketKeys(SslSessionTicketKey[] ticketKeys) {
346+
sessionTicketCallback.setSessionTicketKeys(ticketKeys);
347+
BoringSSL.SSLContext_setSessionTicketKeys(
348+
nativeSslContext.address(), ticketKeys != null && ticketKeys.length != 0);
349+
}
350+
341351
@SuppressWarnings("deprecation")
342352
private static final class QuicheQuicApplicationProtocolNegotiator implements ApplicationProtocolNegotiator {
343353
private final List<String> protocols;
@@ -356,8 +366,7 @@ public List<String> protocols() {
356366
}
357367
}
358368

359-
private static final class QuicheQuicSslSessionContext implements SSLSessionContext {
360-
369+
private static final class QuicheQuicSslSessionContext implements QuicSslSessionContext {
361370
private final QuicheQuicSslContext context;
362371

363372
QuicheQuicSslSessionContext(QuicheQuicSslContext context) {
@@ -403,6 +412,11 @@ public void setSessionCacheSize(int size) throws IllegalArgumentException {
403412
public int getSessionCacheSize() {
404413
return (int) context.sessionCacheSize();
405414
}
415+
416+
@Override
417+
public void setTicketKeys(SslSessionTicketKey... keys) {
418+
context.setSessionTicketKeys(keys);
419+
}
406420
}
407421

408422
static final class NativeSslContext extends AbstractReferenceCounted {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2023 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package io.netty.incubator.codec.quic;
18+
19+
import java.util.Arrays;
20+
21+
/**
22+
* Session Ticket Key
23+
*/
24+
public final class SslSessionTicketKey {
25+
/**
26+
* Size of session ticket key name
27+
*/
28+
public static final int NAME_SIZE = 16;
29+
/**
30+
* Size of session ticket key HMAC key
31+
*/
32+
public static final int HMAC_KEY_SIZE = 16;
33+
/**
34+
* Size of session ticket key AES key
35+
*/
36+
public static final int AES_KEY_SIZE = 16;
37+
/**
38+
* Size of session ticket key
39+
*/
40+
public static final int TICKET_KEY_SIZE = NAME_SIZE + HMAC_KEY_SIZE + AES_KEY_SIZE;
41+
42+
// package private so we can access these in BoringSSLSessionTicketCallback without calling clone() on the byte[].
43+
final byte[] name;
44+
final byte[] hmacKey;
45+
final byte[] aesKey;
46+
47+
/**
48+
* Construct SessionTicketKey.
49+
* @param name the name of the session ticket key
50+
* @param hmacKey the HMAC key of the session ticket key
51+
* @param aesKey the AES key of the session ticket key
52+
*/
53+
public SslSessionTicketKey(byte[] name, byte[] hmacKey, byte[] aesKey) {
54+
if (name == null || name.length != NAME_SIZE) {
55+
throw new IllegalArgumentException("Length of name must be " + NAME_SIZE);
56+
}
57+
if (hmacKey == null || hmacKey.length != HMAC_KEY_SIZE) {
58+
throw new IllegalArgumentException("Length of hmacKey must be " + HMAC_KEY_SIZE);
59+
}
60+
if (aesKey == null || aesKey.length != AES_KEY_SIZE) {
61+
throw new IllegalArgumentException("Length of aesKey must be " + AES_KEY_SIZE);
62+
}
63+
this.name = name.clone();
64+
this.hmacKey = hmacKey.clone();
65+
this.aesKey = aesKey.clone();
66+
}
67+
68+
/**
69+
* Get name.
70+
*
71+
* @return the name of the session ticket key
72+
*/
73+
public byte[] name() {
74+
return name.clone();
75+
}
76+
77+
/**
78+
* Get HMAC key.
79+
* @return the HMAC key of the session ticket key
80+
*/
81+
public byte[] hmacKey() {
82+
return hmacKey.clone();
83+
}
84+
85+
/**
86+
* Get AES Key.
87+
* @return the AES key of the session ticket key
88+
*/
89+
public byte[] aesKey() {
90+
return aesKey.clone();
91+
}
92+
93+
@Override
94+
public boolean equals(Object o) {
95+
if (this == o) {
96+
return true;
97+
}
98+
if (o == null || getClass() != o.getClass()) {
99+
return false;
100+
}
101+
102+
SslSessionTicketKey that = (SslSessionTicketKey) o;
103+
104+
if (!Arrays.equals(name, that.name)) {
105+
return false;
106+
}
107+
if (!Arrays.equals(hmacKey, that.hmacKey)) {
108+
return false;
109+
}
110+
return Arrays.equals(aesKey, that.aesKey);
111+
}
112+
113+
@Override
114+
public int hashCode() {
115+
int result = Arrays.hashCode(name);
116+
result = 31 * result + Arrays.hashCode(hmacKey);
117+
result = 31 * result + Arrays.hashCode(aesKey);
118+
return result;
119+
}
120+
121+
@Override
122+
public String toString() {
123+
return "SessionTicketKey{" +
124+
"name=" + Arrays.toString(name) +
125+
", hmacKey=" + Arrays.toString(hmacKey) +
126+
", aesKey=" + Arrays.toString(aesKey) +
127+
'}';
128+
}
129+
}

0 commit comments

Comments
 (0)