From 29124da2ed676e2a94934e5d38989a2d04b551c4 Mon Sep 17 00:00:00 2001 From: sunnys Date: Fri, 17 May 2024 13:52:22 -0700 Subject: [PATCH 1/9] TLS PSK implementation --- zuul-core/build.gradle | 5 +- .../server/http2/Http2OrHttpHandler.java | 50 +- .../server/psk/ExternalTlsPskProvider.java | 25 + .../zuul/netty/server/psk/TlsPskHandler.java | 459 ++++++++++++++++++ .../server/ssl/SslHandshakeInfoHandler.java | 38 +- 5 files changed, 566 insertions(+), 11 deletions(-) create mode 100644 zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java create mode 100644 zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java diff --git a/zuul-core/build.gradle b/zuul-core/build.gradle index ada379f44a..c462a05194 100644 --- a/zuul-core/build.gradle +++ b/zuul-core/build.gradle @@ -6,8 +6,9 @@ dependencies { 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.76' - implementation 'org.bouncycastle:bcpkix-jdk18on:1.76' + implementation 'org.bouncycastle:bcprov-jdk18on:1.78' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.78' + implementation 'org.bouncycastle:bctls-jdk18on:1.78' implementation 'com.fasterxml.jackson.core:jackson-core:2.16.1' api 'com.fasterxml.jackson.core:jackson-databind:2.16.1' diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java index 5d8c906669..1125c7c0ea 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java @@ -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; @@ -33,7 +34,9 @@ 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; /** @@ -45,6 +48,8 @@ public class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler { public static final AttributeKey PROTOCOL_NAME = AttributeKey.valueOf("protocol_name"); + private static final String FALLBACK_APPLICATION_PROTOCOL = ApplicationProtocolNames.HTTP_1_1; + private static final DynamicHttp2FrameLogger FRAME_LOGGER = new DynamicHttp2FrameLogger(LogLevel.DEBUG, Http2FrameCodec.class); @@ -60,7 +65,7 @@ public Http2OrHttpHandler( ChannelHandler http2StreamHandler, ChannelConfig channelConfig, Consumer 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); @@ -70,6 +75,42 @@ public Http2OrHttpHandler( this.addHttpHandlerFn = addHttpHandlerFn; } + @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)) { @@ -118,4 +159,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); + } + } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java new file mode 100644 index 0000000000..e3507b7f90 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java @@ -0,0 +1,25 @@ +/* + * 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 org.bouncycastle.tls.TlsPSKExternal; + +import java.util.Vector; + +public interface ExternalTlsPskProvider { + TlsPSKExternal provide(Vector clientPskIdentities); +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java new file mode 100644 index 0000000000..100ccfc68f --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java @@ -0,0 +1,459 @@ +/* + * 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 com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Timer; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandler; +import io.netty.channel.ChannelOutboundHandler; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.ssl.SslHandshakeCompletionEvent; +import io.netty.util.ReferenceCountUtil; +import org.bouncycastle.tls.AbstractTlsServer; +import org.bouncycastle.tls.AlertDescription; +import org.bouncycastle.tls.AlertLevel; +import org.bouncycastle.tls.CipherSuite; +import org.bouncycastle.tls.ProtocolName; +import org.bouncycastle.tls.ProtocolVersion; +import org.bouncycastle.tls.TlsCredentials; +import org.bouncycastle.tls.TlsFatalAlert; +import org.bouncycastle.tls.TlsPSKExternal; +import org.bouncycastle.tls.TlsServerProtocol; +import org.bouncycastle.tls.TlsUtils; +import org.bouncycastle.tls.crypto.TlsCrypto; +import org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.security.cert.X509Certificate; +import java.io.IOException; +import java.net.SocketAddress; +import java.security.Principal; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Vector; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class TlsPskHandler extends ByteToMessageDecoder + implements ChannelOutboundHandler, ChannelInboundHandler { + + public static final Map SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP = Map.of( + CipherSuite.TLS_AES_128_GCM_SHA256, + "TLS_AES_128_GCM_SHA256", + CipherSuite.TLS_AES_256_GCM_SHA384, + "TLS_AES_256_GCM_SHA384"); + + private final Registry registry; + private final ExternalTlsPskProvider externalTlsPskProvider; + + private ZuulPskServer tlsPskServer; + + private TlsPskServerProtocol tlsPskServerProtocol; + + public TlsPskHandler(Registry registry, ExternalTlsPskProvider externalTlsPskProvider) { + super(); + this.registry = registry; + this.externalTlsPskProvider = externalTlsPskProvider; + } + + @Override + public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { + ctx.bind(localAddress, promise); + } + + @Override + public void connect( + ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) + throws Exception { + ctx.connect(remoteAddress, localAddress, promise); + } + + @Override + public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + ctx.disconnect(promise); + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + ctx.close(promise); + } + + @Override + public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + ctx.deregister(promise); + } + + @Override + public void read(ChannelHandlerContext ctx) throws Exception { + ctx.read(); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (!(msg instanceof ByteBuf byteBufMsg)) { + ctx.write(msg, promise); + return; + } + byte[] appDataBytes = byteBufMsg.hasArray() ? byteBufMsg.array() : readDirect(byteBufMsg); + ReferenceCountUtil.safeRelease(byteBufMsg); + tlsPskServerProtocol.writeApplicationData(appDataBytes, 0, appDataBytes.length); + int availableOutputBytes = tlsPskServerProtocol.getAvailableOutputBytes(); + if (availableOutputBytes != 0) { + byte[] outputBytes = new byte[availableOutputBytes]; + tlsPskServerProtocol.readOutput(outputBytes, 0, availableOutputBytes); + ctx.write(Unpooled.wrappedBuffer(outputBytes), promise) + .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + } + + @Override + public void flush(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + final byte[] bytesRead = in.hasArray() ? in.array() : readDirect(in); + try { + tlsPskServerProtocol.offerInput(bytesRead); + } catch (TlsFatalAlert tlsFatalAlert) { + writeOutputIfAvailable(ctx); + 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); + } + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + tlsPskServer = + new ZuulPskServer(new JcaTlsCryptoProvider().create(new SecureRandom()), registry, externalTlsPskProvider, ctx); + tlsPskServerProtocol = new TlsPskServerProtocol(); + tlsPskServerProtocol.accept(tlsPskServer); + super.channelRegistered(ctx); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + super.channelRead(ctx, msg); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + } + + private static byte[] readDirect(ByteBuf byteBufMsg) { + int length = byteBufMsg.readableBytes(); + byte[] dest = new byte[length]; + byteBufMsg.readSlice(length).getBytes(0, dest); + return dest; + } + + /** + * Returns the name of the current application-level protocol. + * Returns: + * the protocol name or null if application-level protocol has not been negotiated + */ + public String getApplicationProtocol() { + return tlsPskServer!=null ? tlsPskServer.getApplicationProtocol() : null; + } + + public SSLSession getSession() { + return tlsPskServerProtocol!=null ? tlsPskServerProtocol.getSSLSession() : null; + } + + static class ZuulPskServer extends AbstractTlsServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(ZuulPskServer.class); + + private static class PSKTimings { + private final Timer handshakeCompleteTimer; + + private Long handshakeStartTime; + + PSKTimings(Registry registry) { + handshakeCompleteTimer = registry.timer("zuul.psk.handshake.complete.time"); + } + + public void recordHandshakeStarting() { + handshakeStartTime = System.nanoTime(); + } + + public void recordHandshakeComplete() { + handshakeCompleteTimer.record(System.nanoTime() - handshakeStartTime, TimeUnit.NANOSECONDS); + } + } + + private final PSKTimings pskTimings; + + private final ExternalTlsPskProvider externalTlsPskProvider; + + private final ChannelHandlerContext ctx; + + + public ZuulPskServer( + TlsCrypto crypto, + Registry registry, + ExternalTlsPskProvider externalTlsPskProvider, ChannelHandlerContext ctx) { + super(crypto); + this.pskTimings = new PSKTimings(registry); + this.externalTlsPskProvider = externalTlsPskProvider; + this.ctx = ctx; + } + + @Override + public TlsCredentials getCredentials() { + return null; + } + + @Override + protected Vector getProtocolNames() { + Vector protocolNames = new Vector(); + protocolNames.addElement(ProtocolName.HTTP_1_1); + protocolNames.addElement(ProtocolName.HTTP_2_TLS); + return protocolNames; + } + + @Override + public void notifyHandshakeBeginning() throws IOException { + // TODO: sunnys - handshake timeouts + super.notifyHandshakeBeginning(); + pskTimings.recordHandshakeStarting(); + } + + @Override + public void notifyHandshakeComplete() throws IOException { + super.notifyHandshakeComplete(); + pskTimings.recordHandshakeComplete(); + ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS); + } + + @Override + protected ProtocolVersion[] getSupportedVersions() { + return ProtocolVersion.TLSv13.only(); + } + + @Override + protected int[] getSupportedCipherSuites() { + return TlsUtils.getSupportedCipherSuites( + getCrypto(), + SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.keySet().stream() + .mapToInt(Number::intValue) + .toArray()); + } + + @Override + public ProtocolVersion getServerVersion() throws IOException { + return super.getServerVersion(); + } + + @Override + public TlsPSKExternal getExternalPSK(Vector clientPskIdentities) { + return externalTlsPskProvider.provide(clientPskIdentities); + } + + @Override + public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Throwable cause) { + super.notifyAlertRaised(alertLevel, alertDescription, message, cause); + Consumer loggerFunc = (alertLevel == AlertLevel.fatal) ? LOGGER::error : LOGGER::debug; + loggerFunc.accept("TLS/PSK server raised alert: " + AlertLevel.getText(alertLevel) + ", " + + AlertDescription.getText(alertDescription)); + if (message != null) { + loggerFunc.accept("> " + message); + } + if (cause != null) { + LOGGER.error("TLS/PSK alert stacktrace", cause); + } + } + + @Override + public void notifyAlertReceived(short alertLevel, short alertDescription) { + Consumer loggerFunc = (alertLevel == AlertLevel.fatal) ? LOGGER::error : LOGGER::debug; + loggerFunc.accept("TLS 1.3 PSK server received alert: " + AlertLevel.getText(alertLevel) + ", " + + AlertDescription.getText(alertDescription)); + } + + @Override + public void processClientExtensions(Hashtable clientExtensions) throws IOException { + if (context.getSecurityParametersHandshake().getClientRandom() == null) { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + super.processClientExtensions(clientExtensions); + } + + @Override + public Hashtable getServerExtensions() throws IOException { + if (context.getSecurityParametersHandshake().getServerRandom() == null) { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + return super.getServerExtensions(); + } + + @Override + public void getServerExtensionsForConnection(Hashtable serverExtensions) throws IOException { + if (context.getSecurityParametersHandshake().getServerRandom() == null) { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + super.getServerExtensionsForConnection(serverExtensions); + } + + public String getApplicationProtocol() { + ProtocolName protocolName = + context.getSecurityParametersConnection().getApplicationProtocol(); + if (protocolName!=null) { + return protocolName.getUtf8Decoding(); + } + return null; + } + } + + static class TlsPskServerProtocol extends TlsServerProtocol { + + public SSLSession getSSLSession() { + return new SSLSession() { + @Override + public byte[] getId() { + return tlsSession.getSessionID(); + } + + @Override + public SSLSessionContext getSessionContext() { + return null; + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public void invalidate() {} + + @Override + public boolean isValid() { + return !isClosed(); + } + + @Override + public void putValue(String name, Object value) {} + + @Override + public Object getValue(String name) { + return null; + } + + @Override + public void removeValue(String name) {} + + @Override + public String[] getValueNames() { + return new String[0]; + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return new Certificate[0]; + } + + @Override + public Certificate[] getLocalCertificates() { + return new Certificate[0]; + } + + @Override + public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { + return new X509Certificate[0]; + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + return null; + } + + @Override + public Principal getLocalPrincipal() { + return null; + } + + @Override + public String getCipherSuite() { + return SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.get( + getContext().getSecurityParameters().getCipherSuite()); + } + + @Override + public String getProtocol() { + return getContext().getServerVersion().getName(); + } + + @Override + public String getPeerHost() { + return null; + } + + @Override + public int getPeerPort() { + return 0; + } + + @Override + public int getPacketBufferSize() { + return 0; + } + + @Override + public int getApplicationBufferSize() { + return 0; + } + }; + } + } +} \ No newline at end of file diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java index 79a9154434..c3ec4230c5 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java @@ -23,6 +23,7 @@ import com.netflix.spectator.api.NoopRegistry; import com.netflix.spectator.api.Registry; import com.netflix.zuul.netty.ChannelUtils; +import com.netflix.zuul.netty.server.psk.TlsPskHandler; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; import io.netty.channel.ChannelHandlerContext; @@ -33,15 +34,16 @@ import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.AttributeKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; import java.nio.channels.ClosedChannelException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Stores info about the client and server's SSL certificates in the context, after a successful handshake. @@ -81,10 +83,13 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc CurrentPassport.fromChannel(ctx.channel()).add(PassportState.SERVER_CH_SSL_HANDSHAKE_COMPLETE); - SslHandler sslhandler = ctx.channel().pipeline().get(SslHandler.class); - SSLSession session = sslhandler.engine().getSession(); + SSLSession session = getSSLSession(ctx); + if (session == null) { + logger.warn("Error getting the SSL handshake info. SSLSession is null"); + return; + } - ClientAuth clientAuth = whichClientAuthEnum(sslhandler); + ClientAuth clientAuth = whichClientAuthEnum(ctx); Certificate serverCert = null; X509Certificate peerCert = null; @@ -184,7 +189,24 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc super.userEventTriggered(ctx, evt); } - private ClientAuth whichClientAuthEnum(SslHandler sslhandler) { + private SSLSession getSSLSession(ChannelHandlerContext ctx) { + SslHandler sslhandler = ctx.channel().pipeline().get(SslHandler.class); + if (sslhandler != null) { + return sslhandler.engine().getSession(); + } + TlsPskHandler tlsPskHandler = ctx.channel().pipeline().get(TlsPskHandler.class); + if (tlsPskHandler != null) { + return tlsPskHandler.getSession(); + } + return null; + } + + private ClientAuth whichClientAuthEnum(ChannelHandlerContext ctx) { + SslHandler sslhandler = ctx.channel().pipeline().get(SslHandler.class); + if (sslhandler == null) { + return ClientAuth.NONE; + } + ClientAuth clientAuth; if (sslhandler.engine().getNeedClientAuth()) { clientAuth = ClientAuth.REQUIRE; From e906fb96e29e88f5af09d99d53aa73210b89871e Mon Sep 17 00:00:00 2001 From: sunnys Date: Tue, 21 May 2024 13:09:31 -0700 Subject: [PATCH 2/9] Breaking apart the PSK creation to an interface --- build.gradle | 3 +- zuul-core/build.gradle | 3 ++ .../server/psk/ExternalTlsPskProvider.java | 6 +--- .../psk/PskCreationFailureException.java | 31 ++++++++++++++++++ .../zuul/netty/server/psk/TlsPskHandler.java | 32 ++++++++++++++++++- 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java diff --git a/build.gradle b/build.gradle index e3171a9146..fd34fb7f5c 100644 --- a/build.gradle +++ b/build.gradle @@ -86,7 +86,8 @@ subprojects { mockito: 'org.mockito:mockito-core:5.+', slf4j: "org.slf4j:slf4j-api:1.7.36", truth: 'com.google.truth:truth:1.1.5', - awaitility: 'org.awaitility:awaitility:4.2.0' + awaitility: 'org.awaitility:awaitility:4.2.0', + lombok: 'org.projectlombok:lombok:1.18.30' ] } diff --git a/zuul-core/build.gradle b/zuul-core/build.gradle index c462a05194..dad144b33b 100644 --- a/zuul-core/build.gradle +++ b/zuul-core/build.gradle @@ -3,6 +3,9 @@ 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 diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java index e3507b7f90..07863e103e 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java @@ -16,10 +16,6 @@ package com.netflix.zuul.netty.server.psk; -import org.bouncycastle.tls.TlsPSKExternal; - -import java.util.Vector; - public interface ExternalTlsPskProvider { - TlsPSKExternal provide(Vector clientPskIdentities); + byte[] provide(byte[] clientPskIdentity, byte[] clientRandom) throws PskCreationFailureException; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java new file mode 100644 index 0000000000..ad7658ed68 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java @@ -0,0 +1,31 @@ +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; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java index 100ccfc68f..adf851e3c6 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java @@ -28,18 +28,23 @@ import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.ReferenceCountUtil; +import lombok.SneakyThrows; import org.bouncycastle.tls.AbstractTlsServer; import org.bouncycastle.tls.AlertDescription; import org.bouncycastle.tls.AlertLevel; +import org.bouncycastle.tls.BasicTlsPSKExternal; import org.bouncycastle.tls.CipherSuite; +import org.bouncycastle.tls.PRFAlgorithm; import org.bouncycastle.tls.ProtocolName; import org.bouncycastle.tls.ProtocolVersion; +import org.bouncycastle.tls.PskIdentity; import org.bouncycastle.tls.TlsCredentials; import org.bouncycastle.tls.TlsFatalAlert; import org.bouncycastle.tls.TlsPSKExternal; import org.bouncycastle.tls.TlsServerProtocol; import org.bouncycastle.tls.TlsUtils; import org.bouncycastle.tls.crypto.TlsCrypto; +import org.bouncycastle.tls.crypto.TlsSecret; import org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -291,8 +296,21 @@ public ProtocolVersion getServerVersion() throws IOException { } @Override + @SneakyThrows // TODO: Ask BC folks to see if getExternalPSK can throw a checked exception public TlsPSKExternal getExternalPSK(Vector clientPskIdentities) { - return externalTlsPskProvider.provide(clientPskIdentities); + byte[] clientPskIdentity = ((PskIdentity)clientPskIdentities.get(0)).getIdentity(); + byte[] psk; + try{ + psk = externalTlsPskProvider.provide(clientPskIdentity, this.context.getSecurityParametersHandshake().getClientRandom()); + }catch (PskCreationFailureException e) { + throw switch (e.getTlsAlertMessage()) { + case unknown_psk_identity -> new TlsFatalAlert(AlertDescription.unknown_psk_identity, "Unknown or null client PSk identity"); + case decrypt_error -> new TlsFatalAlert(AlertDescription.decrypt_error, "Invalid or expired client PSk identity"); + }; + } + TlsSecret pskTlsSecret = getCrypto().createSecret(psk); + int prfAlgorithm = getPRFAlgorithm13(getSelectedCipherSuite()); + return new BasicTlsPSKExternal(clientPskIdentity, pskTlsSecret, prfAlgorithm); } @Override @@ -348,6 +366,18 @@ public String getApplicationProtocol() { } return null; } + + private static int getPRFAlgorithm13(int cipherSuite) { + return switch (cipherSuite) { + case CipherSuite.TLS_AES_128_CCM_SHA256, + CipherSuite.TLS_AES_128_CCM_8_SHA256, + CipherSuite.TLS_AES_128_GCM_SHA256, + CipherSuite.TLS_CHACHA20_POLY1305_SHA256 -> PRFAlgorithm.tls13_hkdf_sha256; + case CipherSuite.TLS_AES_256_GCM_SHA384 -> PRFAlgorithm.tls13_hkdf_sha384; + case CipherSuite.TLS_SM4_CCM_SM3, CipherSuite.TLS_SM4_GCM_SM3 -> PRFAlgorithm.tls13_hkdf_sm3; + default -> -1; + }; + } } static class TlsPskServerProtocol extends TlsServerProtocol { From 0ef6c0a2d948c7ede7124b42467c84a1cd181b64 Mon Sep 17 00:00:00 2001 From: sunnys Date: Fri, 24 May 2024 11:10:29 -0700 Subject: [PATCH 3/9] Added license --- .../server/psk/PskCreationFailureException.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java index ad7658ed68..08b1b2966b 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java @@ -1,3 +1,18 @@ +/* + * 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 { From 99d644a67b8d6650764fb937fb53de62c05c9d23 Mon Sep 17 00:00:00 2001 From: sunnys Date: Fri, 14 Jun 2024 10:16:33 -0700 Subject: [PATCH 4/9] Store PSK info in handshake info --- .../netty/common/ssl/SslHandshakeInfo.java | 17 ++++++++++++- .../server/psk/ClientPSKIdentityInfo.java | 3 +++ .../server/psk/ExternalTlsPskProvider.java | 1 + .../zuul/netty/server/psk/TlsPskHandler.java | 14 +++++++++-- .../server/ssl/SslHandshakeInfoHandler.java | 25 +++++++++++++------ 5 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java diff --git a/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java b/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java index 16cfd2342e..6e03bd121d 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java @@ -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; @@ -31,6 +32,8 @@ public class SslHandshakeInfo { private final Certificate serverCertificate; private final X509Certificate clientCertificate; private final boolean isOfIntermediary; + private final boolean usingExternalPSK; + private final ClientPSKIdentityInfo clientPSKIdentityInfo; public SslHandshakeInfo( boolean isOfIntermediary, @@ -38,13 +41,17 @@ public SslHandshakeInfo( String cipherSuite, ClientAuth clientAuthRequirement, Certificate serverCertificate, - X509Certificate clientCertificate) { + 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() { @@ -71,6 +78,14 @@ public X509Certificate getClientCertificate() { return clientCertificate; } + public boolean usingExternalPSK() { + return usingExternalPSK; + } + + public ClientPSKIdentityInfo geClientPSKIdentityInfo() { + return clientPSKIdentityInfo; + } + @Override public String toString() { return "SslHandshakeInfo{" + "protocol='" diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java new file mode 100644 index 0000000000..bdb1a03ada --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java @@ -0,0 +1,3 @@ +package com.netflix.zuul.netty.server.psk; + +public record ClientPSKIdentityInfo(byte[] clientPSKIdentity) {} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java index 07863e103e..03aba1265f 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java @@ -16,6 +16,7 @@ package com.netflix.zuul.netty.server.psk; + public interface ExternalTlsPskProvider { byte[] provide(byte[] clientPskIdentity, byte[] clientRandom) throws PskCreationFailureException; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java index adf851e3c6..9b3b7d5532 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java @@ -27,6 +27,7 @@ import io.netty.channel.ChannelPromise; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.ssl.SslHandshakeCompletionEvent; +import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; import lombok.SneakyThrows; import org.bouncycastle.tls.AbstractTlsServer; @@ -74,6 +75,12 @@ public class TlsPskHandler extends ByteToMessageDecoder CipherSuite.TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384"); + public static final AttributeKey CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY = + AttributeKey.newInstance("_client_psk_identity_info"); + + public static final AttributeKey TLS_HANDSHAKE_USING_EXTERNAL_PSK = + AttributeKey.newInstance("_tls_handshake_using_external_psk"); + private final Registry registry; private final ExternalTlsPskProvider externalTlsPskProvider; @@ -264,15 +271,17 @@ protected Vector getProtocolNames() { @Override public void notifyHandshakeBeginning() throws IOException { + pskTimings.recordHandshakeStarting(); + this.ctx.channel().attr(TLS_HANDSHAKE_USING_EXTERNAL_PSK).set(false); // TODO: sunnys - handshake timeouts super.notifyHandshakeBeginning(); - pskTimings.recordHandshakeStarting(); } @Override public void notifyHandshakeComplete() throws IOException { - super.notifyHandshakeComplete(); pskTimings.recordHandshakeComplete(); + this.ctx.channel().attr(TLS_HANDSHAKE_USING_EXTERNAL_PSK).set(true); + super.notifyHandshakeComplete(); ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS); } @@ -301,6 +310,7 @@ public TlsPSKExternal getExternalPSK(Vector clientPskIdentities) { byte[] clientPskIdentity = ((PskIdentity)clientPskIdentities.get(0)).getIdentity(); byte[] psk; try{ + this.ctx.channel().attr(CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY).set(new ClientPSKIdentityInfo(clientPskIdentity)); psk = externalTlsPskProvider.provide(clientPskIdentity, this.context.getSecurityParametersHandshake().getClientRandom()); }catch (PskCreationFailureException e) { throw switch (e.getTlsAlertMessage()) { diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java index c3ec4230c5..6157afdd31 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java @@ -23,6 +23,7 @@ import com.netflix.spectator.api.NoopRegistry; import com.netflix.spectator.api.Registry; import com.netflix.zuul.netty.ChannelUtils; +import com.netflix.zuul.netty.server.psk.ClientPSKIdentityInfo; import com.netflix.zuul.netty.server.psk.TlsPskHandler; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; @@ -34,16 +35,15 @@ import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.AttributeKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; import java.nio.channels.ClosedChannelException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Stores info about the client and server's SSL certificates in the context, after a successful handshake. @@ -103,13 +103,23 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc serverCert = session.getLocalCertificates()[0]; } + Boolean tlsHandshakeUsingExternalPSK = ctx.channel() + .attr(TlsPskHandler.TLS_HANDSHAKE_USING_EXTERNAL_PSK) + .get(); + + ClientPSKIdentityInfo clientPSKIdentityInfo = ctx.channel() + .attr(TlsPskHandler.CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY) + .get(); + SslHandshakeInfo info = new SslHandshakeInfo( isSSlFromIntermediary, session.getProtocol(), session.getCipherSuite(), clientAuth, serverCert, - peerCert); + peerCert, + tlsHandshakeUsingExternalPSK, + clientPSKIdentityInfo); ctx.channel().attr(ATTR_SSL_INFO).set(info); // Metrics. @@ -134,7 +144,8 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc // without sending anything. // So don't treat these as SSL handshake failures. logger.debug( - "Client closed connection or it idle timed-out without doing an ssl handshake. , client_ip = {}, channel_info = {}", + "Client closed connection or it idle timed-out without doing an ssl handshake. ," + + " client_ip = {}, channel_info = {}", clientIP, ChannelUtils.channelInfoForLogging(ctx.channel())); } else if (cause instanceof SSLException From a19fa609ba724722f6b824ef3ca4ad0200f2fd09 Mon Sep 17 00:00:00 2001 From: sunnys Date: Wed, 24 Jul 2024 15:21:13 -0700 Subject: [PATCH 5/9] Addressed review comments --- .../zuul/netty/server/psk/TlsPskDecoder.java | 51 +++ .../zuul/netty/server/psk/TlsPskHandler.java | 423 +----------------- .../server/psk/TlsPskServerProtocol.java | 117 +++++ .../zuul/netty/server/psk/TlsPskUtils.java | 12 + .../zuul/netty/server/psk/ZuulPskServer.java | 214 +++++++++ .../server/ssl/SslHandshakeInfoHandler.java | 12 +- 6 files changed, 417 insertions(+), 412 deletions(-) create mode 100644 zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java create mode 100644 zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java create mode 100644 zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java create mode 100644 zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java new file mode 100644 index 0000000000..c9fd2323b7 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java @@ -0,0 +1,51 @@ +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 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); + } + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java index 9b3b7d5532..8c77ac1990 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java @@ -17,193 +17,81 @@ package com.netflix.zuul.netty.server.psk; import com.netflix.spectator.api.Registry; -import com.netflix.spectator.api.Timer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandler; -import io.netty.channel.ChannelOutboundHandler; import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; -import lombok.SneakyThrows; -import org.bouncycastle.tls.AbstractTlsServer; -import org.bouncycastle.tls.AlertDescription; -import org.bouncycastle.tls.AlertLevel; -import org.bouncycastle.tls.BasicTlsPSKExternal; import org.bouncycastle.tls.CipherSuite; -import org.bouncycastle.tls.PRFAlgorithm; import org.bouncycastle.tls.ProtocolName; -import org.bouncycastle.tls.ProtocolVersion; -import org.bouncycastle.tls.PskIdentity; -import org.bouncycastle.tls.TlsCredentials; -import org.bouncycastle.tls.TlsFatalAlert; -import org.bouncycastle.tls.TlsPSKExternal; -import org.bouncycastle.tls.TlsServerProtocol; -import org.bouncycastle.tls.TlsUtils; -import org.bouncycastle.tls.crypto.TlsCrypto; -import org.bouncycastle.tls.crypto.TlsSecret; import org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSessionContext; -import javax.security.cert.X509Certificate; -import java.io.IOException; -import java.net.SocketAddress; -import java.security.Principal; import java.security.SecureRandom; -import java.security.cert.Certificate; -import java.util.Hashtable; -import java.util.List; import java.util.Map; -import java.util.Vector; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; +import java.util.Set; -public class TlsPskHandler extends ByteToMessageDecoder - implements ChannelOutboundHandler, ChannelInboundHandler { +public class TlsPskHandler extends ChannelDuplexHandler { public static final Map SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP = Map.of( CipherSuite.TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", CipherSuite.TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384"); - public static final AttributeKey CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY = AttributeKey.newInstance("_client_psk_identity_info"); - - public static final AttributeKey TLS_HANDSHAKE_USING_EXTERNAL_PSK = - AttributeKey.newInstance("_tls_handshake_using_external_psk"); + public static final SecureRandom secureRandom = new SecureRandom(); private final Registry registry; private final ExternalTlsPskProvider externalTlsPskProvider; + private final Set supportedApplicationProtocols; + private final TlsPskServerProtocol tlsPskServerProtocol; private ZuulPskServer tlsPskServer; - private TlsPskServerProtocol tlsPskServerProtocol; - - public TlsPskHandler(Registry registry, ExternalTlsPskProvider externalTlsPskProvider) { + public TlsPskHandler(Registry registry, ExternalTlsPskProvider externalTlsPskProvider, Set supportedApplicationProtocols) { super(); this.registry = registry; this.externalTlsPskProvider = externalTlsPskProvider; - } - - @Override - public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { - ctx.bind(localAddress, promise); - } - - @Override - public void connect( - ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) - throws Exception { - ctx.connect(remoteAddress, localAddress, promise); - } - - @Override - public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - ctx.disconnect(promise); - } - - @Override - public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - ctx.close(promise); - } - - @Override - public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - ctx.deregister(promise); - } - - @Override - public void read(ChannelHandlerContext ctx) throws Exception { - ctx.read(); + this.supportedApplicationProtocols = supportedApplicationProtocols; + this.tlsPskServerProtocol = new TlsPskServerProtocol(); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (!(msg instanceof ByteBuf byteBufMsg)) { - ctx.write(msg, promise); + ReferenceCountUtil.safeRelease(msg); + promise.setFailure(new IllegalStateException("Failed to write message on the channel. Message is not a ByteBuf")); return; } - byte[] appDataBytes = byteBufMsg.hasArray() ? byteBufMsg.array() : readDirect(byteBufMsg); + byte[] appDataBytes = byteBufMsg.hasArray() ? byteBufMsg.array() : TlsPskUtils.readDirect(byteBufMsg); ReferenceCountUtil.safeRelease(byteBufMsg); tlsPskServerProtocol.writeApplicationData(appDataBytes, 0, appDataBytes.length); int availableOutputBytes = tlsPskServerProtocol.getAvailableOutputBytes(); if (availableOutputBytes != 0) { byte[] outputBytes = new byte[availableOutputBytes]; tlsPskServerProtocol.readOutput(outputBytes, 0, availableOutputBytes); - ctx.write(Unpooled.wrappedBuffer(outputBytes), promise) + ctx.writeAndFlush(Unpooled.wrappedBuffer(outputBytes), promise) .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } } @Override - public void flush(ChannelHandlerContext ctx) { - ctx.flush(); - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - final byte[] bytesRead = in.hasArray() ? in.array() : readDirect(in); - try { - tlsPskServerProtocol.offerInput(bytesRead); - } catch (TlsFatalAlert tlsFatalAlert) { - writeOutputIfAvailable(ctx); - 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); - } + public void handlerAdded(ChannelHandlerContext ctx) { + ctx.pipeline().addBefore(ctx.name(), "tls_psk_handler", new TlsPskDecoder(tlsPskServerProtocol)); } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { tlsPskServer = - new ZuulPskServer(new JcaTlsCryptoProvider().create(new SecureRandom()), registry, externalTlsPskProvider, ctx); - tlsPskServerProtocol = new TlsPskServerProtocol(); + new ZuulPskServer(new JcaTlsCryptoProvider().create(secureRandom), registry, externalTlsPskProvider, ctx, supportedApplicationProtocols); tlsPskServerProtocol.accept(tlsPskServer); super.channelRegistered(ctx); } - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - super.channelRead(ctx, msg); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - super.channelActive(ctx); - } - - private static byte[] readDirect(ByteBuf byteBufMsg) { - int length = byteBufMsg.readableBytes(); - byte[] dest = new byte[length]; - byteBufMsg.readSlice(length).getBytes(0, dest); - return dest; - } - /** * Returns the name of the current application-level protocol. * Returns: @@ -217,283 +105,4 @@ public SSLSession getSession() { return tlsPskServerProtocol!=null ? tlsPskServerProtocol.getSSLSession() : null; } - static class ZuulPskServer extends AbstractTlsServer { - - private static final Logger LOGGER = LoggerFactory.getLogger(ZuulPskServer.class); - - private static class PSKTimings { - private final Timer handshakeCompleteTimer; - - private Long handshakeStartTime; - - PSKTimings(Registry registry) { - handshakeCompleteTimer = registry.timer("zuul.psk.handshake.complete.time"); - } - - public void recordHandshakeStarting() { - handshakeStartTime = System.nanoTime(); - } - - public void recordHandshakeComplete() { - handshakeCompleteTimer.record(System.nanoTime() - handshakeStartTime, TimeUnit.NANOSECONDS); - } - } - - private final PSKTimings pskTimings; - - private final ExternalTlsPskProvider externalTlsPskProvider; - - private final ChannelHandlerContext ctx; - - - public ZuulPskServer( - TlsCrypto crypto, - Registry registry, - ExternalTlsPskProvider externalTlsPskProvider, ChannelHandlerContext ctx) { - super(crypto); - this.pskTimings = new PSKTimings(registry); - this.externalTlsPskProvider = externalTlsPskProvider; - this.ctx = ctx; - } - - @Override - public TlsCredentials getCredentials() { - return null; - } - - @Override - protected Vector getProtocolNames() { - Vector protocolNames = new Vector(); - protocolNames.addElement(ProtocolName.HTTP_1_1); - protocolNames.addElement(ProtocolName.HTTP_2_TLS); - return protocolNames; - } - - @Override - public void notifyHandshakeBeginning() throws IOException { - pskTimings.recordHandshakeStarting(); - this.ctx.channel().attr(TLS_HANDSHAKE_USING_EXTERNAL_PSK).set(false); - // TODO: sunnys - handshake timeouts - super.notifyHandshakeBeginning(); - } - - @Override - public void notifyHandshakeComplete() throws IOException { - pskTimings.recordHandshakeComplete(); - this.ctx.channel().attr(TLS_HANDSHAKE_USING_EXTERNAL_PSK).set(true); - super.notifyHandshakeComplete(); - ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS); - } - - @Override - protected ProtocolVersion[] getSupportedVersions() { - return ProtocolVersion.TLSv13.only(); - } - - @Override - protected int[] getSupportedCipherSuites() { - return TlsUtils.getSupportedCipherSuites( - getCrypto(), - SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.keySet().stream() - .mapToInt(Number::intValue) - .toArray()); - } - - @Override - public ProtocolVersion getServerVersion() throws IOException { - return super.getServerVersion(); - } - - @Override - @SneakyThrows // TODO: Ask BC folks to see if getExternalPSK can throw a checked exception - public TlsPSKExternal getExternalPSK(Vector clientPskIdentities) { - byte[] clientPskIdentity = ((PskIdentity)clientPskIdentities.get(0)).getIdentity(); - byte[] psk; - try{ - this.ctx.channel().attr(CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY).set(new ClientPSKIdentityInfo(clientPskIdentity)); - psk = externalTlsPskProvider.provide(clientPskIdentity, this.context.getSecurityParametersHandshake().getClientRandom()); - }catch (PskCreationFailureException e) { - throw switch (e.getTlsAlertMessage()) { - case unknown_psk_identity -> new TlsFatalAlert(AlertDescription.unknown_psk_identity, "Unknown or null client PSk identity"); - case decrypt_error -> new TlsFatalAlert(AlertDescription.decrypt_error, "Invalid or expired client PSk identity"); - }; - } - TlsSecret pskTlsSecret = getCrypto().createSecret(psk); - int prfAlgorithm = getPRFAlgorithm13(getSelectedCipherSuite()); - return new BasicTlsPSKExternal(clientPskIdentity, pskTlsSecret, prfAlgorithm); - } - - @Override - public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Throwable cause) { - super.notifyAlertRaised(alertLevel, alertDescription, message, cause); - Consumer loggerFunc = (alertLevel == AlertLevel.fatal) ? LOGGER::error : LOGGER::debug; - loggerFunc.accept("TLS/PSK server raised alert: " + AlertLevel.getText(alertLevel) + ", " - + AlertDescription.getText(alertDescription)); - if (message != null) { - loggerFunc.accept("> " + message); - } - if (cause != null) { - LOGGER.error("TLS/PSK alert stacktrace", cause); - } - } - - @Override - public void notifyAlertReceived(short alertLevel, short alertDescription) { - Consumer loggerFunc = (alertLevel == AlertLevel.fatal) ? LOGGER::error : LOGGER::debug; - loggerFunc.accept("TLS 1.3 PSK server received alert: " + AlertLevel.getText(alertLevel) + ", " - + AlertDescription.getText(alertDescription)); - } - - @Override - public void processClientExtensions(Hashtable clientExtensions) throws IOException { - if (context.getSecurityParametersHandshake().getClientRandom() == null) { - throw new TlsFatalAlert(AlertDescription.internal_error); - } - super.processClientExtensions(clientExtensions); - } - - @Override - public Hashtable getServerExtensions() throws IOException { - if (context.getSecurityParametersHandshake().getServerRandom() == null) { - throw new TlsFatalAlert(AlertDescription.internal_error); - } - return super.getServerExtensions(); - } - - @Override - public void getServerExtensionsForConnection(Hashtable serverExtensions) throws IOException { - if (context.getSecurityParametersHandshake().getServerRandom() == null) { - throw new TlsFatalAlert(AlertDescription.internal_error); - } - super.getServerExtensionsForConnection(serverExtensions); - } - - public String getApplicationProtocol() { - ProtocolName protocolName = - context.getSecurityParametersConnection().getApplicationProtocol(); - if (protocolName!=null) { - return protocolName.getUtf8Decoding(); - } - return null; - } - - private static int getPRFAlgorithm13(int cipherSuite) { - return switch (cipherSuite) { - case CipherSuite.TLS_AES_128_CCM_SHA256, - CipherSuite.TLS_AES_128_CCM_8_SHA256, - CipherSuite.TLS_AES_128_GCM_SHA256, - CipherSuite.TLS_CHACHA20_POLY1305_SHA256 -> PRFAlgorithm.tls13_hkdf_sha256; - case CipherSuite.TLS_AES_256_GCM_SHA384 -> PRFAlgorithm.tls13_hkdf_sha384; - case CipherSuite.TLS_SM4_CCM_SM3, CipherSuite.TLS_SM4_GCM_SM3 -> PRFAlgorithm.tls13_hkdf_sm3; - default -> -1; - }; - } - } - - static class TlsPskServerProtocol extends TlsServerProtocol { - - public SSLSession getSSLSession() { - return new SSLSession() { - @Override - public byte[] getId() { - return tlsSession.getSessionID(); - } - - @Override - public SSLSessionContext getSessionContext() { - return null; - } - - @Override - public long getCreationTime() { - return 0; - } - - @Override - public long getLastAccessedTime() { - return 0; - } - - @Override - public void invalidate() {} - - @Override - public boolean isValid() { - return !isClosed(); - } - - @Override - public void putValue(String name, Object value) {} - - @Override - public Object getValue(String name) { - return null; - } - - @Override - public void removeValue(String name) {} - - @Override - public String[] getValueNames() { - return new String[0]; - } - - @Override - public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { - return new Certificate[0]; - } - - @Override - public Certificate[] getLocalCertificates() { - return new Certificate[0]; - } - - @Override - public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { - return new X509Certificate[0]; - } - - @Override - public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - return null; - } - - @Override - public Principal getLocalPrincipal() { - return null; - } - - @Override - public String getCipherSuite() { - return SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.get( - getContext().getSecurityParameters().getCipherSuite()); - } - - @Override - public String getProtocol() { - return getContext().getServerVersion().getName(); - } - - @Override - public String getPeerHost() { - return null; - } - - @Override - public int getPeerPort() { - return 0; - } - - @Override - public int getPacketBufferSize() { - return 0; - } - - @Override - public int getApplicationBufferSize() { - return 0; - } - }; - } - } } \ No newline at end of file diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java new file mode 100644 index 0000000000..dadaa83aa9 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java @@ -0,0 +1,117 @@ +package com.netflix.zuul.netty.server.psk; + +import org.bouncycastle.tls.TlsServerProtocol; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.security.cert.X509Certificate; +import java.security.Principal; +import java.security.cert.Certificate; + +public class TlsPskServerProtocol extends TlsServerProtocol { + + public SSLSession getSSLSession() { + return new SSLSession() { + @Override + public byte[] getId() { + return tlsSession.getSessionID(); + } + + @Override + public SSLSessionContext getSessionContext() { + return null; + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public void invalidate() {} + + @Override + public boolean isValid() { + return !isClosed(); + } + + @Override + public void putValue(String name, Object value) {} + + @Override + public Object getValue(String name) { + return null; + } + + @Override + public void removeValue(String name) {} + + @Override + public String[] getValueNames() { + return new String[0]; + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return new Certificate[0]; + } + + @Override + public Certificate[] getLocalCertificates() { + return new Certificate[0]; + } + + @Override + public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { + return new X509Certificate[0]; + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + return null; + } + + @Override + public Principal getLocalPrincipal() { + return null; + } + + @Override + public String getCipherSuite() { + return TlsPskHandler.SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.get( + getContext().getSecurityParameters().getCipherSuite()); + } + + @Override + public String getProtocol() { + return getContext().getServerVersion().getName(); + } + + @Override + public String getPeerHost() { + return null; + } + + @Override + public int getPeerPort() { + return 0; + } + + @Override + public int getPacketBufferSize() { + return 0; + } + + @Override + public int getApplicationBufferSize() { + return 0; + } + }; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java new file mode 100644 index 0000000000..5b2cc32a59 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java @@ -0,0 +1,12 @@ +package com.netflix.zuul.netty.server.psk; + +import io.netty.buffer.ByteBuf; + +class TlsPskUtils { + static byte[] readDirect(ByteBuf byteBufMsg) { + int length = byteBufMsg.readableBytes(); + byte[] dest = new byte[length]; + byteBufMsg.readSlice(length).getBytes(0, dest); + return dest; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java new file mode 100644 index 0000000000..3aa6ee5769 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java @@ -0,0 +1,214 @@ +package com.netflix.zuul.netty.server.psk; + +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Timer; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.ssl.SslHandshakeCompletionEvent; +import io.netty.util.AttributeKey; +import lombok.SneakyThrows; +import org.bouncycastle.tls.AbstractTlsServer; +import org.bouncycastle.tls.AlertDescription; +import org.bouncycastle.tls.AlertLevel; +import org.bouncycastle.tls.BasicTlsPSKExternal; +import org.bouncycastle.tls.CipherSuite; +import org.bouncycastle.tls.PRFAlgorithm; +import org.bouncycastle.tls.ProtocolName; +import org.bouncycastle.tls.ProtocolVersion; +import org.bouncycastle.tls.PskIdentity; +import org.bouncycastle.tls.TlsCredentials; +import org.bouncycastle.tls.TlsFatalAlert; +import org.bouncycastle.tls.TlsPSKExternal; +import org.bouncycastle.tls.TlsUtils; +import org.bouncycastle.tls.crypto.TlsCrypto; +import org.bouncycastle.tls.crypto.TlsSecret; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class ZuulPskServer extends AbstractTlsServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(ZuulPskServer.class); + + public static final AttributeKey TLS_HANDSHAKE_USING_EXTERNAL_PSK = + AttributeKey.newInstance("_tls_handshake_using_external_psk"); + + private static class PSKTimings { + private final Timer handshakeCompleteTimer; + + private Long handshakeStartTime; + + PSKTimings(Registry registry) { + handshakeCompleteTimer = registry.timer("zuul.psk.handshake.complete.time"); + } + + public void recordHandshakeStarting() { + handshakeStartTime = System.nanoTime(); + } + + public void recordHandshakeComplete() { + handshakeCompleteTimer.record(System.nanoTime() - handshakeStartTime, TimeUnit.NANOSECONDS); + } + } + + private final PSKTimings pskTimings; + + private final ExternalTlsPskProvider externalTlsPskProvider; + + private final ChannelHandlerContext ctx; + + private final Set supportedApplicationProtocols; + + public ZuulPskServer( + TlsCrypto crypto, + Registry registry, + ExternalTlsPskProvider externalTlsPskProvider, + ChannelHandlerContext ctx, + Set supportedApplicationProtocols) { + super(crypto); + this.pskTimings = new PSKTimings(registry); + this.externalTlsPskProvider = externalTlsPskProvider; + this.ctx = ctx; + this.supportedApplicationProtocols = supportedApplicationProtocols; + } + + @Override + public TlsCredentials getCredentials() { + return null; + } + + @Override + protected Vector getProtocolNames() { + Vector protocolNames = new Vector(); + if (supportedApplicationProtocols!=null) { + supportedApplicationProtocols.forEach(protocolNames::addElement); + } + return protocolNames; + } + + @Override + public void notifyHandshakeBeginning() throws IOException { + pskTimings.recordHandshakeStarting(); + this.ctx.channel().attr(TLS_HANDSHAKE_USING_EXTERNAL_PSK).set(false); + // TODO: sunnys - handshake timeouts + super.notifyHandshakeBeginning(); + } + + @Override + public void notifyHandshakeComplete() throws IOException { + pskTimings.recordHandshakeComplete(); + this.ctx.channel().attr(TLS_HANDSHAKE_USING_EXTERNAL_PSK).set(true); + super.notifyHandshakeComplete(); + ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS); + } + + @Override + protected ProtocolVersion[] getSupportedVersions() { + return ProtocolVersion.TLSv13.only(); + } + + @Override + protected int[] getSupportedCipherSuites() { + return TlsUtils.getSupportedCipherSuites( + getCrypto(), + TlsPskHandler.SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.keySet().stream() + .mapToInt(Number::intValue) + .toArray()); + } + + @Override + public ProtocolVersion getServerVersion() throws IOException { + return super.getServerVersion(); + } + + @Override + @SneakyThrows + // TODO: Ask BC folks to see if getExternalPSK can throw a checked exception + public TlsPSKExternal getExternalPSK(Vector clientPskIdentities) { + byte[] clientPskIdentity = ((PskIdentity)clientPskIdentities.get(0)).getIdentity(); + byte[] psk; + try{ + this.ctx.channel().attr(TlsPskHandler.CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY).set(new ClientPSKIdentityInfo(clientPskIdentity)); + psk = externalTlsPskProvider.provide(clientPskIdentity, this.context.getSecurityParametersHandshake().getClientRandom()); + }catch (PskCreationFailureException e) { + throw switch (e.getTlsAlertMessage()) { + case unknown_psk_identity -> new TlsFatalAlert(AlertDescription.unknown_psk_identity, "Unknown or null client PSk identity"); + case decrypt_error -> new TlsFatalAlert(AlertDescription.decrypt_error, "Invalid or expired client PSk identity"); + }; + } + TlsSecret pskTlsSecret = getCrypto().createSecret(psk); + int prfAlgorithm = getPRFAlgorithm13(getSelectedCipherSuite()); + return new BasicTlsPSKExternal(clientPskIdentity, pskTlsSecret, prfAlgorithm); + } + + @Override + public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Throwable cause) { + super.notifyAlertRaised(alertLevel, alertDescription, message, cause); + Consumer loggerFunc = (alertLevel == AlertLevel.fatal) ? LOGGER::error : LOGGER::debug; + loggerFunc.accept("TLS/PSK server raised alert: " + AlertLevel.getText(alertLevel) + ", " + + AlertDescription.getText(alertDescription)); + if (message != null) { + loggerFunc.accept("> " + message); + } + if (cause != null) { + LOGGER.error("TLS/PSK alert stacktrace", cause); + } + } + + @Override + public void notifyAlertReceived(short alertLevel, short alertDescription) { + Consumer loggerFunc = (alertLevel == AlertLevel.fatal) ? LOGGER::error : LOGGER::debug; + loggerFunc.accept("TLS 1.3 PSK server received alert: " + AlertLevel.getText(alertLevel) + ", " + + AlertDescription.getText(alertDescription)); + } + + @Override + public void processClientExtensions(Hashtable clientExtensions) throws IOException { + if (context.getSecurityParametersHandshake().getClientRandom() == null) { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + super.processClientExtensions(clientExtensions); + } + + @Override + public Hashtable getServerExtensions() throws IOException { + if (context.getSecurityParametersHandshake().getServerRandom() == null) { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + return super.getServerExtensions(); + } + + @Override + public void getServerExtensionsForConnection(Hashtable serverExtensions) throws IOException { + if (context.getSecurityParametersHandshake().getServerRandom() == null) { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + super.getServerExtensionsForConnection(serverExtensions); + } + + public String getApplicationProtocol() { + ProtocolName protocolName = + context.getSecurityParametersConnection().getApplicationProtocol(); + if (protocolName!=null) { + return protocolName.getUtf8Decoding(); + } + return null; + } + + private static int getPRFAlgorithm13(int cipherSuite) { + return switch (cipherSuite) { + case CipherSuite.TLS_AES_128_CCM_SHA256, + CipherSuite.TLS_AES_128_CCM_8_SHA256, + CipherSuite.TLS_AES_128_GCM_SHA256, + CipherSuite.TLS_CHACHA20_POLY1305_SHA256 -> PRFAlgorithm.tls13_hkdf_sha256; + case CipherSuite.TLS_AES_256_GCM_SHA384 -> PRFAlgorithm.tls13_hkdf_sha384; + case CipherSuite.TLS_SM4_CCM_SM3, CipherSuite.TLS_SM4_GCM_SM3 -> PRFAlgorithm.tls13_hkdf_sm3; + default -> -1; + }; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java index 6157afdd31..7c80c0c853 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java @@ -25,6 +25,7 @@ import com.netflix.zuul.netty.ChannelUtils; import com.netflix.zuul.netty.server.psk.ClientPSKIdentityInfo; import com.netflix.zuul.netty.server.psk.TlsPskHandler; +import com.netflix.zuul.netty.server.psk.ZuulPskServer; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; import io.netty.channel.ChannelHandlerContext; @@ -35,15 +36,16 @@ import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.AttributeKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; import java.nio.channels.ClosedChannelException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Stores info about the client and server's SSL certificates in the context, after a successful handshake. @@ -104,7 +106,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc } Boolean tlsHandshakeUsingExternalPSK = ctx.channel() - .attr(TlsPskHandler.TLS_HANDSHAKE_USING_EXTERNAL_PSK) + .attr(ZuulPskServer.TLS_HANDSHAKE_USING_EXTERNAL_PSK) .get(); ClientPSKIdentityInfo clientPSKIdentityInfo = ctx.channel() From 3cc8293242e24997fb4066d326bb5e5f400ada74 Mon Sep 17 00:00:00 2001 From: sunnys Date: Wed, 24 Jul 2024 15:23:18 -0700 Subject: [PATCH 6/9] Added license --- .../zuul/netty/server/psk/TlsPskDecoder.java | 16 ++++++++++++++++ .../netty/server/psk/TlsPskServerProtocol.java | 16 ++++++++++++++++ .../zuul/netty/server/psk/TlsPskUtils.java | 16 ++++++++++++++++ .../zuul/netty/server/psk/ZuulPskServer.java | 16 ++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java index c9fd2323b7..b7be25a51c 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java @@ -1,3 +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; import io.netty.buffer.ByteBuf; diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java index dadaa83aa9..3f5af46132 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java @@ -1,3 +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; import org.bouncycastle.tls.TlsServerProtocol; diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java index 5b2cc32a59..e364006b48 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java @@ -1,3 +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; import io.netty.buffer.ByteBuf; diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java index 3aa6ee5769..dddaa0febd 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java @@ -1,3 +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; import com.netflix.spectator.api.Registry; From a3c5d391915faa219f8ef5e28b398cdb0505fcd3 Mon Sep 17 00:00:00 2001 From: deeptiv1991 Date: Wed, 16 Oct 2024 13:39:18 -0700 Subject: [PATCH 7/9] 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 --- .../server/http2/Http2OrHttpHandler.java | 6 ++-- .../zuul/netty/server/psk/TlsPskHandler.java | 16 ++++----- .../zuul/netty/server/psk/TlsPskUtils.java | 11 ++++-- .../zuul/netty/server/psk/ZuulPskServer.java | 35 +++++++++++-------- .../server/ssl/SslHandshakeInfoHandler.java | 16 ++++----- 5 files changed, 49 insertions(+), 35 deletions(-) diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java index 1125c7c0ea..e6d5fd6ead 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java @@ -36,12 +36,11 @@ 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 - * + *

* Author: Arthur Gonigberg * Date: December 15, 2017 */ @@ -75,6 +74,9 @@ 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) { diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java index 8c77ac1990..95083fb209 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java @@ -25,14 +25,13 @@ import io.netty.channel.ChannelPromise; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; -import org.bouncycastle.tls.CipherSuite; -import org.bouncycastle.tls.ProtocolName; -import org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider; - -import javax.net.ssl.SSLSession; import java.security.SecureRandom; import java.util.Map; import java.util.Set; +import javax.net.ssl.SSLSession; +import org.bouncycastle.tls.CipherSuite; +import org.bouncycastle.tls.ProtocolName; +import org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider; public class TlsPskHandler extends ChannelDuplexHandler { @@ -67,8 +66,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) promise.setFailure(new IllegalStateException("Failed to write message on the channel. Message is not a ByteBuf")); return; } - byte[] appDataBytes = byteBufMsg.hasArray() ? byteBufMsg.array() : TlsPskUtils.readDirect(byteBufMsg); - ReferenceCountUtil.safeRelease(byteBufMsg); + byte[] appDataBytes = TlsPskUtils.getAppDataBytesAndRelease(byteBufMsg); tlsPskServerProtocol.writeApplicationData(appDataBytes, 0, appDataBytes.length); int availableOutputBytes = tlsPskServerProtocol.getAvailableOutputBytes(); if (availableOutputBytes != 0) { @@ -98,11 +96,11 @@ public void channelRegistered(ChannelHandlerContext ctx) throws Exception { * the protocol name or null if application-level protocol has not been negotiated */ public String getApplicationProtocol() { - return tlsPskServer!=null ? tlsPskServer.getApplicationProtocol() : null; + return tlsPskServer != null ? tlsPskServer.getApplicationProtocol() : null; } public SSLSession getSession() { - return tlsPskServerProtocol!=null ? tlsPskServerProtocol.getSSLSession() : null; + return tlsPskServerProtocol != null ? tlsPskServerProtocol.getSSLSession() : null; } } \ No newline at end of file diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java index e364006b48..455bbaa4a8 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java @@ -17,12 +17,19 @@ package com.netflix.zuul.netty.server.psk; import io.netty.buffer.ByteBuf; +import io.netty.util.ReferenceCountUtil; class TlsPskUtils { - static byte[] readDirect(ByteBuf byteBufMsg) { + protected static byte[] readDirect(ByteBuf byteBufMsg) { int length = byteBufMsg.readableBytes(); byte[] dest = new byte[length]; - byteBufMsg.readSlice(length).getBytes(0, dest); + byteBufMsg.readBytes(dest); return dest; } + + protected static byte[] getAppDataBytesAndRelease(ByteBuf byteBufMsg) { + byte[] appDataBytes = byteBufMsg.hasArray() ? byteBufMsg.array() : TlsPskUtils.readDirect(byteBufMsg); + ReferenceCountUtil.safeRelease(byteBufMsg); + return appDataBytes; + } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java index dddaa0febd..915b17e5a8 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java @@ -19,8 +19,15 @@ import com.netflix.spectator.api.Registry; import com.netflix.spectator.api.Timer; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.ssl.SslCloseCompletionEvent; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.AttributeKey; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import lombok.SneakyThrows; import org.bouncycastle.tls.AbstractTlsServer; import org.bouncycastle.tls.AlertDescription; @@ -40,13 +47,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.Hashtable; -import java.util.Set; -import java.util.Vector; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - public class ZuulPskServer extends AbstractTlsServer { private static final Logger LOGGER = LoggerFactory.getLogger(ZuulPskServer.class); @@ -101,7 +101,7 @@ public TlsCredentials getCredentials() { @Override protected Vector getProtocolNames() { Vector protocolNames = new Vector(); - if (supportedApplicationProtocols!=null) { + if (supportedApplicationProtocols != null) { supportedApplicationProtocols.forEach(protocolNames::addElement); } return protocolNames; @@ -145,16 +145,19 @@ public ProtocolVersion getServerVersion() throws IOException { @Override @SneakyThrows // TODO: Ask BC folks to see if getExternalPSK can throw a checked exception + // https://github.com/bcgit/bc-java/issues/1673 public TlsPSKExternal getExternalPSK(Vector clientPskIdentities) { - byte[] clientPskIdentity = ((PskIdentity)clientPskIdentities.get(0)).getIdentity(); + byte[] clientPskIdentity = ((PskIdentity) clientPskIdentities.get(0)).getIdentity(); byte[] psk; - try{ + try { this.ctx.channel().attr(TlsPskHandler.CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY).set(new ClientPSKIdentityInfo(clientPskIdentity)); psk = externalTlsPskProvider.provide(clientPskIdentity, this.context.getSecurityParametersHandshake().getClientRandom()); - }catch (PskCreationFailureException e) { + } catch (PskCreationFailureException e) { throw switch (e.getTlsAlertMessage()) { - case unknown_psk_identity -> new TlsFatalAlert(AlertDescription.unknown_psk_identity, "Unknown or null client PSk identity"); - case decrypt_error -> new TlsFatalAlert(AlertDescription.decrypt_error, "Invalid or expired client PSk identity"); + case unknown_psk_identity -> + new TlsFatalAlert(AlertDescription.unknown_psk_identity, "Unknown or null client PSk identity"); + case decrypt_error -> + new TlsFatalAlert(AlertDescription.decrypt_error, "Invalid or expired client PSk identity"); }; } TlsSecret pskTlsSecret = getCrypto().createSecret(psk); @@ -174,6 +177,10 @@ public void notifyAlertRaised(short alertLevel, short alertDescription, String m if (cause != null) { LOGGER.error("TLS/PSK alert stacktrace", cause); } + + if (alertDescription == AlertDescription.close_notify) { + ctx.fireUserEventTriggered(SslCloseCompletionEvent.SUCCESS); + } } @Override @@ -210,7 +217,7 @@ public void getServerExtensionsForConnection(Hashtable serverExtensions) throws public String getApplicationProtocol() { ProtocolName protocolName = context.getSecurityParametersConnection().getApplicationProtocol(); - if (protocolName!=null) { + if (protocolName != null) { return protocolName.getUtf8Decoding(); } return null; diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java index 7c80c0c853..cd24a3d7d0 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java @@ -36,16 +36,15 @@ import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.AttributeKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; import java.nio.channels.ClosedChannelException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Stores info about the client and server's SSL certificates in the context, after a successful handshake. @@ -105,9 +104,10 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc serverCert = session.getLocalCertificates()[0]; } - Boolean tlsHandshakeUsingExternalPSK = ctx.channel() + //if attribute is true, then true. If null or false then false + boolean tlsHandshakeUsingExternalPSK = Boolean.TRUE.equals(ctx.channel() .attr(ZuulPskServer.TLS_HANDSHAKE_USING_EXTERNAL_PSK) - .get(); + .get()); ClientPSKIdentityInfo clientPSKIdentityInfo = ctx.channel() .attr(TlsPskHandler.CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY) @@ -138,7 +138,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc CurrentPassport.fromChannel(ctx.channel()).getState(); if (cause instanceof ClosedChannelException && (PassportState.SERVER_CH_INACTIVE.equals(passportState) - || PassportState.SERVER_CH_IDLE_TIMEOUT.equals(passportState))) { + || PassportState.SERVER_CH_IDLE_TIMEOUT.equals(passportState))) { // Either client closed the connection without/before having completed a handshake, or // the connection idle timed-out before handshake. // NOTE: we were seeing a lot of these in prod and can repro by just telnetting to port and then From 44de590b138e342c26ccec396cf55509bda0dc56 Mon Sep 17 00:00:00 2001 From: deeptiv1991 Date: Thu, 24 Oct 2024 13:40:46 -0700 Subject: [PATCH 8/9] adding license header --- .../server/psk/ClientPSKIdentityInfo.java | 18 +++++++++++++++++- .../zuul/netty/server/psk/ZuulPskServer.java | 9 +++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java index bdb1a03ada..566a21f5d3 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java @@ -1,3 +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) {} +public record ClientPSKIdentityInfo(byte[] clientPSKIdentity) { +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java index 915b17e5a8..9add76dfa9 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java @@ -142,10 +142,15 @@ public ProtocolVersion getServerVersion() throws IOException { return super.getServerVersion(); } + /** + * TODO: Ask BC folks to see if getExternalPSK can throw a checked exception + * https://github.com/bcgit/bc-java/issues/1673 + * We are using SneakyThrows here because getExternalPSK is an override and we cant have throws in the method signature + * and we dont want to catch and wrap in RuntimeException. + * SneakyThrows allows up to compile and it will throw the exception at runtime. + */ @Override @SneakyThrows - // TODO: Ask BC folks to see if getExternalPSK can throw a checked exception - // https://github.com/bcgit/bc-java/issues/1673 public TlsPSKExternal getExternalPSK(Vector clientPskIdentities) { byte[] clientPskIdentity = ((PskIdentity) clientPskIdentities.get(0)).getIdentity(); byte[] psk; From 9ce8a3dd5cce8d5cc2c796dd1e1f74c9227ca709 Mon Sep 17 00:00:00 2001 From: deeptiv1991 Date: Sat, 23 Nov 2024 21:17:53 -0800 Subject: [PATCH 9/9] adding back old SslHandshakeInfo constructor for backward compatibility --- .../netty/common/ssl/SslHandshakeInfo.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java b/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java index 6e03bd121d..5a6882c7a8 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java @@ -35,6 +35,24 @@ public class SslHandshakeInfo { private final boolean usingExternalPSK; private final ClientPSKIdentityInfo clientPSKIdentityInfo; + //for backward compatibility + public SslHandshakeInfo( + boolean isOfIntermediary, + String protocol, + String cipherSuite, + ClientAuth clientAuthRequirement, + Certificate serverCertificate, + X509Certificate clientCertificate) { + this.protocol = protocol; + this.cipherSuite = cipherSuite; + this.clientAuthRequirement = clientAuthRequirement; + this.serverCertificate = serverCertificate; + this.clientCertificate = clientCertificate; + this.isOfIntermediary = isOfIntermediary; + this.usingExternalPSK = false; + this.clientPSKIdentityInfo = null; + } + public SslHandshakeInfo( boolean isOfIntermediary, String protocol,