Skip to content

Commit

Permalink
TLS PSK implementation (#1777)
Browse files Browse the repository at this point in the history
* TLS PSK implementation

* Breaking apart the PSK creation to an interface

* Added license

* Store PSK info in handshake info

* Addressed review comments

* Added license

* 1) moving read of byte buff and release to helper method in TLSPSKHandler 2) adding comment in Http2OrHttpHandler and use readBytes instead of readSlice 3) adding SslCloseCompletionEvent on close_notify alert 4) handling null value TLS_HANDSHAKE_USING_EXTERNAL_PSK

* adding license header

* adding back old SslHandshakeInfo constructor for backward compatibility

* Update build.gradle

---------

Co-authored-by: deeptiv1991 <[email protected]>
  • Loading branch information
sunnysingh85 and deeptiv1991 authored Jan 14, 2025
1 parent 545e977 commit 9de74b9
Show file tree
Hide file tree
Showing 13 changed files with 806 additions and 10 deletions.
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,11 @@ subprojects {
jupiterEngine: 'org.junit.jupiter:junit-jupiter-engine:5.+',
jupiterMockito: 'org.mockito:mockito-junit-jupiter:5.+',
mockito: 'org.mockito:mockito-core:5.+',

slf4j: "org.slf4j:slf4j-api:2.0.16",
truth: 'com.google.truth:truth:1.4.4',
awaitility: 'org.awaitility:awaitility:4.2.2'
awaitility: 'org.awaitility:awaitility:4.2.2',
lombok: 'org.projectlombok:lombok:1.18.30'
]
}

Expand Down
6 changes: 6 additions & 0 deletions zuul-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ apply plugin: "java-library"

dependencies {

compileOnly libraries.lombok
annotationProcessor(libraries.lombok)

implementation libraries.guava
// TODO(carl-mastrangelo): this can be implementation; remove Logger from public api points.
api libraries.slf4j

implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.78.1'
implementation 'org.bouncycastle:bctls-jdk18on:1.78.1'

implementation 'com.fasterxml.jackson.core:jackson-core:2.16.1'
api 'com.fasterxml.jackson.core:jackson-databind:2.16.1'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.netflix.netty.common.ssl;

import com.netflix.zuul.netty.server.psk.ClientPSKIdentityInfo;
import io.netty.handler.ssl.ClientAuth;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
Expand All @@ -31,7 +32,10 @@ public class SslHandshakeInfo {
private final Certificate serverCertificate;
private final X509Certificate clientCertificate;
private final boolean isOfIntermediary;
private final boolean usingExternalPSK;
private final ClientPSKIdentityInfo clientPSKIdentityInfo;

//for backward compatibility
public SslHandshakeInfo(
boolean isOfIntermediary,
String protocol,
Expand All @@ -45,6 +49,27 @@ public SslHandshakeInfo(
this.serverCertificate = serverCertificate;
this.clientCertificate = clientCertificate;
this.isOfIntermediary = isOfIntermediary;
this.usingExternalPSK = false;
this.clientPSKIdentityInfo = null;
}

public SslHandshakeInfo(
boolean isOfIntermediary,
String protocol,
String cipherSuite,
ClientAuth clientAuthRequirement,
Certificate serverCertificate,
X509Certificate clientCertificate,
boolean usingExternalPSK,
ClientPSKIdentityInfo clientPSKIdentityInfo) {
this.protocol = protocol;
this.cipherSuite = cipherSuite;
this.clientAuthRequirement = clientAuthRequirement;
this.serverCertificate = serverCertificate;
this.clientCertificate = clientCertificate;
this.isOfIntermediary = isOfIntermediary;
this.usingExternalPSK = usingExternalPSK;
this.clientPSKIdentityInfo = clientPSKIdentityInfo;
}

public boolean isOfIntermediary() {
Expand All @@ -71,6 +96,14 @@ public X509Certificate getClientCertificate() {
return clientCertificate;
}

public boolean usingExternalPSK() {
return usingExternalPSK;
}

public ClientPSKIdentityInfo geClientPSKIdentityInfo() {
return clientPSKIdentityInfo;
}

@Override
public String toString() {
return "SslHandshakeInfo{" + "protocol='"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.netflix.netty.common.channel.config.CommonChannelConfigKeys;
import com.netflix.netty.common.http2.DynamicHttp2FrameLogger;
import com.netflix.zuul.netty.server.BaseZuulChannelInitializer;
import com.netflix.zuul.netty.server.psk.TlsPskHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
Expand All @@ -33,12 +34,13 @@
import io.netty.handler.logging.LogLevel;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.util.AttributeKey;
import java.util.function.Consumer;

/**
* Http2 Or Http Handler
*
* <p>
* Author: Arthur Gonigberg
* Date: December 15, 2017
*/
Expand All @@ -47,6 +49,8 @@ public class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler {
public static final String PROTOCOL_HTTP_1_1 = "HTTP/1.1";
public static final String PROTOCOL_HTTP_2 = "HTTP/2";

private static final String FALLBACK_APPLICATION_PROTOCOL = ApplicationProtocolNames.HTTP_1_1;

private static final DynamicHttp2FrameLogger FRAME_LOGGER =
new DynamicHttp2FrameLogger(LogLevel.DEBUG, Http2FrameCodec.class);

Expand All @@ -62,7 +66,7 @@ public Http2OrHttpHandler(
ChannelHandler http2StreamHandler,
ChannelConfig channelConfig,
Consumer<ChannelPipeline> addHttpHandlerFn) {
super(ApplicationProtocolNames.HTTP_1_1);
super(FALLBACK_APPLICATION_PROTOCOL);
this.http2StreamHandler = http2StreamHandler;
this.maxConcurrentStreams = channelConfig.get(CommonChannelConfigKeys.maxConcurrentStreams);
this.initialWindowSize = channelConfig.get(CommonChannelConfigKeys.initialWindowSize);
Expand All @@ -72,6 +76,45 @@ public Http2OrHttpHandler(
this.addHttpHandlerFn = addHttpHandlerFn;
}

/**
* this method is inspired by ApplicationProtocolNegotiationHandler.userEventTriggered
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof SslHandshakeCompletionEvent handshakeEvent) {
if (handshakeEvent.isSuccess()) {
TlsPskHandler tlsPskHandler = ctx.channel().pipeline().get(TlsPskHandler.class);
if (tlsPskHandler != null) {
// PSK mode
try {
String tlsPskApplicationProtocol = tlsPskHandler.getApplicationProtocol();
configurePipeline(
ctx,
tlsPskApplicationProtocol != null
? tlsPskApplicationProtocol
: FALLBACK_APPLICATION_PROTOCOL);
} catch (Throwable cause) {
exceptionCaught(ctx, cause);
} finally {
// Handshake failures are handled in exceptionCaught(...).
if (handshakeEvent.isSuccess()) {
removeSelfIfPresent(ctx);
}
}
} else {
// non PSK mode
super.userEventTriggered(ctx, evt);
}
} else {
// handshake failures
// TODO sunnys - handle PSK handshake failures
super.userEventTriggered(ctx, evt);
}
} else {
super.userEventTriggered(ctx, evt);
}
}

@Override
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {
if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
Expand Down Expand Up @@ -120,4 +163,11 @@ private void configureHttp2(ChannelPipeline pipeline) {
private void configureHttp1(ChannelPipeline pipeline) {
addHttpHandlerFn.accept(pipeline);
}

private void removeSelfIfPresent(ChannelHandlerContext ctx) {
ChannelPipeline pipeline = ctx.pipeline();
if (!ctx.isRemoved()) {
pipeline.remove(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2024 Netflix, Inc.
*
* Licensed 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 com.netflix.zuul.netty.server.psk;

public record ClientPSKIdentityInfo(byte[] clientPSKIdentity) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2024 Netflix, Inc.
*
* Licensed 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 com.netflix.zuul.netty.server.psk;


public interface ExternalTlsPskProvider {
byte[] provide(byte[] clientPskIdentity, byte[] clientRandom) throws PskCreationFailureException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2024 Netflix, Inc.
*
* Licensed 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 com.netflix.zuul.netty.server.psk;

public class PskCreationFailureException extends Exception {

public enum TlsAlertMessage {
/**
* The server does not recognize the (client) PSK identity
*/
unknown_psk_identity,
/**
* The (client) PSK identity existed but the key was incorrect
*/
decrypt_error,
}

private final TlsAlertMessage tlsAlertMessage;

public PskCreationFailureException(TlsAlertMessage tlsAlertMessage, String message) {
super(message);
this.tlsAlertMessage = tlsAlertMessage;
}

public PskCreationFailureException(TlsAlertMessage tlsAlertMessage, String message, Throwable cause) {
super(message, cause);
this.tlsAlertMessage = tlsAlertMessage;
}

public TlsAlertMessage getTlsAlertMessage() {
return tlsAlertMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2024 Netflix, Inc.
*
* Licensed 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 com.netflix.zuul.netty.server.psk;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import org.bouncycastle.tls.TlsFatalAlert;

import java.util.List;

public class TlsPskDecoder extends ByteToMessageDecoder {

private final TlsPskServerProtocol tlsPskServerProtocol;

public TlsPskDecoder(TlsPskServerProtocol tlsPskServerProtocol) {
this.tlsPskServerProtocol = tlsPskServerProtocol;
}

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
final byte[] bytesRead = in.hasArray() ? in.array() : TlsPskUtils.readDirect(in);
try {
tlsPskServerProtocol.offerInput(bytesRead);
} catch (TlsFatalAlert tlsFatalAlert) {
writeOutputIfAvailable(ctx);
ctx.fireUserEventTriggered(new SslHandshakeCompletionEvent(tlsFatalAlert));
ctx.close();
return;
}
writeOutputIfAvailable(ctx);
final int appDataAvailable = tlsPskServerProtocol.getAvailableInputBytes();
if (appDataAvailable > 0) {
byte[] appData = new byte[appDataAvailable];
tlsPskServerProtocol.readInput(appData, 0, appDataAvailable);
out.add(Unpooled.wrappedBuffer(appData));
}
}

private void writeOutputIfAvailable(ChannelHandlerContext ctx) {
final int availableOutputBytes = tlsPskServerProtocol.getAvailableOutputBytes();
// output is available immediately (handshake not complete), pipe that back to the client right away
if (availableOutputBytes != 0) {
byte[] outputBytes = new byte[availableOutputBytes];
tlsPskServerProtocol.readOutput(outputBytes, 0, availableOutputBytes);
ctx.writeAndFlush(Unpooled.wrappedBuffer(outputBytes))
.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
}
}
Loading

0 comments on commit 9de74b9

Please sign in to comment.