Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@
import static io.aklivity.zilla.runtime.engine.buffer.BufferPool.NO_SLOT;
import static io.aklivity.zilla.runtime.engine.concurrent.Signaler.NO_CANCEL_ID;
import static java.lang.System.currentTimeMillis;
import static java.nio.ByteOrder.BIG_ENDIAN;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.agrona.BitUtil.SIZE_OF_BYTE;
import static org.agrona.BitUtil.SIZE_OF_SHORT;

import java.util.Optional;
import java.util.function.Consumer;
Expand All @@ -36,7 +33,14 @@
import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig;
import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig;
import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsExtensionFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsExtensionsFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsHandshakeClientHelloFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsHandshakeMessageFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsRecordFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsServerNameExtensionFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsServerNameTypeFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsSniHostNameFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.stream.AbortFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.stream.BeginFW;
import io.aklivity.zilla.runtime.binding.tls.internal.types.stream.DataFW;
Expand Down Expand Up @@ -92,6 +96,13 @@ public final class TlsProxyFactory implements TlsStreamFactory
private final ResetFW.Builder resetRW = new ResetFW.Builder();

private final TlsRecordFW tlsRecordRO = new TlsRecordFW();
private final TlsHandshakeMessageFW tlsHandshakeRO = new TlsHandshakeMessageFW();
private final TlsHandshakeClientHelloFW tlsClientHelloRO = new TlsHandshakeClientHelloFW();
private final TlsExtensionsFW tlsExtensionsRO = new TlsExtensionsFW();
private final TlsExtensionFW tlsExtensionRO = new TlsExtensionFW();
private final TlsServerNameExtensionFW tlsServerNameExRO = new TlsServerNameExtensionFW();
private final TlsServerNameTypeFW tlsServerNameTypeRO = new TlsServerNameTypeFW();
private final TlsSniHostNameFW tlsHostnameRO = new TlsSniHostNameFW();

private final TlsProxyDecoder decodeRecord = this::decodeRecord;
private final TlsProxyDecoder decodeRecordBytes = this::decodeRecordBytes;
Expand Down Expand Up @@ -446,94 +457,93 @@ private int decodeRecord(
break decode;
}

DirectBuffer message = tlsRecord.payload().value();
int messageProgress = 0;
byte messageType = message.getByte(messageProgress++);
int messageLength =
(message.getByte(messageProgress++) & 0xff) << 16 |
(message.getByte(messageProgress++) & 0xff) << 8 |
(message.getByte(messageProgress++) & 0xff) << 0;

if (messageType != MESSAGE_TYPE_CLIENT_HELLO ||
messageProgress + messageLength != message.capacity())
TlsHandshakeMessageFW tlsHandshake = tlsRecord.payload().get(tlsHandshakeRO::tryWrap);
if (tlsHandshake == null ||
tlsHandshake.typeAndLength() >> 24 != MESSAGE_TYPE_CLIENT_HELLO ||
(tlsHandshake.typeAndLength() & 0x00ff_ffff) + tlsHandshake.limit() > tlsRecord.limit())
{
proxy.doNetReset(traceId);
proxy.decoder = decodeIgnoreAll;
break decode;
}

// skip version
messageProgress += 2;

// skip random
messageProgress += 32;

// skip session id
messageProgress += SIZE_OF_BYTE + (message.getByte(messageProgress) & 0xff);

// cipher suites
messageProgress += SIZE_OF_SHORT + (message.getShort(messageProgress, BIG_ENDIAN) & 0xffff);

// compress methods
messageProgress += SIZE_OF_BYTE + (message.getByte(messageProgress) & 0xff);

int extensionsLength = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
messageProgress += SIZE_OF_SHORT;

if (messageProgress + extensionsLength != message.capacity())
TlsHandshakeClientHelloFW tlsClientHello =
tlsClientHelloRO.tryWrap(tlsRecord.buffer(), tlsHandshake.limit(), tlsRecord.limit());
if (tlsClientHello == null ||
tlsClientHello.limit() > tlsRecord.limit())
{
proxy.doNetReset(traceId);
proxy.decoder = decodeIgnoreAll;
break decode;
}

String serverName = null;
while (messageProgress < message.capacity())
{
int extensionType = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
messageProgress += SIZE_OF_SHORT;
int extensionLength = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
messageProgress += SIZE_OF_SHORT;

if (messageProgress + extensionLength > message.capacity())
if (tlsClientHello.limit() < tlsRecord.limit())
{
TlsExtensionsFW tlsExtensions =
tlsExtensionsRO.tryWrap(tlsRecord.buffer(), tlsClientHello.limit(), tlsRecord.limit());
if (tlsExtensions == null ||
tlsExtensions.limit() != tlsRecord.limit())
{
proxy.doNetReset(traceId);
proxy.decoder = decodeIgnoreAll;
break decode;
}

if (extensionType == EXTENSION_TYPE_SNI)
{
int sniLength = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
messageProgress += SIZE_OF_SHORT;
DirectBuffer tlsExtensionsBuf = tlsExtensions.value().value();
int tlsExtensionsLimit = tlsExtensions.length();
int tlsExtensionsProgress = 0;

if (messageProgress + sniLength > message.capacity())
while (tlsExtensionsProgress < tlsExtensionsLimit)
{
TlsExtensionFW tlsExtension =
tlsExtensionRO.tryWrap(tlsExtensionsBuf, tlsExtensionsProgress, tlsExtensionsLimit);
if (tlsExtension == null ||
tlsExtension.limit() > tlsRecord.limit())
{
proxy.doNetReset(traceId);
proxy.decoder = decodeIgnoreAll;
break decode;
}

int sniType = message.getByte(messageProgress++);
if (sniType == SNI_TYPE_HOSTNAME)
if (tlsExtension.type() == EXTENSION_TYPE_SNI)
{
int hostnameLength = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
messageProgress += SIZE_OF_SHORT;
TlsServerNameExtensionFW tlsServerNameEx = tlsExtension.data().get(tlsServerNameExRO::tryWrap);
if (tlsServerNameEx == null ||
tlsServerNameEx.limit() > tlsRecord.limit())
{
proxy.doNetReset(traceId);
proxy.decoder = decodeIgnoreAll;
break decode;
}

if (messageProgress + hostnameLength > message.capacity())
int tlsServerNameTypeOffset = tlsServerNameEx.value().offset();
TlsServerNameTypeFW tlsServerNameType =
tlsServerNameTypeRO.tryWrap(tlsExtensionsBuf, tlsServerNameTypeOffset, tlsExtensionsLimit);
if (tlsServerNameType == null)
{
proxy.doNetReset(traceId);
proxy.decoder = decodeIgnoreAll;
break decode;
}

serverName = message.getStringWithoutLengthUtf8(messageProgress, hostnameLength);
messageProgress += hostnameLength;
if (tlsServerNameType.value() == SNI_TYPE_HOSTNAME)
{
TlsSniHostNameFW tlsHostname =
tlsHostnameRO.tryWrap(tlsExtensionsBuf, tlsServerNameType.limit(), tlsExtensionsLimit);
if (tlsHostname == null)
{
proxy.doNetReset(traceId);
proxy.decoder = decodeIgnoreAll;
break decode;
}

serverName = tlsHostname.value().asString();
}
}
}
else
{
messageProgress += extensionLength;

tlsExtensionsProgress = tlsExtension.limit();
}
}

Expand Down
61 changes: 61 additions & 0 deletions runtime/binding-tls/src/main/zilla/protocol.idl
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,66 @@ scope protocol
uint16 length;
octets[length] payload;
}

struct TlsHandshakeMessage
{
int32 typeAndLength;
}

struct TlsSessionId
{
uint8 length;
octets[length] value;
}

struct TlsCipherSuites
{
uint16 length;
octets[length] value;
}

struct TlsCompressionMethods
{
uint8 length;
octets[length] value;
}

struct TlsExtensions
{
uint16 length;
octets[length] value;
}

struct TlsExtension
{
uint16 type;
uint16 length;
octets[length] data;
}

struct TlsServerNameExtension
{
uint16 length;
octets[length] value;
}

struct TlsServerNameType
{
uint8 value;
}

struct TlsSniHostName
{
string16 value;
}

struct TlsHandshakeClientHello
{
TlsProtocolVersion version;
octets[32] random;
TlsSessionId session;
TlsCipherSuites suites;
TlsCompressionMethods methods;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ public class ProxyIT
@Rule
public final TestRule chain = outerRule(engine).around(k3po).around(timeout);

@Test
@Configuration("proxy.yaml")
@Specification({
"${proxy}/client/client.hello.without.ext/client",
"${proxy}/server/client.hello.without.ext/server" })
public void shouldProxyClientHelloWithoutExt() throws Exception
{
k3po.finish();
}

@Test
@Configuration("proxy.sni.yaml")
@Specification({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Copyright 2021-2024 Aklivity Inc.
#
# Aklivity licenses this file to you under the Apache License,
# version 2.0 (the "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#

---
name: test
bindings:
net0:
type: tls
kind: proxy
exit: net1
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#
# Copyright 2021-2024 Aklivity Inc.
#
# Aklivity licenses this file to you under the Apache License,
# version 2.0 (the "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#

connect "zilla://streams/net0"
option zilla:window 65536
option zilla:transmission "duplex"
option zilla:byteorder "network"

connected

write [0x16] # handshake record
[0x03] [0x03] # version 1.2
45s # length
[0x01] # client hello message
[0x00 0x00 0x29] # length
[0x03] [0x03] # version 1.2
[0x00 0x00 0x00 0x00] # random
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00] # legacy session id
2s # cipher suites
[0xc0 0x30] # TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
[0x01] # legacy compression methods
[0x00] # null
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#
# Copyright 2021-2024 Aklivity Inc.
#
# Aklivity licenses this file to you under the Apache License,
# version 2.0 (the "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#

accept "zilla://streams/net0"
option zilla:window 65536
option zilla:transmission "duplex"
option zilla:byteorder "network"
accepted

connected

read [0x16] # handshake record
[0x03] [0x03] # version 1.2
45s # length
[0x01] # client hello message
[0x00 0x00 0x29] # length
[0x03] [0x03] # version 1.2
[0x00 0x00 0x00 0x00] # random
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00 0x00 0x00 0x00]
[0x00] # legacy session id
2s # cipher suites
[0xc0 0x30] # TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
[0x01] # legacy compression methods
[0x00] # null

Loading
Loading