Skip to content

Commit

Permalink
Add support to explicit configure session tickets to be used (java-na…
Browse files Browse the repository at this point in the history
…tive-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
  • Loading branch information
normanmaurer authored Dec 13, 2023
1 parent 849181c commit e82afbc
Show file tree
Hide file tree
Showing 8 changed files with 413 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ static long SSLContext_new(boolean server, String[] applicationProtocols,
BoringSSLKeylogCallback keylogCallback,
BoringSSLSessionCallback sessionCallback,
BoringSSLPrivateKeyMethod privateKeyMethod,
BoringSSLSessionTicketCallback sessionTicketCallback,
int verifyMode,
byte[][] subjectNames) {
return SSLContext_new0(server, toWireFormat(applicationProtocols),
handshakeCompleteCallback, certificateCallback, verifyCallback, servernameCallback,
keylogCallback, sessionCallback, privateKeyMethod, verifyMode, subjectNames);
keylogCallback, sessionCallback, privateKeyMethod, sessionTicketCallback, verifyMode, subjectNames);
}

private static byte[] toWireFormat(String[] applicationProtocols) {
Expand All @@ -74,10 +75,14 @@ private static native long SSLContext_new0(boolean server,
Object servernameCallback, Object keylogCallback,
Object sessionCallback,
Object privateKeyMethod,
Object sessionTicketCallback,
int verifyDepth, byte[][] subjectNames);
static native void SSLContext_set_early_data_enabled(long context, boolean enabled);
static native long SSLContext_setSessionCacheSize(long context, long size);
static native long SSLContext_setSessionCacheTimeout(long context, long size);

static native void SSLContext_setSessionTicketKeys(long context, boolean enableCallback);

static native void SSLContext_free(long context);
static long SSL_new(long context, boolean server, String hostname) {
return SSL_new0(context, server, tlsExtHostName(hostname));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2023 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.incubator.codec.quic;

import io.netty.util.internal.PlatformDependent;

final class BoringSSLSessionTicketCallback {

// As we dont assume to have a lot of keys configured we will just use an array for now as a data store.
private volatile byte[][] sessionKeys;

// Accessed via JNI.
byte[] findSessionTicket(byte[] keyname) {
byte[][] keys = this.sessionKeys;
if (keys == null || keys.length == 0) {
return null;
}
if (keyname == null) {
return keys[0];
}

for (int i = 0; i < keys.length; i++) {
byte[] key = keys[i];
if (PlatformDependent.equals(keyname, 0, key, 1, keyname.length)) {
return key;
}
}
return null;
}

void setSessionTicketKeys(SslSessionTicketKey[] keys) {
if (keys != null && keys.length != 0) {
byte[][] sessionKeys = new byte[keys.length][];
for(int i = 0; i < keys.length; ++i) {
SslSessionTicketKey key = keys[i];
byte[] binaryKey = new byte[49];
// We mark the first key as preferred by using 1 as byte marker
binaryKey[0] = i == 0 ? (byte) 1 : (byte) 0;
int dstCurPos = 1;
System.arraycopy(key.name, 0, binaryKey, dstCurPos, 16);
dstCurPos += 16;
System.arraycopy(key.hmacKey, 0, binaryKey, dstCurPos, 16);
dstCurPos += 16;
System.arraycopy(key.aesKey, 0, binaryKey, dstCurPos, 16);
sessionKeys[i] = binaryKey;
}
this.sessionKeys = sessionKeys;
} else {
sessionKeys = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public abstract class QuicSslContext extends SslContext {
@Override
public abstract QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort);

@Override
public abstract QuicSslSessionContext sessionContext();

static X509Certificate[] toX509Certificates0(InputStream stream)
throws CertificateException {
return SslContext.toX509Certificates(stream);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2023 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.incubator.codec.quic;

import javax.net.ssl.SSLSessionContext;

/**
* {@link SSLSessionContext} which also supports advanced operations.
*/
public interface QuicSslSessionContext extends SSLSessionContext {

/**
* Sets the {@link SslSessionTicketKey}s that should be used. The first key of the array is used for encryption
* and decryption while the rest of the array is only used for decryption. This allows you to better handling
* rotating of the keys. The rotating is the responsibility of the user.
* If {@code null} is used for {@code keys} a key will automatically generated by the library and also rotated.
*
* @param keys the tickets to use.
*/
void setTicketKeys(SslSessionTicketKey... keys);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
Expand All @@ -53,6 +52,7 @@
import java.util.function.LongFunction;

import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static java.util.Objects.requireNonNull;

final class QuicheQuicSslContext extends QuicSslContext {
final ClientAuth clientAuth;
Expand All @@ -64,6 +64,9 @@ final class QuicheQuicSslContext extends QuicSslContext {
private final QuicheQuicSslSessionContext sessionCtx;
private final QuicheQuicSslEngineMap engineMap = new QuicheQuicSslEngineMap();
private final QuicClientSessionCache sessionCache;

private final BoringSSLSessionTicketCallback sessionTicketCallback = new BoringSSLSessionTicketCallback();

final NativeSslContext nativeSslContext;

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

@Override
public SSLSessionContext sessionContext() {
public QuicSslSessionContext sessionContext() {
return sessionCtx;
}

Expand Down Expand Up @@ -338,6 +342,12 @@ void setSessionCacheSize(int size) throws IllegalArgumentException {
}
}

void setSessionTicketKeys(SslSessionTicketKey[] ticketKeys) {
sessionTicketCallback.setSessionTicketKeys(ticketKeys);
BoringSSL.SSLContext_setSessionTicketKeys(
nativeSslContext.address(), ticketKeys != null && ticketKeys.length != 0);
}

@SuppressWarnings("deprecation")
private static final class QuicheQuicApplicationProtocolNegotiator implements ApplicationProtocolNegotiator {
private final List<String> protocols;
Expand All @@ -356,8 +366,7 @@ public List<String> protocols() {
}
}

private static final class QuicheQuicSslSessionContext implements SSLSessionContext {

private static final class QuicheQuicSslSessionContext implements QuicSslSessionContext {
private final QuicheQuicSslContext context;

QuicheQuicSslSessionContext(QuicheQuicSslContext context) {
Expand Down Expand Up @@ -403,6 +412,11 @@ public void setSessionCacheSize(int size) throws IllegalArgumentException {
public int getSessionCacheSize() {
return (int) context.sessionCacheSize();
}

@Override
public void setTicketKeys(SslSessionTicketKey... keys) {
context.setSessionTicketKeys(keys);
}
}

static final class NativeSslContext extends AbstractReferenceCounted {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2023 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package io.netty.incubator.codec.quic;

import java.util.Arrays;

/**
* Session Ticket Key
*/
public final class SslSessionTicketKey {
/**
* Size of session ticket key name
*/
public static final int NAME_SIZE = 16;
/**
* Size of session ticket key HMAC key
*/
public static final int HMAC_KEY_SIZE = 16;
/**
* Size of session ticket key AES key
*/
public static final int AES_KEY_SIZE = 16;
/**
* Size of session ticket key
*/
public static final int TICKET_KEY_SIZE = NAME_SIZE + HMAC_KEY_SIZE + AES_KEY_SIZE;

// package private so we can access these in BoringSSLSessionTicketCallback without calling clone() on the byte[].
final byte[] name;
final byte[] hmacKey;
final byte[] aesKey;

/**
* Construct SessionTicketKey.
* @param name the name of the session ticket key
* @param hmacKey the HMAC key of the session ticket key
* @param aesKey the AES key of the session ticket key
*/
public SslSessionTicketKey(byte[] name, byte[] hmacKey, byte[] aesKey) {
if (name == null || name.length != NAME_SIZE) {
throw new IllegalArgumentException("Length of name must be " + NAME_SIZE);
}
if (hmacKey == null || hmacKey.length != HMAC_KEY_SIZE) {
throw new IllegalArgumentException("Length of hmacKey must be " + HMAC_KEY_SIZE);
}
if (aesKey == null || aesKey.length != AES_KEY_SIZE) {
throw new IllegalArgumentException("Length of aesKey must be " + AES_KEY_SIZE);
}
this.name = name.clone();
this.hmacKey = hmacKey.clone();
this.aesKey = aesKey.clone();
}

/**
* Get name.
*
* @return the name of the session ticket key
*/
public byte[] name() {
return name.clone();
}

/**
* Get HMAC key.
* @return the HMAC key of the session ticket key
*/
public byte[] hmacKey() {
return hmacKey.clone();
}

/**
* Get AES Key.
* @return the AES key of the session ticket key
*/
public byte[] aesKey() {
return aesKey.clone();
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

SslSessionTicketKey that = (SslSessionTicketKey) o;

if (!Arrays.equals(name, that.name)) {
return false;
}
if (!Arrays.equals(hmacKey, that.hmacKey)) {
return false;
}
return Arrays.equals(aesKey, that.aesKey);
}

@Override
public int hashCode() {
int result = Arrays.hashCode(name);
result = 31 * result + Arrays.hashCode(hmacKey);
result = 31 * result + Arrays.hashCode(aesKey);
return result;
}

@Override
public String toString() {
return "SessionTicketKey{" +
"name=" + Arrays.toString(name) +
", hmacKey=" + Arrays.toString(hmacKey) +
", aesKey=" + Arrays.toString(aesKey) +
'}';
}
}
Loading

0 comments on commit e82afbc

Please sign in to comment.