Skip to content

Commit 3d81000

Browse files
authored
Validate DTLS message before decryption (#59)
* Validate DTLS message before decryption
1 parent fb0e60a commit 3d81000

File tree

9 files changed

+102
-11
lines changed

9 files changed

+102
-11
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class DtlsChannelHandler @JvmOverloads constructor(
7777

7878
private fun loadSession(result: DtlsServer.ReceiveResult.CidSessionMissing, msg: DatagramPacket, ctx: ChannelHandlerContext) {
7979
sessionStore.read(result.cid)
80-
.thenApplyAsync({ sessBuf -> dtlsServer.loadSession(sessBuf, msg.sender(), result.cid) }, ctx.executor())
80+
.thenApplyAsync({ sessBuf -> dtlsServer.loadSession(sessBuf, msg.sender(), result.cid, msg.content().nioBuffer()) }, ctx.executor())
8181
.whenComplete { isLoaded: Boolean?, _ ->
8282
if (isLoaded == true) {
8383
channelRead(ctx, msg)

kotlin-mbedtls/src/main/kotlin/org/opencoap/ssl/MbedtlsApi.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ internal object MbedtlsApi {
7878
external fun mbedtls_ssl_get_peer_cid(sslContext: Pointer, enabled: Pointer, peerCid: Pointer, peerCidLen: Pointer): Int
7979
external fun mbedtls_ssl_context_save(sslContext: Pointer, buf: ByteArray, bufLen: Int, outputLen: ByteArray): Int
8080
external fun mbedtls_ssl_context_load(sslContext: Pointer, buf: ByteArray, len: Int): Int
81+
external fun mbedtls_ssl_check_record(sslContext: Pointer, buf: Memory, bufLen: Int): Int
8182
external fun mbedtls_ssl_conf_ca_chain(sslConfig: Pointer, caChain: Pointer, caCrl: Pointer?)
8283
external fun mbedtls_ssl_conf_own_cert(sslConfig: Pointer, ownCert: Memory, pkKey: Pointer): Int
8384
external fun mbedtls_ssl_set_mtu(sslContext: Pointer, mtu: Int)
@@ -96,6 +97,7 @@ internal object MbedtlsApi {
9697
const val MBEDTLS_SSL_TRANSPORT_DATAGRAM = 1
9798
const val MBEDTLS_SSL_VERIFY_NONE = 0
9899
const val MBEDTLS_SSL_VERIFY_REQUIRED = 2
100+
const val MBEDTLS_ERR_SSL_UNEXPECTED_RECORD = -0x6700
99101

100102
// ----- net_sockets.h -----
101103
val MBEDTLS_ERR_NET_RECV_FAILED = -0x004C

kotlin-mbedtls/src/main/kotlin/org/opencoap/ssl/SslContext.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.opencoap.ssl
1818

1919
import com.sun.jna.Memory
20+
import org.opencoap.ssl.MbedtlsApi.MBEDTLS_ERR_SSL_UNEXPECTED_RECORD
2021
import org.opencoap.ssl.MbedtlsApi.mbedtls_ssl_close_notify
2122
import org.opencoap.ssl.MbedtlsApi.mbedtls_ssl_context_save
2223
import org.opencoap.ssl.MbedtlsApi.mbedtls_ssl_free
@@ -27,6 +28,7 @@ import org.opencoap.ssl.MbedtlsApi.mbedtls_ssl_handshake
2728
import org.opencoap.ssl.MbedtlsApi.mbedtls_ssl_read
2829
import org.opencoap.ssl.MbedtlsApi.mbedtls_ssl_write
2930
import org.opencoap.ssl.MbedtlsApi.verify
31+
import org.opencoap.ssl.transport.cloneToMemory
3032
import org.opencoap.ssl.transport.toHex
3133
import org.slf4j.LoggerFactory
3234
import java.io.Closeable
@@ -164,6 +166,20 @@ class SslSession internal constructor(
164166
plainBuffer.limit(size + plainBuffer.position())
165167
}
166168

169+
fun checkRecord(encBuffer: ByteBuffer): VerificationResult {
170+
val memory = encBuffer.cloneToMemory()
171+
try {
172+
val result = MbedtlsApi.mbedtls_ssl_check_record(sslContext, memory, memory.size().toInt())
173+
return if (result == 0 || result != MBEDTLS_ERR_SSL_UNEXPECTED_RECORD) {
174+
VerificationResult.Valid("Success")
175+
} else {
176+
VerificationResult.Invalid(SslException.from(result).localizedMessage)
177+
}
178+
} finally {
179+
memory.close()
180+
}
181+
}
182+
167183
fun decrypt(encBuffer: ByteBuffer, send: (ByteBuffer) -> Unit): ByteBuffer {
168184
val buf = ByteBuffer.allocate(encBuffer.remaining())
169185
decrypt(encBuffer, buf, send)
@@ -215,4 +231,9 @@ class SslSession internal constructor(
215231
override fun close() {
216232
mbedtls_ssl_free(sslContext)
217233
}
234+
235+
sealed interface VerificationResult {
236+
data class Valid(val message: String) : VerificationResult
237+
data class Invalid(val message: String) : VerificationResult
238+
}
218239
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.opencoap.ssl.transport
1818

19+
import com.sun.jna.Memory
1920
import java.nio.ByteBuffer
2021

2122
internal fun ByteArray.toHex(): String {
@@ -29,6 +30,16 @@ fun ByteBuffer.copy(): ByteBuffer {
2930
return bb
3031
}
3132

33+
fun ByteBuffer.cloneToMemory(): Memory {
34+
this.mark() // saves the original position
35+
val remaining = this.remaining()
36+
val memory = Memory(remaining.toLong())
37+
val intermediateBuffer: ByteBuffer = memory.getByteBuffer(0, remaining.toLong())
38+
intermediateBuffer.put(this)
39+
this.reset()
40+
return memory
41+
}
42+
3243
fun ByteBuffer.isNotEmpty(): Boolean = this.hasRemaining()
3344
fun ByteBuffer.isEmpty(): Boolean = !this.hasRemaining()
3445

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,12 @@ class DtlsServer(
125125
private fun closeSession(addr: InetSocketAddress) {
126126
sessions.remove(addr)?.apply {
127127
storeAndClose()
128-
logger.info("[{}] [CID:{}] DTLS session was stored", peerAddress, (this as? DtlsSession)?.sessionContext?.cid?.toHex() ?: "na")
128+
logger.info(
129+
"[{}] [CID:{}] DTLS session was stored",
130+
peerAddress,
131+
(this as? DtlsSession)?.sessionContext?.cid?.toHex()
132+
?: "na"
133+
)
129134
}
130135
}
131136

@@ -138,18 +143,25 @@ class DtlsServer(
138143
updateSessionAuthenticationContext(adr, ctx.authenticationContext)
139144
}
140145

141-
fun loadSession(sessBuf: SessionWithContext?, adr: InetSocketAddress, cid: ByteArray): Boolean {
146+
fun loadSession(sessBuf: SessionWithContext?, adr: InetSocketAddress, cid: ByteArray, dtlsPacket: ByteBuffer): Boolean {
142147
return try {
143148
if (sessBuf == null) {
144149
logger.warn("[{}] [CID:{}] DTLS session not found", adr, cid.toHex())
145150
reportMessageDrop(adr)
146-
false
147-
} else {
148-
sessions[adr] = DtlsSession(sslConfig.loadSession(cid, sessBuf.sessionBlob, adr), adr, sessBuf.authenticationContext, sessBuf.sessionStartTimestamp)
149-
true
151+
return false
152+
}
153+
154+
val sslSession = sslConfig.loadSession(cid, sessBuf.sessionBlob, adr)
155+
val verificationResult = sslSession.checkRecord(dtlsPacket)
156+
if (verificationResult is SslSession.VerificationResult.Invalid) {
157+
logger.warn("[{}] [CID:{}] Record verification failed: {}", adr, cid.toHex(), verificationResult.message)
158+
reportMessageDrop(adr)
159+
return false
150160
}
151-
} catch (ex: SslException) {
152-
logger.warn("[{}] [CID:{}] Failed to load session: {}", adr, cid.toHex(), ex.message)
161+
sessions[adr] = DtlsSession(sslSession, adr, sessBuf.authenticationContext, sessBuf.sessionStartTimestamp)
162+
true
163+
} catch (ex: Exception) {
164+
logger.error("[{}] [CID:{}] DTLS failed to load session: {}", adr, cid.toHex(), ex.message)
153165
reportMessageDrop(adr)
154166
false
155167
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class DtlsServerTransport private constructor(
7979
val copyBuf = buf.copy()
8080

8181
sessionStore.read(result.cid).thenApplyAsync(
82-
{ sessBuf -> dtlsServer.loadSession(sessBuf, adr, result.cid) },
82+
{ sessBuf -> dtlsServer.loadSession(sessBuf, adr, result.cid, copyBuf) },
8383
executor
8484
).thenCompose { isLoaded ->
8585
if (isLoaded) {

kotlin-mbedtls/src/test/kotlin/org/opencoap/ssl/SslContextTest.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,29 @@ class SslContextTest {
123123
assertEquals("perse", serverSession.decrypt(encryptedDtls2, noSend).decodeToString())
124124
}
125125

126+
@Test
127+
fun `should check record is valid authentic and decrypt`() {
128+
val clientSession = clientConf.loadSession(byteArrayOf(), StoredSessionPair.cliSession, localAddress(2_5684))
129+
val serverSession = serverConf.loadSession(byteArrayOf(), StoredSessionPair.srvSession, localAddress(1_5684))
130+
131+
val encryptedDtls = clientSession.encrypt("auto".toByteBuffer())
132+
133+
val verificationResult = serverSession.checkRecord(encryptedDtls)
134+
assertTrue(verificationResult is SslSession.VerificationResult.Valid)
135+
assertEquals("auto", serverSession.decrypt(encryptedDtls, noSend).decodeToString())
136+
}
137+
138+
@Test
139+
fun `should check record is invalid when record is unexpected and replayed`() {
140+
val clientSession = clientConf.loadSession(byteArrayOf(), StoredSessionPair.cliSession, localAddress(2_5684))
141+
val serverSession = serverConf.loadSession(byteArrayOf(), StoredSessionPair.srvSession, localAddress(1_5684))
142+
val encryptedDtls = clientSession.encrypt("auto".toByteBuffer())
143+
144+
serverSession.decrypt(encryptedDtls, noSend)
145+
val result = serverSession.checkRecord(encryptedDtls.rewind() as ByteBuffer)
146+
assertTrue(result is SslSession.VerificationResult.Invalid)
147+
}
148+
126149
@Test
127150
fun `should exchange data with direct byte buffer`() {
128151
val clientSession = clientConf.loadSession(byteArrayOf(), StoredSessionPair.cliSession, localAddress(2_5684))

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.opencoap.ssl.transport
1818

19+
import com.sun.jna.Memory
20+
import org.junit.jupiter.api.Assertions.assertArrayEquals
1921
import org.junit.jupiter.api.Assertions.assertEquals
2022
import org.junit.jupiter.api.Assertions.assertTrue
2123
import org.junit.jupiter.api.Test
@@ -70,4 +72,24 @@ class BytesExtensionsTest {
7072
buf.position(2)
7173
assertEquals("dupa", buf.decodeToString())
7274
}
75+
76+
@Test
77+
fun `should clone buffer to memory`() {
78+
val originalData = byteArrayOf(1, 2, 3, 4, 5)
79+
val byteBuffer = ByteBuffer.wrap(originalData)
80+
81+
val originalPosition = byteBuffer.position()
82+
val originalLimit = byteBuffer.limit()
83+
val originalCapacity = byteBuffer.capacity()
84+
85+
val memory: Memory = byteBuffer.cloneToMemory()
86+
87+
val clonedData = ByteArray(originalData.size)
88+
memory.read(0, clonedData, 0, originalData.size)
89+
assertArrayEquals(originalData, clonedData)
90+
91+
assertEquals(originalPosition, byteBuffer.position(), "Buffer position should not change")
92+
assertEquals(originalLimit, byteBuffer.limit(), "Buffer limit should not change")
93+
assertEquals(originalCapacity, byteBuffer.capacity(), "Buffer capacity should not change")
94+
}
7395
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class DtlsServerTest {
8585
assertTrue(dtlsServer.handleReceived(localAddress(2_5684), dtlsPacket) is ReceiveResult.CidSessionMissing)
8686

8787
// when
88-
dtlsServer.loadSession(SessionWithContext(StoredSessionPair.srvSession, mapOf(), Instant.ofEpochSecond(123456789)), localAddress(2_5684), "f935adc57425e1b214f8640d56e0c733".decodeHex())
88+
dtlsServer.loadSession(SessionWithContext(StoredSessionPair.srvSession, mapOf(), Instant.ofEpochSecond(123456789)), localAddress(2_5684), "f935adc57425e1b214f8640d56e0c733".decodeHex(), dtlsPacket)
8989

9090
// then
9191
val dtlsPacketIn = (dtlsServer.handleReceived(localAddress(2_5684), dtlsPacket) as ReceiveResult.Decrypted).packet

0 commit comments

Comments
 (0)