-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TLS PSK implementation #1777
base: master
Are you sure you want to change the base?
TLS PSK implementation #1777
Conversation
@Override | ||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { | ||
if (!(msg instanceof ByteBuf byteBufMsg)) { | ||
ctx.write(msg, promise); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will continue propagating the message to the handlers. Is that intentional?
For this case, typically we want to do the following:
- release the message using
ReferenceCountUtil.safeRelease(msg)
to avoid memory leaks - set failure on the
promise
with an appropriate exception.
if (availableOutputBytes != 0) { | ||
byte[] outputBytes = new byte[availableOutputBytes]; | ||
tlsPskServerProtocol.readOutput(outputBytes, 0, availableOutputBytes); | ||
ctx.write(Unpooled.wrappedBuffer(outputBytes), promise) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't actually flush the buffer.
You might want to use .writeAndFlush
instead in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was using .writeAndFlush
previously but I ran into some issues during load testing with larger payloads and chunks. I can try to reproduce those and share more details.
try { | ||
tlsPskServerProtocol.offerInput(bytesRead); | ||
} catch (TlsFatalAlert tlsFatalAlert) { | ||
writeOutputIfAvailable(ctx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should explicitly fail the handshake at this point? For ex, see this utility
It's unclear how/where this is handled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out. I'll update the code.
if (appDataAvailable > 0) { | ||
byte[] appData = new byte[appDataAvailable]; | ||
tlsPskServerProtocol.readInput(appData, 0, appDataAvailable); | ||
out.add(Unpooled.wrappedBuffer(appData)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to pass this data down to other handlers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on my understanding, the fact that TlsPskHandler is a ByteToMessageDecoder
, adding the decoded output to List<Object> out
does the job of passing down the data to other handlers. LMK if I'm missing the point that you are making.
|
||
@Override | ||
public String getCipherSuite() { | ||
return SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.get( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the suite map the accurate value here?
Or should it be derived from the underlying session, i.e. SessionParameters
in this.getContext().getSession()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually getting the negotiated cipher suite from context getContext().getSecurityParameters().getCipherSuite()
which returns an integer but SSLSession
requires me to return a name of the suite, the map is actually doing the conversion from int to String.
protected Vector getProtocolNames() { | ||
Vector protocolNames = new Vector(); | ||
protocolNames.addElement(ProtocolName.HTTP_1_1); | ||
protocolNames.addElement(ProtocolName.HTTP_2_TLS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This list should probably be injected, esp. for clients we don't want to advertise h2 for.
@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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the identity we want guaranteed to be first in an ordered list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK we only plan to support one PSK identity in the hint, so it should be safe. And BC only invokes this method if clientPskIdentities is non empty.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear from the API if this fails the handshake. Can you clarify how this aborts the handshake?
We probably need to handle things like firing SslCloseCompletionEvent
and cleanup manually anyway.
import java.util.concurrent.TimeUnit; | ||
import java.util.function.Consumer; | ||
|
||
public class TlsPskHandler extends ByteToMessageDecoder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be nice to break this class into two pieces: a decoder and a handler. That way the TlsPskHandler
can extend ChannelDuplexHandler
and avoid all the boilerplate of needing to implement the full interfaces. A pattern I've seen before (that may work) is to implement handlerAdded
in TlsPskHanlder
and use it to add the decoder to the pipeline automatically
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This made the code so much cleaner, thanks for this comment!
@Override | ||
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { | ||
tlsPskServer = | ||
new ZuulPskServer(new JcaTlsCryptoProvider().create(new SecureRandom()), registry, externalTlsPskProvider, ctx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you make this SecureRandom a static constant instead? There isn't any concurrency benefits to create a new one every time
} | ||
} | ||
|
||
static class TlsPskServerProtocol extends TlsServerProtocol { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for improved readability it might be nice to extract these static inner classes
@@ -98,13 +105,23 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc | |||
serverCert = session.getLocalCertificates()[0]; | |||
} | |||
|
|||
Boolean tlsHandshakeUsingExternalPSK = ctx.channel() | |||
.attr(ZuulPskServer.TLS_HANDSHAKE_USING_EXTERNAL_PSK) | |||
.get(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a NPE risk since the SslHandshakeInfo
ctor will unbox this to a boolean. Is TLS_HANDSHAKE_USING_EXTERNAL_PSK always going to be set, or should this be set to false in the case of the attribute having not been set on the channel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like in ZuulPskServer, it is always set to false in notifyHandshakeBeginning and to true notifyHandshakeComplete. So it should always be set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually talked to Sunny and this code is more generic and independent of ZuulPskServer. Made the change to default to false
} | ||
|
||
@Override | ||
@SneakyThrows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there other checked exceptions that you're not handling?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sunny asked the BC folks but we havent received the response yet
bcgit/bc-java#1673
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Todo Deepti : Why we are using @SneakyThrows
@@ -70,6 +75,42 @@ public Http2OrHttpHandler( | |||
this.addHttpHandlerFn = addHttpHandlerFn; | |||
} | |||
|
|||
@Override | |||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add a javadoc saying this method is inspired by ApplicationProtocolNegotiationHandler.userEventTriggered
static byte[] readDirect(ByteBuf byteBufMsg) { | ||
int length = byteBufMsg.readableBytes(); | ||
byte[] dest = new byte[length]; | ||
byteBufMsg.readSlice(length).getBytes(0, dest); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think byteBufMsg.readBytes(dest)
essentially does the same thing, but is a little easier to parse
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could all of this logic (including the safeRelease) be put in the helper method?
} | ||
|
||
@Override | ||
public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Throwable cause) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is one other event netty fires that we'd be interested in (if possible): https://github.com/netty/netty/blob/4.1/handler/src/main/java/io/netty/handler/ssl/SslCloseCompletionEvent.java netty will fire this event when the the ssl session is closed. Does bouncy castle trigger any similar alerts or anything when that happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Justin
In TLSProtocol.java file I see it raises close_notify alert : https://github.com/bcgit/bc-java/blob/main/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java#L300 which will eventually make it's way to this notifyAlertRaised method. Did you want us to do something specific with that alert?
https://github.com/bcgit/bc-java/blob/main/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java#L1717
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added code to send event SslCloseCompletionEvent on close_notify alert
cd7056b
to
18ef13b
Compare
…dler 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
X509Certificate clientCertificate) { | ||
X509Certificate clientCertificate, | ||
boolean usingExternalPSK, | ||
ClientPSKIdentityInfo clientPSKIdentityInfo) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider keeping the original constructor for backwards compatibility, and adding a new constructor with these two new parameters
This PR adds an implementation to use a pre-shared key (PSK) to complete a TLS handshake (TLS-PSK). This implementation is based on PSK APIs offered by BouncyCastle.