Skip to content
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

feat: dtls connection using mbedtls #10

Merged
merged 46 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f1707f5
feat: dtls connection using mbedtls
lchenut Mar 8, 2024
db96253
Merge remote-tracking branch 'origin/master' into dtls-protocol
lchenut Apr 12, 2024
bef7f8e
refactor: change according to the stun protocol rework
lchenut Apr 12, 2024
a4e6ab9
Merge remote-tracking branch 'origin/master' into dtls-protocol
lchenut May 24, 2024
2da7046
chore: rename init proc into new
lchenut May 30, 2024
d1b9fda
docs: adds object field comments
lchenut May 31, 2024
6652be6
Merge branch 'master' into dtls-protocol
diegomrsantos Jun 17, 2024
66ca741
chore: split dtls.nim into two files & renaming
lchenut Jun 20, 2024
bf240b1
chore: remove useless code
lchenut Jun 21, 2024
66a2aa7
chore: remove TODOs as they were addressed with a Stun refactorization
lchenut Jun 21, 2024
24e42a9
fix: oversight on dtls.new
lchenut Jun 21, 2024
ba9f04a
feat: add dtls test
lchenut Jun 21, 2024
416ff7b
chore: added license & used pragma on testdtls
lchenut Jun 21, 2024
f8c1b2f
fix: remove usage of deprecated TrackerCounter
lchenut Jun 28, 2024
874cff7
fix: trackers counter
lchenut Jun 28, 2024
bf2e53c
Merge remote-tracking branch 'origin/master' into dtls-protocol
lchenut Jul 19, 2024
3eb4b23
fix:
lchenut Jul 19, 2024
0f144ce
chore: renaming test
lchenut Jul 31, 2024
33372bc
docs: update DtlsConn comment
lchenut Jul 31, 2024
59fd302
fix: remove code duplicate
lchenut Jul 31, 2024
ffa8a51
chore: update comment
lchenut Jul 31, 2024
d003d20
chore: remove duplication mbedtls initialization code in accept/conne…
lchenut Jul 31, 2024
a9ec658
feat: add exception management to dtls_transport
lchenut Aug 1, 2024
f49ecea
fix: check address family before handshake
lchenut Aug 2, 2024
afd80aa
fix: exhaustive case
lchenut Aug 2, 2024
45cc272
fix: do not create dtlsConn if the address family is not IP
lchenut Aug 2, 2024
7cf9423
chore: remove entropy from MbedTLSCtx
lchenut Aug 2, 2024
59f76a0
chore: remove asyncspawn of cleanupdtlsconn
lchenut Aug 7, 2024
c0769c0
chore: ctx is no longer public
lchenut Aug 7, 2024
6a894ac
test: add a test with more than 2 nodes
lchenut Aug 7, 2024
f8bb4b8
chore: started is now useful
lchenut Aug 7, 2024
d7a707c
chore: update Dtls.stop
lchenut Aug 7, 2024
df2737a
chore: removed unecessary todos
lchenut Aug 7, 2024
36700ef
docs: add comments on DtlsConn.read and getters
lchenut Aug 7, 2024
ac80c9c
feat: add tracker for dtls connection and transport
lchenut Aug 7, 2024
2c327c5
chore: privatize local and remote certificate
lchenut Aug 7, 2024
6975f76
style: use nph
lchenut Aug 9, 2024
c89590f
fix: remove laddr from dtls_conn (not used)
lchenut Aug 9, 2024
8f51516
style: sort imports
lchenut Aug 9, 2024
54f4523
chore: clean Dtls.stop
lchenut Aug 9, 2024
ab02a68
fix: remote address is no longer exposed
lchenut Aug 9, 2024
c5681ed
fix: raddr change oversight
lchenut Aug 9, 2024
a8692af
chore: change `verify` name
lchenut Aug 9, 2024
e056631
chore: changed `sendFuture: Future[void]` into `dataToSend: seq[byte]`
lchenut Aug 13, 2024
5d7b428
chore: avoid sequence copy
lchenut Aug 13, 2024
a90d85f
chore: change assert message
lchenut Aug 13, 2024
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
1 change: 1 addition & 0 deletions tests/runalltests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
{.used.}

import teststun
import testdtls
45 changes: 45 additions & 0 deletions tests/testdtls.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Nim-WebRTC
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

{.used.}

import chronos
import ../webrtc/udp_transport
import ../webrtc/stun/stun_transport
import ../webrtc/dtls/dtls_transport
import ../webrtc/dtls/dtls_connection
import ./asyncunit

suite "DTLS":
teardown:
checkLeaks()

asyncTest "Simple Test":
diegomrsantos marked this conversation as resolved.
Show resolved Hide resolved
let
udp1 = UdpTransport.new(initTAddress("127.0.0.1:4444"))
udp2 = UdpTransport.new(initTAddress("127.0.0.1:5555"))
stun1 = Stun.new(udp1)
stun2 = Stun.new(udp2)
dtls1 = Dtls.new(stun1)
dtls2 = Dtls.new(stun2)
conn1Fut = dtls1.accept()
conn2 = await dtls2.connect(dtls1.laddr)
conn1 = await conn1Fut

await conn1.write(@[1'u8, 2, 3, 4])
let seq1 = await conn2.read()
check seq1 == @[1'u8, 2, 3, 4]

await conn2.write(@[5'u8, 6, 7, 8])
let seq2 = await conn1.read()
check seq2 == @[5'u8, 6, 7, 8]
await allFutures(conn1.close(), conn2.close())
await allFutures(dtls1.stop(), dtls2.stop())
await allFutures(stun1.stop(), stun2.stop())
await allFutures(udp1.close(), udp2.close())
8 changes: 4 additions & 4 deletions tests/teststun.nim
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ suite "Stun message encoding/decoding":
decoded == msg
messageIntegrity.attributeType == AttrMessageIntegrity.uint16
fingerprint.attributeType == AttrFingerprint.uint16
conn.close()
await conn.close()
await udp.close()

asyncTest "Get BindingResponse from BindingRequest + encode & decode":
Expand All @@ -82,7 +82,7 @@ suite "Stun message encoding/decoding":
bindingResponse == decoded
messageIntegrity.attributeType == AttrMessageIntegrity.uint16
fingerprint.attributeType == AttrFingerprint.uint16
conn.close()
await conn.close()
await udp.close()

suite "Stun checkForError":
Expand Down Expand Up @@ -114,7 +114,7 @@ suite "Stun checkForError":

check:
errorMissUsername.getAttribute(ErrorCode).get().getErrorCode() == ECBadRequest
conn.close()
await conn.close()
await udp.close()

asyncTest "checkForError: UsernameChecker returns false":
Expand All @@ -136,5 +136,5 @@ suite "Stun checkForError":

check:
error.getAttribute(ErrorCode).get().getErrorCode() == ECUnauthorized
conn.close()
await conn.close()
await udp.close()
5 changes: 4 additions & 1 deletion webrtc.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ let lang = getEnv("NIMLANG", "c") # Which backend (c/cpp/js)
let flags = getEnv("NIMFLAGS", "") # Extra flags for the compiler
let verbose = getEnv("V", "") notin ["", "0"]

let cfg =
var cfg =
" --styleCheck:usages --styleCheck:error" &
(if verbose: "" else: " --verbosity:0 --hints:off") &
" --skipParentCfg --skipUserCfg -f" &
" --threads:on --opt:speed"

when defined(windows):
cfg = cfg & " --clib:ws2_32"

import hashes

proc runTest(filename: string) =
Expand Down
222 changes: 222 additions & 0 deletions webrtc/dtls/dtls_connection.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# Nim-WebRTC
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

import chronos, chronicles
import ../errors, ../stun/[stun_connection]

import mbedtls/ssl
import mbedtls/ssl_cookie
import mbedtls/ssl_cache
import mbedtls/pk
import mbedtls/md
import mbedtls/entropy
import mbedtls/ctr_drbg
import mbedtls/rsa
import mbedtls/x509
import mbedtls/x509_crt
import mbedtls/bignum
import mbedtls/error
import mbedtls/net_sockets
import mbedtls/timing

diegomrsantos marked this conversation as resolved.
Show resolved Hide resolved
logScope:
topics = "webrtc dtls_conn"

# -- DtlsConn --
# A Dtls connection to a specific IP address recovered by the receiving part of
diegomrsantos marked this conversation as resolved.
Show resolved Hide resolved
# the Udp "connection"

type
MbedTLSCtx* = object
ssl*: mbedtls_ssl_context
config*: mbedtls_ssl_config
cookie*: mbedtls_ssl_cookie_ctx
cache*: mbedtls_ssl_cache_context
timer*: mbedtls_timing_delay_context
pkey*: mbedtls_pk_context
srvcert*: mbedtls_x509_crt

ctr_drbg*: mbedtls_ctr_drbg_context
entropy*: mbedtls_entropy_context

DtlsConn* = ref object
conn*: StunConn # The wrapper protocol Stun Connection
laddr: TransportAddress # Local address
raddr*: TransportAddress # Remote address
diegomrsantos marked this conversation as resolved.
Show resolved Hide resolved
dataRecv: seq[byte] # data received which will be read by SCTP
sendFuture: Future[void].Raising([CancelledError, WebRtcError])
diegomrsantos marked this conversation as resolved.
Show resolved Hide resolved
# This future is set by synchronous Mbed-TLS callbacks and waited, if set, once
# the synchronous functions ends

# Close connection management
closed: bool
closeEvent: AsyncEvent

# Local and Remote certificate, needed by wrapped protocol DataChannel
# and by libp2p
localCert*: seq[byte]
remoteCert*: seq[byte]

# Mbed-TLS contexts
ctx*: MbedTLSCtx
diegomrsantos marked this conversation as resolved.
Show resolved Hide resolved

proc new*(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T =
## Initialize a Dtls Connection
##
var self = T(conn: conn, laddr: laddr)
self.raddr = conn.raddr
self.closed = false
self.closeEvent = newAsyncEvent()
return self

proc join*(self: DtlsConn) {.async: (raises: [CancelledError]).} =
## Wait for the Dtls Connection to be closed
##
await self.closeEvent.wait()

proc dtlsHandshake*(
self: DtlsConn,
isServer: bool
) {.async: (raises: [CancelledError, WebRtcError]).} =
var shouldRead = isServer
try:
while self.ctx.ssl.private_state != MBEDTLS_SSL_HANDSHAKE_OVER:
if shouldRead:
if isServer:
case self.raddr.family
of AddressFamily.IPv4:
mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v4)
of AddressFamily.IPv6:
mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v6)
else:
raise newException(WebRtcError, "DTLS - Remote address isn't an IP address")
diegomrsantos marked this conversation as resolved.
Show resolved Hide resolved
let (data, _) = await self.conn.read()
self.dataRecv = data
self.sendFuture = nil
let res = mb_ssl_handshake_step(self.ctx.ssl)
if not self.sendFuture.isNil():
await self.sendFuture
shouldRead = false
if res == MBEDTLS_ERR_SSL_WANT_WRITE:
continue
elif res == MBEDTLS_ERR_SSL_WANT_READ:
shouldRead = true
continue
elif res == MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED:
mb_ssl_session_reset(self.ctx.ssl)
shouldRead = isServer
continue
elif res != 0:
raise newException(WebRtcError, "DTLS - " & $(res.mbedtls_high_level_strerr()))
except MbedTLSError as exc:
trace "Dtls handshake error", errorMsg = exc.msg
raise newException(WebRtcError, "DTLS - Handshake error", exc)

proc close*(self: DtlsConn) {.async: (raises: [CancelledError, WebRtcError]).} =
## Close a Dtls Connection
##
if self.closed:
debug "Try to close an already closed DtlsConn"
return

self.closed = true
self.sendFuture = nil
# TODO: proc mbedtls_ssl_close_notify => template mb_ssl_close_notify in nim-mbedtls
let x = mbedtls_ssl_close_notify(addr self.ctx.ssl)
if not self.sendFuture.isNil():
await self.sendFuture
self.closeEvent.fire()

proc write*(self: DtlsConn, msg: seq[byte]) {.async.} =
## Write a message using mbedtls_ssl_write
##
# Mbed-TLS will wrap the message properly and call `dtlsSend` callback.
# `dtlsSend` will write the message on the higher Stun connection.
if self.closed:
debug "Try to write on an already closed DtlsConn"
return
var buf = msg
try:
self.sendFuture = nil
let write = mb_ssl_write(self.ctx.ssl, buf)
if not self.sendFuture.isNil():
let sendFuture = self.sendFuture
await sendFuture
trace "Dtls write", msgLen = msg.len(), actuallyWrote = write
except MbedTLSError as exc:
trace "Dtls write error", errorMsg = exc.msg
raise exc

proc read*(self: DtlsConn): Future[seq[byte]] {.async.} =
if self.closed:
debug "Try to read on an already closed DtlsConn"
return
var res = newSeq[byte](8192)
while true:
let (data, _) = await self.conn.read()
self.dataRecv = data
# TODO: Find a clear way to use the template `mb_ssl_read` without
diegomrsantos marked this conversation as resolved.
Show resolved Hide resolved
# messing up things with exception
let length = mbedtls_ssl_read(addr self.ctx.ssl, cast[ptr byte](addr res[0]), res.len().uint)
if length == MBEDTLS_ERR_SSL_WANT_READ:
continue
if length < 0:
raise newException(WebRtcError, "DTLS - " & $(length.cint.mbedtls_high_level_strerr()))
res.setLen(length)
return res

# -- Remote / Local certificate getter --

proc remoteCertificate*(conn: DtlsConn): seq[byte] =
conn.remoteCert

proc localCertificate*(conn: DtlsConn): seq[byte] =
conn.localCert

# -- MbedTLS Callbacks --

proc verify*(ctx: pointer, pcert: ptr mbedtls_x509_crt,
state: cint, pflags: ptr uint32): cint {.cdecl.} =
# verify is the procedure called by mbedtls when receiving the remote
# certificate. It's usually used to verify the validity of the certificate.
# We use this procedure to store the remote certificate as it's mandatory
# to have it for the Prologue of the Noise protocol, aswell as the localCertificate.
var self = cast[DtlsConn](ctx)
let cert = pcert[]

self.remoteCert = newSeq[byte](cert.raw.len)
copyMem(addr self.remoteCert[0], cert.raw.p, cert.raw.len)
return 0

proc dtlsSend*(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} =
# dtlsSend is the procedure called by mbedtls when data needs to be sent.
# As the StunConn's write proc is asynchronous and dtlsSend cannot be async,
# we store the future of this write and await it after the end of the
# function (see write or dtlsHanshake for example).
var self = cast[DtlsConn](ctx)
var toWrite = newSeq[byte](len)
if len > 0:
copyMem(addr toWrite[0], buf, len)
trace "dtls send", len
self.sendFuture = self.conn.write(toWrite)
result = len.cint

proc dtlsRecv*(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} =
# dtlsRecv is the procedure called by mbedtls when data needs to be received.
# As we cannot asynchronously await for data to be received, we use a data received
# queue. If this queue is empty, we return `MBEDTLS_ERR_SSL_WANT_READ` for us to await
# when the mbedtls proc resumed (see read or dtlsHandshake for example)
let self = cast[DtlsConn](ctx)
if self.dataRecv.len() == 0:
return MBEDTLS_ERR_SSL_WANT_READ

copyMem(buf, addr self.dataRecv[0], self.dataRecv.len())
result = self.dataRecv.len().cint
self.dataRecv = @[]
trace "dtls receive", len, result
Loading
Loading