Skip to content

Commit 823b4b1

Browse files
authored
Add a way to enforce DTLS session expiration (#60)
* Add a way to enforce DTLS session expiration * Fix linting * Address comments * Apply suggestion
1 parent b1558de commit 823b4b1

File tree

8 files changed

+94
-15
lines changed

8 files changed

+94
-15
lines changed

kotlin-mbedtls-netty/src/main/kotlin/org/opencoap/ssl/netty/DatagramPacketWithContext.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2023 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
2+
* Copyright (c) 2022-2024 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
33
* SPDX-License-Identifier: Apache-2.0
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import java.net.InetSocketAddress
2525
class DatagramPacketWithContext(
2626
data: ByteBuf,
2727
recipient: InetSocketAddress?,
28-
sender: InetSocketAddress,
28+
sender: InetSocketAddress?,
2929
val sessionContext: DtlsSessionContext
3030
) : DatagramPacket(data, recipient, sender) {
3131

kotlin-mbedtls-netty/src/main/kotlin/org/opencoap/ssl/netty/DtlsChannelHandler.kt

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2023 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
2+
* Copyright (c) 2022-2024 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
33
* SPDX-License-Identifier: Apache-2.0
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -90,6 +90,14 @@ class DtlsChannelHandler @JvmOverloads constructor(
9090

9191
override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
9292
when (msg) {
93+
is DatagramPacketWithContext -> {
94+
write(msg, promise, ctx)
95+
if (msg.sessionContext.sessionExpirationHint) {
96+
promise.toCompletableFuture().thenAccept {
97+
dtlsServer.closeSession(msg.recipient())
98+
}
99+
}
100+
}
93101
is DatagramPacket -> write(msg, promise, ctx)
94102
is SessionAuthenticationContext -> {
95103
msg.map.forEach { (key, value) ->

kotlin-mbedtls-netty/src/test/kotlin/org/opencoap/ssl/netty/EchoHandler.kt

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2023 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
2+
* Copyright (c) 2022-2024 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
33
* SPDX-License-Identifier: Apache-2.0
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,14 +30,21 @@ class EchoHandler : ChannelInboundHandlerAdapter() {
3030

3131
val sessionContext = DatagramPacketWithContext.contextFrom(msg)
3232
val authContext = (sessionContext.authenticationContext["AUTH"] ?: "")
33+
val dgramContent = dgram.content().toByteArray()
34+
val goToSleep = dgramContent.toString(Charset.defaultCharset()).endsWith(":sleep")
3335

34-
val reply = ctx.alloc().buffer(dgram.content().readableBytes() + 20)
36+
val reply = ctx.alloc().buffer(dgramContent.size + 20)
3537
reply.writeBytes(echoPrefix)
3638
reply.writeCharSequence(authContext, Charset.defaultCharset())
37-
reply.writeBytes(dgram.content())
39+
reply.writeBytes(dgramContent)
3840

39-
dgram.release()
40-
41-
ctx.writeAndFlush(DatagramPacket(reply, dgram.sender()))
41+
ctx.writeAndFlush(
42+
DatagramPacketWithContext(
43+
reply,
44+
dgram.sender(),
45+
null,
46+
sessionContext.copy(sessionExpirationHint = goToSleep)
47+
)
48+
)
4249
}
4350
}

kotlin-mbedtls-netty/src/test/kotlin/org/opencoap/ssl/netty/NettyTest.kt

+26
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.netty.channel.socket.DatagramChannel
2323
import io.netty.channel.socket.DatagramPacket
2424
import io.netty.util.concurrent.DefaultThreadFactory
2525
import org.assertj.core.api.Assertions.assertThatThrownBy
26+
import org.awaitility.kotlin.await
2627
import org.junit.jupiter.api.AfterAll
2728
import org.junit.jupiter.api.AfterEach
2829
import org.junit.jupiter.api.Assertions.assertEquals
@@ -224,4 +225,29 @@ class NettyTest {
224225
assertEquals(0, dtlsServer.numberOfSessions)
225226
client.close()
226227
}
228+
229+
@Test
230+
fun `server should store session if hinted to do so`() {
231+
val client = NettyTransportAdapter.connect(clientConf, srvAddress).mapToString()
232+
233+
// when normal packet is sent
234+
assertTrue(client.send("hi").await())
235+
assertEquals("ECHO:hi", client.receive(5.seconds).await())
236+
237+
// then session should not be stored
238+
assertEquals(1, dtlsServer.numberOfSessions)
239+
assertEquals(0, sessionStore.size())
240+
241+
// when a packet with session expiration hint is sent
242+
assertTrue(client.send("hi:sleep").await())
243+
assertEquals("ECHO:hi:sleep", client.receive(5.seconds).await())
244+
245+
// then session must be stored
246+
await.atMost(5.seconds).untilAsserted {
247+
assertEquals(0, dtlsServer.numberOfSessions)
248+
assertEquals(1, sessionStore.size())
249+
}
250+
251+
client.close()
252+
}
227253
}

kotlin-mbedtls/src/main/kotlin/org/opencoap/ssl/transport/DtlsServer.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2023 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
2+
* Copyright (c) 2022-2024 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
33
* SPDX-License-Identifier: Apache-2.0
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -118,6 +118,10 @@ class DtlsServer(
118118
}
119119
}
120120

121+
fun closeSession(addr: InetSocketAddress) {
122+
sessions.remove(addr)?.storeAndClose()
123+
}
124+
121125
fun loadSession(sessBuf: SessionWithContext?, adr: InetSocketAddress, cid: ByteArray): Boolean {
122126
return try {
123127
if (sessBuf == null) {

kotlin-mbedtls/src/main/kotlin/org/opencoap/ssl/transport/DtlsServerTransport.kt

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2023 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
2+
* Copyright (c) 2022-2024 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
33
* SPDX-License-Identifier: Apache-2.0
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -95,8 +95,16 @@ class DtlsServerTransport private constructor(
9595
override fun send(packet: Packet<ByteBuffer>): CompletableFuture<Boolean> = executor.supply {
9696
val encPacket = dtlsServer.encrypt(packet.buffer, packet.peerAddress)?.let(packet::map)
9797

98-
when (encPacket) {
99-
null -> completedFuture(false)
98+
when {
99+
encPacket == null -> completedFuture(false)
100+
packet.sessionContext.sessionExpirationHint -> {
101+
transport.send(encPacket).thenApply { isSuccess ->
102+
if (isSuccess) {
103+
dtlsServer.closeSession(packet.peerAddress)
104+
}
105+
isSuccess
106+
}
107+
}
100108
else -> transport.send(encPacket)
101109
}
102110
}.thenCompose(Function.identity())

kotlin-mbedtls/src/main/kotlin/org/opencoap/ssl/transport/DtlsSessionContext.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2023 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
2+
* Copyright (c) 2022-2024 kotlin-mbedtls contributors (https://github.com/open-coap/kotlin-mbedtls)
33
* SPDX-License-Identifier: Apache-2.0
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,7 +24,8 @@ data class DtlsSessionContext @JvmOverloads constructor(
2424
val authenticationContext: AuthenticationContext = emptyMap(),
2525
val peerCertificateSubject: String? = null,
2626
val cid: ByteArray? = null,
27-
val sessionStartTimestamp: Instant? = null
27+
val sessionStartTimestamp: Instant? = null,
28+
val sessionExpirationHint: Boolean = false
2829
) {
2930
companion object {
3031
@JvmField
@@ -44,6 +45,7 @@ data class DtlsSessionContext @JvmOverloads constructor(
4445
if (!cid.contentEquals(other.cid)) return false
4546
} else if (other.cid != null) return false
4647
if (sessionStartTimestamp != other.sessionStartTimestamp) return false
48+
if (sessionExpirationHint != other.sessionExpirationHint) return false
4749

4850
return true
4951
}
@@ -53,6 +55,7 @@ data class DtlsSessionContext @JvmOverloads constructor(
5355
result = 31 * result + (peerCertificateSubject?.hashCode() ?: 0)
5456
result = 31 * result + (cid?.contentHashCode() ?: 0)
5557
result = 31 * result + (sessionStartTimestamp?.hashCode() ?: 0)
58+
result = 31 * result + (sessionExpirationHint.hashCode())
5659
return result
5760
}
5861
}

kotlin-mbedtls/src/test/kotlin/org/opencoap/ssl/transport/DtlsServerTransportTest.kt

+23
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,29 @@ class DtlsServerTransportTest {
476476
client.close()
477477
}
478478

479+
@Test
480+
fun `server should store session if hinted to do so`() {
481+
// given
482+
server = DtlsServerTransport.create(conf, sessionStore = sessionStore)
483+
val serverReceived = server.receive(1.seconds)
484+
val client = DtlsTransmitter.connect(server, clientConfig).await().mapToString()
485+
486+
client.send("dupa")
487+
server.send(Packet("dupa".toByteBuffer(), serverReceived.await().peerAddress))
488+
assertEquals("dupa", client.receive(1.seconds).await())
489+
490+
client.send("sleep")
491+
server.send(Packet("sleep".toByteBuffer(), serverReceived.await().peerAddress, sessionContext = DtlsSessionContext(sessionExpirationHint = true)))
492+
assertEquals("sleep", client.receive(1.seconds).await())
493+
494+
await.atMost(5.seconds).untilAsserted {
495+
assertEquals(1, sessionStore.size())
496+
assertEquals(0, server.numberOfSessions())
497+
}
498+
499+
client.close()
500+
}
501+
479502
private fun <T> Transport<T>.dropReceive(drop: (Int) -> Boolean): Transport<T> {
480503
val underlying = this
481504
var i = 0

0 commit comments

Comments
 (0)